From 1915878f9c198b05e25873562078abeaa905c411 Mon Sep 17 00:00:00 2001 From: StarArawn <toasterthegamer@gmail.com> Date: Wed, 24 Nov 2021 14:57:43 -0500 Subject: [PATCH] First commit.. --- .gitignore | 17 +- Cargo.lock | 3641 +++++++++++++++++ bevy_kayak_ui/Cargo.toml | 13 + bevy_kayak_ui/src/bevy_context.rs | 32 + bevy_kayak_ui/src/camera/camera.rs | 58 + bevy_kayak_ui/src/camera/mod.rs | 23 + bevy_kayak_ui/src/camera/ortho.rs | 73 + bevy_kayak_ui/src/lib.rs | 29 + bevy_kayak_ui/src/render/mod.rs | 95 + bevy_kayak_ui/src/render/quad/mod.rs | 43 + bevy_kayak_ui/src/render/quad/pipeline.rs | 344 ++ bevy_kayak_ui/src/render/quad/shader.wgsl | 30 + bevy_kayak_ui/src/render/text/font.rs | 8 + bevy_kayak_ui/src/render/text/font_mapping.rs | 40 + .../src/render/text/font_texture_cache.rs | 201 + bevy_kayak_ui/src/render/text/mod.rs | 141 + bevy_kayak_ui/src/render/text/pipeline.rs | 360 ++ bevy_kayak_ui/src/render/text/shader.wgsl | 45 + bevy_kayak_ui/src/render/ui_pass.rs | 102 + bevy_kayak_ui/src/render/ui_pass_driver.rs | 29 + .../src/render/unified/font/extract.rs | 87 + bevy_kayak_ui/src/render/unified/font/font.rs | 8 + .../src/render/unified/font/font_mapping.rs | 40 + .../render/unified/font/font_texture_cache.rs | 248 ++ bevy_kayak_ui/src/render/unified/font/mod.rs | 112 + bevy_kayak_ui/src/render/unified/mod.rs | 46 + bevy_kayak_ui/src/render/unified/pipeline.rs | 438 ++ .../src/render/unified/quad/extract.rs | 52 + bevy_kayak_ui/src/render/unified/quad/mod.rs | 15 + bevy_kayak_ui/src/render/unified/shader.wgsl | 56 + cargo.toml | 21 + examples/bevy.rs | 65 + kayak_components/.gitignore | 7 + kayak_components/Cargo.toml | 8 + kayak_components/src/app.rs | 12 + kayak_components/src/background.rs | 19 + kayak_components/src/clip.rs | 19 + kayak_components/src/element.rs | 10 + kayak_components/src/lib.rs | 11 + kayak_components/src/text.rs | 24 + kayak_components/src/window.rs | 72 + kayak_core/cargo.toml | 17 + kayak_core/examples/test3.rs | 63 + kayak_core/src/color.rs | 48 + kayak_core/src/context.rs | 102 + kayak_core/src/fragment.rs | 33 + kayak_core/src/generational_arena.rs | 1326 ++++++ kayak_core/src/layout_cache.rs | 358 ++ kayak_core/src/lib.rs | 29 + kayak_core/src/node.rs | 285 ++ kayak_core/src/render_command.rs | 17 + kayak_core/src/render_primitive.rs | 59 + kayak_core/src/styles.rs | 136 + kayak_core/src/tree.rs | 257 ++ kayak_core/src/widget.rs | 15 + kayak_core/src/widget_manager.rs | 216 + kayak_font/.gitignore | 1 + kayak_font/Cargo.toml | 17 + kayak_font/examples/main.rs | 54 + kayak_font/examples/simple.rs | 30 + kayak_font/resources/Roboto-Regular.ttf | Bin 0 -> 171272 bytes kayak_font/src/color_flags.rs | 62 + kayak_font/src/contour.rs | 108 + kayak_font/src/font.rs | 253 ++ kayak_font/src/lib.rs | 27 + kayak_font/src/msdf.rs | 171 + kayak_font/src/path_collector.rs | 134 + kayak_font/src/path_element.rs | 191 + kayak_font/src/recolor.rs | 124 + kayak_font/src/sdf.rs | 87 + kayak_font/src/ttf_parser.rs | 69 + kayak_font/src/utils.rs | 61 + kayak_render_macros/Cargo.toml | 17 + kayak_render_macros/examples/main.rs | 70 + kayak_render_macros/src/arc_function.rs | 28 + kayak_render_macros/src/attribute.rs | 95 + kayak_render_macros/src/child.rs | 58 + kayak_render_macros/src/children.rs | 219 + kayak_render_macros/src/function_component.rs | 192 + kayak_render_macros/src/lib.rs | 100 + kayak_render_macros/src/partial_eq.rs | 16 + kayak_render_macros/src/tags.rs | 78 + kayak_render_macros/src/widget.rs | 97 + kayak_render_macros/src/widget_attributes.rs | 108 + resources/Roboto-Regular.ttf | Bin 0 -> 171272 bytes src/lib.rs | 7 + 86 files changed, 12122 insertions(+), 7 deletions(-) create mode 100644 Cargo.lock create mode 100644 bevy_kayak_ui/Cargo.toml create mode 100644 bevy_kayak_ui/src/bevy_context.rs create mode 100644 bevy_kayak_ui/src/camera/camera.rs create mode 100644 bevy_kayak_ui/src/camera/mod.rs create mode 100644 bevy_kayak_ui/src/camera/ortho.rs create mode 100644 bevy_kayak_ui/src/lib.rs create mode 100644 bevy_kayak_ui/src/render/mod.rs create mode 100644 bevy_kayak_ui/src/render/quad/mod.rs create mode 100644 bevy_kayak_ui/src/render/quad/pipeline.rs create mode 100644 bevy_kayak_ui/src/render/quad/shader.wgsl create mode 100644 bevy_kayak_ui/src/render/text/font.rs create mode 100644 bevy_kayak_ui/src/render/text/font_mapping.rs create mode 100644 bevy_kayak_ui/src/render/text/font_texture_cache.rs create mode 100644 bevy_kayak_ui/src/render/text/mod.rs create mode 100644 bevy_kayak_ui/src/render/text/pipeline.rs create mode 100644 bevy_kayak_ui/src/render/text/shader.wgsl create mode 100644 bevy_kayak_ui/src/render/ui_pass.rs create mode 100644 bevy_kayak_ui/src/render/ui_pass_driver.rs create mode 100644 bevy_kayak_ui/src/render/unified/font/extract.rs create mode 100644 bevy_kayak_ui/src/render/unified/font/font.rs create mode 100644 bevy_kayak_ui/src/render/unified/font/font_mapping.rs create mode 100644 bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs create mode 100644 bevy_kayak_ui/src/render/unified/font/mod.rs create mode 100644 bevy_kayak_ui/src/render/unified/mod.rs create mode 100644 bevy_kayak_ui/src/render/unified/pipeline.rs create mode 100644 bevy_kayak_ui/src/render/unified/quad/extract.rs create mode 100644 bevy_kayak_ui/src/render/unified/quad/mod.rs create mode 100644 bevy_kayak_ui/src/render/unified/shader.wgsl create mode 100644 cargo.toml create mode 100644 examples/bevy.rs create mode 100644 kayak_components/.gitignore create mode 100644 kayak_components/Cargo.toml create mode 100644 kayak_components/src/app.rs create mode 100644 kayak_components/src/background.rs create mode 100644 kayak_components/src/clip.rs create mode 100644 kayak_components/src/element.rs create mode 100644 kayak_components/src/lib.rs create mode 100644 kayak_components/src/text.rs create mode 100644 kayak_components/src/window.rs create mode 100644 kayak_core/cargo.toml create mode 100644 kayak_core/examples/test3.rs create mode 100644 kayak_core/src/color.rs create mode 100644 kayak_core/src/context.rs create mode 100644 kayak_core/src/fragment.rs create mode 100644 kayak_core/src/generational_arena.rs create mode 100644 kayak_core/src/layout_cache.rs create mode 100644 kayak_core/src/lib.rs create mode 100644 kayak_core/src/node.rs create mode 100644 kayak_core/src/render_command.rs create mode 100644 kayak_core/src/render_primitive.rs create mode 100644 kayak_core/src/styles.rs create mode 100644 kayak_core/src/tree.rs create mode 100644 kayak_core/src/widget.rs create mode 100644 kayak_core/src/widget_manager.rs create mode 100644 kayak_font/.gitignore create mode 100644 kayak_font/Cargo.toml create mode 100644 kayak_font/examples/main.rs create mode 100644 kayak_font/examples/simple.rs create mode 100644 kayak_font/resources/Roboto-Regular.ttf create mode 100644 kayak_font/src/color_flags.rs create mode 100644 kayak_font/src/contour.rs create mode 100644 kayak_font/src/font.rs create mode 100644 kayak_font/src/lib.rs create mode 100644 kayak_font/src/msdf.rs create mode 100644 kayak_font/src/path_collector.rs create mode 100644 kayak_font/src/path_element.rs create mode 100644 kayak_font/src/recolor.rs create mode 100644 kayak_font/src/sdf.rs create mode 100644 kayak_font/src/ttf_parser.rs create mode 100644 kayak_font/src/utils.rs create mode 100644 kayak_render_macros/Cargo.toml create mode 100644 kayak_render_macros/examples/main.rs create mode 100644 kayak_render_macros/src/arc_function.rs create mode 100644 kayak_render_macros/src/attribute.rs create mode 100644 kayak_render_macros/src/child.rs create mode 100644 kayak_render_macros/src/children.rs create mode 100644 kayak_render_macros/src/function_component.rs create mode 100644 kayak_render_macros/src/lib.rs create mode 100644 kayak_render_macros/src/partial_eq.rs create mode 100644 kayak_render_macros/src/tags.rs create mode 100644 kayak_render_macros/src/widget.rs create mode 100644 kayak_render_macros/src/widget_attributes.rs create mode 100644 resources/Roboto-Regular.ttf create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore index 088ba6b..afb05e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +target # These are backup files generated by rustfmt **/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +.vscode + +*.log + +msdfgen \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4b9e0ca --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3641 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "ab_glyph" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b228f2c198f98d4337ceb560333fb12cbb2f4948a953bf8c57d09deb219603" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c4da790adcb2ce5e758c064b4f3ec17a30349f9961d3e5e6c9688b052a9e18" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "android_log-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" + +[[package]] +name = "android_log-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" + +[[package]] +name = "android_logger" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cbd542dd180566fad88fd2729a53a62a734843c626638006a9d63ec0688484e" +dependencies = [ + "android_log-sys 0.1.2", + "env_logger", + "lazy_static", + "log", +] + +[[package]] +name = "ansi_term" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d9ff5d688f1c13395289f67db01d4826b46dd694e7580accdc3e8430f2d98e" + +[[package]] +name = "approx" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "as-any" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b564204d863976c470dbd0e41d1fcc351a8cd1b58222aaaae466f82c493fae4" + +[[package]] +name = "ash" +version = "0.33.3+1.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" +dependencies = [ + "libloading", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bevy" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_internal", +] + +[[package]] +name = "bevy-glsl-to-spirv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d5f2f58f0aec3c50a20799792c3705e80dd7df327e79791cacec197e84e5e61" + +[[package]] +name = "bevy_app" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_reflect", + "bevy_utils", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bevy_asset" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "anyhow", + "bevy_app", + "bevy_diagnostic", + "bevy_ecs", + "bevy_log", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "crossbeam-channel", + "downcast-rs", + "js-sys", + "ndk-glue 0.2.1", + "notify", + "parking_lot", + "rand", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "bevy_audio" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "anyhow", + "bevy_app", + "bevy_asset", + "bevy_ecs", + "bevy_reflect", + "bevy_utils", + "parking_lot", + "rodio", +] + +[[package]] +name = "bevy_core" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_derive", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bytemuck", +] + +[[package]] +name = "bevy_core_pipeline" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_ecs", + "bevy_render2", +] + +[[package]] +name = "bevy_derive" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "Inflector", + "bevy_macro_utils", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "bevy_diagnostic" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_core", + "bevy_ecs", + "bevy_log", + "bevy_utils", +] + +[[package]] +name = "bevy_dynamic_plugin" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "libloading", +] + +[[package]] +name = "bevy_ecs" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "async-channel", + "bevy_ecs_macros", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "downcast-rs", + "fixedbitset", + "fxhash", + "rand", + "serde", + "thiserror", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_macro_utils", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "bevy_gilrs" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_utils", + "gilrs", +] + +[[package]] +name = "bevy_gltf2" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "anyhow", + "base64", + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_ecs", + "bevy_log", + "bevy_math", + "bevy_pbr2", + "bevy_reflect", + "bevy_render2", + "bevy_scene", + "bevy_transform", + "gltf", + "percent-encoding", + "thiserror", + "wgpu", +] + +[[package]] +name = "bevy_input" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_utils", +] + +[[package]] +name = "bevy_internal" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_audio", + "bevy_core", + "bevy_core_pipeline", + "bevy_derive", + "bevy_diagnostic", + "bevy_dynamic_plugin", + "bevy_ecs", + "bevy_gilrs", + "bevy_gltf2", + "bevy_input", + "bevy_log", + "bevy_math", + "bevy_pbr", + "bevy_pbr2", + "bevy_reflect", + "bevy_render", + "bevy_render2", + "bevy_scene", + "bevy_sprite", + "bevy_sprite2", + "bevy_tasks", + "bevy_text", + "bevy_transform", + "bevy_ui", + "bevy_utils", + "bevy_wgpu", + "bevy_window", + "bevy_winit", + "ndk-glue 0.2.1", +] + +[[package]] +name = "bevy_kayak_ui" +version = "0.0.1" +dependencies = [ + "bevy", + "bytemuck", + "crevice", + "kayak_components", + "kayak_core", + "kayak_font", + "kayak_render_macros", +] + +[[package]] +name = "bevy_log" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "android_log-sys 0.2.0", + "bevy_app", + "bevy_utils", + "console_error_panic_hook", + "tracing-log", + "tracing-subscriber", + "tracing-wasm", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "cargo-manifest", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "bevy_math" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_reflect", + "glam", +] + +[[package]] +name = "bevy_pbr" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bytemuck", +] + +[[package]] +name = "bevy_pbr2" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_render2", + "bevy_transform", + "bevy_utils", + "bitflags", + "bytemuck", + "crevice", + "wgpu", +] + +[[package]] +name = "bevy_reflect" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_reflect_derive", + "bevy_utils", + "downcast-rs", + "erased-serde", + "glam", + "parking_lot", + "serde", + "smallvec", + "thiserror", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_macro_utils", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", + "uuid", +] + +[[package]] +name = "bevy_render" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "anyhow", + "bevy-glsl-to-spirv", + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_derive", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags", + "downcast-rs", + "hex", + "hexasphere", + "image", + "once_cell", + "parking_lot", + "serde", + "shaderc", + "spirv-reflect", + "thiserror", +] + +[[package]] +name = "bevy_render2" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "anyhow", + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_derive", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags", + "crevice", + "downcast-rs", + "futures-lite", + "hex", + "hexasphere", + "image", + "naga", + "once_cell", + "parking_lot", + "regex", + "serde", + "smallvec", + "thiserror", + "wgpu", +] + +[[package]] +name = "bevy_scene" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "anyhow", + "bevy_app", + "bevy_asset", + "bevy_ecs", + "bevy_reflect", + "bevy_transform", + "bevy_utils", + "ron", + "serde", + "thiserror", + "uuid", +] + +[[package]] +name = "bevy_sprite" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_ecs", + "bevy_log", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bytemuck", + "guillotiere", + "rectangle-pack", + "serde", + "thiserror", +] + +[[package]] +name = "bevy_sprite2" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_log", + "bevy_math", + "bevy_reflect", + "bevy_render2", + "bevy_transform", + "bevy_utils", + "bytemuck", + "guillotiere", + "rectangle-pack", + "serde", + "thiserror", +] + +[[package]] +name = "bevy_tasks" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "async-channel", + "async-executor", + "event-listener", + "futures-lite", + "num_cpus", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_text" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "ab_glyph", + "anyhow", + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_transform", + "bevy_utils", + "bevy_window", + "glyph_brush_layout", + "thiserror", +] + +[[package]] +name = "bevy_transform" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_utils", + "smallvec", +] + +[[package]] +name = "bevy_ui" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_derive", + "bevy_ecs", + "bevy_input", + "bevy_log", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_text", + "bevy_transform", + "bevy_utils", + "bevy_window", + "serde", + "smallvec", + "stretch", +] + +[[package]] +name = "bevy_utils" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "ahash", + "bevy_derive", + "getrandom", + "instant", + "tracing", + "uuid", +] + +[[package]] +name = "bevy_wgpu" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_core", + "bevy_diagnostic", + "bevy_ecs", + "bevy_render", + "bevy_utils", + "bevy_window", + "bevy_winit", + "crossbeam-channel", + "crossbeam-utils", + "futures-lite", + "parking_lot", + "wgpu", +] + +[[package]] +name = "bevy_window" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_utils", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "bevy_winit" +version = "0.5.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_utils", + "bevy_window", + "raw-window-handle", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "bindgen" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2 1.0.32", + "quote 1.0.10", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +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 = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + +[[package]] +name = "cargo-manifest" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6d65c7592744998c67947ec771c62687c76f00179a83ffd563c0482046bb98" +dependencies = [ + "serde", + "serde_derive", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[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 = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" +dependencies = [ + "cc", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[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 = "combine" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +dependencies = [ + "core-foundation-sys 0.6.2", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys 0.8.3", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" +dependencies = [ + "bitflags", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f45f0a21f617cd2c788889ef710b63f075c949259593ea09c826f1e47a2418" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.3", + "coreaudio-rs", + "jni", + "js-sys", + "lazy_static", + "libc", + "mach 0.3.2", + "ndk 0.3.0", + "ndk-glue 0.3.0", + "nix", + "oboe", + "parking_lot", + "stdweb 0.1.3", + "thiserror", + "web-sys", + "winapi", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crevice" +version = "0.8.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "bytemuck", + "crevice-derive", + "glam", + "mint", +] + +[[package]] +name = "crevice-derive" +version = "0.8.0" +source = "git+https://github.com/StarArawn/bevy?rev=b26f563b13c267ffe1ee801bd71fd40b98a256e7#b26f563b13c267ffe1ee801bd71fd40b98a256e7" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading", + "winapi", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.32", + "quote 1.0.10", + "strsim", + "syn 1.0.81", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "diff-struct" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30a0f0aafc8da0d598388b1897310646f45f47f17e31d581b4390f9dd88e826" +dependencies = [ + "diff_derive", + "serde", +] + +[[package]] +name = "diff_derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f9c37d395456d9c1785499421c6288931b6e3dbc2500acb95b8adaa0e69db3" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "difference" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8" + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "erased-serde" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de9ad4541d99dc22b59134e7ff8dc3d6c988c89ecd7324bf10a8362b07a2afa" +dependencies = [ + "serde", +] + +[[package]] +name = "euclid" +version = "0.19.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "596b99621b9477e7a5f94d2d8dd13a9c5c302ac358b822c67a42b6f1054450e1" +dependencies = [ + "euclid_macros", + "num-traits", +] + +[[package]] +name = "euclid" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da96828553a086d7b18dcebfc579bd9628b016f86590d7453c115e490fa74b80" +dependencies = [ + "num-traits", +] + +[[package]] +name = "euclid_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdcb84c18ea5037a1c5a23039b4ff29403abce2e0d6b1daa11cf0bde2b30be15" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", +] + +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + +[[package]] +name = "fastrand" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" +dependencies = [ + "instant", +] + +[[package]] +name = "filetime" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "fixedbitset" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fontdue" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8669141fd879e023ac6fcfe1d90dfe3f83ca7e322781ddf3f5be59147e4b8113" +dependencies = [ + "hashbrown", + "ttf-parser 0.12.3", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fsevent-sys" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e564d24da983c053beff1bb7178e237501206840a3e6bf4e267b9e8ae734a" +dependencies = [ + "libc", +] + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "futures-io" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gilrs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e986f911d937f4395dfc2a39618dcef452773d32dcdbe0828c623f76588f749" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5e5bb97bf9a0d9519a28cf38839cf1d6d9bb572b48e3c67202271fec2ed5e7" +dependencies = [ + "core-foundation 0.6.4", + "io-kit-sys", + "libc", + "libudev-sys", + "log", + "nix", + "rusty-xinput", + "stdweb 0.4.20", + "uuid", + "vec_map", + "winapi", +] + +[[package]] +name = "glam" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c626156a479bfb5a4362c43f5abadd700af33ebc8db068089d9cb92493d6ab" +dependencies = [ + "bytemuck", + "mint", + "serde", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "glow" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gltf" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff38b75359a0096dd0a8599b6e4f37a6ee41d5df300cc7669e62aafa697f7a2" +dependencies = [ + "byteorder", + "gltf-json", + "lazy_static", +] + +[[package]] +name = "gltf-derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a9333e0f9c7bca94dfc20bcf44fa12a61eeec662d6e007563ff748aa59c70" +dependencies = [ + "inflections", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "gltf-json" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1414d3a98cbaabdb2f134328b1f6036d14b282febc1df51952a435d2ca17fb6" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38" +dependencies = [ + "ab_glyph", + "approx", + "xi-unicode", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e64cbb8d36508d3e19da95e56e196a84f674fc190881f2cc010000798838aa6" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid 0.22.6", + "svg_fmt", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexasphere" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dc62dcfd68ec810c4707804556f2e88655012b1a373b0e0bbbe88a9db366627" +dependencies = [ + "glam", + "lazy_static", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-kit-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21dcc74995dd4cd090b147e79789f8d65959cbfb5f0b118002db869ea3bd0a0" +dependencies = [ + "core-foundation-sys 0.6.2", + "mach 0.2.3", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[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.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kayak_components" +version = "0.1.0" +dependencies = [ + "kayak_core", +] + +[[package]] +name = "kayak_core" +version = "0.1.0" +dependencies = [ + "as-any", + "derivative", + "diff-struct", + "fontdue", + "kayak_render_macros", + "morphorm", + "resources", +] + +[[package]] +name = "kayak_font" +version = "0.1.0" +dependencies = [ + "arrayvec 0.7.2", + "bitflags", + "fontdue", + "lyon_geom", + "lyon_path", + "png", + "ttf-parser 0.13.3", +] + +[[package]] +name = "kayak_render_macros" +version = "0.1.0" +dependencies = [ + "kayak_core", + "pretty_assertions", + "proc-macro-error", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "kayak_ui" +version = "0.1.0" +dependencies = [ + "bevy", + "bevy_kayak_ui", + "kayak_components", + "kayak_core", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" + +[[package]] +name = "libloading" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "lyon_geom" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb9bf1f1d43be9a9cc2343a7a096dc113cc25337a13e8f99721b01d1d548b60" +dependencies = [ + "arrayvec 0.4.12", + "euclid 0.19.9", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dc8e0746b7cca11960b602f7fe037bb067746a01eab4aa502fed1494544843" +dependencies = [ + "lyon_geom", +] + +[[package]] +name = "mach" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" +dependencies = [ + "libc", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "minimp3" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" +dependencies = [ + "minimp3-sys", + "slice-deque", + "thiserror", +] + +[[package]] +name = "minimp3-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" +dependencies = [ + "cc", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "mint" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162e591484b4b8fe9e1ca16ebf07ab584fdc3334508d76a788cd54d89cfc20dc" + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "mio-misc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ddf05411bb159cdb5801bb10002afb66cb4572be656044315e363460ce69dc2" +dependencies = [ + "crossbeam", + "crossbeam-queue", + "log", + "mio", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "morphorm" +version = "0.2.0" +source = "git+https://github.com/geom3trik/morphorm#df4129d0e4fa390e92ede1a303aeeae71be5d3f4" +dependencies = [ + "bitflags", + "smallvec", +] + +[[package]] +name = "naga" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda66d09f712e1f0a6ab436137da4fac312f78301f6d4ac7cb8bfe96e988734f" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "fxhash", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "petgraph", + "pp-rs", + "spirv", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb167c1febed0a496639034d0c76b3b74263636045db5489eee52143c246e73" +dependencies = [ + "jni-sys", + "ndk-sys", + "num_enum 0.4.3", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" +dependencies = [ + "jni-sys", + "ndk-sys", + "num_enum 0.5.4", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum 0.5.4", + "thiserror", +] + +[[package]] +name = "ndk-glue" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf399b8b7a39c6fb153c4ec32c72fd5fe789df24a647f229c239aa7adb15241" +dependencies = [ + "android_logger", + "lazy_static", + "libc", + "log", + "ndk 0.2.1", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-glue" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.3.0", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-glue" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e9e94628f24e7a3cb5b96a2dc5683acd9230bf11991c2a1677b87695138420" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.4.0", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +dependencies = [ + "darling", + "proc-macro-crate 0.1.5", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "notify" +version = "5.0.0-pre.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f18203a26893ca1d3526cf58084025d5639f91c44f8b70ab3b724f60e819a0" +dependencies = [ + "bitflags", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "libc", + "mio", + "walkdir", + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4" +dependencies = [ + "derivative", + "num_enum_derive 0.4.3", +] + +[[package]] +name = "num_enum" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +dependencies = [ + "derivative", + "num_enum_derive 0.5.4", +] + +[[package]] +name = "num_enum_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" +dependencies = [ + "proc-macro-crate 0.1.5", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +dependencies = [ + "proc-macro-crate 1.1.0", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[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_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "oboe" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e15e22bc67e047fe342a32ecba55f555e3be6166b04dd157cd0f803dfa9f48e1" +dependencies = [ + "jni", + "ndk 0.4.0", + "ndk-glue 0.4.0", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338142ae5ab0aaedc8275aa8f67f460e43ae0fca76a695a742d56da0a269eadc" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "owned_ttf_parser" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ee3f72636e6f164cc41c9f9057f4e58c4e13507699ea7f5e5242b64b8198ee" +dependencies = [ + "ttf-parser 0.13.3", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pkg-config" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "pp-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" +dependencies = [ + "unicode-xid 0.2.2", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + +[[package]] +name = "pretty_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5dc44a7f8ba9e19aeb6f900eb518343170f2aae27d3c9c78e4d5c3db623638d" +dependencies = [ + "ansi_term 0.9.0", + "difference", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid 0.2.2", +] + +[[package]] +name = "profiling" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9926767b8b8244d7b6b64546585121d193c3d0b4856ccd656b7bfa9deb91ab6a" + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2 1.0.32", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + +[[package]] +name = "raw-window-handle" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" +dependencies = [ + "libc", +] + +[[package]] +name = "rectangle-pack" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "resources" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42070ea13709eb92d2977b48c7d3bd44866fa328e14248f8d1f00d6ea14d5066" +dependencies = [ + "downcast-rs", + "fxhash", + "parking_lot", +] + +[[package]] +name = "rodio" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d98f5e557b61525057e2bc142c8cd7f0e70d75dc32852309bec440e6e046bf9" +dependencies = [ + "cpal", + "minimp3", +] + +[[package]] +name = "ron" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86018df177b1beef6c7c8ef949969c4f7cb9a9344181b92486b23c79995bdaa4" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rusty-xinput" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2aa654bc32eb9ca14cce1a084abc9dfe43949a4547c35269a094c39272db3bb" +dependencies = [ + "lazy_static", + "log", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[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_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "serde_json" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "shaderc" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58da8aaf4ad3508598cdf098567114c98d5f455de7d69b1213232ac557bc67ea" +dependencies = [ + "libc", + "shaderc-sys", +] + +[[package]] +name = "shaderc-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd76ec0bd25f2017a65250373485e43cdc81b5cb8fd83c6115375c8d018cdf9" +dependencies = [ + "cmake", + "libc", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "slice-deque" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" +dependencies = [ + "libc", + "mach 0.3.2", + "winapi", +] + +[[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.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +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", + "num-traits", +] + +[[package]] +name = "spirv-reflect" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecc7af6a7d3ca6d15f4d6b5077df89c77ad1f4b314d0cabee221656d041dad7" +dependencies = [ + "bitflags", + "cc", + "num-traits", + "serde", + "serde_derive", + "spirv_headers", +] + +[[package]] +name = "spirv_headers" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c" +dependencies = [ + "bitflags", + "num-traits", +] + +[[package]] +name = "stdweb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "serde", + "serde_json", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "serde", + "serde_derive", + "syn 1.0.81", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2 1.0.32", + "quote 1.0.10", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.81", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "stretch" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0dc6d20ce137f302edf90f9cd3d278866fd7fb139efca6f246161222ad6d87" +dependencies = [ + "lazy_static", + "libm", +] + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "svg_fmt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "unicode-xid 0.2.2", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "indexmap", + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + +[[package]] +name = "tracing-core" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd0568dbfe3baf7048b7908d2b32bca0d81cd56bec6d2a8f894b01d74f86be3" +dependencies = [ + "ansi_term 0.12.1", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae741706df70547fca8715f74a8569677666e7be3454313af70f6e158034485" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "ttf-parser" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" + +[[package]] +name = "ttf-parser" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47094e61a758dfddc5bc6de0862c2640ee2643997835e58332a4863bcd5852e9" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote 1.0.10", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1577ecc4f6992b9e965878ac594efb24eed2bdf089c11f45b3d1c5f216e2e30" +dependencies = [ + "arrayvec 0.7.2", + "js-sys", + "log", + "naga", + "parking_lot", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdcbfa4885b32c2b1feb2faeb8b6a76065b752b8f08751b82f994e937687f46" +dependencies = [ + "arrayvec 0.7.2", + "bitflags", + "cfg_aliases", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e493835d9edb153d5c8a9d8d016e1811dbe32ddb707a110be1453c7b051d3ec" +dependencies = [ + "arrayvec 0.7.2", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e15e44ba88ec415466e18e91881319e7c9e96cb905dc623305168aea65b85ccc" +dependencies = [ + "bitflags", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +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 = "winit" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79610794594d5e86be473ef7763f604f2159cbac8c94debd00df8fb41e86c2f8" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio", + "mio-misc", + "ndk 0.3.0", + "ndk-glue 0.3.0", + "ndk-sys", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "scopeguard", + "wasm-bindgen", + "web-sys", + "winapi", + "x11-dl", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" diff --git a/bevy_kayak_ui/Cargo.toml b/bevy_kayak_ui/Cargo.toml new file mode 100644 index 0000000..1ffcf7e --- /dev/null +++ b/bevy_kayak_ui/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bevy_kayak_ui" +version = "0.0.1" +edition = "2021" + +[dependencies] +bytemuck = "1.7.2" +bevy = { git = "https://github.com/StarArawn/bevy", rev = "b26f563b13c267ffe1ee801bd71fd40b98a256e7" } +kayak_core = { path = "../kayak_core" } +kayak_components = { path = "../kayak_components" } +kayak_render_macros = { path = "../kayak_render_macros" } +kayak_font = { path = "../kayak_font" } +crevice = { git = "https://github.com/StarArawn/bevy", rev = "b26f563b13c267ffe1ee801bd71fd40b98a256e7" } diff --git a/bevy_kayak_ui/src/bevy_context.rs b/bevy_kayak_ui/src/bevy_context.rs new file mode 100644 index 0000000..9322b73 --- /dev/null +++ b/bevy_kayak_ui/src/bevy_context.rs @@ -0,0 +1,32 @@ +use std::sync::{Arc, RwLock}; + +use kayak_core::{ + context::KayakContext, + styles::{Style, StyleProp, Units}, +}; + +pub struct BevyContext { + pub kayak_context: Arc<RwLock<KayakContext>>, +} + +impl BevyContext { + pub fn new<F: Fn(&mut Style, &mut KayakContext)>(width: f32, height: f32, f: F) -> Self { + let mut app_styles = Style { + width: StyleProp::Value(Units::Pixels(width)), + height: StyleProp::Value(Units::Pixels(height)), + ..Style::default() + }; + + let kayak_context = Arc::new(RwLock::new(KayakContext::new())); + + if let Ok(mut kayak_context) = kayak_context.write() { + f(&mut app_styles, &mut kayak_context); + + kayak_context.render(); + + kayak_context.widget_manager.dirty(true); + } + + Self { kayak_context } + } +} diff --git a/bevy_kayak_ui/src/camera/camera.rs b/bevy_kayak_ui/src/camera/camera.rs new file mode 100644 index 0000000..f0ed588 --- /dev/null +++ b/bevy_kayak_ui/src/camera/camera.rs @@ -0,0 +1,58 @@ +use bevy::{ + prelude::{Bundle, GlobalTransform, Transform}, + render2::{ + camera::{Camera, CameraPlugin, CameraProjection, DepthCalculation, WindowOrigin}, + primitives::Frustum, + view::VisibleEntities, + }, +}; + +use super::ortho::UIOrthographicProjection; + +#[derive(Bundle)] +pub struct UICameraBundle { + pub camera: Camera, + pub orthographic_projection: UIOrthographicProjection, + pub visible_entities: VisibleEntities, + pub frustum: Frustum, + pub transform: Transform, + pub global_transform: GlobalTransform, +} + +impl UICameraBundle { + pub const UI_CAMERA: &'static str = "KAYAK_UI_CAMERA"; + pub fn new() -> Self { + // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset + // the camera's translation by far and use a right handed coordinate system + let far = 1000.0; + + let orthographic_projection = UIOrthographicProjection { + far, + depth_calculation: DepthCalculation::ZDifference, + window_origin: WindowOrigin::BottomLeft, + ..Default::default() + }; + + let transform = Transform::from_xyz(0.0, 0.0, far - 0.1); + + let view_projection = + orthographic_projection.get_projection_matrix() * transform.compute_matrix().inverse(); + let frustum = Frustum::from_view_projection( + &view_projection, + &transform.translation, + &transform.back(), + orthographic_projection.far(), + ); + UICameraBundle { + camera: Camera { + name: Some(Self::UI_CAMERA.to_string()), + ..Default::default() + }, + orthographic_projection, + frustum, + visible_entities: VisibleEntities::default(), + transform, + global_transform: Default::default(), + } + } +} diff --git a/bevy_kayak_ui/src/camera/mod.rs b/bevy_kayak_ui/src/camera/mod.rs new file mode 100644 index 0000000..2d0c1db --- /dev/null +++ b/bevy_kayak_ui/src/camera/mod.rs @@ -0,0 +1,23 @@ +use bevy::{ + prelude::{CoreStage, Plugin}, + render2::camera::ActiveCameras, +}; + +mod camera; +mod ortho; + +pub use camera::UICameraBundle; +pub(crate) use ortho::UIOrthographicProjection; + +pub struct KayakUICameraPlugin; + +impl Plugin for KayakUICameraPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let mut active_cameras = app.world.get_resource_mut::<ActiveCameras>().unwrap(); + active_cameras.add(UICameraBundle::UI_CAMERA); + app.add_system_to_stage( + CoreStage::PostUpdate, + bevy::render2::camera::camera_system::<UIOrthographicProjection>, + ); + } +} diff --git a/bevy_kayak_ui/src/camera/ortho.rs b/bevy_kayak_ui/src/camera/ortho.rs new file mode 100644 index 0000000..e4e6621 --- /dev/null +++ b/bevy_kayak_ui/src/camera/ortho.rs @@ -0,0 +1,73 @@ +use bevy::ecs::reflect::ReflectComponent; +use bevy::{ + math::Mat4, + reflect::Reflect, + render2::camera::{CameraProjection, DepthCalculation, ScalingMode, WindowOrigin}, +}; + +#[derive(Debug, Clone, Reflect)] +#[reflect(Component)] +pub struct UIOrthographicProjection { + pub left: f32, + pub right: f32, + pub bottom: f32, + pub top: f32, + pub near: f32, + pub far: f32, + pub window_origin: WindowOrigin, + pub scaling_mode: ScalingMode, + pub scale: f32, + pub depth_calculation: DepthCalculation, +} + +impl CameraProjection for UIOrthographicProjection { + fn get_projection_matrix(&self) -> Mat4 { + Mat4::orthographic_rh( + self.left * self.scale, + self.right * self.scale, + self.bottom * self.scale, + self.top * self.scale, + // NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0] + // This is for interoperability with pipelines using infinite reverse perspective projections. + self.far, + self.near, + ) + } + + fn update(&mut self, width: f32, height: f32) { + match (&self.scaling_mode, &self.window_origin) { + (ScalingMode::WindowSize, WindowOrigin::BottomLeft) => { + self.left = 0.0; + self.right = width; + self.top = 0.0; + self.bottom = height; + } + _ => {} + } + } + + fn depth_calculation(&self) -> DepthCalculation { + self.depth_calculation + } + + fn far(&self) -> f32 { + self.far + } +} + +impl Default for UIOrthographicProjection { + fn default() -> Self { + UIOrthographicProjection { + left: -1.0, + right: 1.0, + bottom: -1.0, + top: 1.0, + near: 0.0, + far: 1000.0, + window_origin: WindowOrigin::Center, + scaling_mode: ScalingMode::WindowSize, + scale: 1.0, + depth_calculation: DepthCalculation::Distance, + } + } +} diff --git a/bevy_kayak_ui/src/lib.rs b/bevy_kayak_ui/src/lib.rs new file mode 100644 index 0000000..c5f8e11 --- /dev/null +++ b/bevy_kayak_ui/src/lib.rs @@ -0,0 +1,29 @@ +use bevy::{prelude::{Plugin, ResMut}, render2::color::Color}; + +mod bevy_context; +mod camera; +mod render; + +pub use bevy_context::BevyContext; +pub use camera::*; + +#[derive(Default)] +pub struct BevyKayakUIPlugin; + +impl Plugin for BevyKayakUIPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.add_plugin(render::BevyKayakUIRenderPlugin) + .add_plugin(camera::KayakUICameraPlugin) + .add_system(update); + } +} + +pub(crate) fn to_bevy_color(color: &kayak_core::color::Color) -> Color { + Color::rgba(color.r, color.g, color.b, color.a) +} + +pub fn update(bevy_context: ResMut<BevyContext>) { + if let Ok(mut context) = bevy_context.kayak_context.write() { + context.render(); + } +} diff --git a/bevy_kayak_ui/src/render/mod.rs b/bevy_kayak_ui/src/render/mod.rs new file mode 100644 index 0000000..051005c --- /dev/null +++ b/bevy_kayak_ui/src/render/mod.rs @@ -0,0 +1,95 @@ +use bevy::{ + prelude::{Commands, Entity, Plugin, Query, Res, ResMut, With}, + render2::{ + camera::ActiveCameras, + render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, + render_phase::{sort_phase_system, DrawFunctions, RenderPhase}, + render_resource::{ + Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + renderer::RenderDevice, + texture::TextureCache, + view::{ExtractedView, ViewDepthTexture}, + RenderApp, RenderStage, + }, +}; + +use crate::{ + render::{ + ui_pass::MainPassUINode, ui_pass_driver::UIPassDriverNode, unified::UnifiedRenderPlugin, + }, + UICameraBundle, +}; + +use self::ui_pass::TransparentUI; + +mod ui_pass; +mod ui_pass_driver; +mod unified; + +pub mod node { + pub const UI_PASS_DEPENDENCIES: &str = "ui_pass_dependencies"; + pub const UI_PASS_DRIVER: &str = "ui_pass_driver"; +} + +pub mod draw_ui_graph { + pub const NAME: &str = "draw_ui"; + pub mod input { + pub const VIEW_ENTITY: &str = "view_entity"; + } + pub mod node { + pub const MAIN_PASS: &str = "ui_pass"; + } +} + +pub struct BevyKayakUIRenderPlugin; + +impl Plugin for BevyKayakUIRenderPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let render_app = app.sub_app(RenderApp); + render_app + .init_resource::<DrawFunctions<TransparentUI>>() + .add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<TransparentUI>); + + let pass_node_ui = MainPassUINode::new(&mut render_app.world); + let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap(); + + let mut draw_ui_graph = RenderGraph::default(); + draw_ui_graph.add_node(draw_ui_graph::node::MAIN_PASS, pass_node_ui); + let input_node_id = draw_ui_graph.set_input(vec![SlotInfo::new( + draw_ui_graph::input::VIEW_ENTITY, + SlotType::Entity, + )]); + draw_ui_graph + .add_slot_edge( + input_node_id, + draw_ui_graph::input::VIEW_ENTITY, + draw_ui_graph::node::MAIN_PASS, + MainPassUINode::IN_VIEW, + ) + .unwrap(); + graph.add_sub_graph(draw_ui_graph::NAME, draw_ui_graph); + + graph.add_node(node::UI_PASS_DEPENDENCIES, EmptyNode); + graph.add_node(node::UI_PASS_DRIVER, UIPassDriverNode); + graph + .add_node_edge(node::UI_PASS_DEPENDENCIES, node::UI_PASS_DRIVER) + .unwrap(); + + app.add_plugin(UnifiedRenderPlugin); + } +} + +pub fn extract_core_pipeline_camera_phases( + mut commands: Commands, + active_cameras: Res<ActiveCameras>, +) { + if let Some(camera_2d) = active_cameras.get(UICameraBundle::UI_CAMERA) { + if let Some(entity) = camera_2d.entity { + commands + .get_or_spawn(entity) + .insert(RenderPhase::<TransparentUI>::default()); + } + } +} diff --git a/bevy_kayak_ui/src/render/quad/mod.rs b/bevy_kayak_ui/src/render/quad/mod.rs new file mode 100644 index 0000000..222f983 --- /dev/null +++ b/bevy_kayak_ui/src/render/quad/mod.rs @@ -0,0 +1,43 @@ +use bevy::{ + prelude::{Assets, HandleUntyped, Plugin}, + reflect::TypeUuid, + render2::{render_phase::DrawFunctions, render_resource::Shader, RenderApp, RenderStage}, +}; + +use crate::render::{ + quad::pipeline::{DrawQuad, QuadMeta, QuadPipeline}, + ui_pass::TransparentUI, +}; + +mod pipeline; + +pub const QUAD_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7604018236855288450); + +#[derive(Default)] +pub struct QuadRendererPlugin; + +impl Plugin for QuadRendererPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap(); + let quad_shader = Shader::from_wgsl(include_str!("shader.wgsl")); + shaders.set_untracked(QUAD_SHADER_HANDLE, quad_shader); + + let render_app = app.sub_app(RenderApp); + render_app + .init_resource::<QuadPipeline>() + .init_resource::<QuadMeta>() + .add_system_to_stage(RenderStage::Extract, pipeline::extract_quads) + .add_system_to_stage(RenderStage::Prepare, pipeline::prepare_quads) + .add_system_to_stage(RenderStage::Queue, pipeline::queue_quads); + + let draw_quad = DrawQuad::new(&mut render_app.world); + + render_app + .world + .get_resource::<DrawFunctions<TransparentUI>>() + .unwrap() + .write() + .add(draw_quad); + } +} diff --git a/bevy_kayak_ui/src/render/quad/pipeline.rs b/bevy_kayak_ui/src/render/quad/pipeline.rs new file mode 100644 index 0000000..8c5d857 --- /dev/null +++ b/bevy_kayak_ui/src/render/quad/pipeline.rs @@ -0,0 +1,344 @@ +use bevy::{ + core::FloatOrd, + ecs::system::{ + lifetimeless::{Read, SQuery, SRes}, + SystemState, + }, + math::{const_vec3, Mat4, Quat, Vec2, Vec3}, + prelude::{Bundle, Color, Commands, Entity, FromWorld, Query, Res, ResMut, World}, + render2::{ + render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, + render_resource::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent, + BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages, + BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, CompareFunction, + DepthBiasState, DepthStencilState, FragmentState, FrontFace, MultisampleState, + PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineCache, + RenderPipelineDescriptor, Shader, ShaderStages, StencilFaceState, StencilState, + TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, + VertexStepMode, + }, + renderer::{RenderDevice, RenderQueue}, + texture::BevyDefault, + view::{ViewUniformOffset, ViewUniforms}, + }, + sprite2::Rect, +}; +use bytemuck::{Pod, Zeroable}; +use kayak_core::render_primitive::RenderPrimitive; + +use crate::{ + render::{quad::QUAD_SHADER_HANDLE, ui_pass::TransparentUI}, + to_bevy_color, BevyContext, +}; + +pub struct QuadPipeline { + view_layout: BindGroupLayout, + pipeline: CachedPipelineId, +} + +const QUAD_VERTEX_POSITIONS: &[Vec3] = &[ + const_vec3!([0.0, 0.0, 0.0]), + const_vec3!([1.0, 1.0, 0.0]), + const_vec3!([0.0, 1.0, 0.0]), + const_vec3!([0.0, 0.0, 0.0]), + const_vec3!([1.0, 0.0, 0.0]), + const_vec3!([1.0, 1.0, 0.0]), +]; + +impl FromWorld for QuadPipeline { + fn from_world(world: &mut World) -> Self { + let world = world.cell(); + let render_device = world.get_resource::<RenderDevice>().unwrap(); + let mut pipeline_cache = world.get_resource_mut::<RenderPipelineCache>().unwrap(); + + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + // TODO: change this to ViewUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(144), + }, + count: None, + }], + label: Some("quad_view_layout"), + }); + + let vertex_buffer_layout = VertexBufferLayout { + array_stride: 28, + step_mode: VertexStepMode::Vertex, + attributes: vec![ + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + VertexAttribute { + format: VertexFormat::Float32x4, + offset: 12, + shader_location: 1, + }, + ], + }; + + let pipeline_desc = RenderPipelineDescriptor { + vertex: VertexState { + shader: QUAD_SHADER_HANDLE.typed::<Shader>(), + entry_point: "vertex".into(), + shader_defs: vec![], + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: QUAD_SHADER_HANDLE.typed::<Shader>(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![ColorTargetState { + format: TextureFormat::bevy_default(), + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }), + write_mask: ColorWrites::ALL, + }], + }), + layout: Some(vec![view_layout.clone()]), + primitive: PrimitiveState { + front_face: FrontFace::Ccw, + cull_mode: None, + polygon_mode: PolygonMode::Fill, + clamp_depth: false, + conservative: false, + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + }, + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: CompareFunction::Greater, + stencil: StencilState { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("quad_pipeline".into()), + }; + + QuadPipeline { + pipeline: pipeline_cache.queue(pipeline_desc), + view_layout, + } + } +} + +#[derive(Bundle)] +pub struct ExtractQuadBundle { + extracted_quad: ExtractedQuad, +} + +pub struct ExtractedQuad { + rect: Rect, + background_color: Color, + vertex_index: usize, +} + +pub fn extract_quads(mut commands: Commands, context: Res<BevyContext>) { + let render_commands = if let Ok(context) = context.kayak_context.read() { + context.widget_manager.build_render_primitives() + } else { + vec![] + }; + + let quad_commands: Vec<&RenderPrimitive> = render_commands + .iter() + .filter(|command| matches!(command, RenderPrimitive::Quad { .. })) + .collect::<Vec<_>>(); + + let mut extracted_quads = Vec::new(); + for render_primitive in quad_commands { + let (background_color, layout) = match render_primitive { + RenderPrimitive::Quad { + background_color, + layout, + } => (background_color, layout), + _ => panic!(""), + }; + + extracted_quads.push(ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, layout.posy), + max: Vec2::new(layout.width, layout.height), + }, + background_color: to_bevy_color(background_color), + vertex_index: 0, + }, + }); + } + commands.spawn_batch(extracted_quads); +} + +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +struct QuadVertex { + pub position: [f32; 3], + pub color: [f32; 4], +} + +pub struct QuadMeta { + vertices: BufferVec<QuadVertex>, + view_bind_group: Option<BindGroup>, +} + +impl Default for QuadMeta { + fn default() -> Self { + Self { + vertices: BufferVec::new(BufferUsages::VERTEX), + view_bind_group: None, + } + } +} + +pub fn prepare_quads( + render_device: Res<RenderDevice>, + render_queue: Res<RenderQueue>, + mut sprite_meta: ResMut<QuadMeta>, + mut extracted_sprites: Query<&mut ExtractedQuad>, +) { + let extracted_sprite_len = extracted_sprites.iter_mut().len(); + // dont create buffers when there are no quads + if extracted_sprite_len == 0 { + return; + } + + sprite_meta.vertices.clear(); + sprite_meta.vertices.reserve( + extracted_sprite_len * QUAD_VERTEX_POSITIONS.len(), + &render_device, + ); + + for (i, mut extracted_sprite) in extracted_sprites.iter_mut().enumerate() { + let sprite_rect = extracted_sprite.rect; + let color = extracted_sprite.background_color.as_linear_rgba_f32(); + + extracted_sprite.vertex_index = i; + for vertex_position in QUAD_VERTEX_POSITIONS.iter() { + let world = Mat4::from_scale_rotation_translation( + sprite_rect.size().extend(1.0), + Quat::default(), + sprite_rect.min.extend(0.0), + ); + let final_position = (world * Vec3::from(*vertex_position).extend(1.0)).truncate(); + sprite_meta.vertices.push(QuadVertex { + position: final_position.into(), + color, + }); + } + } + sprite_meta + .vertices + .write_buffer(&render_device, &render_queue); +} + +pub fn queue_quads( + draw_functions: Res<DrawFunctions<TransparentUI>>, + render_device: Res<RenderDevice>, + mut sprite_meta: ResMut<QuadMeta>, + view_uniforms: Res<ViewUniforms>, + quad_pipeline: Res<QuadPipeline>, + mut extracted_sprites: Query<(Entity, &ExtractedQuad)>, + mut views: Query<&mut RenderPhase<TransparentUI>>, +) { + if let Some(view_binding) = view_uniforms.uniforms.binding() { + sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("quad_view_bind_group"), + layout: &quad_pipeline.view_layout, + })); + let draw_quad = draw_functions.read().get_id::<DrawQuad>().unwrap(); + for mut transparent_phase in views.iter_mut() { + for (entity, quad) in extracted_sprites.iter_mut() { + transparent_phase.add(TransparentUI { + draw_function: draw_quad, + pipeline: quad_pipeline.pipeline, + entity, + sort_key: FloatOrd(0.0), + }); + } + } + } +} + +pub struct DrawQuad { + params: SystemState<( + SRes<QuadMeta>, + SRes<RenderPipelineCache>, + SQuery<Read<ViewUniformOffset>>, + SQuery<Read<ExtractedQuad>>, + )>, +} + +impl DrawQuad { + pub fn new(world: &mut World) -> Self { + Self { + params: SystemState::new(world), + } + } +} + +impl Draw<TransparentUI> for DrawQuad { + fn draw<'w>( + &mut self, + world: &'w World, + pass: &mut TrackedRenderPass<'w>, + view: Entity, + item: &TransparentUI, + ) { + let (quad_meta, pipelines, views, quads) = self.params.get(world); + let view_uniform = views.get(view).unwrap(); + let quad_meta = quad_meta.into_inner(); + let extracted_quad = quads.get(item.entity).unwrap(); + if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) { + pass.set_render_pipeline(pipeline); + pass.set_vertex_buffer(0, quad_meta.vertices.buffer().unwrap().slice(..)); + pass.set_bind_group( + 0, + quad_meta.view_bind_group.as_ref().unwrap(), + &[view_uniform.offset], + ); + + pass.draw( + (extracted_quad.vertex_index * QUAD_VERTEX_POSITIONS.len()) as u32 + ..((extracted_quad.vertex_index + 1) * QUAD_VERTEX_POSITIONS.len()) as u32, + 0..1, + ); + } + } +} diff --git a/bevy_kayak_ui/src/render/quad/shader.wgsl b/bevy_kayak_ui/src/render/quad/shader.wgsl new file mode 100644 index 0000000..df4e649 --- /dev/null +++ b/bevy_kayak_ui/src/render/quad/shader.wgsl @@ -0,0 +1,30 @@ +[[block]] +struct View { + view_proj: mat4x4<f32>; + world_position: vec3<f32>; +}; +[[group(0), binding(0)]] +var<uniform> view: View; + +struct VertexOutput { + [[builtin(position)]] position: vec4<f32>; + [[location(0)]] color: vec4<f32>; + [[location(1)]] uv: vec3<f32>; +}; + +[[stage(vertex)]] +fn vertex( + [[location(0)]] vertex_position: vec3<f32>, + [[location(1)]] vertex_color: vec4<f32>, + [[location(2)]] vertex_uv: vec3<f32>; +) -> VertexOutput { + var out: VertexOutput; + out.color = vertex_color; + out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); + return out; +} + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { + return in.color; +} \ No newline at end of file diff --git a/bevy_kayak_ui/src/render/text/font.rs b/bevy_kayak_ui/src/render/text/font.rs new file mode 100644 index 0000000..7a4424c --- /dev/null +++ b/bevy_kayak_ui/src/render/text/font.rs @@ -0,0 +1,8 @@ +use bevy::reflect::TypeUuid; +use kayak_font::Font; + +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"] +pub struct KayakFont { + pub font: Font, +} diff --git a/bevy_kayak_ui/src/render/text/font_mapping.rs b/bevy_kayak_ui/src/render/text/font_mapping.rs new file mode 100644 index 0000000..eeb405a --- /dev/null +++ b/bevy_kayak_ui/src/render/text/font_mapping.rs @@ -0,0 +1,40 @@ +use bevy::{prelude::Handle, utils::HashMap}; + +use super::font::KayakFont; + +pub struct FontMapping { + count: u16, + font_ids: HashMap<Handle<KayakFont>, u16>, + font_handles: HashMap<u16, Handle<KayakFont>>, +} + +impl Default for FontMapping { + fn default() -> Self { + Self { + count: 0, + font_ids: HashMap::default(), + font_handles: HashMap::default(), + } + } +} + +impl FontMapping { + pub(crate) fn add(&mut self, handle: Handle<KayakFont>) -> u16 { + if !self.font_ids.contains_key(&handle) { + let id = self.count; + self.font_ids.insert(handle.clone(), id); + self.font_handles.insert(id, handle); + self.count += 1; + + id + } else { + *self.font_ids.get(&handle).unwrap() + } + } + + pub(crate) fn get_handle(&self, id: u16) -> Option<Handle<KayakFont>> { + self.font_handles + .get(&id) + .and_then(|item| Some(item.clone())) + } +} diff --git a/bevy_kayak_ui/src/render/text/font_texture_cache.rs b/bevy_kayak_ui/src/render/text/font_texture_cache.rs new file mode 100644 index 0000000..a15bb07 --- /dev/null +++ b/bevy_kayak_ui/src/render/text/font_texture_cache.rs @@ -0,0 +1,201 @@ +use bevy::{ + prelude::Handle, + render2::{ + render_resource::{ + AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, Extent3d, + FilterMode, ImageCopyTexture, ImageDataLayout, Origin3d, SamplerDescriptor, + TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + TextureViewDescriptor, TextureViewDimension, + }, + renderer::{RenderDevice, RenderQueue}, + texture::{GpuImage, TextureFormatPixelInfo}, + }, + utils::HashMap, +}; + +use super::{font::KayakFont, pipeline::TextPipeline}; + +pub const MAX_CHARACTERS: u32 = 100; + +pub struct FontTextureCache { + images: HashMap<Handle<KayakFont>, GpuImage>, + pub(crate) bind_groups: HashMap<Handle<KayakFont>, BindGroup>, + fonts: HashMap<Handle<KayakFont>, KayakFont>, + new_fonts: Vec<Handle<KayakFont>>, + updated_fonts: Vec<Handle<KayakFont>>, +} + +impl Default for FontTextureCache { + fn default() -> Self { + Self::new() + } +} + +impl FontTextureCache { + pub fn new() -> Self { + Self { + images: HashMap::default(), + bind_groups: HashMap::default(), + fonts: HashMap::default(), + new_fonts: Vec::new(), + updated_fonts: Vec::new(), + } + } + + pub fn add(&mut self, kayak_font_handle: Handle<KayakFont>, font: KayakFont) { + if !self.fonts.contains_key(&kayak_font_handle) { + self.fonts.insert(kayak_font_handle.clone(), font); + self.new_fonts.push(kayak_font_handle); + } else { + if let Some(old_font) = self.fonts.get_mut(&kayak_font_handle) { + *old_font = font; + self.updated_fonts.push(kayak_font_handle); + } + } + } + + pub fn process_new(&mut self, device: &RenderDevice, pipeline: &TextPipeline) { + let new_fonts = self.new_fonts.drain(..); + for kayak_font_handle in new_fonts { + if let Some(font) = self.fonts.get(&kayak_font_handle) { + Self::create_texture( + &mut self.images, + kayak_font_handle.clone_weak(), + font, + device, + ); + + let gpu_image = self.images.get(&kayak_font_handle).unwrap(); + + // create bind group + let binding = device.create_bind_group(&BindGroupDescriptor { + label: Some("text_image_bind_group"), + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&gpu_image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + layout: &pipeline.image_layout, + }); + + self.bind_groups.insert(kayak_font_handle, binding); + } + } + } + + pub fn process_updated(&mut self, queue: &RenderQueue) { + let updated_fonts = self.updated_fonts.drain(..); + for kayak_font_handle in updated_fonts { + if let Some(font) = self.fonts.get_mut(&kayak_font_handle) { + Self::process_new_chars_into_texture( + &mut self.images, + kayak_font_handle, + font, + queue, + ); + } + } + } + + fn create_texture( + images: &mut HashMap<Handle<KayakFont>, GpuImage>, + font_handle: Handle<KayakFont>, + font: &KayakFont, + device: &RenderDevice, + ) { + let texture_descriptor = TextureDescriptor { + label: Some("font_texture_array"), + size: Extent3d { + width: font.font.cache.dimensions, + height: font.font.cache.dimensions, + depth_or_array_layers: MAX_CHARACTERS, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba32Float, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }; + + let sampler_descriptor = SamplerDescriptor { + label: Some("font_texture_array_sampler"), + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + mipmap_filter: FilterMode::Nearest, + lod_min_clamp: 0.0, + lod_max_clamp: std::f32::MAX, + compare: None, + anisotropy_clamp: None, + border_color: None, + }; + + let texture = device.create_texture(&texture_descriptor); + let sampler = device.create_sampler(&sampler_descriptor); + + let texture_view = texture.create_view(&TextureViewDescriptor { + label: Some("font_texture_array_view"), + format: None, + dimension: Some(TextureViewDimension::D2Array), + aspect: bevy::render2::render_resource::TextureAspect::All, + base_mip_level: 0, + base_array_layer: 0, + mip_level_count: None, + array_layer_count: std::num::NonZeroU32::new(MAX_CHARACTERS), + }); + + let image = GpuImage { + texture, + sampler, + texture_view, + }; + + images.insert(font_handle, image); + } + + pub fn process_new_chars_into_texture( + images: &mut HashMap<Handle<KayakFont>, GpuImage>, + kayak_font_handle: Handle<KayakFont>, + font: &mut KayakFont, + queue: &RenderQueue, + ) { + let size = font.font.cache.dimensions; + if let Some(gpu_image) = images.get_mut(&kayak_font_handle) { + for (_, id, pixels) in font.font.get_data_to_process() { + let format_size = TextureFormat::Rgba32Float.pixel_size(); + queue.write_texture( + ImageCopyTexture { + texture: &gpu_image.texture, + mip_level: 0, + origin: Origin3d { + x: 0, + y: 0, + z: id as u32, + }, + aspect: TextureAspect::All, + }, + &pixels, + ImageDataLayout { + offset: 0, + bytes_per_row: Some( + std::num::NonZeroU32::new(size * format_size as u32).unwrap(), + ), + rows_per_image: None, + }, + Extent3d { + width: size, + height: size, + depth_or_array_layers: 1, + }, + ); + } + } + } +} diff --git a/bevy_kayak_ui/src/render/text/mod.rs b/bevy_kayak_ui/src/render/text/mod.rs new file mode 100644 index 0000000..bfe4381 --- /dev/null +++ b/bevy_kayak_ui/src/render/text/mod.rs @@ -0,0 +1,141 @@ +use bevy::{ + prelude::{ + AddAsset, AssetEvent, Assets, Commands, EventReader, Handle, HandleUntyped, Plugin, Res, + ResMut, + }, + reflect::TypeUuid, + render2::{ + render_phase::DrawFunctions, + render_resource::Shader, + renderer::{RenderDevice, RenderQueue}, + RenderApp, RenderStage, + }, + utils::HashSet, +}; + +use crate::render::{ + text::{ + font_mapping::FontMapping, + pipeline::{DrawText, TextMeta, TextPipeline}, + }, + ui_pass::TransparentUI, +}; + +use self::{font::KayakFont, font_texture_cache::FontTextureCache}; + +mod font; +mod font_mapping; +mod font_texture_cache; +mod pipeline; + +pub const TEXT_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1561765330850392701); + +#[derive(Default)] +pub struct TextRendererPlugin; + +impl Plugin for TextRendererPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap(); + let text_shader = Shader::from_wgsl(include_str!("shader.wgsl")); + shaders.set_untracked(TEXT_SHADER_HANDLE, text_shader); + + let render_app = app.sub_app(RenderApp); + render_app + .init_resource::<TextPipeline>() + .init_resource::<TextMeta>() + .add_system_to_stage(RenderStage::Extract, pipeline::extract_texts) + .add_system_to_stage(RenderStage::Prepare, pipeline::prepare_texts) + .add_system_to_stage(RenderStage::Queue, pipeline::queue_texts); + + let draw_text = DrawText::new(&mut render_app.world); + + render_app + .world + .get_resource::<DrawFunctions<TransparentUI>>() + .unwrap() + .write() + .add(draw_text); + + render_app + .init_resource::<FontTextureCache>() + .init_resource::<ExtractedFonts>() + .add_system_to_stage(RenderStage::Extract, extract_fonts) + .add_system_to_stage(RenderStage::Prepare, prepare_fonts) + .add_system_to_stage(RenderStage::Queue, create_and_update_font_cache_texture); + + app.add_asset::<KayakFont>() + .init_resource::<FontMapping>() + .add_startup_system(load_fonts); + } +} + +#[derive(Default)] +pub struct ExtractedFonts { + pub fonts: Vec<(Handle<KayakFont>, KayakFont)>, +} + +fn load_fonts(mut font_assets: ResMut<Assets<KayakFont>>, mut font_mapping: ResMut<FontMapping>) { + let font_bytes = include_bytes!("../../../../resources/Roboto-Regular.ttf"); + let font = kayak_font::Font::new(font_bytes, 128); + + let handle = font_assets.add(KayakFont { font }); + font_mapping.add(handle); + + dbg!("Loaded base font!"); +} + +fn extract_fonts( + mut commands: Commands, + font_assets: Res<Assets<KayakFont>>, + mut events: EventReader<AssetEvent<KayakFont>>, +) { + let mut extracted_fonts = ExtractedFonts { fonts: Vec::new() }; + let mut changed_assets = HashSet::default(); + let mut removed = Vec::new(); + for event in events.iter() { + match event { + AssetEvent::Created { handle } => { + changed_assets.insert(handle); + dbg!("New font added!"); + } + AssetEvent::Modified { handle } => { + changed_assets.insert(handle); + dbg!("Font changed!"); + } + AssetEvent::Removed { handle } => { + if !changed_assets.remove(handle) { + removed.push(handle.clone_weak()); + } + } + } + } + + for handle in changed_assets { + let font_asset = font_assets.get(handle).unwrap(); + let font = font_asset.clone(); + + extracted_fonts.fonts.push((handle.clone_weak(), font)); + } + + commands.insert_resource(extracted_fonts); +} + +fn prepare_fonts( + mut extracted_fonts: ResMut<ExtractedFonts>, + mut font_texture_cache: ResMut<FontTextureCache>, +) { + for (handle, font) in extracted_fonts.fonts.drain(..) { + font_texture_cache.add(handle, font); + } +} + +fn create_and_update_font_cache_texture( + device: Res<RenderDevice>, + queue: Res<RenderQueue>, + pipeline: Res<TextPipeline>, + mut font_texture_cache: ResMut<FontTextureCache>, +) { + font_texture_cache.process_new(&device, &pipeline); + font_texture_cache.process_updated(&queue); +} diff --git a/bevy_kayak_ui/src/render/text/pipeline.rs b/bevy_kayak_ui/src/render/text/pipeline.rs new file mode 100644 index 0000000..6b2691e --- /dev/null +++ b/bevy_kayak_ui/src/render/text/pipeline.rs @@ -0,0 +1,360 @@ +use bevy::{ + core::FloatOrd, + ecs::system::{ + lifetimeless::{Read, SQuery, SRes}, + SystemState, + }, + math::{const_vec3, Mat4, Quat, Vec2, Vec3}, + prelude::{ + Assets, Bundle, Color, Commands, Entity, FromWorld, Handle, Query, Res, ResMut, World, + }, + render2::{ + render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, + render_resource::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent, + BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages, + BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, CompareFunction, + DepthBiasState, DepthStencilState, FragmentState, FrontFace, MultisampleState, + PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineCache, + RenderPipelineDescriptor, Shader, ShaderStages, StencilFaceState, StencilState, + TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute, + VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + }, + renderer::{RenderDevice, RenderQueue}, + texture::BevyDefault, + view::{ViewUniformOffset, ViewUniforms}, + }, + sprite2::Rect, +}; +use bytemuck::{Pod, Zeroable}; +use kayak_core::render_primitive::RenderPrimitive; + +use crate::{ + render::{text::TEXT_SHADER_HANDLE, ui_pass::TransparentUI}, + to_bevy_color, BevyContext, +}; + +use super::{font::KayakFont, font_mapping::FontMapping, font_texture_cache::FontTextureCache}; + +pub struct TextPipeline { + view_layout: BindGroupLayout, + pub(crate) image_layout: BindGroupLayout, + pipeline: CachedPipelineId, +} + +const QUAD_VERTEX_POSITIONS: &[Vec3] = &[ + const_vec3!([0.0, 0.0, 0.0]), + const_vec3!([1.0, 1.0, 0.0]), + const_vec3!([0.0, 1.0, 0.0]), + const_vec3!([0.0, 0.0, 0.0]), + const_vec3!([1.0, 0.0, 0.0]), + const_vec3!([1.0, 1.0, 0.0]), +]; + +impl FromWorld for TextPipeline { + fn from_world(world: &mut World) -> Self { + let world = world.cell(); + let render_device = world.get_resource::<RenderDevice>().unwrap(); + let mut pipeline_cache = world.get_resource_mut::<RenderPipelineCache>().unwrap(); + + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + // TODO: change this to ViewUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(144), + }, + count: None, + }], + label: Some("text_view_layout"), + }); + + let image_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2Array, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, + ], + label: Some("text_image_layout"), + }); + + let vertex_buffer_layout = VertexBufferLayout { + array_stride: 40, + step_mode: VertexStepMode::Vertex, + attributes: vec![ + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + VertexAttribute { + format: VertexFormat::Float32x4, + offset: 12, + shader_location: 1, + }, + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 28, + shader_location: 2, + }, + ], + }; + + let pipeline_desc = RenderPipelineDescriptor { + vertex: VertexState { + shader: TEXT_SHADER_HANDLE.typed::<Shader>(), + entry_point: "vertex".into(), + shader_defs: vec![], + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: TEXT_SHADER_HANDLE.typed::<Shader>(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![ColorTargetState { + format: TextureFormat::bevy_default(), + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }), + write_mask: ColorWrites::ALL, + }], + }), + layout: Some(vec![view_layout.clone(), image_layout.clone()]), + primitive: PrimitiveState { + front_face: FrontFace::Ccw, + cull_mode: None, + polygon_mode: PolygonMode::Fill, + clamp_depth: false, + conservative: false, + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + }, + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: CompareFunction::Greater, + stencil: StencilState { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("text_pipeline".into()), + }; + + TextPipeline { + pipeline: pipeline_cache.queue(pipeline_desc), + view_layout, + image_layout, + } + } +} + + + +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +struct TextVertex { + pub position: [f32; 3], + pub color: [f32; 4], + pub uv: [f32; 3], +} + +pub struct TextMeta { + vertices: BufferVec<TextVertex>, + view_bind_group: Option<BindGroup>, +} + +impl Default for TextMeta { + fn default() -> Self { + Self { + vertices: BufferVec::new(BufferUsages::VERTEX), + view_bind_group: None, + } + } +} + +pub fn prepare_texts( + render_device: Res<RenderDevice>, + render_queue: Res<RenderQueue>, + mut sprite_meta: ResMut<TextMeta>, + mut extracted_sprites: Query<&mut ExtractedText>, +) { + let extracted_sprite_len = extracted_sprites.iter_mut().len(); + // dont create buffers when there are no texts + if extracted_sprite_len == 0 { + return; + } + + sprite_meta.vertices.clear(); + sprite_meta.vertices.reserve( + extracted_sprite_len * QUAD_VERTEX_POSITIONS.len(), + &render_device, + ); + + for (i, mut extracted_sprite) in extracted_sprites.iter_mut().enumerate() { + let sprite_rect = extracted_sprite.rect; + let color = extracted_sprite.background_color.as_linear_rgba_f32(); + + let bottom_left = Vec3::new(0.0, 1.0, extracted_sprite.char_id as f32); + let top_left = Vec3::new(0.0, 0.0, extracted_sprite.char_id as f32); + let top_right = Vec3::new(1.0, 0.0, extracted_sprite.char_id as f32); + let bottom_right = Vec3::new(1.0, 1.0, extracted_sprite.char_id as f32); + + let uvs: [[f32; 3]; 6] = [ + bottom_left.into(), + top_right.into(), + top_left.into(), + bottom_left.into(), + bottom_right.into(), + top_right.into(), + ]; + + extracted_sprite.vertex_index = i; + for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() { + let world = Mat4::from_scale_rotation_translation( + sprite_rect.size().extend(1.0), + Quat::default(), + sprite_rect.min.extend(extracted_sprite.z_index), + ); + let final_position = (world * Vec3::from(*vertex_position).extend(1.0)).truncate(); + sprite_meta.vertices.push(TextVertex { + position: final_position.into(), + color, + uv: uvs[index], + }); + } + } + sprite_meta + .vertices + .write_buffer(&render_device, &render_queue); +} + +pub fn queue_texts( + draw_functions: Res<DrawFunctions<TransparentUI>>, + render_device: Res<RenderDevice>, + mut sprite_meta: ResMut<TextMeta>, + view_uniforms: Res<ViewUniforms>, + text_pipeline: Res<TextPipeline>, + mut extracted_sprites: Query<(Entity, &ExtractedText)>, + mut views: Query<&mut RenderPhase<TransparentUI>>, +) { + if let Some(view_binding) = view_uniforms.uniforms.binding() { + sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("text_view_bind_group"), + layout: &text_pipeline.view_layout, + })); + let draw_text = draw_functions.read().get_id::<DrawText>().unwrap(); + for mut transparent_phase in views.iter_mut() { + for (entity, _) in extracted_sprites.iter_mut() { + transparent_phase.add(TransparentUI { + draw_function: draw_text, + pipeline: text_pipeline.pipeline, + entity, + sort_key: FloatOrd(0.0), + }); + } + } + } +} + +pub struct DrawText { + params: SystemState<( + SRes<TextMeta>, + SRes<RenderPipelineCache>, + SRes<FontTextureCache>, + SQuery<Read<ViewUniformOffset>>, + SQuery<Read<ExtractedText>>, + )>, +} + +impl DrawText { + pub fn new(world: &mut World) -> Self { + Self { + params: SystemState::new(world), + } + } +} + +impl Draw<TransparentUI> for DrawText { + fn draw<'w>( + &mut self, + world: &'w World, + pass: &mut TrackedRenderPass<'w>, + view: Entity, + item: &TransparentUI, + ) { + let (text_meta, pipelines, font_texture_cache, views, texts) = self.params.get(world); + let view_uniform = views.get(view).unwrap(); + let text_meta = text_meta.into_inner(); + let extracted_text = texts.get(item.entity).unwrap(); + if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) { + pass.set_render_pipeline(pipeline); + pass.set_vertex_buffer(0, text_meta.vertices.buffer().unwrap().slice(..)); + pass.set_bind_group( + 0, + text_meta.view_bind_group.as_ref().unwrap(), + &[view_uniform.offset], + ); + + if let Some(image_bindings) = font_texture_cache + .into_inner() + .bind_groups + .get(&extracted_text.font_handle) + { + pass.set_bind_group(1, image_bindings, &[]); + + pass.draw( + (extracted_text.vertex_index * QUAD_VERTEX_POSITIONS.len()) as u32 + ..((extracted_text.vertex_index + 1) * QUAD_VERTEX_POSITIONS.len()) as u32, + 0..1, + ); + } + } + } +} diff --git a/bevy_kayak_ui/src/render/text/shader.wgsl b/bevy_kayak_ui/src/render/text/shader.wgsl new file mode 100644 index 0000000..5a2b1ec --- /dev/null +++ b/bevy_kayak_ui/src/render/text/shader.wgsl @@ -0,0 +1,45 @@ +[[block]] +struct View { + view_proj: mat4x4<f32>; + world_position: vec3<f32>; +}; +[[group(0), binding(0)]] +var<uniform> view: View; + +struct VertexOutput { + [[builtin(position)]] position: vec4<f32>; + [[location(0)]] color: vec4<f32>; + [[location(1)]] uv: vec3<f32>; +}; + +[[stage(vertex)]] +fn vertex( + [[location(0)]] vertex_position: vec3<f32>, + [[location(1)]] vertex_color: vec4<f32>, + [[location(2)]] vertex_uv: vec3<f32>, +) -> VertexOutput { + var out: VertexOutput; + out.color = vertex_color; + out.uv = vertex_uv; + out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); + return out; +} + +[[group(1), binding(0)]] +var sprite_texture: texture_2d_array<f32>; +[[group(1), binding(1)]] +var sprite_sampler: sampler; + +let RADIUS: f32 = 0.5; + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { + var x = textureSample(sprite_texture, sprite_sampler, in.uv.xy, i32(in.uv.z)); + var v = max(min(x.r, x.g), min(max(x.r, x.g), x.b)); + var c = v; //remap(v); + + var v2 = c / fwidth( c ); + var a = v2 + RADIUS; //clamp( v2 + RADIUS, 0.0, 1.0 ); + + return vec4<f32>(in.color.rgb, a); +} \ No newline at end of file diff --git a/bevy_kayak_ui/src/render/ui_pass.rs b/bevy_kayak_ui/src/render/ui_pass.rs new file mode 100644 index 0000000..d04383c --- /dev/null +++ b/bevy_kayak_ui/src/render/ui_pass.rs @@ -0,0 +1,102 @@ +use bevy::core::FloatOrd; +use bevy::core_pipeline::ClearColor; +use bevy::ecs::prelude::*; +use bevy::render2::render_phase::{DrawFunctionId, PhaseItem}; +use bevy::render2::render_resource::{CachedPipelineId, RenderPassColorAttachment}; +use bevy::render2::{ + render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass}, + render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor}, + renderer::RenderContext, + view::{ExtractedView, ViewDepthTexture, ViewTarget}, +}; + +pub struct TransparentUI { + pub sort_key: FloatOrd, + pub entity: Entity, + pub pipeline: CachedPipelineId, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for TransparentUI { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + self.sort_key + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} + +pub struct MainPassUINode { + query: + QueryState<(&'static RenderPhase<TransparentUI>, &'static ViewTarget), With<ExtractedView>>, +} + +impl MainPassUINode { + pub const IN_VIEW: &'static str = "view"; + + pub fn new(world: &mut World) -> Self { + Self { + query: QueryState::new(world), + } + } +} + +impl Node for MainPassUINode { + fn input(&self) -> Vec<SlotInfo> { + vec![SlotInfo::new(MainPassUINode::IN_VIEW, SlotType::Entity)] + } + + fn update(&mut self, world: &mut World) { + self.query.update_archetypes(world); + } + + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let (transparent_phase, target) = self + .query + .get_manual(world, view_entity) + .expect("view entity should exist"); + let clear_color = world.get_resource::<ClearColor>().unwrap(); + { + let pass_descriptor = RenderPassDescriptor { + label: Some("main_transparent_pass_UI"), + color_attachments: &[RenderPassColorAttachment { + view: &target.view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(clear_color.0.into()), + store: true, + }, + }], + depth_stencil_attachment: None, + }; + + let draw_functions = world + .get_resource::<DrawFunctions<TransparentUI>>() + .unwrap(); + + let render_pass = render_context + .command_encoder + .begin_render_pass(&pass_descriptor); + let mut draw_functions = draw_functions.write(); + let mut tracked_pass = TrackedRenderPass::new(render_pass); + for item in transparent_phase.items.iter() { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_entity, item); + } + } + + Ok(()) + } +} diff --git a/bevy_kayak_ui/src/render/ui_pass_driver.rs b/bevy_kayak_ui/src/render/ui_pass_driver.rs new file mode 100644 index 0000000..edce5b8 --- /dev/null +++ b/bevy_kayak_ui/src/render/ui_pass_driver.rs @@ -0,0 +1,29 @@ +use bevy::ecs::world::World; +use bevy::render2::{ + camera::{CameraPlugin, ExtractedCameraNames}, + render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue}, + renderer::RenderContext, +}; + +use crate::UICameraBundle; + +pub struct UIPassDriverNode; + +impl Node for UIPassDriverNode { + fn run( + &self, + graph: &mut RenderGraphContext, + _render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let extracted_cameras = world.get_resource::<ExtractedCameraNames>().unwrap(); + if let Some(camera_ui) = extracted_cameras.entities.get(UICameraBundle::UI_CAMERA) { + graph.run_sub_graph( + super::draw_ui_graph::NAME, + vec![SlotValue::Entity(*camera_ui)], + )?; + } + + Ok(()) + } +} diff --git a/bevy_kayak_ui/src/render/unified/font/extract.rs b/bevy_kayak_ui/src/render/unified/font/extract.rs new file mode 100644 index 0000000..2b55316 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/font/extract.rs @@ -0,0 +1,87 @@ +use bevy::{ + math::Vec2, + prelude::{Assets, Bundle, Commands, Handle, Res, ResMut}, + render2::color::Color, + sprite2::Rect, +}; +use kayak_core::render_primitive::RenderPrimitive; + +use crate::{ + render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType}, + to_bevy_color, BevyContext, +}; + +use super::{font::KayakFont, font_mapping::FontMapping}; + +pub fn extract_texts( + mut commands: Commands, + context: Res<BevyContext>, + mut fonts: ResMut<Assets<KayakFont>>, + font_mapping: Res<FontMapping>, +) { + let render_commands = if let Ok(context) = context.kayak_context.read() { + context.widget_manager.build_render_primitives() + } else { + vec![] + }; + + let text_commands: Vec<&RenderPrimitive> = render_commands + .iter() + .filter(|command| matches!(command, RenderPrimitive::Text { .. })) + .collect::<Vec<_>>(); + + let mut extracted_texts = Vec::new(); + for render_primitive in text_commands { + let (background_color, layout, font_size, content, font) = match render_primitive { + RenderPrimitive::Text { + color, + layout, + size, + content, + font, + } => (color, layout, *size, content, *font), + _ => panic!(""), + }; + + let font_handle = font_mapping.get_handle(font).unwrap(); + let new_chars = { + let font = fonts.get(font_handle.clone()).unwrap(); + font.font.check_chars(content.chars()) + }; + // Filter out non-renderable spaces. + let new_chars: Vec<_> = new_chars.into_iter().filter(|c| *c != ' ').collect(); + // Add chars to font. + if new_chars.len() > 0 { + let font = fonts.get_mut(font_handle.clone()).unwrap(); + for c in new_chars { + dbg!("Adding char: "); + dbg!(c); + font.font.add_character(c); + } + } + let font = fonts.get(font_handle.clone()).unwrap(); + let char_layouts = font.font.get_layout(content, font_size); + + for (c, (x, y), (width, height)) in char_layouts { + // let size = font.font.get_size(c, font_size); + let position_x = layout.posx + x; + let position_y = layout.posy + y; + extracted_texts.push(ExtractQuadBundle { + extracted_quad: ExtractedQuad { + font_handle: Some(font_handle.clone()), + rect: Rect { + min: Vec2::new(position_x, position_y), + max: Vec2::new(position_x + width, position_y + height), + }, + color: to_bevy_color(background_color), + vertex_index: 0, + char_id: font.font.get_char_id(c), + z_index: layout.z_index, + quad_type: UIQuadType::Text, + type_index: 0, + }, + }); + } + } + commands.spawn_batch(extracted_texts); +} diff --git a/bevy_kayak_ui/src/render/unified/font/font.rs b/bevy_kayak_ui/src/render/unified/font/font.rs new file mode 100644 index 0000000..7a4424c --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/font/font.rs @@ -0,0 +1,8 @@ +use bevy::reflect::TypeUuid; +use kayak_font::Font; + +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"] +pub struct KayakFont { + pub font: Font, +} diff --git a/bevy_kayak_ui/src/render/unified/font/font_mapping.rs b/bevy_kayak_ui/src/render/unified/font/font_mapping.rs new file mode 100644 index 0000000..eeb405a --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/font/font_mapping.rs @@ -0,0 +1,40 @@ +use bevy::{prelude::Handle, utils::HashMap}; + +use super::font::KayakFont; + +pub struct FontMapping { + count: u16, + font_ids: HashMap<Handle<KayakFont>, u16>, + font_handles: HashMap<u16, Handle<KayakFont>>, +} + +impl Default for FontMapping { + fn default() -> Self { + Self { + count: 0, + font_ids: HashMap::default(), + font_handles: HashMap::default(), + } + } +} + +impl FontMapping { + pub(crate) fn add(&mut self, handle: Handle<KayakFont>) -> u16 { + if !self.font_ids.contains_key(&handle) { + let id = self.count; + self.font_ids.insert(handle.clone(), id); + self.font_handles.insert(id, handle); + self.count += 1; + + id + } else { + *self.font_ids.get(&handle).unwrap() + } + } + + pub(crate) fn get_handle(&self, id: u16) -> Option<Handle<KayakFont>> { + self.font_handles + .get(&id) + .and_then(|item| Some(item.clone())) + } +} diff --git a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs new file mode 100644 index 0000000..d96b6d0 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs @@ -0,0 +1,248 @@ +use bevy::{prelude::Handle, render2::{render_resource::{AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindingResource, Extent3d, FilterMode, ImageCopyTexture, ImageDataLayout, Origin3d, SamplerDescriptor, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor, TextureViewDimension}, renderer::{RenderDevice, RenderQueue}, texture::{GpuImage, TextureFormatPixelInfo}}, utils::HashMap}; + +use crate::render::unified::pipeline::UnifiedPipeline; + +use super::font::KayakFont; + +pub const MAX_CHARACTERS: u32 = 100; + +pub struct FontTextureCache { + images: HashMap<Handle<KayakFont>, GpuImage>, + pub(crate) bind_groups: HashMap<Handle<KayakFont>, BindGroup>, + fonts: HashMap<Handle<KayakFont>, KayakFont>, + new_fonts: Vec<Handle<KayakFont>>, + updated_fonts: Vec<Handle<KayakFont>>, +} + +impl Default for FontTextureCache { + fn default() -> Self { + Self::new() + } +} + +impl FontTextureCache { + pub fn new() -> Self { + Self { + images: HashMap::default(), + bind_groups: HashMap::default(), + fonts: HashMap::default(), + new_fonts: Vec::new(), + updated_fonts: Vec::new(), + } + } + + pub fn add(&mut self, kayak_font_handle: Handle<KayakFont>, font: KayakFont) { + if !self.fonts.contains_key(&kayak_font_handle) { + self.fonts.insert(kayak_font_handle.clone(), font); + self.new_fonts.push(kayak_font_handle); + } else { + if let Some(old_font) = self.fonts.get_mut(&kayak_font_handle) { + *old_font = font; + self.updated_fonts.push(kayak_font_handle); + } + } + } + + pub fn process_new(&mut self, device: &RenderDevice, pipeline: &UnifiedPipeline) { + let new_fonts = self.new_fonts.drain(..); + for kayak_font_handle in new_fonts { + if let Some(font) = self.fonts.get(&kayak_font_handle) { + Self::create_texture( + &mut self.images, + kayak_font_handle.clone_weak(), + font, + device, + ); + + let gpu_image = self.images.get(&kayak_font_handle).unwrap(); + + // create bind group + let binding = device.create_bind_group(&BindGroupDescriptor { + label: Some("text_image_bind_group"), + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&gpu_image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + layout: &pipeline.image_layout, + }); + + self.bind_groups.insert(kayak_font_handle, binding); + } + } + } + + pub fn process_updated(&mut self, queue: &RenderQueue) { + let updated_fonts = self.updated_fonts.drain(..); + for kayak_font_handle in updated_fonts { + if let Some(font) = self.fonts.get_mut(&kayak_font_handle) { + Self::process_new_chars_into_texture( + &mut self.images, + kayak_font_handle, + font, + queue, + ); + } + } + } + + fn create_texture( + images: &mut HashMap<Handle<KayakFont>, GpuImage>, + font_handle: Handle<KayakFont>, + font: &KayakFont, + device: &RenderDevice, + ) { + let texture_descriptor = TextureDescriptor { + label: Some("font_texture_array"), + size: Extent3d { + width: font.font.cache.dimensions, + height: font.font.cache.dimensions, + depth_or_array_layers: MAX_CHARACTERS, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba32Float, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }; + + let sampler_descriptor = SamplerDescriptor { + label: Some("font_texture_array_sampler"), + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + mipmap_filter: FilterMode::Nearest, + lod_min_clamp: 0.0, + lod_max_clamp: std::f32::MAX, + compare: None, + anisotropy_clamp: None, + border_color: None, + }; + + let texture = device.create_texture(&texture_descriptor); + let sampler = device.create_sampler(&sampler_descriptor); + + let texture_view = texture.create_view(&TextureViewDescriptor { + label: Some("font_texture_array_view"), + format: None, + dimension: Some(TextureViewDimension::D2Array), + aspect: bevy::render2::render_resource::TextureAspect::All, + base_mip_level: 0, + base_array_layer: 0, + mip_level_count: None, + array_layer_count: std::num::NonZeroU32::new(MAX_CHARACTERS), + }); + + let image = GpuImage { + texture, + sampler, + texture_view, + }; + + images.insert(font_handle, image); + } + + pub fn process_new_chars_into_texture( + images: &mut HashMap<Handle<KayakFont>, GpuImage>, + kayak_font_handle: Handle<KayakFont>, + font: &mut KayakFont, + queue: &RenderQueue, + ) { + let size = font.font.cache.dimensions; + if let Some(gpu_image) = images.get_mut(&kayak_font_handle) { + for (_, id, pixels) in font.font.get_data_to_process() { + let format_size = TextureFormat::Rgba32Float.pixel_size(); + queue.write_texture( + ImageCopyTexture { + texture: &gpu_image.texture, + mip_level: 0, + origin: Origin3d { + x: 0, + y: 0, + z: id as u32, + }, + aspect: TextureAspect::All, + }, + &pixels, + ImageDataLayout { + offset: 0, + bytes_per_row: Some( + std::num::NonZeroU32::new(size * format_size as u32).unwrap(), + ), + rows_per_image: None, + }, + Extent3d { + width: size, + height: size, + depth_or_array_layers: 1, + }, + ); + } + } + } + + pub fn get_empty( + device: &RenderDevice, + image_layout: &BindGroupLayout, + ) -> (GpuImage, BindGroup) { + let texture_descriptor = TextureDescriptor { + label: Some("font_texture_array"), + size: Extent3d { + width: 1, + height: 1, + depth_or_array_layers: MAX_CHARACTERS, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba32Float, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }; + + let sampler_descriptor = SamplerDescriptor::default(); + + let texture = device.create_texture(&texture_descriptor); + let sampler = device.create_sampler(&sampler_descriptor); + + let texture_view = texture.create_view(&TextureViewDescriptor { + label: Some("font_texture_array_view"), + format: None, + dimension: Some(TextureViewDimension::D2Array), + aspect: bevy::render2::render_resource::TextureAspect::All, + base_mip_level: 0, + base_array_layer: 0, + mip_level_count: None, + array_layer_count: std::num::NonZeroU32::new(MAX_CHARACTERS), + }); + + let image = GpuImage { + texture, + sampler, + texture_view, + }; + + let binding = device.create_bind_group(&BindGroupDescriptor { + label: Some("text_image_bind_group"), + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&image.sampler), + }, + ], + layout: image_layout, + }); + + (image, binding) + } +} diff --git a/bevy_kayak_ui/src/render/unified/font/mod.rs b/bevy_kayak_ui/src/render/unified/font/mod.rs new file mode 100644 index 0000000..e356d38 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/font/mod.rs @@ -0,0 +1,112 @@ +use bevy::{ + prelude::{AddAsset, AssetEvent, Assets, Commands, EventReader, Handle, Plugin, Res, ResMut}, + render2::{ + renderer::{RenderDevice, RenderQueue}, + RenderApp, RenderStage, + }, + utils::HashSet, +}; + +use self::extract::extract_texts; + +use super::pipeline::UnifiedPipeline; + +mod extract; +mod font; +mod font_mapping; +mod font_texture_cache; + +pub use font::*; +pub use font_mapping::*; +pub(crate) use font_texture_cache::FontTextureCache; + +#[derive(Default)] +pub struct TextRendererPlugin; + +impl Plugin for TextRendererPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.add_asset::<KayakFont>() + .init_resource::<FontMapping>() + .add_startup_system(load_fonts); + + let render_app = app.sub_app(RenderApp); + render_app.add_system_to_stage(RenderStage::Extract, extract_texts); + + render_app + .init_resource::<FontTextureCache>() + .init_resource::<ExtractedFonts>() + .add_system_to_stage(RenderStage::Extract, extract_fonts) + .add_system_to_stage(RenderStage::Prepare, prepare_fonts) + .add_system_to_stage(RenderStage::Queue, create_and_update_font_cache_texture); + } +} + +#[derive(Default)] +pub struct ExtractedFonts { + pub fonts: Vec<(Handle<KayakFont>, KayakFont)>, +} + +fn load_fonts(mut font_assets: ResMut<Assets<KayakFont>>, mut font_mapping: ResMut<FontMapping>) { + let font_bytes = include_bytes!("../../../../../resources/Roboto-Regular.ttf"); + let font = kayak_font::Font::new(font_bytes, 128); + + let handle = font_assets.add(KayakFont { font }); + font_mapping.add(handle); + + dbg!("Loaded base font!"); +} + +fn extract_fonts( + mut commands: Commands, + font_assets: Res<Assets<KayakFont>>, + mut events: EventReader<AssetEvent<KayakFont>>, +) { + let mut extracted_fonts = ExtractedFonts { fonts: Vec::new() }; + let mut changed_assets = HashSet::default(); + let mut removed = Vec::new(); + for event in events.iter() { + match event { + AssetEvent::Created { handle } => { + changed_assets.insert(handle); + dbg!("New font added!"); + } + AssetEvent::Modified { handle } => { + changed_assets.insert(handle); + dbg!("Font changed!"); + } + AssetEvent::Removed { handle } => { + if !changed_assets.remove(handle) { + removed.push(handle.clone_weak()); + } + } + } + } + + for handle in changed_assets { + let font_asset = font_assets.get(handle).unwrap(); + let font = font_asset.clone(); + + extracted_fonts.fonts.push((handle.clone_weak(), font)); + } + + commands.insert_resource(extracted_fonts); +} + +fn prepare_fonts( + mut extracted_fonts: ResMut<ExtractedFonts>, + mut font_texture_cache: ResMut<FontTextureCache>, +) { + for (handle, font) in extracted_fonts.fonts.drain(..) { + font_texture_cache.add(handle, font); + } +} + +fn create_and_update_font_cache_texture( + device: Res<RenderDevice>, + queue: Res<RenderQueue>, + pipeline: Res<UnifiedPipeline>, + mut font_texture_cache: ResMut<FontTextureCache>, +) { + font_texture_cache.process_new(&device, &pipeline); + font_texture_cache.process_updated(&queue); +} diff --git a/bevy_kayak_ui/src/render/unified/mod.rs b/bevy_kayak_ui/src/render/unified/mod.rs new file mode 100644 index 0000000..37aeb58 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/mod.rs @@ -0,0 +1,46 @@ +use bevy::{ + prelude::{Assets, HandleUntyped, Plugin}, + reflect::TypeUuid, + render2::{render_phase::DrawFunctions, render_resource::Shader, RenderApp, RenderStage}, +}; + +use crate::render::{ + ui_pass::TransparentUI, + unified::pipeline::{DrawUI, QuadMeta, UnifiedPipeline}, +}; + +pub mod font; +mod pipeline; +mod quad; + +pub const UNIFIED_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7604018236855288450); + +pub struct UnifiedRenderPlugin; + +impl Plugin for UnifiedRenderPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap(); + let unified_shader = Shader::from_wgsl(include_str!("shader.wgsl")); + shaders.set_untracked(UNIFIED_SHADER_HANDLE, unified_shader); + + let render_app = app.sub_app(RenderApp); + render_app + .init_resource::<UnifiedPipeline>() + .init_resource::<QuadMeta>() + .add_system_to_stage(RenderStage::Prepare, pipeline::prepare_quads) + .add_system_to_stage(RenderStage::Queue, pipeline::queue_quads); + + let draw_quad = DrawUI::new(&mut render_app.world); + + render_app + .world + .get_resource::<DrawFunctions<TransparentUI>>() + .unwrap() + .write() + .add(draw_quad); + + app.add_plugin(font::TextRendererPlugin) + .add_plugin(quad::QuadRendererPlugin); + } +} diff --git a/bevy_kayak_ui/src/render/unified/pipeline.rs b/bevy_kayak_ui/src/render/unified/pipeline.rs new file mode 100644 index 0000000..043f0c5 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/pipeline.rs @@ -0,0 +1,438 @@ +use bevy::{ + core::FloatOrd, + ecs::system::{ + lifetimeless::{Read, SQuery, SRes}, + SystemState, + }, + math::{const_vec3, Mat4, Quat, Vec2, Vec3}, + prelude::{Bundle, Commands, Entity, FromWorld, Handle, Query, Res, ResMut, World}, + render2::{ + color::Color, + render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, + render_resource::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent, + BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages, + BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, CompareFunction, + DepthBiasState, DepthStencilState, DynamicUniformVec, FragmentState, FrontFace, + MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineCache, + RenderPipelineDescriptor, Shader, ShaderStages, StencilFaceState, StencilState, + TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute, + VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + }, + renderer::{RenderDevice, RenderQueue}, + texture::{BevyDefault, GpuImage}, + view::{ViewUniformOffset, ViewUniforms}, + }, + sprite2::Rect, +}; +use bytemuck::{Pod, Zeroable}; +use crevice::std140::AsStd140; + +use super::font::{FontTextureCache, KayakFont}; +use super::UNIFIED_SHADER_HANDLE; +use crate::{render::ui_pass::TransparentUI, BevyContext}; + +pub struct UnifiedPipeline { + view_layout: BindGroupLayout, + types_layout: BindGroupLayout, + pub(crate) image_layout: BindGroupLayout, + pipeline: CachedPipelineId, + empty_font_texture: (GpuImage, BindGroup), +} + +const QUAD_VERTEX_POSITIONS: &[Vec3] = &[ + const_vec3!([0.0, 0.0, 0.0]), + const_vec3!([1.0, 1.0, 0.0]), + const_vec3!([0.0, 1.0, 0.0]), + const_vec3!([0.0, 0.0, 0.0]), + const_vec3!([1.0, 0.0, 0.0]), + const_vec3!([1.0, 1.0, 0.0]), +]; + +impl FromWorld for UnifiedPipeline { + fn from_world(world: &mut World) -> Self { + let world = world.cell(); + let render_device = world.get_resource::<RenderDevice>().unwrap(); + let mut pipeline_cache = world.get_resource_mut::<RenderPipelineCache>().unwrap(); + + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + // TODO: change this to ViewUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(144), + }, + count: None, + }], + label: Some("ui_view_layout"), + }); + + let types_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + // TODO: change this to ViewUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(4), + }, + count: None, + }], + label: Some("ui_types_layout"), + }); + + let image_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2Array, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, + ], + label: Some("text_image_layout"), + }); + + let vertex_buffer_layout = VertexBufferLayout { + array_stride: 40, + step_mode: VertexStepMode::Vertex, + attributes: vec![ + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + VertexAttribute { + format: VertexFormat::Float32x4, + offset: 12, + shader_location: 1, + }, + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 28, + shader_location: 2, + }, + ], + }; + + let empty_font_texture = FontTextureCache::get_empty(&render_device, &image_layout); + + let pipeline_desc = RenderPipelineDescriptor { + vertex: VertexState { + shader: UNIFIED_SHADER_HANDLE.typed::<Shader>(), + entry_point: "vertex".into(), + shader_defs: vec![], + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: UNIFIED_SHADER_HANDLE.typed::<Shader>(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![ColorTargetState { + format: TextureFormat::bevy_default(), + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }), + write_mask: ColorWrites::ALL, + }], + }), + layout: Some(vec![ + view_layout.clone(), + image_layout.clone(), + types_layout.clone(), + ]), + primitive: PrimitiveState { + front_face: FrontFace::Ccw, + cull_mode: None, + polygon_mode: PolygonMode::Fill, + clamp_depth: false, + conservative: false, + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + }, + depth_stencil: None, + multisample: MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("quad_pipeline".into()), + }; + + UnifiedPipeline { + pipeline: pipeline_cache.queue(pipeline_desc), + view_layout, + image_layout, + empty_font_texture, + types_layout, + } + } +} + +#[derive(Bundle)] +pub struct ExtractQuadBundle { + pub(crate) extracted_quad: ExtractedQuad, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UIQuadType { + Quad, + Text, +} + +pub struct ExtractedQuad { + pub rect: Rect, + pub color: Color, + pub vertex_index: usize, + pub char_id: usize, + pub z_index: f32, + pub font_handle: Option<Handle<KayakFont>>, + pub quad_type: UIQuadType, + pub type_index: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +struct QuadVertex { + pub position: [f32; 3], + pub color: [f32; 4], + pub uv: [f32; 3], +} + +#[repr(C)] +#[derive(Copy, Clone, AsStd140)] +struct QuadType { + pub t: i32, +} + +pub struct QuadMeta { + vertices: BufferVec<QuadVertex>, + view_bind_group: Option<BindGroup>, + types_buffer: DynamicUniformVec<QuadType>, + types_bind_group: Option<BindGroup>, +} + +impl Default for QuadMeta { + fn default() -> Self { + Self { + vertices: BufferVec::new(BufferUsages::VERTEX), + view_bind_group: None, + types_buffer: DynamicUniformVec::default(), + types_bind_group: None, + } + } +} + +pub fn prepare_quads( + render_device: Res<RenderDevice>, + render_queue: Res<RenderQueue>, + mut sprite_meta: ResMut<QuadMeta>, + mut extracted_quads: Query<&mut ExtractedQuad>, +) { + let extracted_sprite_len = extracted_quads.iter_mut().len(); + // dont create buffers when there are no quads + if extracted_sprite_len == 0 { + return; + } + + sprite_meta.types_buffer.clear(); + sprite_meta.types_buffer.reserve(2, &render_device); + let quad_type_offset = sprite_meta.types_buffer.push(QuadType { t: 0 }); + let text_type_offset = sprite_meta.types_buffer.push(QuadType { t: 1 }); + sprite_meta + .types_buffer + .write_buffer(&render_device, &render_queue); + + sprite_meta.vertices.clear(); + sprite_meta.vertices.reserve( + extracted_sprite_len * QUAD_VERTEX_POSITIONS.len(), + &render_device, + ); + + for (i, mut extracted_sprite) in extracted_quads.iter_mut().enumerate() { + let sprite_rect = extracted_sprite.rect; + let color = extracted_sprite.color.as_linear_rgba_f32(); + + match extracted_sprite.quad_type { + UIQuadType::Quad => extracted_sprite.type_index = quad_type_offset, + UIQuadType::Text => extracted_sprite.type_index = text_type_offset, + }; + + let bottom_left = Vec3::new(0.0, 1.0, extracted_sprite.char_id as f32); + let top_left = Vec3::new(0.0, 0.0, extracted_sprite.char_id as f32); + let top_right = Vec3::new(1.0, 0.0, extracted_sprite.char_id as f32); + let bottom_right = Vec3::new(1.0, 1.0, extracted_sprite.char_id as f32); + + let uvs: [[f32; 3]; 6] = [ + bottom_left.into(), + top_right.into(), + top_left.into(), + bottom_left.into(), + bottom_right.into(), + top_right.into(), + ]; + + extracted_sprite.vertex_index = i; + for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() { + let world = Mat4::from_scale_rotation_translation( + sprite_rect.size().extend(1.0), + Quat::default(), + sprite_rect.min.extend(0.0), + ); + let final_position = (world * Vec3::from(*vertex_position).extend(1.0)).truncate(); + sprite_meta.vertices.push(QuadVertex { + position: final_position.into(), + color, + uv: uvs[index], + }); + } + } + sprite_meta + .vertices + .write_buffer(&render_device, &render_queue); +} + +pub fn queue_quads( + draw_functions: Res<DrawFunctions<TransparentUI>>, + render_device: Res<RenderDevice>, + mut sprite_meta: ResMut<QuadMeta>, + view_uniforms: Res<ViewUniforms>, + quad_pipeline: Res<UnifiedPipeline>, + mut extracted_sprites: Query<(Entity, &ExtractedQuad)>, + mut views: Query<&mut RenderPhase<TransparentUI>>, +) { + if let Some(type_binding) = sprite_meta.types_buffer.binding() { + sprite_meta.types_bind_group = + Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: type_binding, + }], + label: Some("quad_type_bind_group"), + layout: &quad_pipeline.types_layout, + })); + } + + if let Some(view_binding) = view_uniforms.uniforms.binding() { + sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("quad_view_bind_group"), + layout: &quad_pipeline.view_layout, + })); + let draw_quad = draw_functions.read().get_id::<DrawUI>().unwrap(); + for mut transparent_phase in views.iter_mut() { + for (entity, quad) in extracted_sprites.iter_mut() { + transparent_phase.add(TransparentUI { + draw_function: draw_quad, + pipeline: quad_pipeline.pipeline, + entity, + sort_key: FloatOrd(quad.z_index), + }); + } + } + } +} + +pub struct DrawUI { + params: SystemState<( + SRes<QuadMeta>, + SRes<UnifiedPipeline>, + SRes<RenderPipelineCache>, + SRes<FontTextureCache>, + SQuery<Read<ViewUniformOffset>>, + SQuery<Read<ExtractedQuad>>, + )>, +} + +impl DrawUI { + pub fn new(world: &mut World) -> Self { + Self { + params: SystemState::new(world), + } + } +} + +impl Draw<TransparentUI> for DrawUI { + fn draw<'w>( + &mut self, + world: &'w World, + pass: &mut TrackedRenderPass<'w>, + view: Entity, + item: &TransparentUI, + ) { + let (quad_meta, unified_pipeline, pipelines, font_texture_cache, views, quads) = + self.params.get(world); + let view_uniform = views.get(view).unwrap(); + let quad_meta = quad_meta.into_inner(); + let extracted_quad = quads.get(item.entity).unwrap(); + if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) { + pass.set_render_pipeline(pipeline); + pass.set_vertex_buffer(0, quad_meta.vertices.buffer().unwrap().slice(..)); + pass.set_bind_group( + 0, + quad_meta.view_bind_group.as_ref().unwrap(), + &[view_uniform.offset], + ); + + pass.set_bind_group( + 2, + quad_meta.types_bind_group.as_ref().unwrap(), + &[extracted_quad.type_index], + ); + + if let Some(font_handle) = extracted_quad.font_handle.as_ref() { + if let Some(image_bindings) = + font_texture_cache.into_inner().bind_groups.get(font_handle) + { + pass.set_bind_group(1, image_bindings, &[]); + } else { + pass.set_bind_group( + 1, + &unified_pipeline.into_inner().empty_font_texture.1, + &[], + ); + } + } else { + pass.set_bind_group(1, &unified_pipeline.into_inner().empty_font_texture.1, &[]); + } + + pass.draw( + (extracted_quad.vertex_index * QUAD_VERTEX_POSITIONS.len()) as u32 + ..((extracted_quad.vertex_index + 1) * QUAD_VERTEX_POSITIONS.len()) as u32, + 0..1, + ); + } + } +} diff --git a/bevy_kayak_ui/src/render/unified/quad/extract.rs b/bevy_kayak_ui/src/render/unified/quad/extract.rs new file mode 100644 index 0000000..523ed83 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/quad/extract.rs @@ -0,0 +1,52 @@ +use bevy::{ + math::Vec2, + prelude::{Commands, Res}, + sprite2::Rect, +}; +use kayak_core::render_primitive::RenderPrimitive; + +use crate::{ + render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType}, + to_bevy_color, BevyContext, +}; + +pub fn extract_quads(mut commands: Commands, context: Res<BevyContext>) { + let render_commands = if let Ok(context) = context.kayak_context.read() { + context.widget_manager.build_render_primitives() + } else { + vec![] + }; + + let quad_commands: Vec<&RenderPrimitive> = render_commands + .iter() + .filter(|command| matches!(command, RenderPrimitive::Quad { .. })) + .collect::<Vec<_>>(); + + let mut extracted_quads = Vec::new(); + for render_primitive in quad_commands { + let (background_color, layout) = match render_primitive { + RenderPrimitive::Quad { + background_color, + layout, + } => (background_color, layout), + _ => panic!(""), + }; + + extracted_quads.push(ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, layout.posy), + max: Vec2::new(layout.width, layout.height), + }, + color: to_bevy_color(background_color), + vertex_index: 0, + char_id: 0, + z_index: layout.z_index, + font_handle: None, + quad_type: UIQuadType::Quad, + type_index: 0, + }, + }); + } + commands.spawn_batch(extracted_quads); +} diff --git a/bevy_kayak_ui/src/render/unified/quad/mod.rs b/bevy_kayak_ui/src/render/unified/quad/mod.rs new file mode 100644 index 0000000..6d9ba65 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/quad/mod.rs @@ -0,0 +1,15 @@ +use bevy::{ + prelude::Plugin, + render2::{RenderApp, RenderStage}, +}; + +mod extract; + +pub struct QuadRendererPlugin; + +impl Plugin for QuadRendererPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let render_app = app.sub_app(RenderApp); + render_app.add_system_to_stage(RenderStage::Extract, extract::extract_quads); + } +} diff --git a/bevy_kayak_ui/src/render/unified/shader.wgsl b/bevy_kayak_ui/src/render/unified/shader.wgsl new file mode 100644 index 0000000..0032221 --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/shader.wgsl @@ -0,0 +1,56 @@ +[[block]] +struct View { + view_proj: mat4x4<f32>; + world_position: vec3<f32>; +}; +[[group(0), binding(0)]] +var<uniform> view: View; + +[[block]] +struct QuadType { + t: i32; +}; +[[group(2), binding(0)]] +var<uniform> quad_type: QuadType; + + +struct VertexOutput { + [[builtin(position)]] position: vec4<f32>; + [[location(0)]] color: vec4<f32>; + [[location(1)]] uv: vec3<f32>; +}; + +[[stage(vertex)]] +fn vertex( + [[location(0)]] vertex_position: vec3<f32>, + [[location(1)]] vertex_color: vec4<f32>, + [[location(2)]] vertex_uv: vec3<f32>, +) -> VertexOutput { + var out: VertexOutput; + out.color = vertex_color; + out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); + out.uv = vertex_uv; + return out; +} + +[[group(1), binding(0)]] +var sprite_texture: texture_2d_array<f32>; +[[group(1), binding(1)]] +var sprite_sampler: sampler; + +let RADIUS: f32 = 0.5; + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { + if (quad_type.t == 1) { + var x = textureSample(sprite_texture, sprite_sampler, in.uv.xy, i32(in.uv.z)); + var v = max(min(x.r, x.g), min(max(x.r, x.g), x.b)); + var c = v; //remap(v); + + var v2 = c / fwidth( c ); + var a = v2 + RADIUS; //clamp( v2 + RADIUS, 0.0, 1.0 ); + + return vec4<f32>(in.color.rgb, a); + } + return in.color; +} \ No newline at end of file diff --git a/cargo.toml b/cargo.toml new file mode 100644 index 0000000..86833ba --- /dev/null +++ b/cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "kayak_ui" +version = "0.1.0" +edition = "2021" +resolver = "2" + +[workspace] +members = [ + "kayak_components", + "kayak_core", + "kayak_render_macros", + "kayak_font", +] + +[dependencies] +kayak_components = { path = "kayak_components" } +kayak_core = { path = "kayak_core" } + +[dev-dependencies] +bevy = { git = "https://github.com/StarArawn/bevy", rev = "b26f563b13c267ffe1ee801bd71fd40b98a256e7" } +bevy_kayak_ui = { path = "bevy_kayak_ui" } diff --git a/examples/bevy.rs b/examples/bevy.rs new file mode 100644 index 0000000..dedfde9 --- /dev/null +++ b/examples/bevy.rs @@ -0,0 +1,65 @@ +use bevy::{ + math::Vec2, + prelude::{App as BevyApp, Commands, Res}, + window::{WindowDescriptor, Windows}, + PipelinedDefaultPlugins, +}; +use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, UICameraBundle}; +use kayak_components::Window; +use kayak_core::Index; +use kayak_ui::components::App; +use kayak_ui::core::{rsx, widget}; + +#[widget] +fn TestState() { + let _new_x = { + let x = context.create_state(0.0f32).unwrap(); + *x + 0.1 + }; + rsx! { + <> + <Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"Window 1".to_string()}> + {} + </Window> + <Window position={(550.0, 50.0)} size={(200.0, 200.0)} title={"Window 2".to_string()}> + {} + </Window> + </> + } +} + +fn startup(mut commands: Commands, windows: Res<Windows>) { + commands.spawn_bundle(UICameraBundle::new()); + + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + + let context = BevyContext::new(window_size.x, window_size.y, |styles, context| { + // Hack to trick the proc macro for right now.. + let parent_id: Option<Index> = None; + rsx! { + <App styles={Some(styles.clone())}> + <TestState>{}</TestState> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} diff --git a/kayak_components/.gitignore b/kayak_components/.gitignore new file mode 100644 index 0000000..cdd43ad --- /dev/null +++ b/kayak_components/.gitignore @@ -0,0 +1,7 @@ +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb \ No newline at end of file diff --git a/kayak_components/Cargo.toml b/kayak_components/Cargo.toml new file mode 100644 index 0000000..4f8fea6 --- /dev/null +++ b/kayak_components/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "kayak_components" +version = "0.1.0" +edition = "2021" +resolver = "2" + +[dependencies] +kayak_core = { path = "../kayak_core", version = "0.1.0" } diff --git a/kayak_components/src/app.rs b/kayak_components/src/app.rs new file mode 100644 index 0000000..31167ba --- /dev/null +++ b/kayak_components/src/app.rs @@ -0,0 +1,12 @@ +use kayak_core::{rsx, widget, Children}; + +use kayak_core::derivative::*; + +#[widget] +pub fn App(children: Children) { + rsx! { + <> + {children} + </> + } +} diff --git a/kayak_components/src/background.rs b/kayak_components/src/background.rs new file mode 100644 index 0000000..bd1ad1f --- /dev/null +++ b/kayak_components/src/background.rs @@ -0,0 +1,19 @@ +use kayak_core::{ + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, Children, Fragment, +}; + +#[widget] +pub fn Background(children: Children, styles: Option<Style>) { + if styles.is_none() { + *styles = Some(Style::default()) + } + styles.as_mut().unwrap().render_command = StyleProp::Value(RenderCommand::Quad); + rsx! { + <Fragment> + {children} + </Fragment> + } +} diff --git a/kayak_components/src/clip.rs b/kayak_components/src/clip.rs new file mode 100644 index 0000000..d5883f6 --- /dev/null +++ b/kayak_components/src/clip.rs @@ -0,0 +1,19 @@ +use kayak_core::{ + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, Children, +}; + +#[widget] +pub fn Clip(children: Children, styles: Option<Style>) { + if styles.is_none() { + *styles = Some(Style::default()) + } + styles.as_mut().unwrap().render_command = StyleProp::Value(RenderCommand::Clip); + rsx! { + <> + {children} + </> + } +} diff --git a/kayak_components/src/element.rs b/kayak_components/src/element.rs new file mode 100644 index 0000000..2972b94 --- /dev/null +++ b/kayak_components/src/element.rs @@ -0,0 +1,10 @@ +use kayak_core::{component, rsx, Render, Update}; + +#[component] +pub fn Element<Children: Render + Update + Clone>(children: Children) { + rsx! { + <> + {children} + </> + } +} diff --git a/kayak_components/src/lib.rs b/kayak_components/src/lib.rs new file mode 100644 index 0000000..9e72b31 --- /dev/null +++ b/kayak_components/src/lib.rs @@ -0,0 +1,11 @@ +mod app; +mod background; +mod clip; +mod text; +mod window; + +pub use app::*; +pub use background::*; +pub use clip::*; +pub use text::*; +pub use window::*; diff --git a/kayak_components/src/text.rs b/kayak_components/src/text.rs new file mode 100644 index 0000000..8fd1a98 --- /dev/null +++ b/kayak_components/src/text.rs @@ -0,0 +1,24 @@ +use kayak_core::{ + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, +}; + +#[widget] +pub fn Text(size: f32, content: String, styles: Option<Style>) { + let render_command = RenderCommand::Text { + content: content.clone(), + size: *size, + font: 0, // TODO: Support font passing here. Perhaps move to style? + }; + *styles = Some(Style { + render_command: StyleProp::Value(render_command), + ..styles.clone().unwrap_or_default() + }); + rsx! { + <> + {} + </> + } +} diff --git a/kayak_components/src/window.rs b/kayak_components/src/window.rs new file mode 100644 index 0000000..3abf6e4 --- /dev/null +++ b/kayak_components/src/window.rs @@ -0,0 +1,72 @@ +use kayak_core::{ + color::Color, + render_command::RenderCommand, + rsx, + styles::{PositionType, Style, StyleProp, Units}, + widget, Children, Fragment, +}; + +use crate::{Background, Clip, Text}; + +#[widget] +pub fn Window( + children: Children, + styles: Option<Style>, + position: (f32, f32), + size: (f32, f32), + title: String, +) { + // let mut changed_styles = styles.clone().unwrap_or_default(); + // changed_styles.render_command = RenderCommand::Quad; + // changed_styles.position_type = Some(PositionType::Absolute); + // changed_styles.background = Some(Color::new(0.0588, 0.0588, 0.588, 1.0)); + // changed_styles.position = Some(Rect { + // start: Dimension::Points(position.x), + // end: Dimension::Points(position.x + size.width), + // top: Dimension::Points(position.y), + // bottom: Dimension::Points(position.y + size.height), + // }); + // changed_styles.size = Some(Size { + // width: Dimension::Points(size.width), + // height: Dimension::Points(size.height), + // }); + // styles = Some(changed_styles); + + *styles = Some(Style { + background_color: StyleProp::Value(Color::new(0.125, 0.125, 0.125, 1.0)), + render_command: StyleProp::Value(RenderCommand::Quad), + position_type: StyleProp::Value(PositionType::SelfDirected), + left: StyleProp::Value(Units::Pixels(position.0)), + top: StyleProp::Value(Units::Pixels(position.1)), + width: StyleProp::Value(Units::Pixels(size.0)), + height: StyleProp::Value(Units::Pixels(size.1)), + ..styles.clone().unwrap_or_default() + }); + + let title_background_styles = Style { + background_color: StyleProp::Value(Color::new(0.0781, 0.0898, 0.101, 1.0)), + height: StyleProp::Value(Units::Pixels(24.0)), + ..Style::default() + }; + + let title_text_styles = Style { + position_type: StyleProp::Value(PositionType::SelfDirected), + top: StyleProp::Value(Units::Pixels(-22.0)), + left: StyleProp::Value(Units::Pixels(5.0)), + ..Style::default() + }; + + let title = title.clone(); + rsx! { + <Fragment> + <Clip> + <Background styles={Some(title_background_styles)}> + <Text styles={Some(title_text_styles)} size={14.0} content={title}>{}</Text> + </Background> + </Clip> + <Clip> + {children} + </Clip> + </Fragment> + } +} diff --git a/kayak_core/cargo.toml b/kayak_core/cargo.toml new file mode 100644 index 0000000..1f1eb40 --- /dev/null +++ b/kayak_core/cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "kayak_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +as-any = "0.2" +diff-struct = "0.3" +derivative = "2.2" +fontdue = "0.6" + +kayak_render_macros = { path = "../kayak_render_macros" } +morphorm = { git = "https://github.com/geom3trik/morphorm" } +# dyn_partial_eq = "0.1" +resources = "1.1" diff --git a/kayak_core/examples/test3.rs b/kayak_core/examples/test3.rs new file mode 100644 index 0000000..34079bc --- /dev/null +++ b/kayak_core/examples/test3.rs @@ -0,0 +1,63 @@ +use kayak_core::color::Color; +use kayak_core::context::KayakContext; +use kayak_core::render_command::RenderCommand; +use kayak_core::styles::{Style, StyleProp}; +use kayak_core::{rsx, widget, Children, Index}; +use morphorm::{PositionType, Units}; + +#[widget] +fn MyWidget(context: &mut KayakContext, children: Children) { + let number = *context.create_state::<u32>(0).unwrap(); + let my_styles = Style { + render_command: StyleProp::Value(RenderCommand::Quad), + width: StyleProp::Value(Units::Pixels(300.0)), + height: StyleProp::Value(Units::Pixels(300.0)), + background_color: StyleProp::Value(Color::BLACK), + ..Style::default() + }; + rsx! { + <MyWidget2 styles={Some(my_styles)} test={number}> + {children} + </MyWidget2> + } +} + +#[widget] +fn MyWidget2(test: u32, children: Children) { + dbg!(test); + rsx! { + <> + {children} + </> + } +} + +fn main() { + let mut context = KayakContext::new(); + + let my_widget = MyWidget { + id: Index::default(), + children: None, + styles: Some(Style { + position_type: StyleProp::Value(PositionType::SelfDirected), + width: StyleProp::Value(Units::Pixels(1280.0)), + height: StyleProp::Value(Units::Pixels(720.0)), + ..Style::default() + }), + }; + + let (_, widget_id) = context.widget_manager.create_widget(0, my_widget, None); + + let mut my_widget = context.widget_manager.take(widget_id); + my_widget.render(&mut context); + context.set_current_id(widget_id); + context.set_state::<u32>(1); + my_widget.render(&mut context); + context.widget_manager.repossess(my_widget); + + context.widget_manager.render(); + + context.widget_manager.calculate_layout(); + + dbg!(context.widget_manager.build_render_primitives()); +} diff --git a/kayak_core/src/color.rs b/kayak_core/src/color.rs new file mode 100644 index 0000000..b71a107 --- /dev/null +++ b/kayak_core/src/color.rs @@ -0,0 +1,48 @@ +/// A color in the sRGB color space. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Color { + /// Red component, 0.0 - 1.0 + pub r: f32, + /// Green component, 0.0 - 1.0 + pub g: f32, + /// Blue component, 0.0 - 1.0 + pub b: f32, + /// Transparency, 0.0 - 1.0 + pub a: f32, +} + +impl Default for Color { + fn default() -> Self { + Self::WHITE + } +} + +impl Color { + /// The black color. + pub const BLACK: Color = Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }; + + /// The white color. + pub const WHITE: Color = Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }; + + /// A color with no opacity. + pub const TRANSPARENT: Color = Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }; + + pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Self { r, g, b, a } + } +} diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs new file mode 100644 index 0000000..74cd09b --- /dev/null +++ b/kayak_core/src/context.rs @@ -0,0 +1,102 @@ +use std::collections::HashMap; + +use resources::Ref; + +use crate::widget_manager::WidgetManager; + +pub struct KayakContext { + component_states: HashMap<crate::Index, resources::Resources>, + current_id: crate::Index, + pub widget_manager: WidgetManager, +} + +impl std::fmt::Debug for KayakContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KayakContext") + .field("current_id", &self.current_id) + .finish() + } +} + +impl KayakContext { + pub fn new() -> Self { + Self { + component_states: HashMap::new(), + current_id: crate::Index::default(), + widget_manager: WidgetManager::new(), + } + } + + pub fn set_current_id(&mut self, id: crate::Index) { + self.current_id = id; + } + + pub fn create_state<T: resources::Resource + Clone>( + &mut self, + initial_state: T, + ) -> Option<Ref<T>> { + if self.component_states.contains_key(&self.current_id) { + let states = self.component_states.get_mut(&self.current_id).unwrap(); + if !states.contains::<T>() { + states.insert(initial_state); + } + } else { + let mut states = resources::Resources::default(); + states.insert(initial_state); + self.component_states.insert(self.current_id, states); + } + return self.get_state(); + } + + fn get_state<T: resources::Resource + Clone>(&self) -> Option<Ref<T>> { + if self.component_states.contains_key(&self.current_id) { + let states = self.component_states.get(&self.current_id).unwrap(); + if let Ok(state) = states.get::<T>() { + return Some(state); + } + } + return None; + } + + pub fn set_state<T: resources::Resource + Clone>(&mut self, state: T) { + if self.component_states.contains_key(&self.current_id) { + let states = self.component_states.get(&self.current_id).unwrap(); + if states.contains::<T>() { + let mut mutate_t = states.get_mut::<T>().unwrap(); + self.widget_manager.dirty_nodes.push(self.current_id); + *mutate_t = state; + } else { + panic!( + "No specific state created for component with id: {:?}!", + self.current_id + ); + } + } else { + panic!( + "No state created for component with id: {:?}!", + self.current_id + ); + } + } + + pub fn render(&mut self) { + let dirty_nodes = self.widget_manager.dirty_nodes.clone(); + for node_index in dirty_nodes { + if self + .widget_manager + .dirty_nodes + .iter() + .any(|dirty_index| node_index == *dirty_index) + { + let mut widget = self.widget_manager.take(node_index); + widget.render(self); + self.widget_manager.repossess(widget); + } + } + + self.widget_manager.dirty_nodes.clear(); + + self.widget_manager.render(); + self.widget_manager.calculate_layout(); + } +} diff --git a/kayak_core/src/fragment.rs b/kayak_core/src/fragment.rs new file mode 100644 index 0000000..837a5e6 --- /dev/null +++ b/kayak_core/src/fragment.rs @@ -0,0 +1,33 @@ +use derivative::*; + +use crate::{context::KayakContext, styles::Style, Index, Widget}; + +#[derive(Derivative)] +#[derivative(Debug, PartialEq)] +pub struct Fragment { + pub id: Index, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub children: crate::Children, + pub styles: Option<Style>, +} + +impl Widget for Fragment { + fn get_id(&self) -> Index { + self.id + } + + fn set_id(&mut self, id: Index) { + self.id = id; + } + + fn get_styles(&self) -> Option<Style> { + self.styles.clone() + } + + fn render(&mut self, context: &mut KayakContext) { + dbg!("Rendering fragment children!"); + if let Some(children) = self.children.as_ref() { + children(Some(self.get_id()), context); + } + } +} diff --git a/kayak_core/src/generational_arena.rs b/kayak_core/src/generational_arena.rs new file mode 100644 index 0000000..e363ea5 --- /dev/null +++ b/kayak_core/src/generational_arena.rs @@ -0,0 +1,1326 @@ +/*! +[](https://docs.rs/generational-arena/) +[](https://crates.io/crates/generational-arena) +[](https://crates.io/crates/generational-arena) +[](https://travis-ci.org/fitzgen/generational-arena) +A safe arena allocator that allows deletion without suffering from [the ABA +problem](https://en.wikipedia.org/wiki/ABA_problem) by using generational +indices. +Inspired by [Catherine West's closing keynote at RustConf +2018](https://www.youtube.com/watch?v=aKLntZcp27M), where these ideas (and many +more!) were presented in the context of an Entity-Component-System for games +programming. +## What? Why? +Imagine you are working with a graph and you want to add and delete individual +nodes at a time, or you are writing a game and its world consists of many +inter-referencing objects with dynamic lifetimes that depend on user +input. These are situations where matching Rust's ownership and lifetime rules +can get tricky. +It doesn't make sense to use shared ownership with interior mutability (i.e. +`Rc<RefCell<T>>` or `Arc<Mutex<T>>`) nor borrowed references (ie `&'a T` or `&'a +mut T`) for structures. The cycles rule out reference counted types, and the +required shared mutability rules out borrows. Furthermore, lifetimes are dynamic +and don't follow the borrowed-data-outlives-the-borrower discipline. +In these situations, it is tempting to store objects in a `Vec<T>` and have them +reference each other via their indices. No more borrow checker or ownership +problems! Often, this solution is good enough. +However, now we can't delete individual items from that `Vec<T>` when we no +longer need them, because we end up either +* messing up the indices of every element that follows the deleted one, or +* suffering from the [ABA + problem](https://en.wikipedia.org/wiki/ABA_problem). To elaborate further, if + we tried to replace the `Vec<T>` with a `Vec<Option<T>>`, and delete an + element by setting it to `None`, then we create the possibility for this buggy + sequence: + * `obj1` references `obj2` at index `i` + * someone else deletes `obj2` from index `i`, setting that element to `None` + * a third thing allocates `obj3`, which ends up at index `i`, because the + element at that index is `None` and therefore available for allocation + * `obj1` attempts to get `obj2` at index `i`, but incorrectly is given + `obj3`, when instead the get should fail. +By introducing a monotonically increasing generation counter to the collection, +associating each element in the collection with the generation when it was +inserted, and getting elements from the collection with the *pair* of index and +the generation at the time when the element was inserted, then we can solve the +aforementioned ABA problem. When indexing into the collection, if the index +pair's generation does not match the generation of the element at that index, +then the operation fails. +## Features +* Zero `unsafe` +* Well tested, including quickchecks +* `no_std` compatibility +* All the trait implementations you expect: `IntoIterator`, `FromIterator`, + `Extend`, etc... +## Usage +First, add `generational-arena` to your `Cargo.toml`: +```toml +[dependencies] +generational-arena = "0.2" +``` +Then, import the crate and use the +[`generational_arena::Arena`](./struct.Arena.html) type! +```rust +extern crate generational_arena; +use generational_arena::Arena; +let mut arena = Arena::new(); +// Insert some elements into the arena. +let rza = arena.insert("Robert Fitzgerald Diggs"); +let gza = arena.insert("Gary Grice"); +let bill = arena.insert("Bill Gates"); +// Inserted elements can be accessed infallibly via indexing (and missing +// entries will panic). +assert_eq!(arena[rza], "Robert Fitzgerald Diggs"); +// Alternatively, the `get` and `get_mut` methods provide fallible lookup. +if let Some(genius) = arena.get(gza) { + println!("The gza gza genius: {}", genius); +} +if let Some(val) = arena.get_mut(bill) { + *val = "Bill Gates doesn't belong in this set..."; +} +// We can remove elements. +arena.remove(bill); +// Insert a new one. +let murray = arena.insert("Bill Murray"); +// The arena does not contain `bill` anymore, but it does contain `murray`, even +// though they are almost certainly at the same index within the arena in +// practice. Ambiguities are resolved with an associated generation tag. +assert!(!arena.contains(bill)); +assert!(arena.contains(murray)); +// Iterate over everything inside the arena. +for (idx, value) in &arena { + println!("{:?} is at {:?}", value, idx); +} +``` +## `no_std` +To enable `no_std` compatibility, disable the on-by-default "std" feature. +```toml +[dependencies] +generational-arena = { version = "0.2", default-features = false } +``` +### Serialization and Deserialization with [`serde`](https://crates.io/crates/serde) +To enable serialization/deserialization support, enable the "serde" feature. +```toml +[dependencies] +generational-arena = { version = "0.2", features = ["serde"] } +``` + */ + +#![forbid(unsafe_code, missing_docs, missing_debug_implementations)] + +use std::vec::{self, Vec}; + +use core::cmp; +use core::iter::{self, Extend, FromIterator, FusedIterator}; +use core::mem; +use core::ops; +use core::slice; + +#[cfg(feature = "serde")] +mod serde_impl; + +/// The `Arena` allows inserting and removing elements that are referred to by +/// `Index`. +/// +/// [See the module-level documentation for example usage and motivation.](./index.html) +#[derive(Clone, Debug)] +pub struct Arena<T> { + items: Vec<Entry<T>>, + generation: u64, + free_list_head: Option<usize>, + len: usize, +} + +#[derive(Clone, Debug)] +enum Entry<T> { + Free { next_free: Option<usize> }, + Occupied { generation: u64, value: T }, +} + +/// An index (and generation) into an `Arena`. +/// +/// To get an `Index`, insert an element into an `Arena`, and the `Index` for +/// that element will be returned. +/// +/// # Examples +/// +/// ``` +/// use generational_arena::Arena; +/// +/// let mut arena = Arena::new(); +/// let idx = arena.insert(123); +/// assert_eq!(arena[idx], 123); +/// ``` +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Index { + index: usize, + generation: u64, +} + +impl Index { + /// Create a new `Index` from its raw parts. + /// + /// The parts must have been returned from an earlier call to + /// `into_raw_parts`. + /// + /// Providing arbitrary values will lead to malformed indices and ultimately + /// panics. + pub fn from_raw_parts(a: usize, b: u64) -> Index { + Index { + index: a, + generation: b, + } + } + + /// Convert this `Index` into its raw parts. + /// + /// This niche method is useful for converting an `Index` into another + /// identifier type. Usually, you should prefer a newtype wrapper around + /// `Index` like `pub struct MyIdentifier(Index);`. However, for external + /// types whose definition you can't customize, but which you can construct + /// instances of, this method can be useful. + pub fn into_raw_parts(self) -> (usize, u64) { + (self.index, self.generation) + } +} + +const DEFAULT_CAPACITY: usize = 4; + +impl<T> Default for Arena<T> { + fn default() -> Arena<T> { + Arena::new() + } +} + +impl<T> Arena<T> { + /// Constructs a new, empty `Arena`. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::<usize>::new(); + /// # let _ = arena; + /// ``` + pub fn new() -> Arena<T> { + Arena::with_capacity(DEFAULT_CAPACITY) + } + + /// Constructs a new, empty `Arena<T>` with the specified capacity. + /// + /// The `Arena<T>` will be able to hold `n` elements without further allocation. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::with_capacity(10); + /// + /// // These insertions will not require further allocation. + /// for i in 0..10 { + /// assert!(arena.try_insert(i).is_ok()); + /// } + /// + /// // But now we are at capacity, and there is no more room. + /// assert!(arena.try_insert(99).is_err()); + /// ``` + pub fn with_capacity(n: usize) -> Arena<T> { + let n = cmp::max(n, 1); + let mut arena = Arena { + items: Vec::new(), + generation: 0, + free_list_head: None, + len: 0, + }; + arena.reserve(n); + arena + } + + /// Clear all the items inside the arena, but keep its allocation. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::with_capacity(1); + /// arena.insert(42); + /// arena.insert(43); + /// + /// arena.clear(); + /// + /// assert_eq!(arena.capacity(), 2); + /// ``` + pub fn clear(&mut self) { + self.items.clear(); + + let end = self.items.capacity(); + self.items.extend((0..end).map(|i| { + if i == end - 1 { + Entry::Free { next_free: None } + } else { + Entry::Free { + next_free: Some(i + 1), + } + } + })); + if !self.is_empty() { + // Increment generation, but if there are no elements, do nothing to + // avoid unnecessary incrementing generation. + self.generation += 1; + } + self.free_list_head = Some(0); + self.len = 0; + } + + /// Attempts to insert `value` into the arena using existing capacity. + /// + /// This method will never allocate new capacity in the arena. + /// + /// If insertion succeeds, then the `value`'s index is returned. If + /// insertion fails, then `Err(value)` is returned to give ownership of + /// `value` back to the caller. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// + /// match arena.try_insert(42) { + /// Ok(idx) => { + /// // Insertion succeeded. + /// assert_eq!(arena[idx], 42); + /// } + /// Err(x) => { + /// // Insertion failed. + /// assert_eq!(x, 42); + /// } + /// }; + /// ``` + #[inline] + pub fn try_insert(&mut self, value: T) -> Result<Index, T> { + match self.try_alloc_next_index() { + None => Err(value), + Some(index) => { + self.items[index.index] = Entry::Occupied { + generation: self.generation, + value, + }; + Ok(index) + } + } + } + + /// Attempts to insert the value returned by `create` into the arena using existing capacity. + /// `create` is called with the new value's associated index, allowing values that know their own index. + /// + /// This method will never allocate new capacity in the arena. + /// + /// If insertion succeeds, then the new index is returned. If + /// insertion fails, then `Err(create)` is returned to give ownership of + /// `create` back to the caller. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::{Arena, Index}; + /// + /// let mut arena = Arena::new(); + /// + /// match arena.try_insert_with(|idx| (42, idx)) { + /// Ok(idx) => { + /// // Insertion succeeded. + /// assert_eq!(arena[idx].0, 42); + /// assert_eq!(arena[idx].1, idx); + /// } + /// Err(x) => { + /// // Insertion failed. + /// } + /// }; + /// ``` + #[inline] + pub fn try_insert_with<F: FnOnce(Index) -> T>(&mut self, create: F) -> Result<Index, F> { + match self.try_alloc_next_index() { + None => Err(create), + Some(index) => { + self.items[index.index] = Entry::Occupied { + generation: self.generation, + value: create(index), + }; + Ok(index) + } + } + } + + #[inline] + fn try_alloc_next_index(&mut self) -> Option<Index> { + match self.free_list_head { + None => None, + Some(i) => match self.items[i] { + Entry::Occupied { .. } => panic!("corrupt free list"), + Entry::Free { next_free } => { + self.free_list_head = next_free; + self.len += 1; + Some(Index { + index: i, + generation: self.generation, + }) + } + }, + } + } + + /// Insert `value` into the arena, allocating more capacity if necessary. + /// + /// The `value`'s associated index in the arena is returned. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// + /// let idx = arena.insert(42); + /// assert_eq!(arena[idx], 42); + /// ``` + #[inline] + pub fn insert(&mut self, value: T) -> Index { + match self.try_insert(value) { + Ok(i) => i, + Err(value) => self.insert_slow_path(value), + } + } + + /// Insert the value returned by `create` into the arena, allocating more capacity if necessary. + /// `create` is called with the new value's associated index, allowing values that know their own index. + /// + /// The new value's associated index in the arena is returned. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::{Arena, Index}; + /// + /// let mut arena = Arena::new(); + /// + /// let idx = arena.insert_with(|idx| (42, idx)); + /// assert_eq!(arena[idx].0, 42); + /// assert_eq!(arena[idx].1, idx); + /// ``` + #[inline] + pub fn insert_with(&mut self, create: impl FnOnce(Index) -> T) -> Index { + match self.try_insert_with(create) { + Ok(i) => i, + Err(create) => self.insert_with_slow_path(create), + } + } + + #[inline(never)] + fn insert_slow_path(&mut self, value: T) -> Index { + let len = if self.capacity() == 0 { + // `drain()` sets the capacity to 0 and if the capacity is 0, the + // next `try_insert() `will refer to an out-of-range index because + // the next `reserve()` does not add element, resulting in a panic. + // So ensure that `self` have at least 1 capacity here. + // + // Ideally, this problem should be handled within `drain()`,but + // this problem cannot be handled within `drain()` because `drain()` + // returns an iterator that borrows `self` mutably. + 1 + } else { + self.items.len() + }; + self.reserve(len); + self.try_insert(value) + .map_err(|_| ()) + .expect("inserting will always succeed after reserving additional space") + } + + #[inline(never)] + fn insert_with_slow_path(&mut self, create: impl FnOnce(Index) -> T) -> Index { + let len = self.items.len(); + self.reserve(len); + self.try_insert_with(create) + .map_err(|_| ()) + .expect("inserting will always succeed after reserving additional space") + } + + /// Remove the element at index `i` from the arena. + /// + /// If the element at index `i` is still in the arena, then it is + /// returned. If it is not in the arena, then `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// let idx = arena.insert(42); + /// + /// assert_eq!(arena.remove(idx), Some(42)); + /// assert_eq!(arena.remove(idx), None); + /// ``` + pub fn remove(&mut self, i: Index) -> Option<T> { + if i.index >= self.items.len() { + return None; + } + + match self.items[i.index] { + Entry::Occupied { generation, .. } if i.generation == generation => { + let entry = mem::replace( + &mut self.items[i.index], + Entry::Free { + next_free: self.free_list_head, + }, + ); + self.generation += 1; + self.free_list_head = Some(i.index); + self.len -= 1; + + match entry { + Entry::Occupied { + generation: _, + value, + } => Some(value), + _ => unreachable!(), + } + } + _ => None, + } + } + + /// Retains only the elements specified by the predicate. + /// + /// In other words, remove all indices such that `predicate(index, &value)` returns `false`. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut crew = Arena::new(); + /// crew.extend(&["Jim Hawkins", "John Silver", "Alexander Smollett", "Israel Hands"]); + /// let pirates = ["John Silver", "Israel Hands"]; // too dangerous to keep them around + /// crew.retain(|_index, member| !pirates.contains(member)); + /// let mut crew_members = crew.iter().map(|(_, member)| **member); + /// assert_eq!(crew_members.next(), Some("Jim Hawkins")); + /// assert_eq!(crew_members.next(), Some("Alexander Smollett")); + /// assert!(crew_members.next().is_none()); + /// ``` + pub fn retain(&mut self, mut predicate: impl FnMut(Index, &mut T) -> bool) { + for i in 0..self.capacity() { + let remove = match &mut self.items[i] { + Entry::Occupied { generation, value } => { + let index = Index { + index: i, + generation: *generation, + }; + if predicate(index, value) { + None + } else { + Some(index) + } + } + + _ => None, + }; + if let Some(index) = remove { + self.remove(index); + } + } + } + + /// Is the element at index `i` in the arena? + /// + /// Returns `true` if the element at `i` is in the arena, `false` otherwise. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// let idx = arena.insert(42); + /// + /// assert!(arena.contains(idx)); + /// arena.remove(idx); + /// assert!(!arena.contains(idx)); + /// ``` + pub fn contains(&self, i: Index) -> bool { + self.get(i).is_some() + } + + /// Get a shared reference to the element at index `i` if it is in the + /// arena. + /// + /// If the element at index `i` is not in the arena, then `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// let idx = arena.insert(42); + /// + /// assert_eq!(arena.get(idx), Some(&42)); + /// arena.remove(idx); + /// assert!(arena.get(idx).is_none()); + /// ``` + pub fn get(&self, i: Index) -> Option<&T> { + match self.items.get(i.index) { + Some(Entry::Occupied { generation, value }) if *generation == i.generation => { + Some(value) + } + _ => None, + } + } + + /// Get an exclusive reference to the element at index `i` if it is in the + /// arena. + /// + /// If the element at index `i` is not in the arena, then `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// let idx = arena.insert(42); + /// + /// *arena.get_mut(idx).unwrap() += 1; + /// assert_eq!(arena.remove(idx), Some(43)); + /// assert!(arena.get_mut(idx).is_none()); + /// ``` + pub fn get_mut(&mut self, i: Index) -> Option<&mut T> { + match self.items.get_mut(i.index) { + Some(Entry::Occupied { generation, value }) if *generation == i.generation => { + Some(value) + } + _ => None, + } + } + + /// Get a pair of exclusive references to the elements at index `i1` and `i2` if it is in the + /// arena. + /// + /// If the element at index `i1` or `i2` is not in the arena, then `None` is returned for this + /// element. + /// + /// # Panics + /// + /// Panics if `i1` and `i2` are pointing to the same item of the arena. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// let idx1 = arena.insert(0); + /// let idx2 = arena.insert(1); + /// + /// { + /// let (item1, item2) = arena.get2_mut(idx1, idx2); + /// + /// *item1.unwrap() = 3; + /// *item2.unwrap() = 4; + /// } + /// + /// assert_eq!(arena[idx1], 3); + /// assert_eq!(arena[idx2], 4); + /// ``` + pub fn get2_mut(&mut self, i1: Index, i2: Index) -> (Option<&mut T>, Option<&mut T>) { + let len = self.items.len(); + + if i1.index == i2.index { + assert!(i1.generation != i2.generation); + + if i1.generation > i2.generation { + return (self.get_mut(i1), None); + } + return (None, self.get_mut(i2)); + } + + if i1.index >= len { + return (None, self.get_mut(i2)); + } else if i2.index >= len { + return (self.get_mut(i1), None); + } + + let (raw_item1, raw_item2) = { + let (xs, ys) = self.items.split_at_mut(cmp::max(i1.index, i2.index)); + if i1.index < i2.index { + (&mut xs[i1.index], &mut ys[0]) + } else { + (&mut ys[0], &mut xs[i2.index]) + } + }; + + let item1 = match raw_item1 { + Entry::Occupied { generation, value } if *generation == i1.generation => Some(value), + _ => None, + }; + + let item2 = match raw_item2 { + Entry::Occupied { generation, value } if *generation == i2.generation => Some(value), + _ => None, + }; + + (item1, item2) + } + + /// Get the length of this arena. + /// + /// The length is the number of elements the arena holds. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// assert_eq!(arena.len(), 0); + /// + /// let idx = arena.insert(42); + /// assert_eq!(arena.len(), 1); + /// + /// let _ = arena.insert(0); + /// assert_eq!(arena.len(), 2); + /// + /// assert_eq!(arena.remove(idx), Some(42)); + /// assert_eq!(arena.len(), 1); + /// ``` + pub fn len(&self) -> usize { + self.len + } + + /// Returns true if the arena contains no elements + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// assert!(arena.is_empty()); + /// + /// let idx = arena.insert(42); + /// assert!(!arena.is_empty()); + /// + /// assert_eq!(arena.remove(idx), Some(42)); + /// assert!(arena.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Get the capacity of this arena. + /// + /// The capacity is the maximum number of elements the arena can hold + /// without further allocation, including however many it currently + /// contains. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::with_capacity(10); + /// assert_eq!(arena.capacity(), 10); + /// + /// // `try_insert` does not allocate new capacity. + /// for i in 0..10 { + /// assert!(arena.try_insert(1).is_ok()); + /// assert_eq!(arena.capacity(), 10); + /// } + /// + /// // But `insert` will if the arena is already at capacity. + /// arena.insert(0); + /// assert!(arena.capacity() > 10); + /// ``` + pub fn capacity(&self) -> usize { + self.items.len() + } + + /// Allocate space for `additional_capacity` more elements in the arena. + /// + /// # Panics + /// + /// Panics if this causes the capacity to overflow. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::with_capacity(10); + /// arena.reserve(5); + /// assert_eq!(arena.capacity(), 15); + /// # let _: Arena<usize> = arena; + /// ``` + pub fn reserve(&mut self, additional_capacity: usize) { + let start = self.items.len(); + let end = self.items.len() + additional_capacity; + let old_head = self.free_list_head; + self.items.reserve_exact(additional_capacity); + self.items.extend((start..end).map(|i| { + if i == end - 1 { + Entry::Free { + next_free: old_head, + } + } else { + Entry::Free { + next_free: Some(i + 1), + } + } + })); + self.free_list_head = Some(start); + } + + /// Iterate over shared references to the elements in this arena. + /// + /// Yields pairs of `(Index, &T)` items. + /// + /// Order of iteration is not defined. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// for i in 0..10 { + /// arena.insert(i * i); + /// } + /// + /// for (idx, value) in arena.iter() { + /// println!("{} is at index {:?}", value, idx); + /// } + /// ``` + pub fn iter(&self) -> Iter<T> { + Iter { + len: self.len, + inner: self.items.iter().enumerate(), + } + } + + /// Iterate over exclusive references to the elements in this arena. + /// + /// Yields pairs of `(Index, &mut T)` items. + /// + /// Order of iteration is not defined. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// for i in 0..10 { + /// arena.insert(i * i); + /// } + /// + /// for (_idx, value) in arena.iter_mut() { + /// *value += 5; + /// } + /// ``` + pub fn iter_mut(&mut self) -> IterMut<T> { + IterMut { + len: self.len, + inner: self.items.iter_mut().enumerate(), + } + } + + /// Iterate over elements of the arena and remove them. + /// + /// Yields pairs of `(Index, T)` items. + /// + /// Order of iteration is not defined. + /// + /// Note: All elements are removed even if the iterator is only partially consumed or not consumed at all. + /// + /// # Examples + /// + /// ``` + /// use generational_arena::Arena; + /// + /// let mut arena = Arena::new(); + /// let idx_1 = arena.insert("hello"); + /// let idx_2 = arena.insert("world"); + /// + /// assert!(arena.get(idx_1).is_some()); + /// assert!(arena.get(idx_2).is_some()); + /// for (idx, value) in arena.drain() { + /// assert!((idx == idx_1 && value == "hello") || (idx == idx_2 && value == "world")); + /// } + /// assert!(arena.get(idx_1).is_none()); + /// assert!(arena.get(idx_2).is_none()); + /// ``` + pub fn drain(&mut self) -> Drain<T> { + let old_len = self.len; + if !self.is_empty() { + // Increment generation, but if there are no elements, do nothing to + // avoid unnecessary incrementing generation. + self.generation += 1; + } + self.free_list_head = None; + self.len = 0; + Drain { + len: old_len, + inner: self.items.drain(..).enumerate(), + } + } + + /// Given an i of `usize` without a generation, get a shared reference + /// to the element and the matching `Index` of the entry behind `i`. + /// + /// This method is useful when you know there might be an element at the + /// position i, but don't know its generation or precise Index. + /// + /// Use cases include using indexing such as Hierarchical BitMap Indexing or + /// other kinds of bit-efficient indexing. + /// + /// You should use the `get` method instead most of the time. + pub fn get_unknown_gen(&self, i: usize) -> Option<(&T, Index)> { + match self.items.get(i) { + Some(Entry::Occupied { generation, value }) => Some(( + value, + Index { + generation: *generation, + index: i, + }, + )), + _ => None, + } + } + + /// Given an i of `usize` without a generation, get an exclusive reference + /// to the element and the matching `Index` of the entry behind `i`. + /// + /// This method is useful when you know there might be an element at the + /// position i, but don't know its generation or precise Index. + /// + /// Use cases include using indexing such as Hierarchical BitMap Indexing or + /// other kinds of bit-efficient indexing. + /// + /// You should use the `get_mut` method instead most of the time. + pub fn get_unknown_gen_mut(&mut self, i: usize) -> Option<(&mut T, Index)> { + match self.items.get_mut(i) { + Some(Entry::Occupied { generation, value }) => Some(( + value, + Index { + generation: *generation, + index: i, + }, + )), + _ => None, + } + } +} + +impl<T> IntoIterator for Arena<T> { + type Item = T; + type IntoIter = IntoIter<T>; + fn into_iter(self) -> Self::IntoIter { + IntoIter { + len: self.len, + inner: self.items.into_iter(), + } + } +} + +/// An iterator over the elements in an arena. +/// +/// Yields `T` items. +/// +/// Order of iteration is not defined. +/// +/// # Examples +/// +/// ``` +/// use generational_arena::Arena; +/// +/// let mut arena = Arena::new(); +/// for i in 0..10 { +/// arena.insert(i * i); +/// } +/// +/// for value in arena { +/// assert!(value < 100); +/// } +/// ``` +#[derive(Clone, Debug)] +pub struct IntoIter<T> { + len: usize, + inner: vec::IntoIter<Entry<T>>, +} + +impl<T> Iterator for IntoIter<T> { + type Item = T; + + fn next(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next() { + Some(Entry::Free { .. }) => continue, + Some(Entry::Occupied { value, .. }) => { + self.len -= 1; + return Some(value); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.len, Some(self.len)) + } +} + +impl<T> DoubleEndedIterator for IntoIter<T> { + fn next_back(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next_back() { + Some(Entry::Free { .. }) => continue, + Some(Entry::Occupied { value, .. }) => { + self.len -= 1; + return Some(value); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } +} + +impl<T> ExactSizeIterator for IntoIter<T> { + fn len(&self) -> usize { + self.len + } +} + +impl<T> FusedIterator for IntoIter<T> {} + +impl<'a, T> IntoIterator for &'a Arena<T> { + type Item = (Index, &'a T); + type IntoIter = Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// An iterator over shared references to the elements in an arena. +/// +/// Yields pairs of `(Index, &T)` items. +/// +/// Order of iteration is not defined. +/// +/// # Examples +/// +/// ``` +/// use generational_arena::Arena; +/// +/// let mut arena = Arena::new(); +/// for i in 0..10 { +/// arena.insert(i * i); +/// } +/// +/// for (idx, value) in &arena { +/// println!("{} is at index {:?}", value, idx); +/// } +/// ``` +#[derive(Clone, Debug)] +pub struct Iter<'a, T: 'a> { + len: usize, + inner: iter::Enumerate<slice::Iter<'a, Entry<T>>>, +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = (Index, &'a T); + + fn next(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next() { + Some((_, &Entry::Free { .. })) => continue, + Some(( + index, + &Entry::Occupied { + generation, + ref value, + }, + )) => { + self.len -= 1; + let idx = Index { index, generation }; + return Some((idx, value)); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + fn next_back(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next_back() { + Some((_, &Entry::Free { .. })) => continue, + Some(( + index, + &Entry::Occupied { + generation, + ref value, + }, + )) => { + self.len -= 1; + let idx = Index { index, generation }; + return Some((idx, value)); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a, T> FusedIterator for Iter<'a, T> {} + +impl<'a, T> IntoIterator for &'a mut Arena<T> { + type Item = (Index, &'a mut T); + type IntoIter = IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +/// An iterator over exclusive references to elements in this arena. +/// +/// Yields pairs of `(Index, &mut T)` items. +/// +/// Order of iteration is not defined. +/// +/// # Examples +/// +/// ``` +/// use generational_arena::Arena; +/// +/// let mut arena = Arena::new(); +/// for i in 0..10 { +/// arena.insert(i * i); +/// } +/// +/// for (_idx, value) in &mut arena { +/// *value += 5; +/// } +/// ``` +#[derive(Debug)] +pub struct IterMut<'a, T: 'a> { + len: usize, + inner: iter::Enumerate<slice::IterMut<'a, Entry<T>>>, +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = (Index, &'a mut T); + + fn next(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next() { + Some((_, &mut Entry::Free { .. })) => continue, + Some(( + index, + &mut Entry::Occupied { + generation, + ref mut value, + }, + )) => { + self.len -= 1; + let idx = Index { index, generation }; + return Some((idx, value)); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { + fn next_back(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next_back() { + Some((_, &mut Entry::Free { .. })) => continue, + Some(( + index, + &mut Entry::Occupied { + generation, + ref mut value, + }, + )) => { + self.len -= 1; + let idx = Index { index, generation }; + return Some((idx, value)); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } +} + +impl<'a, T> ExactSizeIterator for IterMut<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a, T> FusedIterator for IterMut<'a, T> {} + +/// An iterator that removes elements from the arena. +/// +/// Yields pairs of `(Index, T)` items. +/// +/// Order of iteration is not defined. +/// +/// Note: All elements are removed even if the iterator is only partially consumed or not consumed at all. +/// +/// # Examples +/// +/// ``` +/// use generational_arena::Arena; +/// +/// let mut arena = Arena::new(); +/// let idx_1 = arena.insert("hello"); +/// let idx_2 = arena.insert("world"); +/// +/// assert!(arena.get(idx_1).is_some()); +/// assert!(arena.get(idx_2).is_some()); +/// for (idx, value) in arena.drain() { +/// assert!((idx == idx_1 && value == "hello") || (idx == idx_2 && value == "world")); +/// } +/// assert!(arena.get(idx_1).is_none()); +/// assert!(arena.get(idx_2).is_none()); +/// ``` +#[derive(Debug)] +pub struct Drain<'a, T: 'a> { + len: usize, + inner: iter::Enumerate<vec::Drain<'a, Entry<T>>>, +} + +impl<'a, T> Iterator for Drain<'a, T> { + type Item = (Index, T); + + fn next(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next() { + Some((_, Entry::Free { .. })) => continue, + Some((index, Entry::Occupied { generation, value })) => { + let idx = Index { index, generation }; + self.len -= 1; + return Some((idx, value)); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for Drain<'a, T> { + fn next_back(&mut self) -> Option<Self::Item> { + loop { + match self.inner.next_back() { + Some((_, Entry::Free { .. })) => continue, + Some((index, Entry::Occupied { generation, value })) => { + let idx = Index { index, generation }; + self.len -= 1; + return Some((idx, value)); + } + None => { + debug_assert_eq!(self.len, 0); + return None; + } + } + } + } +} + +impl<'a, T> ExactSizeIterator for Drain<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a, T> FusedIterator for Drain<'a, T> {} + +impl<T> Extend<T> for Arena<T> { + fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) { + for t in iter { + self.insert(t); + } + } +} + +impl<T> FromIterator<T> for Arena<T> { + fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { + let iter = iter.into_iter(); + let (lower, upper) = iter.size_hint(); + let cap = upper.unwrap_or(lower); + let cap = cmp::max(cap, 1); + let mut arena = Arena::with_capacity(cap); + arena.extend(iter); + arena + } +} + +impl<T> ops::Index<Index> for Arena<T> { + type Output = T; + + fn index(&self, index: Index) -> &Self::Output { + self.get(index).expect("No element at index") + } +} + +impl<T> ops::IndexMut<Index> for Arena<T> { + fn index_mut(&mut self, index: Index) -> &mut Self::Output { + self.get_mut(index).expect("No element at index") + } +} diff --git a/kayak_core/src/layout_cache.rs b/kayak_core/src/layout_cache.rs new file mode 100644 index 0000000..59f251e --- /dev/null +++ b/kayak_core/src/layout_cache.rs @@ -0,0 +1,358 @@ +use std::collections::HashMap; + +use morphorm::{Cache, GeometryChanged}; + +use crate::node::NodeIndex; + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct Rect { + pub posx: f32, + pub posy: f32, + pub width: f32, + pub height: f32, + pub z_index: f32, +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct Space { + pub left: f32, + pub right: f32, + pub top: f32, + pub bottom: f32, +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct Size { + pub width: f32, + pub height: f32, +} + +#[derive(Default, Debug)] +pub struct LayoutCache { + // Computed Outputs + pub rect: HashMap<NodeIndex, Rect>, + + // Intermediate Values + space: HashMap<NodeIndex, Space>, + size: HashMap<NodeIndex, Size>, + + child_width_max: HashMap<NodeIndex, f32>, + child_height_max: HashMap<NodeIndex, f32>, + child_width_sum: HashMap<NodeIndex, f32>, + child_height_sum: HashMap<NodeIndex, f32>, + + grid_row_max: HashMap<NodeIndex, f32>, + grid_col_max: HashMap<NodeIndex, f32>, + + horizontal_free_space: HashMap<NodeIndex, f32>, + horizontal_stretch_sum: HashMap<NodeIndex, f32>, + + vertical_free_space: HashMap<NodeIndex, f32>, + vertical_stretch_sum: HashMap<NodeIndex, f32>, + + stack_first_child: HashMap<NodeIndex, bool>, + stack_last_child: HashMap<NodeIndex, bool>, + + geometry_changed: HashMap<NodeIndex, GeometryChanged>, + + visible: HashMap<NodeIndex, bool>, +} + +impl LayoutCache { + pub fn add(&mut self, node_index: NodeIndex) { + self.rect.insert(node_index, Default::default()); + + self.space.insert(node_index, Default::default()); + + self.child_width_max.insert(node_index, Default::default()); + self.child_height_max.insert(node_index, Default::default()); + self.child_width_sum.insert(node_index, Default::default()); + self.child_height_sum.insert(node_index, Default::default()); + + self.grid_row_max.insert(node_index, Default::default()); + self.grid_col_max.insert(node_index, Default::default()); + + self.horizontal_free_space + .insert(node_index, Default::default()); + self.horizontal_stretch_sum + .insert(node_index, Default::default()); + + self.vertical_free_space + .insert(node_index, Default::default()); + self.vertical_stretch_sum + .insert(node_index, Default::default()); + + self.stack_first_child + .insert(node_index, Default::default()); + self.stack_last_child.insert(node_index, Default::default()); + + self.size.insert(node_index, Default::default()); + + self.geometry_changed.insert(node_index, Default::default()); + + self.visible.insert(node_index, true); + } +} + +impl Cache for LayoutCache { + type Item = NodeIndex; + + fn visible(&self, node: Self::Item) -> bool { + if let Some(value) = self.visible.get(&node) { + return *value; + } + + true + } + + fn geometry_changed(&self, node: Self::Item) -> GeometryChanged { + if let Some(geometry_changed) = self.geometry_changed.get(&node) { + return *geometry_changed; + } + + GeometryChanged::default() + } + + fn set_geo_changed(&mut self, node: Self::Item, flag: GeometryChanged, value: bool) { + if let Some(geometry_changed) = self.geometry_changed.get_mut(&node) { + geometry_changed.set(flag, value); + } + } + + fn width(&self, node: Self::Item) -> f32 { + if let Some(rect) = self.rect.get(&node) { + return rect.width; + } + + 0.0 + } + + fn height(&self, node: Self::Item) -> f32 { + if let Some(rect) = self.rect.get(&node) { + return rect.height; + } + + 0.0 + } + + fn posx(&self, node: Self::Item) -> f32 { + if let Some(rect) = self.rect.get(&node) { + return rect.posx; + } + + 0.0 + } + + fn posy(&self, node: Self::Item) -> f32 { + if let Some(rect) = self.rect.get(&node) { + return rect.posy; + } + + 0.0 + } + + fn left(&self, node: Self::Item) -> f32 { + if let Some(space) = self.space.get(&node) { + return space.left; + } + + 0.0 + } + + fn right(&self, node: Self::Item) -> f32 { + if let Some(space) = self.space.get(&node) { + return space.right; + } + + 0.0 + } + + fn top(&self, node: Self::Item) -> f32 { + if let Some(space) = self.space.get(&node) { + return space.top; + } + + 0.0 + } + + fn bottom(&self, node: Self::Item) -> f32 { + if let Some(space) = self.space.get(&node) { + return space.bottom; + } + + 0.0 + } + + fn new_width(&self, node: Self::Item) -> f32 { + if let Some(size) = self.size.get(&node) { + return size.width; + } + + 0.0 + } + + fn new_height(&self, node: Self::Item) -> f32 { + if let Some(size) = self.size.get(&node) { + return size.height; + } + + 0.0 + } + + fn child_width_max(&self, node: Self::Item) -> f32 { + *self.child_width_max.get(&node).unwrap() + } + + /// Get the computed sum of the widths of the child nodes + fn child_width_sum(&self, node: Self::Item) -> f32 { + *self.child_width_sum.get(&node).unwrap() + } + + /// Get the computed maximum width of the child nodes + fn child_height_max(&self, node: Self::Item) -> f32 { + *self.child_height_max.get(&node).unwrap() + } + + /// Get the computed sum of the widths of the child nodes + fn child_height_sum(&self, node: Self::Item) -> f32 { + *self.child_height_sum.get(&node).unwrap() + } + + /// Get the computed maximum grid row + fn grid_row_max(&self, node: Self::Item) -> f32 { + *self.grid_row_max.get(&node).unwrap() + } + + /// Get the computed maximum grid column + fn grid_col_max(&self, node: Self::Item) -> f32 { + *self.grid_col_max.get(&node).unwrap() + } + + // Setters + fn set_visible(&mut self, node: Self::Item, value: bool) { + *self.visible.get_mut(&node).unwrap() = value; + } + + fn set_child_width_sum(&mut self, node: Self::Item, value: f32) { + *self.child_width_sum.get_mut(&node).unwrap() = value; + } + + fn set_child_height_sum(&mut self, node: Self::Item, value: f32) { + *self.child_height_sum.get_mut(&node).unwrap() = value; + } + + fn set_child_width_max(&mut self, node: Self::Item, value: f32) { + *self.child_width_max.get_mut(&node).unwrap() = value; + } + + fn set_child_height_max(&mut self, node: Self::Item, value: f32) { + *self.child_height_max.get_mut(&node).unwrap() = value; + } + + fn horizontal_free_space(&self, node: Self::Item) -> f32 { + *self.horizontal_free_space.get(&node).unwrap() + } + fn set_horizontal_free_space(&mut self, node: Self::Item, value: f32) { + *self.horizontal_free_space.get_mut(&node).unwrap() = value; + } + fn vertical_free_space(&self, node: Self::Item) -> f32 { + *self.vertical_free_space.get(&node).unwrap() + } + fn set_vertical_free_space(&mut self, node: Self::Item, value: f32) { + *self.vertical_free_space.get_mut(&node).unwrap() = value; + } + + fn horizontal_stretch_sum(&self, node: Self::Item) -> f32 { + *self.horizontal_stretch_sum.get(&node).unwrap() + } + fn set_horizontal_stretch_sum(&mut self, node: Self::Item, value: f32) { + *self.horizontal_stretch_sum.get_mut(&node).unwrap() = value; + } + fn vertical_stretch_sum(&self, node: Self::Item) -> f32 { + *self.vertical_stretch_sum.get(&node).unwrap() + } + fn set_vertical_stretch_sum(&mut self, node: Self::Item, value: f32) { + *self.vertical_stretch_sum.get_mut(&node).unwrap() = value; + } + + fn set_grid_row_max(&mut self, node: Self::Item, value: f32) { + *self.grid_row_max.get_mut(&node).unwrap() = value; + } + + fn set_grid_col_max(&mut self, node: Self::Item, value: f32) { + *self.grid_row_max.get_mut(&node).unwrap() = value; + } + + fn set_width(&mut self, node: Self::Item, value: f32) { + if let Some(rect) = self.rect.get_mut(&node) { + rect.width = value; + } + } + fn set_height(&mut self, node: Self::Item, value: f32) { + if let Some(rect) = self.rect.get_mut(&node) { + rect.height = value; + } + } + fn set_posx(&mut self, node: Self::Item, value: f32) { + if let Some(rect) = self.rect.get_mut(&node) { + rect.posx = value; + } + } + fn set_posy(&mut self, node: Self::Item, value: f32) { + if let Some(rect) = self.rect.get_mut(&node) { + rect.posy = value; + } + } + + fn set_left(&mut self, node: Self::Item, value: f32) { + if let Some(space) = self.space.get_mut(&node) { + space.left = value; + } + } + + fn set_right(&mut self, node: Self::Item, value: f32) { + if let Some(space) = self.space.get_mut(&node) { + space.right = value; + } + } + + fn set_top(&mut self, node: Self::Item, value: f32) { + if let Some(space) = self.space.get_mut(&node) { + space.top = value; + } + } + + fn set_bottom(&mut self, node: Self::Item, value: f32) { + if let Some(space) = self.space.get_mut(&node) { + space.bottom = value; + } + } + + fn set_new_width(&mut self, node: Self::Item, value: f32) { + if let Some(size) = self.size.get_mut(&node) { + size.width = value; + } + } + + fn set_new_height(&mut self, node: Self::Item, value: f32) { + if let Some(size) = self.size.get_mut(&node) { + size.height = value; + } + } + + fn stack_first_child(&self, node: Self::Item) -> bool { + *self.stack_first_child.get(&node).unwrap() + } + + fn set_stack_first_child(&mut self, node: Self::Item, value: bool) { + *self.stack_first_child.get_mut(&node).unwrap() = value; + } + + fn stack_last_child(&self, node: Self::Item) -> bool { + *self.stack_last_child.get(&node).unwrap() + } + + fn set_stack_last_child(&mut self, node: Self::Item, value: bool) { + *self.stack_last_child.get_mut(&node).unwrap() = value; + } +} diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs new file mode 100644 index 0000000..73ad0d8 --- /dev/null +++ b/kayak_core/src/lib.rs @@ -0,0 +1,29 @@ +pub mod color; +pub mod context; +pub mod fragment; +pub mod layout_cache; +pub mod node; +pub mod render_command; +pub mod render_primitive; +pub mod styles; +pub mod tree; +pub mod widget; +pub mod widget_manager; + +pub(crate) mod generational_arena; + +pub use generational_arena::{Arena, Index}; + +pub use widget::Widget; + +pub use kayak_render_macros::{render, rsx, widget}; + +pub use fragment::Fragment; + +pub type Children = Option< + std::sync::Arc<dyn Fn(Option<crate::Index>, &mut crate::context::KayakContext) + Send + Sync>, +>; + +pub mod derivative { + pub use derivative::*; +} diff --git a/kayak_core/src/node.rs b/kayak_core/src/node.rs new file mode 100644 index 0000000..704cc01 --- /dev/null +++ b/kayak_core/src/node.rs @@ -0,0 +1,285 @@ +use crate::{ + styles::{Style, StyleProp}, + Arena, Index, +}; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct NodeIndex(pub Index); + +#[derive(Debug, Clone)] +pub struct Node { + pub children: Vec<Index>, + pub id: Index, + pub styles: Style, + pub z: f32, +} + +impl Node {} + +pub struct NodeBuilder { + children: Vec<Index>, + id: Index, + styles: Style, +} + +impl NodeBuilder { + pub fn empty() -> Self { + Self { + children: Vec::new(), + id: Index::default(), + styles: Style::default(), + } + } + + pub fn new(id: Index, styles: Style) -> Self { + Self { + children: Vec::new(), + id, + styles, + } + } + + pub fn with_id(mut self, id: Index) -> Self { + self.id = id; + self + } + + pub fn with_children(mut self, children: Vec<Index>) -> Self { + self.children.extend(children); + self + } + + pub fn with_styles(mut self, styles: Style) -> Self { + self.styles = styles; + self + } + + pub fn build(self) -> Node { + Node { + children: self.children, + id: self.id, + styles: self.styles, + z: 0.0, + } + } +} + +impl<'a> morphorm::Node<'a> for NodeIndex { + type Data = Arena<Option<Node>>; + + fn layout_type(&self, store: &'_ Self::Data) -> Option<morphorm::LayoutType> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.layout_type { + StyleProp::Default => Some(morphorm::LayoutType::default()), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn position_type(&self, store: &'_ Self::Data) -> Option<morphorm::PositionType> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.position_type { + StyleProp::Default => Some(morphorm::PositionType::default()), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn width(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.width { + StyleProp::Default => Some(morphorm::Units::Stretch(1.0)), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn height(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.height { + StyleProp::Default => Some(morphorm::Units::Stretch(1.0)), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn min_width(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn min_height(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn max_width(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn max_height(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn left(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.left { + StyleProp::Default => Some(morphorm::Units::Auto), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn right(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.right { + StyleProp::Default => Some(morphorm::Units::Auto), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn top(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.top { + StyleProp::Default => Some(morphorm::Units::Auto), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn bottom(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(self.0) { + if let Some(node) = node { + return match node.styles.bottom { + StyleProp::Default => Some(morphorm::Units::Auto), + StyleProp::Value(prop) => Some(prop), + _ => None, + }; + } + } + return None; + } + + fn min_left(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn max_left(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn min_right(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn max_right(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn min_top(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn max_top(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn min_bottom(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn max_bottom(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn child_left(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn child_right(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn child_top(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn child_bottom(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn row_between(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn col_between(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn grid_rows(&self, _store: &'_ Self::Data) -> Option<Vec<morphorm::Units>> { + Some(vec![]) + } + + fn grid_cols(&self, _store: &'_ Self::Data) -> Option<Vec<morphorm::Units>> { + Some(vec![]) + } + + fn row_index(&self, _store: &'_ Self::Data) -> Option<usize> { + Some(0) + } + + fn col_index(&self, _store: &'_ Self::Data) -> Option<usize> { + Some(0) + } + + fn row_span(&self, _store: &'_ Self::Data) -> Option<usize> { + Some(1) + } + + fn col_span(&self, _store: &'_ Self::Data) -> Option<usize> { + Some(1) + } + + fn border_left(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn border_right(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn border_top(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } + + fn border_bottom(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + Some(morphorm::Units::Auto) + } +} diff --git a/kayak_core/src/render_command.rs b/kayak_core/src/render_command.rs new file mode 100644 index 0000000..eb75d0b --- /dev/null +++ b/kayak_core/src/render_command.rs @@ -0,0 +1,17 @@ +#[derive(Debug, Clone, PartialEq)] +pub enum RenderCommand { + Empty, + Clip, + Quad, + Text { + content: String, + size: f32, + font: u16, + }, +} + +impl Default for RenderCommand { + fn default() -> Self { + Self::Empty + } +} diff --git a/kayak_core/src/render_primitive.rs b/kayak_core/src/render_primitive.rs new file mode 100644 index 0000000..1c54054 --- /dev/null +++ b/kayak_core/src/render_primitive.rs @@ -0,0 +1,59 @@ +use crate::{color::Color, layout_cache::Rect, render_command::RenderCommand, styles::Style}; + +#[derive(Debug, Clone, PartialEq)] +pub enum RenderPrimitive { + Empty, + Clip { + layout: Rect, + }, + Quad { + layout: Rect, + background_color: Color, + }, + Text { + layout: Rect, + color: Color, + size: f32, + content: String, + font: u16, + }, +} + +impl RenderPrimitive { + pub fn set_layout(&mut self, new_layout: Rect) { + match self { + RenderPrimitive::Clip { layout, .. } => *layout = new_layout, + RenderPrimitive::Quad { layout, .. } => *layout = new_layout, + RenderPrimitive::Text { layout, .. } => *layout = new_layout, + _ => (), + } + } +} + +impl From<&Style> for RenderPrimitive { + fn from(style: &Style) -> Self { + let render_command = style.render_command.resolve(); + + match render_command { + RenderCommand::Empty => Self::Empty, + RenderCommand::Clip => Self::Clip { + layout: Rect::default(), + }, + RenderCommand::Quad => Self::Quad { + background_color: style.background_color.resolve(), + layout: Rect::default(), + }, + RenderCommand::Text { + content, + size, + font, + } => Self::Text { + layout: Rect::default(), + color: style.color.resolve(), + size, + content, + font, + }, + } + } +} diff --git a/kayak_core/src/styles.rs b/kayak_core/src/styles.rs new file mode 100644 index 0000000..d75533a --- /dev/null +++ b/kayak_core/src/styles.rs @@ -0,0 +1,136 @@ +pub use morphorm::{LayoutType, PositionType, Units}; + +use crate::{color::Color, render_command::RenderCommand}; + +#[derive(Debug, Clone, PartialEq)] +pub enum StyleProp<T: Default + Clone> { + Default, + Inherit, + Value(T), +} + +impl<T> Default for StyleProp<T> +where + T: Default + Clone, +{ + fn default() -> Self { + Self::Default + } +} + +impl<T> StyleProp<T> +where + T: Default + Clone, +{ + pub fn resolve(&self) -> T { + match self { + StyleProp::Default => T::default(), + StyleProp::Value(value) => value.clone(), + StyleProp::Inherit => panic!("All styles should be merged before resolving!"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Style { + pub background_color: StyleProp<Color>, + pub bottom: StyleProp<Units>, + pub color: StyleProp<Color>, + pub height: StyleProp<Units>, + pub layout_type: StyleProp<LayoutType>, + pub left: StyleProp<Units>, + pub position_type: StyleProp<PositionType>, + pub render_command: StyleProp<RenderCommand>, + pub right: StyleProp<Units>, + pub top: StyleProp<Units>, + pub width: StyleProp<Units>, +} + +impl Default for Style { + fn default() -> Self { + Self { + background_color: StyleProp::Value(Color::TRANSPARENT), + render_command: StyleProp::Value(RenderCommand::Empty), + bottom: StyleProp::Default, + color: StyleProp::Inherit, + height: StyleProp::Default, + layout_type: StyleProp::Default, + left: StyleProp::Default, + position_type: StyleProp::Default, + right: StyleProp::Default, + top: StyleProp::Default, + width: StyleProp::Default, + } + } +} + +impl Style { + pub fn merge(&mut self, other: &Self) { + match self.background_color { + StyleProp::Inherit => { + self.background_color = other.background_color.clone(); + } + _ => (), + } + match self.bottom { + StyleProp::Inherit => { + self.bottom = other.bottom.clone(); + } + _ => (), + } + match self.color { + StyleProp::Inherit => { + self.color = other.color.clone(); + } + _ => (), + } + match self.height { + StyleProp::Inherit => { + self.height = other.height.clone(); + } + _ => (), + } + match self.layout_type { + StyleProp::Inherit => { + self.layout_type = other.layout_type.clone(); + } + _ => (), + } + match self.left { + StyleProp::Inherit => { + self.left = other.left.clone(); + } + _ => (), + } + match self.position_type { + StyleProp::Inherit => { + self.position_type = other.position_type.clone(); + } + _ => (), + } + match self.render_command { + StyleProp::Inherit => { + self.render_command = other.render_command.clone(); + } + _ => (), + } + match self.right { + StyleProp::Inherit => { + self.right = other.right.clone(); + } + _ => (), + } + match self.top { + StyleProp::Inherit => { + self.top = other.top.clone(); + } + _ => (), + } + match self.width { + StyleProp::Inherit => { + self.width = other.width.clone(); + } + _ => (), + } + } +} diff --git a/kayak_core/src/tree.rs b/kayak_core/src/tree.rs new file mode 100644 index 0000000..b9e2fe4 --- /dev/null +++ b/kayak_core/src/tree.rs @@ -0,0 +1,257 @@ +use std::{collections::HashMap, iter::Rev}; + +use morphorm::Hierarchy; + +use crate::{node::NodeIndex, Index}; + +#[derive(Default, Debug)] +pub struct Tree { + pub children: HashMap<Index, Vec<Index>>, + pub parents: HashMap<Index, Index>, + pub root_node: Index, +} + +impl Tree { + pub fn add(&mut self, _child_index: usize, index: Index, parent: Option<Index>) { + if let Some(parent_index) = parent { + self.parents.insert(index, parent_index); + if let Some(parent_children) = self.children.get_mut(&parent_index) { + parent_children.push(index); + } else { + self.children.insert(parent_index, vec![index]); + } + } else { + self.root_node = index; + } + } + + pub fn flatten(&self) -> Vec<NodeIndex> { + let iterator = DownwardIterator { + tree: &self, + current_node: Some(NodeIndex(self.root_node)), + starting: true, + }; + + iterator.collect::<Vec<_>>() + } + + pub fn get_parent(&self, index: NodeIndex) -> Option<NodeIndex> { + self.parents + .get(&index.0) + .map_or(None, |parent| Some(NodeIndex(*parent))) + } + + pub fn get_first_child(&self, index: NodeIndex) -> Option<NodeIndex> { + self.children.get(&index.0).map_or(None, |children| { + children + .first() + .map_or(None, |first_child| Some(NodeIndex(*first_child))) + }) + } + + pub fn get_last_child(&self, _index: NodeIndex) -> Option<NodeIndex> { + todo!() + } + + pub fn get_next_sibling(&self, index: NodeIndex) -> Option<NodeIndex> { + if let Some(parent_index) = self.get_parent(index) { + self.children.get(&parent_index.0).map_or(None, |children| { + children + .iter() + .position(|child| *child == index.0) + .map_or(None, |child_index| { + children + .get(child_index + 1) + .map_or(None, |next_child| Some(NodeIndex(*next_child))) + }) + }) + } else { + None + } + } + + pub fn get_prev_sibling(&self, index: NodeIndex) -> Option<NodeIndex> { + self.children.get(&index.0).map_or(None, |children| { + children + .iter() + .position(|child| *child == index.0) + .map_or(None, |child_index| { + children + .get(child_index - 1) + .map_or(None, |next_child| Some(NodeIndex(*next_child))) + }) + }) + } +} + +pub struct DownwardIterator<'a> { + tree: &'a Tree, + current_node: Option<NodeIndex>, + starting: bool, +} + +impl<'a> DownwardIterator<'a> {} + +impl<'a> Iterator for DownwardIterator<'a> { + type Item = NodeIndex; + fn next(&mut self) -> Option<NodeIndex> { + if self.starting { + self.starting = false; + return self.current_node; + } + + if let Some(current_index) = self.current_node { + if let Some(first_child) = self.tree.get_first_child(current_index) { + self.current_node = Some(first_child); + return Some(first_child); + } else if let Some(next_sibling) = self.tree.get_next_sibling(current_index) { + self.current_node = Some(next_sibling); + return Some(next_sibling); + } else { + let mut current_parent = self.tree.get_parent(current_index); + while current_parent.is_some() { + if let Some(current_parent) = current_parent { + if let Some(next_parent_sibling) = + self.tree.get_next_sibling(current_parent) + { + self.current_node = Some(next_parent_sibling); + return Some(next_parent_sibling); + } + } + current_parent = self.tree.get_parent(current_parent.unwrap()); + } + } + } + + return None; + } +} + +// pub struct UpwardIterator<'a> { +// tree: &'a Tree, +// current_node: Option<NodeIndex>, +// } + +// impl<'a> Iterator for UpwardIterator<'a> { +// type Item = NodeIndex; + +// // TODO - Needs Testing +// fn next(&mut self) -> Option<NodeIndex> { +// None +// } +// } + +pub struct ChildIterator<'a> { + pub tree: &'a Tree, + pub current_node: Option<NodeIndex>, +} + +impl<'a> Iterator for ChildIterator<'a> { + type Item = NodeIndex; + fn next(&mut self) -> Option<Self::Item> { + if let Some(entity) = self.current_node { + self.current_node = self.tree.get_next_sibling(entity); + return Some(entity); + } + + None + } +} + +impl<'a> Hierarchy<'a> for Tree { + type Item = NodeIndex; + type DownIter = std::vec::IntoIter<NodeIndex>; + type UpIter = Rev<std::vec::IntoIter<NodeIndex>>; + type ChildIter = ChildIterator<'a>; + + fn up_iter(&'a self) -> Self::UpIter { + self.flatten().into_iter().rev() + } + + fn down_iter(&'a self) -> Self::DownIter { + self.flatten().into_iter() + } + + fn child_iter(&'a self, node: Self::Item) -> Self::ChildIter { + let first_child = self.get_first_child(node); + ChildIterator { + tree: self, + current_node: first_child, + } + } + + fn parent(&self, node: Self::Item) -> Option<Self::Item> { + if let Some(parent_index) = self.parents.get(&node.0) { + return Some(NodeIndex(*parent_index)); + } + + None + } + + fn is_first_child(&self, node: Self::Item) -> bool { + if let Some(parent) = self.parent(node) { + if let Some(first_child) = self.get_first_child(parent) { + if first_child == node { + return true; + } else { + return false; + } + } + } + + false + } + + fn is_last_child(&self, node: Self::Item) -> bool { + if let Some(parent) = self.parent(node) { + if let Some(parent_children) = self.children.get(&parent.0) { + if let Some(last_child) = parent_children.last() { + return *last_child == node.0; + } + } + } + + false + } +} + +#[test] +fn test_tree() { + use crate::node::NodeBuilder; + use crate::Arena; + let mut store = Arena::new(); + let root = store.insert(NodeBuilder::empty().build()); + // Child 1 of root + let index1 = store.insert(NodeBuilder::empty().build()); + // Children of child 1. + let index2 = store.insert(NodeBuilder::empty().build()); + let index3 = store.insert(NodeBuilder::empty().build()); + // Child 2 of root + let index4 = store.insert(NodeBuilder::empty().build()); + + let mut tree = Tree::default(); + tree.root_node = root; + + // Setup Parents.. + tree.parents.insert(index1, root); + tree.parents.insert(index4, root); + + tree.parents.insert(index2, index1); + tree.parents.insert(index3, index1); + + tree.children.insert(root, vec![index1, index4]); + tree.children.insert(index1, vec![index2, index3]); + + let flattened = tree.flatten(); + + let mapped = flattened + .iter() + .map(|x| x.0.into_raw_parts().0) + .collect::<Vec<_>>(); + + assert!(mapped[0] == 0); + assert!(mapped[1] == 1); + assert!(mapped[2] == 2); + assert!(mapped[3] == 3); + assert!(mapped[4] == 4); +} diff --git a/kayak_core/src/widget.rs b/kayak_core/src/widget.rs new file mode 100644 index 0000000..3e63d87 --- /dev/null +++ b/kayak_core/src/widget.rs @@ -0,0 +1,15 @@ +use as_any::AsAny; + +use crate::{context::KayakContext, styles::Style, Index}; + +pub trait Widget: std::fmt::Debug + AsAny + Send + Sync { + fn get_id(&self) -> Index; + fn set_id(&mut self, id: Index); + fn get_styles(&self) -> Option<Style>; + fn render(&mut self, context: &mut KayakContext); +} + +impl as_any::Downcast for dyn Widget {} +impl as_any::Downcast for dyn Widget + Send {} +impl as_any::Downcast for dyn Widget + Sync {} +impl as_any::Downcast for dyn Widget + Send + Sync {} diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs new file mode 100644 index 0000000..f4e3b70 --- /dev/null +++ b/kayak_core/src/widget_manager.rs @@ -0,0 +1,216 @@ +use crate::{ + layout_cache::LayoutCache, + node::{Node, NodeBuilder, NodeIndex}, + render_primitive::RenderPrimitive, + styles::Style, + tree::Tree, + Arena, Index, Widget, +}; +use as_any::Downcast; + +#[derive(Debug)] +pub struct WidgetManager { + current_widgets: Arena<Option<Box<dyn Widget>>>, + pub(crate) dirty_render_nodes: Vec<Index>, + pub(crate) dirty_nodes: Vec<Index>, + pub(crate) nodes: Arena<Option<Node>>, + pub tree: Tree, + pub layout_cache: LayoutCache, + current_z: f32, +} + +impl WidgetManager { + pub fn new() -> Self { + Self { + current_widgets: Arena::new(), + dirty_render_nodes: Vec::new(), + dirty_nodes: Vec::new(), + nodes: Arena::new(), + tree: Tree::default(), + layout_cache: LayoutCache::default(), + current_z: 0.0, + } + } + + /// Re-renders from the root. + /// If force is true sets ALL nodes to re-render. + /// Can be slow. + pub fn dirty(&mut self, force: bool) { + // Force tree to re-render from root. + self.dirty_nodes.push(self.tree.root_node); + + if force { + for (node_index, _) in self.current_widgets.iter() { + self.dirty_nodes.push(node_index); + self.dirty_render_nodes.push(node_index); + } + } + } + + pub fn create_widget<T: Widget + PartialEq + 'static>( + &mut self, + index: usize, + mut widget: T, + parent: Option<Index>, + ) -> (bool, Index) { + if let Some(parent) = parent.clone() { + if let Some(parent_children) = self.tree.children.get_mut(&parent) { + // Pull child and update. + if let Some(widget_id) = parent_children.get(index) { + widget.set_id(*widget_id); + // Remove from the dirty nodes lists. + if let Some(index) = self.dirty_nodes.iter().position(|id| *widget_id == *id) { + self.dirty_nodes.remove(index); + } + + if &widget + != self.current_widgets[*widget_id] + .as_ref() + .unwrap() + .downcast_ref::<T>() + .unwrap() + { + let boxed_widget: Box<dyn Widget> = Box::new(widget); + *self.current_widgets[*widget_id].as_mut().unwrap() = boxed_widget; + dbg!("Widget changed!"); + // Tell renderer that the nodes changed. + self.dirty_render_nodes.push(*widget_id); + return (true, *widget_id); + } else { + dbg!("No widget changes!"); + return (false, *widget_id); + } + } + } + } + + // Create Flow + // We should only have one widget that doesn't have a parent. + // The root widget. + let widget_id = self.current_widgets.insert(Some(Box::new(widget))); + self.nodes.insert(None); + self.current_widgets[widget_id] + .as_mut() + .unwrap() + .set_id(widget_id); + + // Tell renderer that the nodes changed. + self.dirty_render_nodes.push(widget_id); + + // Remove from the dirty nodes lists. + if let Some(index) = self.dirty_nodes.iter().position(|id| widget_id == *id) { + self.dirty_nodes.remove(index); + } + + self.tree.add(0, widget_id, parent); + self.layout_cache.add(NodeIndex(widget_id)); + + (true, widget_id) + } + + pub fn take(&mut self, id: Index) -> Box<dyn Widget> { + self.current_widgets[id].take().unwrap() + } + + pub fn repossess(&mut self, widget: Box<dyn Widget>) { + let widget_id = widget.get_id(); + self.current_widgets[widget_id] = Some(widget); + } + + pub fn render(&mut self) { + let default_styles = Style { + background_color: crate::styles::StyleProp::Default, + bottom: crate::styles::StyleProp::Default, + color: crate::styles::StyleProp::Default, + height: crate::styles::StyleProp::Default, + layout_type: crate::styles::StyleProp::Default, + left: crate::styles::StyleProp::Default, + position_type: crate::styles::StyleProp::Default, + render_command: crate::styles::StyleProp::Default, + right: crate::styles::StyleProp::Default, + top: crate::styles::StyleProp::Default, + width: crate::styles::StyleProp::Default, + }; + for dirty_node_index in self.dirty_render_nodes.drain(..) { + let dirty_widget = self.current_widgets[dirty_node_index].as_ref().unwrap(); + let parent_styles = + if let Some(parent_widget_id) = self.tree.parents.get(&dirty_node_index) { + if let Some(parent) = self.current_widgets[*parent_widget_id].as_ref() { + if let Some(styles) = parent.get_styles() { + styles + } else { + default_styles.clone() + } + } else { + default_styles.clone() + } + } else { + default_styles.clone() + }; + + // Get parent Z + let parent_z = if let Some(parent_widget_id) = self.tree.parents.get(&dirty_node_index) + { + if let Some(parent) = &self.nodes[*parent_widget_id] { + parent.z + } else { + -1.0 + } + } else { + -1.0 + }; + + let current_z = { + if parent_z > -1.0 { + parent_z + 1.0 + } else { + let z = self.current_z; + self.current_z += 1.0; + z + } + }; + dbg!(current_z); + + let mut styles = dirty_widget.get_styles(); + if styles.is_some() { + styles.as_mut().unwrap().merge(&parent_styles); + } + let children = self + .tree + .children + .get(&dirty_node_index) + .cloned() + .unwrap_or(vec![]); + let mut node = NodeBuilder::empty() + .with_id(dirty_node_index) + .with_styles(styles.unwrap_or(default_styles.clone())) + .with_children(children) + .build(); + node.z = current_z; + + self.nodes[dirty_node_index] = Some(node); + } + } + + pub fn calculate_layout(&mut self) { + morphorm::layout(&mut self.layout_cache, &self.tree, &self.nodes); + } + + pub fn build_render_primitives(&self) -> Vec<RenderPrimitive> { + let mut render_primitives = Vec::new(); + + for (index, node) in self.nodes.iter() { + if let Some(layout) = self.layout_cache.rect.get(&NodeIndex(index)) { + if let Some(node) = node { + let mut render_primitive: RenderPrimitive = (&node.styles).into(); + let mut layout = *layout; + layout.z_index = node.z; + render_primitive.set_layout(layout); + render_primitives.push(render_primitive); + } + } + } + + render_primitives + } +} diff --git a/kayak_font/.gitignore b/kayak_font/.gitignore new file mode 100644 index 0000000..aab52d9 --- /dev/null +++ b/kayak_font/.gitignore @@ -0,0 +1 @@ +*.png \ No newline at end of file diff --git a/kayak_font/Cargo.toml b/kayak_font/Cargo.toml new file mode 100644 index 0000000..79436b7 --- /dev/null +++ b/kayak_font/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "kayak_font" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +arrayvec = "0.7" +bitflags = "1.0" +lyon_path = "0.12" +lyon_geom = "0.12" +fontdue = "0.6" +ttf-parser = "0.13" + +[dev-dependencies] +png = "0.16.3" diff --git a/kayak_font/examples/main.rs b/kayak_font/examples/main.rs new file mode 100644 index 0000000..2bed828 --- /dev/null +++ b/kayak_font/examples/main.rs @@ -0,0 +1,54 @@ +use std::{fs::File, io::BufWriter}; + +use kayak_font::{ + compute_msdf, recolor_contours, rescale_contours, Angle, FlatPathBuilder, PathCollector, Point, + Rect, +}; + +pub fn main() { + let font_data = include_bytes!("../resources/Roboto-Regular.ttf"); + let face = ttf_parser::Face::from_slice(font_data, 0).unwrap(); + + let char_dim: u32 = 64; + + let c = 'A'; + if let Some(glyph) = face.glyph_index(c) { + let mut path_collector = PathCollector::new(); + path_collector.scale = 1.0; //0.0001; //1024 as f32 / face.units_per_em() as f32; + let rect = face.outline_glyph(glyph, &mut path_collector).unwrap(); + let contours = path_collector.build(); + let uv_rect = Rect::new(Point::new(0.0, 0.0), lyon_geom::math::Size::new(1.0, 1.0)); + + let font_rect = Rect::new( + Point::new(rect.x_min as f32, rect.y_min as f32), + lyon_geom::math::Size::new(rect.width() as f32, rect.height() as f32), + ); + + let (contours, _) = rescale_contours(contours, font_rect, uv_rect, 0); + + let contours = recolor_contours(contours, Angle::degrees(3.0), 1); + let msdf = compute_msdf(&contours, char_dim as usize); + + let file = File::create(format!("./test-{}.png", c)).unwrap(); + let ref mut w = BufWriter::new(file); + + let pixels: Vec<u8> = msdf + .iter() + .flat_map(|y| { + y.iter().flat_map(|pixel| { + vec![ + (pixel.0 * 255.0) as u8, + (pixel.1 * 255.0) as u8, + (pixel.2 * 255.0) as u8, + 255u8, + ] + }) + }) + .collect(); + + let mut encoder = png::Encoder::new(w, char_dim, char_dim); + encoder.set_color(png::ColorType::RGBA); + let mut writer = encoder.write_header().unwrap(); + writer.write_image_data(&pixels).unwrap(); + } +} diff --git a/kayak_font/examples/simple.rs b/kayak_font/examples/simple.rs new file mode 100644 index 0000000..09f8903 --- /dev/null +++ b/kayak_font/examples/simple.rs @@ -0,0 +1,30 @@ +use std::{fs::File, io::BufWriter}; + +use kayak_font::Font; + +fn main() { + let font_bytes = include_bytes!("../resources/Roboto-Regular.ttf"); + let mut font = Font::new(font_bytes, 64); + + font.add_character('A'); + font.add_character('B'); + font.add_character('C'); + font.add_character('!'); + font.add_character('&'); + + // Characters that have already been calculated wont be calculated again! + for _ in 0..1000000 { + font.add_character('A'); + } + + let dimensions = font.cache.dimensions; + for (c, _, pixels) in font.get_data_to_process() { + let file = File::create(format!("./test-{}.png", c)).unwrap(); + let ref mut w = BufWriter::new(file); + + let mut encoder = png::Encoder::new(w, dimensions, dimensions); + encoder.set_color(png::ColorType::RGBA); + let mut writer = encoder.write_header().unwrap(); + writer.write_image_data(&pixels).unwrap(); + } +} diff --git a/kayak_font/resources/Roboto-Regular.ttf b/kayak_font/resources/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2b6392ffe8712b9c5450733320cd220d6c0f4bce GIT binary patch literal 171272 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	$$K|G-##kgg<a}7Wmh5J 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 literal 0 HcmV?d00001 diff --git a/kayak_font/src/color_flags.rs b/kayak_font/src/color_flags.rs new file mode 100644 index 0000000..7322912 --- /dev/null +++ b/kayak_font/src/color_flags.rs @@ -0,0 +1,62 @@ +bitflags::bitflags! { + pub struct ColorFlags: u32 { + const BLACK = 0b000; + const RED = 0b001; + const GREEN = 0b010; + const BLUE = 0b100; + const YELLOW = 0b011; + const MAGENTA = 0b101; + const CYAN = 0b110; + const WHITE = 0b111; + } +} + +impl ColorFlags { + pub(crate) fn switch(self, seed: &mut u64) -> Self { + match self { + ColorFlags::WHITE | ColorFlags::BLACK => { + const START: [ColorFlags; 3] = + [ColorFlags::CYAN, ColorFlags::MAGENTA, ColorFlags::YELLOW]; + let tr = START[(*seed % 3) as usize]; + *seed /= 3; + tr + } + ColorFlags::RED | ColorFlags::GREEN | ColorFlags::BLUE => self ^ ColorFlags::WHITE, + _ => { + let v = self.bits(); + let v = (v << (1 + (*seed & 1))) & 0b111; + let v = match v.count_ones() { + 0 => 0b11, /* Somehow we lost all the bits. Default to yellow */ + 1 => v | 0b001, /* We just shifted a bit off the left side, add one on the right */ + 2 => v, /* We already have 2 bits, nothing to do */ + _ => unreachable!(), /* There should never be 3+ bits set */ + }; + *seed >>= 1; + + Self::from_bits_truncate(v) + } + } + } + + pub(crate) fn switch_banned(self, seed: &mut u64, banned: ColorFlags) -> Self { + let combined = self & banned; + match combined { + ColorFlags::RED | ColorFlags::GREEN | ColorFlags::BLUE => combined ^ ColorFlags::WHITE, + _ => self.switch(seed), + } + } + + pub fn float_color(self) -> [f32; 3] { + match self { + ColorFlags::BLACK => [0.0f32, 0.0f32, 0.0f32], + ColorFlags::RED => [1.0f32, 0.0f32, 0.0f32], + ColorFlags::GREEN => [0.0f32, 1.0f32, 0.0f32], + ColorFlags::BLUE => [0.0f32, 0.0f32, 1.0f32], + ColorFlags::CYAN => [0.0f32, 1.0f32, 1.0f32], + ColorFlags::MAGENTA => [1.0f32, 0.0f32, 1.0f32], + ColorFlags::YELLOW => [1.0f32, 1.0f32, 0.0f32], + ColorFlags::WHITE => [1.0f32, 1.0f32, 1.0f32], + _ => [0.5, 0.7, 0.5], + } + } +} diff --git a/kayak_font/src/contour.rs b/kayak_font/src/contour.rs new file mode 100644 index 0000000..c963de3 --- /dev/null +++ b/kayak_font/src/contour.rs @@ -0,0 +1,108 @@ +use lyon_geom::math::{Point, Rect}; +use lyon_path::Segment; + +use crate::path_element::PathElement; + +/// A list of path elements forming a closed loop +#[derive(Clone, Debug)] +pub struct Contour { + pub elements: Vec<PathElement>, +} + +impl Contour { + pub fn winding(&self) -> f32 { + let shoelace = |a: Point, b: Point| (b.x - a.x) * (a.y + b.y); + let n = self.elements.len(); + match n { + 0 => 0.0, + 1 => { + let a = self.elements[0].sample(0.0); + let b = self.elements[0].sample(1.0 / 3.0); + let c = self.elements[0].sample(2.0 / 3.0); + + shoelace(a, b) + shoelace(b, c) + shoelace(c, a) + } + 2 => { + let a = self.elements[0].sample(0.0); + let b = self.elements[0].sample(0.5); + let c = self.elements[1].sample(0.0); + let d = self.elements[1].sample(0.5); + + shoelace(a, b) + shoelace(b, c) + shoelace(c, d) + shoelace(d, a) + } + _ => { + let mut total = 0.0; + let mut prev = self.elements[n - 1].sample(0.0); + + for e in &self.elements { + let curr = e.sample(0.0); + total += shoelace(prev, curr); + prev = curr; + } + + total + } + } + .signum() + } +} + +/// Rescale contours so they fit in the provided rectangle. +/// Returns the scaled contours along with the transformation used to rescale the contours +pub fn rescale_contours( + mut contours: Vec<Contour>, + initial_bounds: Rect, + bounds: Rect, + _units_per_em: u16, +) -> (Vec<Contour>, lyon_geom::math::Transform2D) { + // let (new_width, new_height) = if initial_bounds.size.width > initial_bounds.size.height { + // let new_width = 1.0; + // let new_height = aspect_ratio_height( + // initial_bounds.size.height, + // initial_bounds.size.width, + // new_width, + // ); + + // (new_width, new_height) + // } else { + // let new_height = 1.0; + // let new_width = aspect_ratio_width( + // initial_bounds.size.height, + // initial_bounds.size.width, + // new_height, + // ); + + // (new_width, new_height) + // }; + + // dbg!(new_width, new_height); + + // let x_scale = new_width / initial_bounds.size.width; + // let y_scale = new_height / initial_bounds.size.height; + + let initial_scale = initial_bounds.size.height.max(initial_bounds.size.width); + let bounds_scale = bounds.size.width.max(bounds.size.height); + + // let size = 128.0 / units_per_em as f32; + let transformation = lyon_geom::math::Transform2D::create_translation( + -initial_bounds.origin.x as f32, + -initial_bounds.origin.y as f32, + ) + .post_scale(bounds_scale / initial_scale, bounds_scale / initial_scale) + .post_translate(bounds.origin.to_vector()); + for contour in &mut contours { + for mut elem in &mut contour.elements { + elem.segment = match elem.segment { + Segment::Line(s) => Segment::Line(s.transform(&transformation)), + Segment::Quadratic(s) => Segment::Quadratic(s.transform(&transformation)), + Segment::Cubic(s) => Segment::Cubic(s.transform(&transformation)), + Segment::Arc(s) => Segment::Arc(lyon_geom::Arc { + center: transformation.transform_point(&s.center), + ..s + }), + } + } + } + + (contours, transformation) +} diff --git a/kayak_font/src/font.rs b/kayak_font/src/font.rs new file mode 100644 index 0000000..806d47e --- /dev/null +++ b/kayak_font/src/font.rs @@ -0,0 +1,253 @@ +use std::collections::{HashMap, HashSet}; + +use lyon_geom::math::{Angle, Point, Rect, Size}; +use lyon_path::builder::FlatPathBuilder; + +use crate::{compute_msdf, recolor_contours, rescale_contours, PathCollector}; + +#[derive(Debug, Clone)] +pub struct FontCache { + count: usize, + pub dimensions: u32, + chars: HashMap<char, (usize, Vec<u8>)>, + needs_processing: HashSet<usize>, + id_to_char_mappings: HashMap<usize, char>, +} + +impl FontCache { + pub fn new(texture_size: u32) -> Self { + Self { + count: 0, + dimensions: texture_size, + chars: HashMap::default(), + needs_processing: HashSet::default(), + id_to_char_mappings: HashMap::default(), + } + } + + fn set_texture(&mut self, c: char, texture_data: Vec<Vec<(f32, f32, f32)>>) { + // let pixels: Vec<u8> = texture_data + // .iter() + // .flat_map(|y| { + // y.iter().flat_map(|pixel| { + // vec![ + // (pixel.0 * 255.0) as u8, + // (pixel.1 * 255.0) as u8, + // (pixel.2 * 255.0) as u8, + // 255u8, + // ] + // }) + // }) + // .collect(); + let pixels = texture_data + .iter() + .flat_map(|x| { + x.iter() + .flat_map(|p| { + vec![ + p.0.to_le_bytes(), + p.1.to_le_bytes(), + p.2.to_le_bytes(), + 1.0f32.to_le_bytes(), + ] + .into_iter() + .flatten() + .collect::<Vec<u8>>() + }) + .collect::<Vec<u8>>() + }) + .collect(); + self.chars.insert(c, (self.count, pixels)); + self.needs_processing.insert(self.count); + self.id_to_char_mappings.insert(self.count, c); + self.count += 1; + } + + fn has_character(&self, c: char) -> bool { + self.chars.contains_key(&c) + } + + fn get_dimensions(&self) -> u32 { + self.dimensions + } +} + +#[derive(Debug, Clone)] +pub struct Font { + internal_face: ttf_parser::Face<'static>, + font: fontdue::Font, + pub cache: FontCache, +} + +impl Font { + pub fn new(font_data: &'static [u8], texture_size: u32) -> Font { + Font { + internal_face: ttf_parser::Face::from_slice(&font_data, 0).unwrap(), + font: fontdue::Font::from_bytes(font_data.clone(), fontdue::FontSettings::default()) + .unwrap(), + cache: FontCache::new(texture_size), + } + } + + /// Adds all of the common known characters. + pub fn add_all_common(&mut self) { + let chars = vec![ + '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '~', '!', '@', '#', + '$', '%', '^', '&', '*', '(', ')', '_', '+', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', '\\', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', + '|', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', 'A', 'S', 'D', 'F', 'G', + 'H', 'J', 'K', 'L', ':', '"', 'z', 'x', 'c', 'v', 'b', 'n', 'n', 'm', ',', '.', '/', + 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', + ]; + + for char in chars { + self.add_character(char); + } + } + + pub fn get_layout( + &self, + content: &String, + font_size: f32, + ) -> Vec<(char, (f32, f32), (f32, f32))> { + let mut layout = + fontdue::layout::Layout::new(fontdue::layout::CoordinateSystem::PositiveYDown); + layout.append( + &[&self.font], + &fontdue::layout::TextStyle::new(content, font_size, 0), + ); + + let glyphs = layout.glyphs(); + let glyphs: Vec<_> = glyphs + .iter() + .filter_map(|glyph_position| { + if glyph_position.parent == ' ' { + return None; + } + // let metrics = self.font.metrics(glyph_position.parent, font_size); + let font_scale = font_size / self.font.units_per_em() as f32; + let scale = + if let Some(glyph) = self.internal_face.glyph_index(glyph_position.parent) { + // TODO: Cache this in add_character.. + let mut path_collector = PathCollector::new(); + let rect = self + .internal_face + .outline_glyph(glyph, &mut path_collector) + .unwrap(); + let width = rect.width(); + let height = rect.height(); + width.max(height) as f32 * font_scale + } else { + 0.0 + }; + + // let shift_x = if scale > glyph_position.width as f32 { + // scale - glyph_position.width as f32 + // } else { + // // glyph_position.width as f32 - scale + // 0.0 + // }; + let shift_y = if scale > glyph_position.height as f32 { + scale - glyph_position.height as f32 + } else { + 0.0 + }; + Some(( + glyph_position.parent, + (glyph_position.x, glyph_position.y - shift_y), + (scale, scale), + )) + }) + .collect(); + glyphs + } + + // pub fn get_size(&self, c: char, font_size: f32) -> (f32, f32) { + // if let Some(glyph) = self.internal_face.glyph_index(c) { + // // Collect our path's from the glyph's outline shape. + // let mut path_collector = PathCollector::new(); + // let rect = self + // .internal_face + // .outline_glyph(glyph, &mut path_collector) + // .unwrap(); + // let metrics = font_size / self.font.units_per_em(); + + // (width as f32 * metrics, height as f32 * metrics) + // } else { + // panic!("") + // } + // } + + pub fn get_char_id(&self, c: char) -> usize { + if self.cache.has_character(c) { + if let Some((id, _)) = self.cache.chars.get(&c) { + return *id; + } + } + panic!("No char found!"); + } + + pub fn add_character(&mut self, c: char) { + if !self.cache.has_character(c) { + if let Some(glyph) = self.internal_face.glyph_index(c) { + // Collect our path's from the glyph's outline shape. + let mut path_collector = PathCollector::new(); + let rect = self + .internal_face + .outline_glyph(glyph, &mut path_collector) + .unwrap(); + let contours = path_collector.build(); + + // Bounds of our texture in UV's + // TODO: Allow this to change because some people may want texture atlases instead. + let uv_rect = Rect::new(Point::new(0.0, 0.0), Size::new(1.0, 1.0)); + + // Bounds of our rect in font space coords. + let font_rect = Rect::new( + Point::new(rect.x_min as f32, rect.y_min as f32), + Size::new(rect.width() as f32, rect.height() as f32), + ); + + let (contours, _transform) = rescale_contours( + contours, + font_rect, + uv_rect, + self.internal_face.units_per_em(), + ); + let contours = recolor_contours(contours, Angle::degrees(3.0), 1); + let msdf = compute_msdf(&contours, self.cache.get_dimensions() as usize); + + self.cache.set_texture(c, msdf); + } + } + } + + pub fn get_data_to_process<'b>(&'b mut self) -> Vec<(char, usize, &'b Vec<u8>)> { + let data = self + .cache + .needs_processing + .iter() + .filter_map(|unprocessed_id| { + if let Some(c) = self.cache.id_to_char_mappings.get(unprocessed_id) { + if let Some((_, data)) = self.cache.chars.get(c) { + return Some((*c, *unprocessed_id, data)); + } + } + + None + }) + .collect(); + + self.cache.needs_processing.clear(); + + data + } + + // Checks the given chars and returns ones that haven't been seen before. + pub fn check_chars(&self, chars: std::str::Chars<'_>) -> Vec<char> { + chars + .into_iter() + .filter(|c| !self.cache.chars.contains_key(&c)) + .collect() + } +} diff --git a/kayak_font/src/lib.rs b/kayak_font/src/lib.rs new file mode 100644 index 0000000..07499ca --- /dev/null +++ b/kayak_font/src/lib.rs @@ -0,0 +1,27 @@ +mod color_flags; +mod contour; +mod font; +mod msdf; +mod path_collector; +mod path_element; +mod recolor; +mod sdf; +mod ttf_parser; +mod utils; + +pub use color_flags::ColorFlags; +pub use contour::{rescale_contours, Contour}; +pub use font::{Font, FontCache}; +pub use lyon_geom::math::{Angle, Point, Rect, Vector}; +pub use lyon_path::builder::FlatPathBuilder; +pub use msdf::compute_msdf; +pub use path_collector::PathCollector; +pub use path_element::PathElement; +pub use recolor::recolor_contours; +pub use sdf::compute_sdf; + +pub(crate) fn median(a: f32, b: f32, c: f32) -> f32 { + let min = |a: f32, b: f32| a.min(b); + let max = |a: f32, b: f32| a.max(b); + max(min(a, b), min(max(a, b), c)) +} diff --git a/kayak_font/src/msdf.rs b/kayak_font/src/msdf.rs new file mode 100644 index 0000000..8429e93 --- /dev/null +++ b/kayak_font/src/msdf.rs @@ -0,0 +1,171 @@ +use lyon_geom::math::Vector; + +use crate::{contour::Contour, median, utils::EdgeDistance, ColorFlags}; + +/// Computes an MSDF from a list of contours. The returned vectors are a `dim` by `dim` +/// matrix of signed distance values. The output represents the signed distances to the contours +/// within [0, 1] x [0, 1] +pub fn compute_msdf(contours: &[Contour], dim: usize) -> Vec<Vec<(f32, f32, f32)>> { + #[derive(Copy, Clone, PartialEq)] + struct MultiDistance { + r: f32, + g: f32, + b: f32, + med: f32, + } + impl MultiDistance { + fn new(v: f32) -> Self { + Self { + r: v, + g: v, + b: v, + med: v, + } + } + } + let scale: f32 = 1.0 / (dim as f32); + let windings: Vec<i32> = contours.iter().map(|c| c.winding() as i32).collect(); + + (0..dim) + .map(|y| { + let py = (y as f32 + 0.5) * scale; + (0..dim) + .map(|x| { + // We assume there is at least 1 contour + // If there isn't make everything magenta + if contours.len() == 0 { + return (1.0f32, 0.0, 1.0); + } + + let px = (x as f32 + 0.5) * scale; + let p = Vector::new(px, py); + + let mut neg_dist = 1e24f32; + let mut pos_dist = -1e24f32; + let mut d = 1e24f32; + let mut winding = 0; + let mut contour_distances = Vec::new(); + contour_distances.reserve(contours.len()); + + let mut sr = EdgeDistance::new(); + let mut sg = EdgeDistance::new(); + let mut sb = EdgeDistance::new(); + + for (i, contour) in contours.iter().enumerate() { + let mut contour_min_r = EdgeDistance::new(); + let mut contour_min_g = EdgeDistance::new(); + let mut contour_min_b = EdgeDistance::new(); + + for elem in &contour.elements { + let (d, na) = elem.distance(p.to_point()); + + if elem.color.contains(ColorFlags::RED) && d < contour_min_r.dist { + contour_min_r.dist = d; + contour_min_r.edge = Some(&elem); + contour_min_r.nearest_approach = na; + } + if elem.color.contains(ColorFlags::GREEN) && d < contour_min_g.dist { + contour_min_g.dist = d; + contour_min_g.edge = Some(&elem); + contour_min_g.nearest_approach = na; + } + if elem.color.contains(ColorFlags::BLUE) && d < contour_min_b.dist { + contour_min_b.dist = d; + contour_min_b.edge = Some(&elem); + contour_min_b.nearest_approach = na; + } + } + + if contour_min_r.dist < sr.dist { + sr = contour_min_r; + } + if contour_min_g.dist < sg.dist { + sg = contour_min_g; + } + if contour_min_b.dist < sb.dist { + sb = contour_min_b; + } + + let med_min_dist = median( + contour_min_r.dist.distance, + contour_min_g.dist.distance, + contour_min_b.dist.distance, + ) + .abs(); + if med_min_dist < d { + d = med_min_dist; + winding = -windings[i]; + } + + contour_min_r.to_pseudodistance(p); + contour_min_g.to_pseudodistance(p); + contour_min_b.to_pseudodistance(p); + + let med_min_dist = median( + contour_min_r.dist.distance, + contour_min_g.dist.distance, + contour_min_b.dist.distance, + ); + + let mut msd = MultiDistance::new(med_min_dist); + msd.r = contour_min_r.dist.distance; + msd.g = contour_min_g.dist.distance; + msd.b = contour_min_b.dist.distance; + msd.med = med_min_dist; + contour_distances.push(msd); + if windings[i] > 0 + && med_min_dist >= 0.0 + && med_min_dist.abs() < pos_dist.abs() + { + pos_dist = med_min_dist; + } + if windings[i] < 0 + && med_min_dist <= 0.0 + && med_min_dist.abs() < neg_dist.abs() + { + neg_dist = med_min_dist; + } + } + + assert!(contour_distances.len() == windings.len()); + + sr.to_pseudodistance(p); + sg.to_pseudodistance(p); + sb.to_pseudodistance(p); + + let mut mmsd = MultiDistance::new(-1e24); + if pos_dist >= 0.0 && pos_dist.abs() <= neg_dist.abs() { + mmsd.med = -1e24; + winding = 1; + for (csd, cw) in contour_distances.iter().zip(windings.iter()) { + if *cw > 0 && csd.med > mmsd.med && csd.med.abs() < neg_dist.abs() { + mmsd = *csd; + } + } + } else if neg_dist <= 0.0 && neg_dist.abs() <= pos_dist.abs() { + mmsd.med = 1e24; + winding = -1; + for (csd, cw) in contour_distances.iter().zip(windings.iter()) { + if *cw < 0 && csd.med < mmsd.med && csd.med.abs() < pos_dist.abs() { + mmsd = *csd; + } + } + } + for (csd, w) in contour_distances.iter().zip(windings.iter()) { + if *w != winding && csd.med.abs() < mmsd.med.abs() { + mmsd = *csd; + } + } + + if median(sr.dist.distance, sg.dist.distance, sb.dist.distance) == mmsd.med { + mmsd.r = sr.dist.distance; + mmsd.g = sg.dist.distance; + mmsd.b = sb.dist.distance; + } + + (mmsd.r / 0.5, mmsd.g / 0.5, mmsd.b / 0.5) + }) + .collect() + }) + .collect() +} diff --git a/kayak_font/src/path_collector.rs b/kayak_font/src/path_collector.rs new file mode 100644 index 0000000..077bc44 --- /dev/null +++ b/kayak_font/src/path_collector.rs @@ -0,0 +1,134 @@ +use lyon_geom::math::{Angle, Point, Vector}; +use lyon_path::{ + builder::{FlatPathBuilder, PathBuilder}, + Segment, +}; + +use crate::{color_flags::ColorFlags, contour::Contour, path_element::PathElement}; + +/// This is a path collector which produces our custom contour type. +pub struct PathCollector { + /// The start point of the last contour + pub(crate) contour_start: Point, + /// The current pen location + pub(crate) pen: Point, + /// in-flight path elements + pub(crate) elements: Vec<PathElement>, + /// Completed contours + pub(crate) contours: Vec<Contour>, + pub scale: f32, +} + +impl PathCollector { + pub fn new() -> Self { + Self { + contour_start: Point::new(0.0, 0.0), + pen: Point::new(0.0, 0.0), + elements: Vec::new(), + contours: Vec::new(), + scale: 1.0, + } + } +} + +impl PathBuilder for PathCollector { + fn quadratic_bezier_to(&mut self, ctrl: Point, to: Point) { + self.elements.push(PathElement::new( + Segment::Quadratic(lyon_geom::QuadraticBezierSegment { + from: self.pen * self.scale, + to: to * self.scale, + ctrl: ctrl * self.scale, + }), + ColorFlags::WHITE, + )); + self.pen = to; + } + + fn cubic_bezier_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point) { + self.elements.push(PathElement::new( + Segment::Cubic(lyon_geom::CubicBezierSegment { + from: self.pen * self.scale, + to: to * self.scale, + ctrl1: ctrl1 * self.scale, + ctrl2: ctrl2 * self.scale, + }), + ColorFlags::WHITE, + )); + + self.pen = to; + } + + fn arc(&mut self, _center: Point, _radii: Vector, _sweep_angle: Angle, _x_rotation: Angle) { + unimplemented!() + } +} + +impl FlatPathBuilder for PathCollector { + type PathType = Vec<Contour>; + + fn move_to(&mut self, to: Point) { + self.pen = to * self.scale; + self.contour_start = to * self.scale; + } + + fn line_to(&mut self, to: Point) { + self.elements.push(PathElement::new( + Segment::Line(lyon_geom::LineSegment { + from: self.pen * self.scale, + to: to * self.scale, + }), + ColorFlags::WHITE, + )); + self.pen = to * self.scale; + } + + fn close(&mut self) { + if (self.pen - self.contour_start).length() > 1E-14 { + self.elements.push(PathElement::new( + Segment::Line(lyon_geom::LineSegment { + from: self.pen * self.scale, + to: self.contour_start * self.scale, + }), + ColorFlags::WHITE, + )); + } + + self.pen = self.contour_start; + let elements = std::mem::replace(&mut self.elements, Vec::new()); + + self.contours.push(Contour { elements }); + } + + fn build(self) -> Self::PathType { + let mut contours = self.contours; + if self.elements.len() > 0 { + let final_contour = Contour { + elements: self.elements, + }; + + contours.push(final_contour); + } + + contours + } + + fn build_and_reset(&mut self) -> Self::PathType { + let elements = std::mem::replace(&mut self.elements, Vec::new()); + if elements.len() > 0 { + let final_contour = Contour { elements }; + + self.contours.push(final_contour); + } + + let tr = std::mem::replace(&mut self.contours, Vec::new()); + + self.contour_start = Point::new(0.0, 0.0); + self.pen = Point::new(0.0, 0.0); + + tr + } + + fn current_position(&self) -> Point { + self.pen + } +} diff --git a/kayak_font/src/path_element.rs b/kayak_font/src/path_element.rs new file mode 100644 index 0000000..7500fcc --- /dev/null +++ b/kayak_font/src/path_element.rs @@ -0,0 +1,191 @@ +use crate::{color_flags::ColorFlags, utils::AugmentedDistance}; +use lyon_geom::math::{Point, Vector}; +use lyon_path::Segment; + +#[derive(Clone, Debug, Copy)] +pub struct PathElement { + pub segment: Segment, + pub color: ColorFlags, +} + +impl PathElement { + pub fn new(segment: Segment, color: ColorFlags) -> PathElement { + Self { segment, color } + } + + pub fn sample(&self, t: f32) -> Point { + match self.segment { + Segment::Line(s) => s.sample(t), + Segment::Quadratic(s) => s.sample(t), + Segment::Cubic(s) => s.sample(t), + Segment::Arc(s) => s.sample(t), + } + } + + pub fn direction(&self, f: f32) -> Vector { + use lyon_geom::Segment as SegmentTrait; + let f = f.min(1.0).max(0.0); + match self.segment { + Segment::Line(s) => s.derivative(f), + Segment::Quadratic(s) => s.derivative(f), + Segment::Cubic(s) => s.derivative(f), + Segment::Arc(s) => s.derivative(f), + } + } + + /// Split a path element into 3rds + pub fn split_in_thirds(&self) -> [PathElement; 3] { + macro_rules! segment_case { + ($i:expr, $s:expr) => {{ + let (a, b) = ($i).split(1.0 / 3.0); + let (b, c) = b.split(1.0 / 2.0); + + [a, b, c] + .into_iter() + .map(|x| PathElement::new(($s)(x), self.color)) + .collect() + }}; + } + let segments: arrayvec::ArrayVec<PathElement, 3> = match self.segment { + Segment::Line(s) => segment_case!(s, Segment::Line), + Segment::Quadratic(s) => segment_case!(s, Segment::Quadratic), + Segment::Cubic(s) => segment_case!(s, Segment::Cubic), + Segment::Arc(s) => segment_case!(s, Segment::Arc), + }; + + segments + .into_inner() + .expect("We should have precisely the right capacity") + } + + /// Computes the distance from p to this path element + /// Returns the distance from the point to this path element, + /// and the distance along this element to the closest point. + pub fn distance(&self, p: Point) -> (AugmentedDistance, f32) { + use lyon_geom::{LineSegment, QuadraticBezierSegment}; + match self.segment { + Segment::Line(LineSegment { from: s, to: e }) => { + let aq = p - s; + let ab = e - s; + let f = aq.dot(ab) / ab.dot(ab); + let eq = if f >= 0.5 { p - e } else { p - s }; + + let dist_to_endpoint = eq.length(); + let endpoint_sd = AugmentedDistance::new( + aq.cross(ab).signum() * dist_to_endpoint, + // ab.normalize().cross(eq.normalize()), + ab.normalize().dot(eq.normalize()).abs(), + ); + + if 0.0 < f && f < 1.0 { + let ortho = Vector::new(ab.y, -ab.x).normalize(); + let ortho_dist = ortho.dot(aq); + if ortho_dist.abs() < endpoint_sd.distance.abs() { + (AugmentedDistance::new(ortho_dist, 0.0), f) + } else { + (endpoint_sd, f) + } + } else { + (endpoint_sd, f) + } + } + + Segment::Quadratic(QuadraticBezierSegment { + from: p0, + ctrl: p1, + to: p2, + }) => { + use lyon_geom::utils::cubic_polynomial_roots; + let qa = p0 - p; + let ab = p1 - p0; + let br = (p0 - p1) + (p2 - p1); + let a = br.dot(br); + let b = 3.0 * ab.dot(br); + let c = 2.0 * ab.dot(ab) + qa.dot(br); + let d = qa.dot(ab); + let solutions = cubic_polynomial_roots(a, b, c, d); + + let mut min_dist = ab.cross(qa).signum() * qa.length(); + + let mut f = -qa.dot(ab) / ab.dot(ab); + { + let ec = p2 - p1; + let ep = p2 - p; + let dist = ec.cross(ep).signum() * ep.length(); + if dist.abs() < min_dist.abs() { + min_dist = dist; + f = (p - p1).dot(ec) / ec.dot(ec); + } + } + for t in solutions { + if t <= 0.0 || 1.0 <= t { + continue; + } + let endpoint = p0 + (ab * 2.0 * t) + (br * t * t); + let delta = endpoint - p; + let dist = (p2 - p0).cross(delta).signum() * delta.length(); + + if dist.abs() < min_dist.abs() { + min_dist = dist; + f = t; + } + } + + if 0.0 <= f && f <= 1.0 { + (AugmentedDistance::new(min_dist, 0.0), f) + // (AugmentedDistance::new(200f32, 0.0), f) + } else if f < 0.5 { + ( + AugmentedDistance::new(min_dist, ab.normalize().dot(qa.normalize()).abs()), + f, + ) + } else { + ( + AugmentedDistance::new( + min_dist, + (p2 - p1).normalize().dot((p2 - p).normalize()).abs(), + ), + f, + ) + } + } + + _ => unimplemented!(), + } + } + + pub(crate) fn to_psuedodistance( + &self, + dist: AugmentedDistance, + p: Vector, + near: f32, + ) -> AugmentedDistance { + if near <= 0.0 { + let dir = self.direction(0.0).normalize(); + let aq = p - self.sample(0.0).to_vector(); + let ts = aq.dot(dir); + if ts < 0.0 { + let ds = aq.cross(dir); + if ds.abs() <= dist.distance.abs() { + return AugmentedDistance::new(ds, 0.0); + } + } + + dist + } else if near >= 1.0 { + let dir = self.direction(1.0).normalize(); + let aq = p - self.sample(1.0).to_vector(); + let ts = aq.dot(dir); + if ts > 0.0 { + let ds = aq.cross(dir); + if ds.abs() <= dist.distance.abs() { + return AugmentedDistance::new(ds, 0.0); + } + } + + dist + } else { + dist + } + } +} diff --git a/kayak_font/src/recolor.rs b/kayak_font/src/recolor.rs new file mode 100644 index 0000000..61cbbe8 --- /dev/null +++ b/kayak_font/src/recolor.rs @@ -0,0 +1,124 @@ +use lyon_geom::math::{Angle, Vector}; + +use crate::contour::Contour; +use crate::ColorFlags; + +/// Recolor the contours prior to MSDF computation. +/// This function uses a simple technique, +/// again based on Viktor's implementation. +/// It is left as a separate step so you can implement more complex techniques as desired. +pub fn recolor_contours(contours: Vec<Contour>, threshold: Angle, mut seed: u64) -> Vec<Contour> { + let (threshold, _) = threshold.sin_cos(); + + // Determine if a point is a corner, assuming i and o are incoming and + // outgoing normalized direction vectors + let is_corner = |i: Vector, o: Vector| { + let d = i.dot(o); /* |i| |o| cos(t) */ + let c = i.cross(o).abs(); /* |i| |o| sin(t) */ + + // if this corner turns more than 90 degrees (detected by dot product/cos) + // or if it turns more than the threshold angle (detected by cross product/sin) + (d <= 0.0) || (c > threshold) + }; + contours + .into_iter() + .map(|mut c| { + let mut corners = Vec::new(); + let n = c.elements.len(); + // Find all the corners + if n != 0 { + let mut prev_dir = c.elements[n - 1].direction(1.0).normalize(); + for (i, e) in c.elements.iter().enumerate() { + let c_dir = e.direction(0.0).normalize(); + if is_corner(prev_dir, c_dir) { + corners.push(i) + } + prev_dir = e.direction(1.0).normalize(); + } + } + + match corners.len() { + 0 => { + // The whole contour is smooth, and we initialized all colors to white. + // No work to do + c + } + 1 => { + // "Teardrop" case: there is only one sharp corner so we + // just pick 3 colors up front and cycle through them + let mut colors = [ + (ColorFlags::WHITE).switch(&mut seed), + ColorFlags::WHITE, + ColorFlags::WHITE, + ]; + colors[1] = colors[0].switch(&mut seed); + colors[2] = colors[1].switch(&mut seed); + let corner = corners[0]; + match n { + 0 => { + unreachable!(); + } + 1 => { + // Only a single edge segment, but it's a teardrop. + // We split it in 3 to make the colors happen + let mut split = c.elements[0].split_in_thirds(); + split[0].color = colors[0]; + split[1].color = colors[1]; + split[2].color = colors[2]; + + c.elements.clear(); + c.elements.extend_from_slice(&split); + } + 2 => { + // 2 segments. We split it into 6, and assign colors by hand + let mut split0 = c.elements[0].split_in_thirds(); + let mut split1 = c.elements[1].split_in_thirds(); + split0[0].color = colors[0]; + split0[1].color = colors[0]; + split0[2].color = colors[1]; + split1[0].color = colors[1]; + split1[1].color = colors[2]; + split1[2].color = colors[2]; + + c.elements.clear(); + c.elements.extend_from_slice(&split0); + c.elements.extend_from_slice(&split1); + } + _ => { + // We have more than 3 edges to rotate colors through + for (i, e) in c.elements.iter_mut().enumerate() { + // ported from this cursed C++ code: + // contour->edges[(corner+i)%m]->color = (colors+1)[int(3+2.875*i/(m-1)-1.4375+.5)-3]; + let i = (n + i - corner) % n; // Emulate the ( corner + i) % m + let idx_fractional = + 3.5f32 + 2.875f32 * (i as f32) / ((n - 1) as f32) - 1.4375f32; + let idx = idx_fractional.floor() as usize - 2; + e.color = colors[idx]; + } + } + } + c + } + _ => { + // We have 2 or more corners + // Cycle through colors, switching whenever we hit another corner + let n_corners = corners.len(); + let mut spline = 0; + let start = corners[0]; + let mut color = ColorFlags::WHITE; + color = color.switch(&mut seed); + let initial_color = color; + for i in 0..n { + let i = (start + i) % n; + if spline + 1 < n_corners && corners[spline + 1] == i { + spline = spline + 1; + color = color.switch_banned(&mut seed, initial_color); + } + c.elements[i].color = color; + } + c + } + } + }) + .collect() +} diff --git a/kayak_font/src/sdf.rs b/kayak_font/src/sdf.rs new file mode 100644 index 0000000..c1cd976 --- /dev/null +++ b/kayak_font/src/sdf.rs @@ -0,0 +1,87 @@ +use lyon_geom::math::Vector; + +use crate::{contour::Contour, utils::EdgeDistance}; + +/// Computes a SDF from a list of contours. The returned vectors are a `dim` by `dim` +/// matrix of signed distances. The output represents the signed distances to the contours +/// within [0, 1] x [0, 1] +pub fn compute_sdf(contours: &[Contour], dim: usize) -> Vec<Vec<f32>> { + let scale: f32 = 1.0 / (dim as f32); + let windings: Vec<i32> = contours.iter().map(|c| c.winding() as i32).collect(); + + (0..dim) + .map(|y| { + let py = (y as f32 + 0.5) * scale; + (0..dim) + .map(|x| { + if contours.len() == 0 { + return 1.0f32; + } + + let px = (x as f32 + 0.5) * scale; + let p = Vector::new(px, py); + + let mut neg_dist = 1e24f32; + let mut pos_dist = -1e24f32; + let mut winding = 0; + let mut contour_distances = Vec::new(); + contour_distances.reserve(contours.len()); + + for (i, contour) in contours.iter().enumerate() { + let mut contour_min = EdgeDistance::new(); + + for elem in contour.elements.iter() { + let (d, na) = elem.distance(p.to_point()); + + if d < contour_min.dist { + contour_min.dist = d; + contour_min.edge = Some(&elem); + contour_min.nearest_approach = na; + } + } + + // contour_min.to_pseudodistance(p); + let cmdd = contour_min.dist.distance; + + contour_distances.push(cmdd); + + if windings[i] > 0 && cmdd >= 0.0 && cmdd.abs() < pos_dist.abs() { + pos_dist = cmdd; + } + if windings[i] < 0 && cmdd <= 0.0 && cmdd.abs() < neg_dist.abs() { + neg_dist = cmdd; + } + } + + assert!(contour_distances.len() == windings.len()); + + let mut md = -1e24; + if pos_dist >= 0.0 && pos_dist.abs() <= neg_dist.abs() { + md = pos_dist; + winding = 1; + for (d, w) in contour_distances.iter().zip(windings.iter()) { + if *w > 0 && *d > md && d.abs() < neg_dist.abs() { + md = *d; + } + } + } else if neg_dist <= 0.0 && neg_dist.abs() <= pos_dist.abs() { + md = neg_dist; + winding = -1; + for (d, w) in contour_distances.iter().zip(windings.iter()) { + if *w < 0 && *d < md && d.abs() < pos_dist.abs() { + md = *d; + } + } + } + for (c, w) in contour_distances.iter().zip(windings.iter()) { + if *w != winding && c.abs() < md.abs() { + md = *c; + } + } + + md / 0.5 + }) + .collect() + }) + .collect() +} diff --git a/kayak_font/src/ttf_parser.rs b/kayak_font/src/ttf_parser.rs new file mode 100644 index 0000000..4c8607c --- /dev/null +++ b/kayak_font/src/ttf_parser.rs @@ -0,0 +1,69 @@ +use lyon_geom::math::Point; +use lyon_path::Segment; + +use crate::{ColorFlags, Contour, PathCollector, PathElement}; + +impl ttf_parser::OutlineBuilder for PathCollector { + fn move_to(&mut self, x: f32, y: f32) { + let to = Point::new(x, y); + self.pen = to; + self.contour_start = to; + } + + fn line_to(&mut self, x: f32, y: f32) { + let to = Point::new(x, y) * self.scale; + self.elements.push(PathElement::new( + Segment::Line(lyon_geom::LineSegment { from: self.pen, to }), + ColorFlags::WHITE, + )); + self.pen = to; + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + let ctrl = Point::new(x1, y1) * self.scale; + let to = Point::new(x, y) * self.scale; + self.elements.push(PathElement::new( + Segment::Quadratic(lyon_geom::QuadraticBezierSegment { + from: self.pen, + to, + ctrl, + }), + ColorFlags::WHITE, + )); + self.pen = to; + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + let ctrl1 = Point::new(x1, y1) * self.scale; + let ctrl2 = Point::new(x2, y2) * self.scale; + let to = Point::new(x, y) * self.scale; + self.elements.push(PathElement::new( + Segment::Cubic(lyon_geom::CubicBezierSegment { + from: self.pen, + to, + ctrl1, + ctrl2, + }), + ColorFlags::WHITE, + )); + + self.pen = to; + } + + fn close(&mut self) { + if (self.pen - self.contour_start).length() > 1E-14 { + self.elements.push(PathElement::new( + Segment::Line(lyon_geom::LineSegment { + from: self.pen * self.scale, + to: self.contour_start * self.scale, + }), + ColorFlags::WHITE, + )); + } + + self.pen = self.contour_start; + let elements = std::mem::replace(&mut self.elements, Vec::new()); + + self.contours.push(Contour { elements }); + } +} diff --git a/kayak_font/src/utils.rs b/kayak_font/src/utils.rs new file mode 100644 index 0000000..5e8e35f --- /dev/null +++ b/kayak_font/src/utils.rs @@ -0,0 +1,61 @@ +use lyon_path::math::Vector; + +use crate::path_element::PathElement; + +/// Represents a distance to an edge segment +#[derive(Copy, Clone)] +pub(crate) struct EdgeDistance<'a> { + pub(crate) dist: AugmentedDistance, + pub(crate) edge: Option<&'a PathElement>, + pub(crate) nearest_approach: f32, +} + +impl<'a> EdgeDistance<'a> { + /// Create a new edge point, initialized to infinite distance + pub(crate) fn new() -> Self { + Self { + dist: AugmentedDistance::new(-1e24, 0.0), + edge: None, + nearest_approach: 0.0, + } + } + + pub(crate) fn to_pseudodistance(&mut self, p: Vector) { + match self.edge { + Some(edge) => self.dist = edge.to_psuedodistance(self.dist, p, self.nearest_approach), + None => {} + } + } +} + +/// A signed distance, augmented with the cosine of the angle +/// between the tangent of the edge and the vector from the +/// point of nearest approach to the measured point. +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct AugmentedDistance { + /// The actual distance + pub(crate) distance: f32, + /// The cosine of the angle between the tangent vector of the path segment + /// at the point of closest approach and the vector from the point of + /// closest approach to the point to which distance was measured. This is used to + dot: f32, +} + +impl AugmentedDistance { + pub(crate) fn new(distance: f32, dot: f32) -> Self { + Self { distance, dot } + } +} + +impl std::cmp::PartialOrd for AugmentedDistance { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + use std::cmp::Ordering; + + match self.distance.abs().partial_cmp(&other.distance.abs()) { + Some(Ordering::Less) => Some(Ordering::Less), + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Equal) => self.dot.partial_cmp(&other.dot), + None => None, + } + } +} diff --git a/kayak_render_macros/Cargo.toml b/kayak_render_macros/Cargo.toml new file mode 100644 index 0000000..8707f9a --- /dev/null +++ b/kayak_render_macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "kayak_render_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" +proc-macro-error = "1.0" + +[dev-dependencies] +kayak_core = { path = "../kayak_core", version = "0.1.0" } +pretty_assertions = "0.3.4" diff --git a/kayak_render_macros/examples/main.rs b/kayak_render_macros/examples/main.rs new file mode 100644 index 0000000..75d7ccc --- /dev/null +++ b/kayak_render_macros/examples/main.rs @@ -0,0 +1,70 @@ +use kayak_core::{context::KayakContext, styles::Style, Children, Index}; +use kayak_core::{derivative::*, Fragment, Widget}; +use kayak_render_macros::rsx; + +#[derive(Derivative)] +#[derivative(Debug, PartialEq)] +#[allow(dead_code)] +struct Test { + id: Index, + styles: Option<Style>, + foo: u32, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + children: Children, +} + +impl Widget for Test { + fn get_id(&self) -> Index { + todo!() + } + + fn set_id(&mut self, _id: Index) { + todo!() + } + + fn get_styles(&self) -> Option<Style> { + todo!() + } + + fn render(&mut self, _context: &mut KayakContext) { + todo!() + } +} + +fn main() { + let mut context = KayakContext::new(); + { + let context = &mut context; + let foo = 10; + let test_styles = Style::default(); + let parent_id: Option<Index> = None; + rsx! { + <Fragment> + <Test foo={10}> + <Test foo={1}> + <Test foo={5}> + <Test foo={foo} styles={Some(test_styles)}> + {} + </Test> + </Test> + </Test> + </Test> + </Fragment> + }; + + let foo = 10; + let test_styles = Style::default(); + + let parent_id: Option<Index> = None; + rsx! { + <Fragment> + <Test foo={foo} styles={Some(test_styles)}> + {} + </Test> + <Test foo={5} styles={Some(test_styles)}> + {} + </Test> + </Fragment> + } + } +} diff --git a/kayak_render_macros/src/arc_function.rs b/kayak_render_macros/src/arc_function.rs new file mode 100644 index 0000000..bc5c1ad --- /dev/null +++ b/kayak_render_macros/src/arc_function.rs @@ -0,0 +1,28 @@ +use proc_macro2::TokenStream; +use quote::quote; + +pub fn build_arc_function( + widget_name: TokenStream, + children_quotes: TokenStream, + has_parent: bool, + index: usize, +) -> TokenStream { + let parent = if has_parent { + quote! { parent_id } + } else { + quote! { None } + }; + + quote! { + let #widget_name = #children_quotes; + let (should_rerender, child_id) = + context + .widget_manager + .create_widget(#index, #widget_name, #parent); + if should_rerender { + let mut child_widget = context.widget_manager.take(child_id); + child_widget.render(context); + context.widget_manager.repossess(child_widget); + } + } +} diff --git a/kayak_render_macros/src/attribute.rs b/kayak_render_macros/src/attribute.rs new file mode 100644 index 0000000..5cedf71 --- /dev/null +++ b/kayak_render_macros/src/attribute.rs @@ -0,0 +1,95 @@ +use quote::quote; +use std::hash::{Hash, Hasher}; + +use syn::{ + ext::IdentExt, + parse::{Parse, ParseStream, Result}, + spanned::Spanned, +}; + +pub type AttributeKey = syn::punctuated::Punctuated<syn::Ident, syn::Token![-]>; + +#[derive(Debug, Clone)] +pub enum Attribute { + Punned(AttributeKey), + WithValue(AttributeKey, syn::Block), +} + +impl Attribute { + pub fn ident(&self) -> &AttributeKey { + match self { + Self::Punned(ident) | Self::WithValue(ident, _) => ident, + } + } + + pub fn value_tokens(&self) -> proc_macro2::TokenStream { + match self { + Self::WithValue(_, value) => { + if value.stmts.len() == 1 { + let first = &value.stmts[0]; + quote!(#first) + } else { + quote!(#value) + } + } + Self::Punned(ident) => quote!(#ident), + } + } + + pub fn idents(&self) -> Vec<&syn::Ident> { + self.ident().iter().collect::<Vec<_>>() + } + + pub(crate) fn validate(self) -> Result<Self> { + if self.idents().len() < 2 { + Ok(self) + } else { + let alternative_name = self + .idents() + .iter() + .map(|x| x.to_string()) + .collect::<Vec<_>>() + .join("_"); + + let error_message = format!( + "Can't use dash-delimited values on custom components. Did you mean `{}`?", + alternative_name + ); + + Err(syn::Error::new(self.ident().span(), error_message)) + } + } +} + +impl PartialEq for Attribute { + fn eq(&self, other: &Self) -> bool { + let self_idents: Vec<_> = self.ident().iter().collect(); + let other_idents: Vec<_> = other.ident().iter().collect(); + self_idents == other_idents + } +} + +impl Eq for Attribute {} + +impl Hash for Attribute { + fn hash<H: Hasher>(&self, state: &mut H) { + let ident = self.idents(); + Hash::hash(&ident, state) + } +} + +impl Parse for Attribute { + fn parse(input: ParseStream) -> Result<Self> { + let name = AttributeKey::parse_separated_nonempty_with(input, syn::Ident::parse_any)?; + let not_punned = input.peek(syn::Token![=]); + + if !not_punned { + return Ok(Self::Punned(name)); + } + + input.parse::<syn::Token![=]>()?; + let value = input.parse::<syn::Block>()?; + + Ok(Self::WithValue(name, value)) + } +} diff --git a/kayak_render_macros/src/child.rs b/kayak_render_macros/src/child.rs new file mode 100644 index 0000000..ed2c17f --- /dev/null +++ b/kayak_render_macros/src/child.rs @@ -0,0 +1,58 @@ +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream, Result}; + +use crate::widget::Widget; + +#[derive(Debug, Clone)] +pub enum Child { + Widget(Widget), + RawBlock(syn::Block), +} + +impl ToTokens for Child { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + Self::Widget(widget) => widget.to_tokens(tokens), + Self::RawBlock(block) => { + let ts = if block.stmts.len() == 1 { + let first = &block.stmts[0]; + quote!(#first) + } else { + quote!(#block) + }; + ts.to_tokens(tokens); + } + } + } +} + +impl Parse for Child { + fn parse(input: ParseStream) -> Result<Self> { + match Widget::custom_parse(input, true, true) { + Ok(widget) => Ok(Self::Widget(widget)), + Err(_) => { + let block = input.parse::<syn::Block>()?; + Ok(Self::RawBlock(block)) + } + } + } +} + +pub fn walk_block_to_variable(block: &syn::Block) -> Option<proc_macro2::TokenStream> { + if let Some(statement) = block.stmts.first() { + return walk_statement(statement); + } + + return None; +} + +pub fn walk_statement(statement: &syn::Stmt) -> Option<proc_macro2::TokenStream> { + match statement { + syn::Stmt::Expr(expr) => match expr { + syn::Expr::Call(call) => Some(call.args.to_token_stream()), + syn::Expr::Path(path) => Some(path.to_token_stream()), + _ => None, + }, + _ => None, + } +} diff --git a/kayak_render_macros/src/children.rs b/kayak_render_macros/src/children.rs new file mode 100644 index 0000000..db5093c --- /dev/null +++ b/kayak_render_macros/src/children.rs @@ -0,0 +1,219 @@ +use crate::{ + arc_function::build_arc_function, + attribute::Attribute, + child::{walk_block_to_variable, Child}, +}; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream, Result}; + +#[derive(Debug, Clone)] +pub struct Children { + pub nodes: Vec<Child>, +} + +impl Children { + pub fn new(nodes: Vec<Child>) -> Self { + Children { nodes } + } + + pub fn len(&self) -> usize { + self.nodes.len() + } + + pub fn get_clonable_attributes(&self, index: usize) -> Vec<proc_macro2::TokenStream> { + let mut tokens = Vec::new(); + + let regular_tokens: Vec<_> = match &self.nodes[index] { + Child::Widget(widget) => widget + .attributes + .attributes + .iter() + .filter_map(|attr| match attr { + Attribute::WithValue(_, block) => walk_block_to_variable(block), + _ => None, + }) + .collect(), + _ => vec![], + }; + tokens.extend(regular_tokens); + + let children_tokens: Vec<proc_macro2::TokenStream> = match &self.nodes[index] { + Child::Widget(widget) => (0..widget.children.nodes.len()) + .into_iter() + .map(|child_id| widget.children.get_clonable_attributes(child_id)) + .flatten() + .collect(), + _ => vec![], + }; + + tokens.extend(children_tokens); + + tokens.dedup_by(|a, b| a.to_string().eq(&b.to_string())); + + tokens + } + + pub fn as_option_of_tuples_tokens(&self) -> proc_macro2::TokenStream { + let children_quotes: Vec<_> = self + .nodes + .iter() + .map(|child| { + quote! { #child } + }) + .collect(); + + match children_quotes.len() { + 0 => quote! { Option::<()>::None }, + 1 => { + let child = if children_quotes[0].to_string() == "{ }" { + quote! { None } + } else { + let children_attributes: Vec<_> = self.get_clonable_attributes(0); + + let cloned_attrs = quote! { + #(let #children_attributes = #children_attributes.clone();)*; + }; + if children_quotes[0].to_string() == "children" { + quote! { + #(#children_quotes)*.clone() + } + } else { + let children_builder = build_arc_function( + quote! { child_widget }, + quote! { #(#children_quotes),* }, + true, + 0, + ); + quote! { + Some(std::sync::Arc::new(move |parent_id: Option<kayak_core::Index>, context: &mut kayak_core::context::KayakContext| { + #cloned_attrs + #children_builder + })) + } + } + }; + quote! { + #child + } + } + _ => { + let mut iter = children_quotes.iter(); + + let first = iter.next().unwrap(); + let second = iter.next().unwrap(); + + let first = build_arc_function(quote! { child1 }, first.clone(), true, 0); + let second = build_arc_function(quote! { child2 }, second.clone(), true, 1); + + let children_attributes0: Vec<_> = self.get_clonable_attributes(0); + let children_attributes1: Vec<_> = self.get_clonable_attributes(1); + let (children_attributes0, children_attributes1, matching) = + handle_tuple_attributes(&children_attributes0, &children_attributes1); + + let base_matching: Vec<proc_macro2::TokenStream> = matching + .iter() + .map(|a| { + format!("base_{}", a.to_string()) + .to_string() + .parse() + .unwrap() + }) + .collect(); + + let base_clone = quote! { + #(let #base_matching = #matching.clone();)* + }; + + let base_clones_inner = quote! { + #(let #matching = #base_matching.clone();)* + }; + + let cloned_attrs0 = quote! { + #(let #children_attributes0 = #children_attributes0.clone();)* + }; + let cloned_attrs1 = quote! { + #(let #children_attributes1 = #children_attributes1.clone();)* + }; + + let tuple_of_tuples = iter.fold( + quote! { + #base_clone + #cloned_attrs0 + #base_clones_inner + #first + #base_clones_inner + #cloned_attrs1 + #second + }, + |renderable, current| quote!((#renderable, #current)), + ); + + quote! { + Some(std::sync::Arc::new(move |parent_id: Option<kayak_core::Index>, context: &mut kayak_core::context::KayakContext| { + #tuple_of_tuples + })) + } + } + } + } +} + +impl Parse for Children { + fn parse(input: ParseStream) -> Result<Self> { + let mut nodes = vec![]; + + while !input.peek(syn::Token![<]) || !input.peek2(syn::Token![/]) { + let child = input.parse::<Child>()?; + nodes.push(child); + } + + Ok(Self::new(nodes)) + } +} + +impl ToTokens for Children { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.as_option_of_tuples_tokens().to_tokens(tokens); + } +} + +// Takes two incoming attribute streams like: "styles foo bar" and "styles" and resolves +// them into three separate lists like: "foo bar", "", and "styles" +fn handle_tuple_attributes( + a: &Vec<proc_macro2::TokenStream>, + b: &Vec<proc_macro2::TokenStream>, +) -> ( + Vec<proc_macro2::TokenStream>, + Vec<proc_macro2::TokenStream>, + Vec<proc_macro2::TokenStream>, +) { + let mut stream1: Vec<String> = a.iter().map(|a| a.to_string()).collect(); + let mut stream2: Vec<String> = b.iter().map(|b| b.to_string()).collect(); + let matching1: Vec<&String> = stream1 + .iter() + .filter(|a| stream2.iter().any(|b| *a == b)) + .collect(); + let matching2: Vec<&String> = stream2 + .iter() + .filter(|a| stream1.iter().any(|b| *a == b)) + .collect(); + let mut matching: Vec<String> = Vec::new(); + matching.extend(matching1.iter().map(|x| (*x).clone()).collect::<Vec<_>>()); + matching.extend(matching2.iter().map(|x| (*x).clone()).collect::<Vec<_>>()); + matching.sort_unstable(); + matching.dedup_by(|a, b| a.eq(&b)); + + stream1 = stream1 + .into_iter() + .filter(|a| !matching.iter().any(|b| a == b)) + .collect(); + stream2 = stream2 + .into_iter() + .filter(|a| !matching.iter().any(|b| a == b)) + .collect(); + + let matching = matching.iter().map(|m| m.parse().unwrap()).collect(); + let stream1 = stream1.iter().map(|m| m.parse().unwrap()).collect(); + let stream2 = stream2.iter().map(|m| m.parse().unwrap()).collect(); + (stream1, stream2, matching) +} diff --git a/kayak_render_macros/src/function_component.rs b/kayak_render_macros/src/function_component.rs new file mode 100644 index 0000000..55c82f3 --- /dev/null +++ b/kayak_render_macros/src/function_component.rs @@ -0,0 +1,192 @@ +use proc_macro::TokenStream; +use proc_macro_error::emit_error; +use quote::{quote, ToTokens}; +use syn::spanned::Spanned; + +pub fn create_function_component(f: syn::ItemFn) -> TokenStream { + let struct_name = f.sig.ident; + let (impl_generics, ty_generics, where_clause) = f.sig.generics.split_for_impl(); + let inputs = f.sig.inputs; + let block = f.block; + let vis = f.vis; + + let mut input_names: Vec<_> = inputs + .iter() + .filter_map(|argument| match argument { + syn::FnArg::Typed(typed) => { + let typed_info = typed.ty.to_token_stream().to_string(); + let attr_info = typed.pat.to_token_stream().to_string(); + if (typed_info.contains("KayakContext") && !typed_info.contains("Fn")) + || (attr_info.contains("styles") && typed_info.contains("Style")) + { + None + } else { + Some(typed) + } + } + syn::FnArg::Receiver(rec) => { + emit_error!(rec.span(), "Don't use `self` on components"); + None + } + }) + .map(|value| { + let pat = &value.pat; + quote!(#pat) + }) + .collect(); + + // let missing_names = vec!["styles"]; + // missing_names.iter().for_each(|missing| { + // if !input_names + // .iter() + // .any(|input| input.to_string() == missing.to_string()) + // { + // input_names.push(quote! { #missing }); + // } + // }); + + let mut input_block_names: Vec<_> = inputs + .iter() + .filter(|input| { + let input = (quote! { #input }).to_string(); + !(input.contains("parent_styles") + || (input.contains("KayakContext") && !input.contains("Fn"))) + }) + .map(|item| quote! { #item }) + .collect(); + input_block_names.iter_mut().for_each(|input| { + let input_string = (quote! { #input }).to_string(); + if input_string.contains("children : Children") { + *input = quote! { + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub children: Children + }; + } else { + *input = quote! { + pub #input + } + } + }); + + let missing_struct_inputs = vec![ + ( + vec![ + "styles : Option < Style >", + "styles : Option< kayak_core :: styles :: Style >", + ], + quote! { + pub styles: Option<kayak_core::styles::Style> + }, + ), + ( + vec!["children : Children"], + quote! { + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub children: kayak_core::Children + }, + ), + ]; + + for (names, token) in missing_struct_inputs { + if !input_block_names.iter().any(|block_name| { + names + .iter() + .any(|name| block_name.to_string().contains(name)) + }) { + input_block_names.push(token); + } else { + } + } + + let inputs_block = quote!( + #(#input_block_names),* + ); + + if !input_names + .iter() + .any(|item_name| item_name.to_string().contains("children")) + { + input_names.push(quote! { + children + }); + } + + let inputs_reading_ref = if inputs.len() == 0 { + quote! { + let #struct_name { children, styles, .. } = self; + } + } else { + quote!( + let #struct_name { #(#input_names),*, styles, .. } = self; + ) + }; + + TokenStream::from(quote! { + use kayak_core::derivative::*; + + #[derive(Derivative)] + #[derivative(Debug, PartialEq)] + #vis struct #struct_name #impl_generics { + pub id: ::kayak_core::Index, + #inputs_block + } + + impl #impl_generics ::kayak_core::Widget for #struct_name #ty_generics #where_clause { + fn get_id(&self) -> ::kayak_core::Index { + self.id + } + + fn set_id(&mut self, id: ::kayak_core::Index) { + self.id = id; + } + + fn get_styles(&self) -> Option<::kayak_core::styles::Style> { + self.styles.clone() + } + + fn render(&mut self, context: &mut ::kayak_core::context::KayakContext) { + dbg!(stringify!(Rendering widget: #struct_name)); + let parent_id = self.get_id(); + context.set_current_id(parent_id); + let parent_id = Some(parent_id); + #inputs_reading_ref + let children = children.clone(); + #block + } + } + + // impl #impl_generics ::kayak_core::Render for #struct_name #ty_generics #where_clause { + // fn render_into(&self, nodes: &mut Vec<::kayak_core::Node>, context: ::std::sync::Arc<::std::sync::RwLock<::kayak_core::KayakContext>>, parent_styles: Option<kayak_core::Style>) -> Option<usize> { + // let _ = rsx! { + // <>{}</> + // }; // Used to fake out the compiler into thinking we require rsx! still. + // let result = { + // #inputs_reading_ref + // let mut styles = styles.clone(); + // let result = if let Ok(mut context) = context.write() { + // context.set_current_id(self.component_id); + // #ref_block + // } else { panic!("Couldn't get write lock for context!"); }; + + // (result, styles) + // }; + // let child_index = ::kayak_core::Render::render_into(&result.0, nodes, context, result.1.clone()); + // let mut builder = ::kayak_core::NodeBuilder::empty() + // .with_id(self.component_id) + // .with_children(vec![ + // child_index + // ].iter().filter_map(|x| *x).collect()); + // if let Some(styles) = result.1 { + // let new_styles = if let Some(parent_styles) = parent_styles { + // styles // parent_styles.merge(&styles) + // } else { styles }; + // builder = builder.with_styles(new_styles) + // } + // let node = builder.build(); + // let node_index = nodes.len(); + // nodes.push(node); + // Some(node_index) + // } + // } + }) +} diff --git a/kayak_render_macros/src/lib.rs b/kayak_render_macros/src/lib.rs new file mode 100644 index 0000000..dcedd3d --- /dev/null +++ b/kayak_render_macros/src/lib.rs @@ -0,0 +1,100 @@ +extern crate proc_macro; + +mod function_component; +mod tags; + +mod arc_function; +mod attribute; +mod partial_eq; +mod widget; +mod widget_attributes; +mod child; +mod children; + +use partial_eq::impl_dyn_partial_eq; +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use quote::quote; +use syn::{parse_macro_input, parse_quote, token::Comma}; + +use crate::widget::Widget; + +// use crate::prebuilt::element::Element; + +#[proc_macro] +#[proc_macro_error] +pub fn render(input: TokenStream) -> TokenStream { + let mut input = input.into_iter(); + let context = proc_macro2::TokenStream::from(TokenStream::from(input.next().unwrap())); + let comma_input = TokenStream::from(input.next().unwrap()); + let _ = parse_macro_input!(comma_input as Comma); + let rsx_data = proc_macro2::TokenStream::from_iter( + input.map(|token_tree| proc_macro2::TokenStream::from(TokenStream::from(token_tree))), + ); + let el = proc_macro2::TokenStream::from(rsx(TokenStream::from(rsx_data))); + let result = quote! { ::kayak_core::Render::render_into(&#el, #context, None) }; + TokenStream::from(result) +} + +/// Generate a renderable component tree, before rendering it +#[proc_macro] +#[proc_macro_error] +pub fn rsx(input: TokenStream) -> TokenStream { + let el = parse_macro_input!(input as Widget); + let result = quote! { #el }; + TokenStream::from(result) +} + +// #[proc_macro] +// #[proc_macro_error] +// pub fn rsx_create(input: TokenStream) -> TokenStream { +// let el = parse_macro_input!(input as WidgetElement); +// let result = quote! { #el }; +// TokenStream::from(result) +// } + +// #[proc_macro] +// #[proc_macro_error] +// pub fn rsx_update(input: TokenStream) -> TokenStream { +// let el = parse_macro_input!(input as Element); +// let result = quote! { #el }; +// TokenStream::from(result) +// } + +#[proc_macro_attribute] +#[proc_macro_error] +pub fn widget(_attr: TokenStream, item: TokenStream) -> TokenStream { + let f = parse_macro_input!(item as syn::ItemFn); + function_component::create_function_component(f) +} + +#[proc_macro_derive(DynPartialEq)] +pub fn dyn_partial_eq_macro_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + + impl_dyn_partial_eq(&ast) +} + +#[proc_macro_attribute] +pub fn dyn_partial_eq(_: TokenStream, input: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(input as syn::ItemTrait); + + let name = &input.ident; + + let bound: syn::TypeParamBound = parse_quote! { + DynPartialEq + }; + + input.supertraits.push(bound); + + (quote! { + #input + + impl core::cmp::PartialEq for Box<dyn #name> { + fn eq(&self, other: &Self) -> bool { + self.box_eq(other.as_any()) + } + } + }) + .into() +} diff --git a/kayak_render_macros/src/partial_eq.rs b/kayak_render_macros/src/partial_eq.rs new file mode 100644 index 0000000..aa26500 --- /dev/null +++ b/kayak_render_macros/src/partial_eq.rs @@ -0,0 +1,16 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; + +pub fn impl_dyn_partial_eq(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + let gen = quote! { + impl DynPartialEq for #name { + fn box_eq(&self, other: &dyn core::any::Any) -> bool { + other.downcast_ref::<Self>().map_or(false, |a| self == a) + } + } + }; + gen.into() +} diff --git a/kayak_render_macros/src/tags.rs b/kayak_render_macros/src/tags.rs new file mode 100644 index 0000000..fffe931 --- /dev/null +++ b/kayak_render_macros/src/tags.rs @@ -0,0 +1,78 @@ +use crate::widget_attributes::WidgetAttributes; +use proc_macro_error::abort; +use quote::quote; +use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; + +pub struct OpenTag { + pub name: syn::Path, + pub attributes: WidgetAttributes, + pub self_closing: bool, + pub is_custom_element: bool, +} + +fn name_or_fragment(maybe_name: Result<syn::Path>) -> syn::Path { + maybe_name.unwrap_or_else(|_| syn::parse_str::<syn::Path>("::kayak_core::Fragment").unwrap()) +} + +fn is_custom_element_name(path: &syn::Path) -> bool { + match path.get_ident() { + None => true, + Some(ident) => { + let name = ident.to_string(); + let first_letter = name.get(0..1).unwrap(); + first_letter.to_uppercase() == first_letter + } + } +} + +impl Parse for OpenTag { + fn parse(input: ParseStream) -> Result<Self> { + input.parse::<syn::Token![<]>()?; + let maybe_name = syn::Path::parse_mod_style(input); + let name = name_or_fragment(maybe_name); + let is_custom_element = is_custom_element_name(&name); + let attributes = WidgetAttributes::custom_parse(input)?; + let self_closing = input.parse::<syn::Token![/]>().is_ok(); + input.parse::<syn::Token![>]>()?; + + Ok(Self { + name, + attributes, + self_closing, + is_custom_element, + }) + } +} + +pub struct ClosingTag { + name: syn::Path, +} + +impl ClosingTag { + pub fn validate(&self, open_tag: &OpenTag) { + let open_tag_path = &open_tag.name; + let open_tag_path_str = quote!(#open_tag_path).to_string(); + let self_path = &self.name; + let self_path_str = quote!(#self_path).to_string(); + if self_path_str != open_tag_path_str { + abort!( + self.name.span(), + "Expected closing tag for: <{}>", + &open_tag_path_str + ); + } + } +} + +impl Parse for ClosingTag { + fn parse(input: ParseStream) -> Result<Self> { + input.parse::<syn::Token![<]>()?; + input.parse::<syn::Token![/]>()?; + let maybe_name = input.parse::<syn::Path>(); + input.parse::<syn::Token![>]>()?; + Ok(Self { + name: name_or_fragment(maybe_name), + }) + } +} diff --git a/kayak_render_macros/src/widget.rs b/kayak_render_macros/src/widget.rs new file mode 100644 index 0000000..96d0c70 --- /dev/null +++ b/kayak_render_macros/src/widget.rs @@ -0,0 +1,97 @@ +use proc_macro2::TokenStream; +use quote::quote; +use quote::ToTokens; +use syn::parse::{Parse, ParseStream, Result}; + +use crate::arc_function::build_arc_function; +use crate::children::Children; +use crate::tags::ClosingTag; +use crate::{tags::OpenTag, widget_attributes::WidgetAttributes}; + +#[derive(Debug, Clone)] +pub struct Widget { + name: syn::Path, + pub attributes: WidgetAttributes, + pub children: Children, + declaration: TokenStream, +} + +impl Parse for Widget { + fn parse(input: ParseStream) -> Result<Self> { + Self::custom_parse(input, false, true) + } +} + +impl Widget { + pub fn is_custom_element(name: &syn::Path) -> bool { + match name.get_ident() { + None => true, + Some(ident) => { + let name = ident.to_string(); + let first_letter = name.get(0..1).unwrap(); + first_letter.to_uppercase() == first_letter + } + } + } + + pub fn custom_parse(input: ParseStream, as_prop: bool, has_parent: bool) -> Result<Widget> { + let open_tag = input.parse::<OpenTag>()?; + + let children = if open_tag.self_closing { + Children::new(vec![]) + } else { + let children = input.parse::<Children>()?; + let closing_tag = input.parse::<ClosingTag>()?; + closing_tag.validate(&open_tag); + children + }; + + let name = open_tag.name; + let declaration = if Self::is_custom_element(&name) { + let attrs = &open_tag.attributes.for_custom_element(&children); + let attrs = attrs.to_token_stream(); + // let builder = quote! { + // let built_widget = #name #attrs; + // let (should_rerender, child_id) = + // context + // .widget_manager + // .create_widget(0, built_widget, Some(parent_id)); + // if should_rerender { + // let mut child_widget = context.widget_manager.take(child_id); + // child_widget.render(context); + // context.widget_manager.repossess(child_widget); + // } + // }; + // quote! { + // #builder + // } + if !as_prop { + let attrs = quote! { #name #attrs }; + let widget_block = + build_arc_function(quote! { built_widget }, attrs, has_parent, 0); + quote! { + #widget_block + } + } else { + quote! { + #name #attrs + } + } + } else { + panic!("Couldn't find widget!"); + }; + + Ok(Widget { + name, + attributes: open_tag.attributes, + children, + declaration, + }) + } +} + +impl ToTokens for Widget { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.declaration.to_tokens(tokens); + } +} diff --git a/kayak_render_macros/src/widget_attributes.rs b/kayak_render_macros/src/widget_attributes.rs new file mode 100644 index 0000000..3f1c9db --- /dev/null +++ b/kayak_render_macros/src/widget_attributes.rs @@ -0,0 +1,108 @@ +use proc_macro_error::emit_error; +use quote::{quote, ToTokens}; +use std::collections::HashSet; +use syn::{ + ext::IdentExt, + parse::{Parse, ParseStream, Result}, + spanned::Spanned, +}; + +use crate::{attribute::Attribute, children::Children}; + +#[derive(Debug, Clone)] +pub struct WidgetAttributes { + pub attributes: HashSet<Attribute>, +} + +impl WidgetAttributes { + pub fn new(attributes: HashSet<Attribute>) -> Self { + Self { attributes } + } + + pub fn for_custom_element<'c>(&self, children: &'c Children) -> CustomWidgetAttributes<'_, 'c> { + CustomWidgetAttributes { + attributes: &self.attributes, + children, + } + } + + pub fn custom_parse(input: ParseStream) -> Result<Self> { + let mut parsed_self = input.parse::<Self>()?; + let new_attributes: HashSet<Attribute> = parsed_self + .attributes + .drain() + .filter_map(|attribute| match attribute.validate() { + Ok(x) => Some(x), + Err(err) => { + emit_error!(err.span(), "Invalid attribute: {}", err); + None + } + }) + .collect(); + + Ok(WidgetAttributes::new(new_attributes)) + } +} + +impl Parse for WidgetAttributes { + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { + let mut attributes: HashSet<Attribute> = HashSet::new(); + while input.peek(syn::Ident::peek_any) { + let attribute = input.parse::<Attribute>()?; + let ident = attribute.ident(); + if attributes.contains(&attribute) { + emit_error!( + ident.span(), + "There is a previous definition of the {} attribute", + quote!(#ident) + ); + } + attributes.insert(attribute); + } + Ok(WidgetAttributes::new(attributes)) + } +} + +pub struct CustomWidgetAttributes<'a, 'c> { + attributes: &'a HashSet<Attribute>, + children: &'c Children, +} + +impl<'a, 'c> ToTokens for CustomWidgetAttributes<'a, 'c> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let mut attrs: Vec<_> = self + .attributes + .iter() + .map(|attribute| { + let ident = attribute.ident(); + let value = attribute.value_tokens(); + + quote! { + #ident: #value + } + }) + .collect(); + + if self.children.len() > 0 { + let children_tuple = self.children.as_option_of_tuples_tokens(); + attrs.push(quote! { + children: #children_tuple + }); + } + let quoted = if attrs.len() == 0 { + quote!({ id: kayak_core::Index::default(), styles: None, children: None }) + } else { + if !self + .attributes + .iter() + .any(|attribute| attribute.ident().to_token_stream().to_string() == "styles") + { + quote!({ #(#attrs),*, id: kayak_core::Index::default(), styles: None }) + } else { + quote!({ #(#attrs),*, id: kayak_core::Index::default() }) + } + }; + + quoted.to_tokens(tokens); + } +} diff --git a/resources/Roboto-Regular.ttf b/resources/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2b6392ffe8712b9c5450733320cd220d6c0f4bce GIT binary patch literal 171272 zcmbTf2YeJ&+c!LCW_C9{yQ%b)g#>8<(iEkL(iKp;+(_>rRXU2)(0d5INC#mv0+N7` z(4_`Znuwx!+Yl_MK(Z&_|2ngi%%IQvyx*%oW_NZsGuOF#JtHwmlEQFMrPyXKH)*=B zv1h6zCpMQPxmUBcZQ2!=3%e%C&+L|@Zs(h|>(Kb;sdu|b@~m-^6uGEPyQI1+re<%K zWd9+!N{`+{dk$w~J6DqAkh{2O)81o7$5!9&SS!huQ}BKJe#83@9s8)qE=i87AxZ3T z|DL0UOMX%~?)L(|59&W;e7}U;z=!y*hQxjxGN8}UaUCY~n1Jh_mn2)60eyS+DH`<Y z1c^Poj^C#Zzy|@j0ekR0;FT&57&>~~o{<YzNbFCiBt<+sWLWQ>Q^pn_lGslNB`KiN z(4OOlI~pkdIM2fGW8dmIv~OBm&#Qnh1M6KfeAuYb#Y3e&fTsd|KYV20;hXwB`9zZA ze*jD^Bpbeyl&Ut5q)2wjUkZ{!r4d??)8z_#&J*PHNCkAXi=)3d1{X*ksYz##oK!wr zuGFB5IFWE7g*E7+sj`H>)NJ~TLx1rTFWCV>549lulVI`Uk)7EgK@V%!iHmc^DK5rb zOo?NuEKaHFeL+5v#_#i77IaruIA^lgYx6wWY;$-g%VP^&@;L9C@|zj*o02STDE^a8 z6e|dlYX1vxfdDQLz-8NQo`C9|<F1!~*hOP@8GzQV!`w=;l9E<8D#9M0z`AY=KK4oa zkY}GAlFnAEkxxFTS-<`p&p!`TN*QXH?6wnE`AcObkCLRx0SaMBPVzVkJc`rfSAfeY z?(k$A9av<gVQd}?p`&daU&Zcc&;eH(i;G+Vyyc}BX|WD(5I_tvkm<yU(>M`m2JiyS z<?u~8LUY9+f_{>xVp5rGNhw*CM=kJF6y(A&u)q_Tl<jBwHqhw_!G}&ySb@jS>4^|O zjw<j-PEQ%}NhMlA9H4in#)Ze>U$R2~k|Tj6{Bz?hPWJLgZ5OtE!2FwlEEQj0_&W1@ zebMXZzi)D<u<fFC%&!T5#8&ZsY}IVmYcAWsd(gkReBEr`Pj<6jU_0rp;@YaqzDBAp z4b<1~kA?XgOOD4$JgriW_a_AVJ3T=Kbac`&JDioS7YtO^bQ<sU#1wdNOrT@-Go@c) zT`38P2??nwY3ZrSkx@~R?vw;~ygf1^O2tWp-4W?d#n;IZQOR}Fl+N$1{QUUQZOgYG zIyiIW*mq{II=fyw_tCQ4D>+MMPRnFt|MO1ri<dWU{xsr?ThaF}%-=fkt$yQsk6kxt z$HnlYM_u2anw$9!#9JCz{IhME?W7bem6H;r6zN%MlD=LAOH_>Yx^dzr5`%{V3OoTx z*)FwoiHbA3jOo<v^i%}hl00r_b~V%I6`k32N>8;^x8TjxBrRQW6B5&tYS#%*NlUI9 z6^Yfl;}cS&#ZpsKQxbp%mXa1LzKJz|K?t%Xvgm=^rp?&0?Y*fx=X7q_tW(Ej9p&e@ zG5hvyyof$&-mz1QW?i^rpnQ6unl)|bu8d~Ww{4s2Xx+D0hZe2-^?SYO^0Xb>=Qf+R zW7}MNo4&m}v~1I-cl$pYt0Nmj>+~(Yr~Y9(AytwRrDSQ=zwko^;HQEi0%dSghL|J1 z0x^eH&A$S@DDl<k#aS($UR?Tz;rdU?>Mv2QviQzZ!I>RX$`@K(j8Jvpt2(8h3RI;F zZXtw$I~*(xyy1v;C)$C$%C@@c%t6efL`0{jr2&UPRo}O_^lW@$d*+EQ!v?dLU*0yc z;C9cJr~7{nF}b#M0$&@qZDstJF)!Ec^UCWz*sOt`x5q4Mw)ODd8J(BE-iA+}y1sb( z<57*9eA8+;+fioX)G-T`?|Lk1SG!Y#m%0pt%=A~|B(KmvPF?%heA)-VlD_dqzQ>l^ z7V|~yEBtx}uO=_I50+feR-TX~k1EI?7M31umlcOIJSy6ekSNa>|Lc1ROAh(7`5oR) zm@)pI+~ym$fwgZsVK`6W-*({d@Vh&EjA*%swI``fz%|pKx+aT|Wk+IKSa@oREGMQ% zg@wuKZ~rzuan&KY^V(jC(;tkLn|#Il^Q|pLjA6gAq}Wf{Kvt>kh!%V&?}KYrkb5c} zpbJB!Se^1J=&K6JC@AZZlFE4e@#>#c1_E3q-3DD<CE6D5P?SX0l7Cu@)$(5%z@Dka zFPxf`yKk~`?Tr<~S!e$KTUlNCZ*Q_#{(V=1ytTAMO;Ha>fzY|Q7+EzC++Ip>k0LZ* zWSq<Gg5Gl_%j;MsAC$`nvCLd0crQ<3=l8N5bghc~HcOGdfR!&p*UDB)jS5{?B19(& zB@<zvIq*$&e{X)f`Lwy)7WX}izpf(3$ieaf@O_vB%aP4WVIb9;8Yc(yGVBLAW(D1A z5iHvn=`v_oLC1~**l~dSv*pse)~=II_6uAZpRnk;k<ULr^0}V18#St3t3d<0Mp*GQ z#VM>~IIe-Un{0ROI?-;HoV{M|v2L=xCRw>rkj4N=V=yrFynfFB+%v$?RYGx*ECx8U zU8S%?b!C?+J;hR-vO8W@Qk`MRb;H6Fg?u<%LgS>X0k`k{__ccbm!EGdInx&|m@3bn zJ9~yA59NpW37G%nzuAlIS$>6|3jX=p)!Y2~y*r=X#Y%Ppz#F!Y;HT8nA+rH8n*mv= z3nJM_&ef!Br4%AfR_If>8g7>@wMw#lBbU#W!z!wmXKz1^Yj=Z9PR6>Ur8fGtEk@Y5 zc6M~>6+Lo+rZ}VNR!(QOzv&An*()tF+Ns|#E|nP?0!F8@P$-PWsmVo6*`whd=8T%N zGKVQQ&fSB%GeQ1hhFtsI^&LhoSv&8ON1tb3<JWi>!1reHJ>@arOOPh&@C9S-!N%Il z;-oAoMJZV5I4McHB(ZMVdWntDlIUG`iH-CGTX{a%3E_hA&rT=>UJFkk2hLO0>8VhF zqghvis>xW1ldOVUHzm-FWIff`%09~pO_=CrGv`jrEFQNo$9w$ZPZI}?n^r9Ge!Mt0 z^PQOs)-0W?)RkwBWYX*rAAEQ7Sa+>Pg6G{6|Gj-+)SP+K-p!DS3sXUjw&2+S=!KU0 zdMzG;P!hCW=C2a8EGb(qU4sYiDkJ|#^RoxyYb{v(iolO_3jt@zEY9UnW}ZEp>m4Z1 zJL!Foos42XuHbvv>qC{_ik_3tRwdb41!<@WI4VkgbSM}Q%?zvy5|At~XSP~;d(9Ed z2q;HVb~)3hHb*)76i9a!5G5i?zw<cAs6%gr$3?~|u+d?W5fGn*#N;UQMuc^7I4-u7 zfAW4<V{pdlpZ>U+|IoG9wP?(HOV>{y-=vOwQ@*;3k7~#te|?90uYNrFUC-(z*{N8A zEKSE6D%i562q{i_O<#pz=YOmMj9}76ScPB&tRm6`DFClfdJ2?Ay6vi?1Y{{S)hSg` z;^9u|-bG4+(kA|Tu@o`t^pS7>ym*-xu}-Yrr`<QiY#Kj#;bL3%TJ_PL>HOhmclht@ zMQ<~<gzd0-hmY*|^1fRKmuKb>6<!55Q~?$2@DGIgvdFz42~ol3v<ST96L`klq*v7{ z-Yv)EE=Bj0>$c`k4%*6OK?e^9ZbF3A$H5o%(Kr$`jx?|%*p4_Y(La*Nmk7y9WZf*3 zRmvseD=jop5EzEV<X?Cqo!Rjgg!x29xL|Nb4JSh(H%#D+39qzn8c=s{RIiaUiVHsV zzBjVx@O$}rzpPmOWW~~HQ<w3dhR&LCd&V3!W$4b@bq<gH`0G1|#~i6sd*_gY*RL0C zoUkhE(Sn6)xmlx!&7OUG9=M=Sak279thBP!RmY+Ti;Q)KW6|NXP9>Z~hQb?35%0pS ztZG&C2qw&{1wW8`B-TVc;fi!hHg_ttMwPlSiH66mG^n?+$2*7m4LhH4{W16O4=N{q z_ZPq4ZEfXs<0mXzC{Jn9e$t(cB|lB#AM+p6cqcyIwnqK_$;kFC58OSpa(RAnu{0Bw zaj5z-^kLXv5_7=H{jH3W;2Dv56M|W;L6_Qf@XKdluRGwEiTKS|$|z=+oI#TDOJUB$ zFTrQcrQ^y3wI%p0%EV{*7OEb$8jG_<!aPMXKY3))MQE|<-V<!%5;oxk$ypX#sN7U8 zfvX(27o;CP1<;r+l~NyaZdMeyAo&{jr`#+$qBN#|O0A9FybZ+lGo=&CEwxoCG$Ilt zXlw-@8$lyrNma^w2P++mcFJ>@a)i%Z!e`_GnpVYsDq}$JGLZUq`f4l|Ef(~S1--M& zmpT%fRGv(hraFb>|GQO_bgKrFmY(kOdn4UxAU4c^11@oCEZtYG`|7mzZw>4>DM$YH zz{giM4Q-L{3$0g;ozs3+_n`yF4(Yn-^ttT4JBPfM#gCW9+JpG>_N%rPumg>yA(E%A z?zQ*<W<Qd2r9ca<>ql%FnxxtMLg}5KGy6HYG-`@0@YHo?mp6TafVWzKr&?0B+w@_m zGrLjgDX}l~u5j@a($m6dRndwaDmXn%ii=lehdoTCvuF_n$l+mOFz0F*vq#aV>ERRw z*v|%C-+w4IZEnj2OTt6PA07DGl(s41OGnJJhw~h+eZtS|3k+Js5--28|Ai_IE)0Ca zvpx(8y3Hm%c+C3jDf~U;wazaLtITRW+vx3;?62SX58FGm`{BYCrYx)8(0ld!ulKPT zEbBDS`0Ej0nqR{`DzWYzwvCl%;q0RaU-LKkq6Jy~y8E-gE8ZurMApYv5xUa>TiRX= z2n-@z1vrr?(3zst-8S4pLNE!D9oV*$M(|T9*WmH9u(B2)J|z}b;6jp)Rg??fb+b>& zHMYw&PkLWv4<4~ed^(agZRK+E4#)-EXEg(`gh0Mxq|Q137K>{5Sz5FtOlWjcc4(>i zXnvu>-GZTVmVQds*Q9W3*GYE8=`ov#>)i~ea9ZN3&<iwB+zxi@mj|Q!jG4`U;HO#r znXCCXJdee{vtr&te%IzXk=Ji+^|+iVC%%?<cpuK4z#OaI88U7tByudQ#dXM31*w@1 zyG4VRc*e-Dgi@9zJIn+m)TTL6fk?jsMI3h39V@v~Q;3hGXm<h_NJe}Pr#UTMy<VQ* zxz9QOWHAfa`vWTzonLla=E2LSv<<r|u#4XpvXLxp-Z_@Cg?qo-vxfg#IR6*^!{U9Q zI$`=0FdZmG8eaUr;+b$4WP%cy4K6*^QK^PS!y8c@iVF%I*#bFn2U~rFRbJ25^BX6w zoW1?`k5_G;?fmQ~-7fO8TV-2V;anC`+~E-mhcCwjEKLP0KJP;?mSJ~FJh?#CPF#kJ zm04k`POD?*%=R<G2QLmDlm1Wn+B;kRvZ%MR)LT;?D{u4`t+jd9aZ1Yy7?J>kKlF)U z?tf)_&0iMYB!ar9U@%3B#PQ@q(ruOV-nDtkOm988w>-@|nQj+?yG;O}+ybA(knSQ; z`d|3ue~zQHO1cfMl(g??rAw9MZu>$j#n4N`N^S6xUD1q;DqyDg%5ow+u(-Nvv|Elt z0OsQ*GBWC|mi%vz#_z3=A+S+*SyW738o!-?ntgqA$fi`jS9Ts9G;kYBIrlDW!3O<! zmBsPjc=6aj`B$}{W6jp+c(nNmTK3mhW}&4eP&7oj6x~9xh>*iSNqgYWy6wB<|CPTy zLR;uhQ3^iL*88)OW`c`wjnEC5b|u^V^1bnSdGhUiP`A6y<6R(+B<g;I!&pG2z)u&q z``6U8xK=V7o0o^5;;&K>QJu%zP$^0OD~newTug!(5fU6rlaPP`l3jWRO-~l4D}nii zmv8)@H+$;XrOf6je0=%d?K6|-WzO<p%qB24TEC0McNO8hAqtjEuaUFXB$?vYMy{69 z8`2<Yf;2~3g+Mr0^6REOM}eK)M%uYic$&Sby`#OKeT;pkJ=4C`e!%YE4WK#%@<4Cq zmdk5E-mD+I$K^r)mAB;Z2%xaGT;QEtbVY|0ae@a~%V%^*|5ZlJl2N-(6%vDFHb~Zr z=I5`4yy@4mzm9hyQkQ?htA#X%@C%}qSa*^bkq;;1!z2<(&7r>ph?m-R{N-e<D;!oQ z9ItF6yTxuj&I{x5OSwB7^ez04x*29CUG7uZSIzRS_kIF^eAFQR^_O1JSOB@V|3VZ2 zh(Qz!8yhNKlWn5P;SevxSd7rjbQX2z=gVImReKe#10XBa{?Nx`itpL{p%Glr3BIg~ zC4MhK1i~wGv<3BNjkchObr}~HLk){e6nPdzTa;%>xA`yPk375iUrXgBEO7T;7P#nW z!Jz|}l`A>9=M{O!Ri3|n2Yc?~z)xA@T|4;E&t;~tNdEI*gA4f`7V0IBfounfNC2u> zZM1+05%$1i2=aLh0tp6sjNnTPRD{8PN`1rXnT#OV5om&LLc+l9GslT>Y+#;T_5lm! zfB(&Qur8}MZ(hjP$o0IiUk^X|?7Ov#XHQ+D0Is3M0X92u7%9aAE<bl=P>-q@WqokD z;IFt0xC~~}6hD#Pby>|XoW)qP>O>aPVRKYL=tBDQpSX<$YT3;3Or6FrG;dpiUk~t` zcj3tX%gSXon(%vtU+Q>%{KK#k9Pi}$pELXyO*nqSzxLsHJ8(=a8G?LMZ_QRlByDU? zPt^bFl^Hn)&8d53PK&M50)>Ehz&BBr^$C+jh_^csu`}HjN{o|_@}2qYo4=U<(rK*Y zMekcIap8`QS^TE_s`o>i=j*E(XX|=1gXEu<O`5d`6;&p+g>4%NDkMmKG%2xai3C{; zfl;RN*eMHxV|GX>G+IJAVd)dBab-DCx+(W`v`nESrOckL*N_+()tZz9x#Qn=Sop2X zpWn;hzH-6(6>RW@-u&M8nH*~A`1@I#GUeILE@kb$Gy44a=_@7=>oT$5#LdI9KOc4G z-RUbQU40wtssoCw07V>zHLxtGL^We67S}*zjftsYURUrMM|n-PpDpakeOuT%!qz-s zYbUN6Ce_z$;SnX+vX~l6X3MZUW{i>C*d>P}UP^=^)blDXbtmJ+w~`<5yYi7e8{hxH z<|&H5$e%c6CV!)RU6inH@1Awa7k~i~fa(PdcIjD7a!}Ny>pY7?Xt7EqYEEKQVt|?# z4t}zXYTl>byF0z#T`pF$pHPAh*RL;0_Fu#refr*_AS}w%BBH`u5IzC)eJF%CROovp z0Jqfa`b)5Q!TO`q0YY>-s;X|5=)fVFeOuuf7Q2a+ts3)9K3~6=e-<6hKiSJS<gs&k zA1IHC{5&U=yHDDxRfY{K`~vVX={aEHBHjf`gZ~8u7Vo7*A(JsOlJ1(s$QU*9pDATZ zTcrb%3iIpxXtsT&eTO~Aj*n1CrzQB?$wmNnVSFhI#ggUetlKxJC0x79SMjWG*>#?L zN0}<u^49Xk%g=hx$hYNB38$fe^bjER178Z`Xd#xxYcL@70jJ-OjAF9fmrc6K1M+yl zE%|f#i=tuPJ93P&n6BRu*Pnpv`%6vr>su~qaJ!k+HB}N(ATk&>lPvq&9Ac5=2%v7C z8W+i)Q(i2*rBo_<p!~gd?ILyRy7_d+CH#yWXrBW4sw^QjPsve>AX##ESOm-|dDwE` z(W8788*lsJ@whApS{|5G74?i~0lNbGM74LKkReY<p@KuIdgLA+o98ikHU9hzn<nmy zoD6==lBnsxv;+!)Ebe_cZkr1C!k<mxGxPK9Qyw+XvS-kp8_72m_)L(bM2jSqGJJu? z5GTPd;WjqX4ZhfT=aJp4`n#;!F7*(vBwzDBgR7~<0~PAoP>kA+A$DfO)UIQr^iWpO z5M|j4bb(0EsW;h8Q~?#qE#WR&C}Z7FcG62_NP3G*)xJ<vnPhVRNKkIB=lm=DSpr%2 zq40WtQd^_iT2-2)ML$DTQ%|(h6YH})(GbLFN-_~z4c;onF^Q-gEnKod@q*X}e{PA= z!vaN=4jW_&%PAgqmog4~ri4V8TcO>PeThNT6hy|w6%idN@`dhLs<2jd2E6y-h6{}S zxNr1`-ZOJYog8!MGc|z+c3R_J%y*BDeSPNsxjPPZ=sBuSv)5L1KD3#KEbr``|3>rM zr#tncHIo*O1<pdyx6w}vD_5S@9Om>WShJtbK*HZNmTeG1EL+$CTDHxPD60ho2?7UM zJR2&1nMy-IJmv2b9Td2v#fG^={mbE^ERh;}H}Ar5|D8F**_=B$OJ;x6w!^*|%VgIh zer54wyW~ASmtW;x+s7Ao@)|oYg5v)H#qNP(6{S|1vr-IT&_!0H^9+y;f5*%_<QyRd ziE0!YJ&??^07&>v4grvqebCV^vJZA-AEM+2y_fmzwT(IX)|b3+^o?Z)e)HSww{YP6 z)vKnBd!yU-J}i+*G3)-yxVdNGtaEwpLuU!g-2vyXz;Y;h9r|fy%2Qe1Q%1+KUB|LO ztO2s&;tMPr@M|`OGE`cCctPutrQ5@rdxo&5!0U|$j!~6I;zaLgNOvV53)lVL(Idlh zcKQ!Hb@-Q@teKwI+U?HBk`o@Yq^WYs6KQk?OL^otSg?-$wh|gwmbwA@KY-;(;CYDV zC-6)NG(0i^paHrO6lmrAM5eSH!t-*M${`>(#fctkno0}Te+$>s;+omwQ8N(~K(;(i z)O(O#L=C_Zhkg|K)m!}D#4q2w@{`xCemJLBM)HUZeq(r6m|V#(BZ9^K9>3AMkpINJ zuWmTmwsGgn%GvjqhRk10+6w(=@>zQ%R@7GujUtiM`9`cH)+gZ>iU{-k^csn^O=Tg< zvzk&w(4L;~0x%fmije5XN<a|@Q6Msss%_uz+LgPrQNw_w)Hiy4|Glzn;jleNT$%m@ zdyQOJ)B$+!!8<Cy1Mg*F7iMblRT7A6(7+Su(?=Eng)ABXiU>Kvw+^AUgp?|@QY|!E z)GnZOOvx23QhZ7J%9J>v1zIXJI#;Fpf_(HeKx$J{iNVsz_tN>R$4~jEhWYP!{OQ|Y zKi^~Q?pZ5_<sJDsH*?nB*E{T-1xE9->hs1Ge*Nk1eL4+Wnl-1}6jt|-k1nrg_g8-k z+RTFj{|d6=l3Hp3Vc~){PF+TX@io?Hc!NSlLZF&MXpMSGfb3X+S);PF<X8%aDT!zo zaX)J^@g(b{<gkIm-pEME$x%L9!6$oD<@0Y3@6o!5nn^$bGS36x(JScvYpB_SLx=)! z+?kI87Jv-S$IX4MO7_zFfu3P0EdX@L$vLAZ{MC9are=c%Z@fTES$veYVqI{@Amo9e z)1u4J9igTq-yQ#{j6r5|;*VMqk>HO^%66LX&Rov8_{3B}FBiZ-*dUtvTJi7dr^Kc- zcriRZH4UwOX==T~7W_=uGQXGFsfSohjfOg8jBud_0WCq&+q$p_3up+7MF$v8k|0fK zw#H9nV<PLJY;DcgoRtFLE&9IxXR_v$J)ksQL(VQtckbw%oZjrUJG?8qdV~Ero=>++ zMcv1;KKupm9B|PZP_6@@wdTDHXbn>7RAC?n(VIzg;jfPq_GFx<n(T>1(kx3AS29@A zSgKfe1XwZxEGbUP<D@+CC-WoWgTWLs2!&zJ)|Vl-C>pehhSd@L>gx@0yHeco#I~%S zZS2`^Ur+mB4C`ah88o?nMquvu2VTli>y4YYr*#iL%UfMOJ9^Z_0p7au$$P7dep2hv zx`a$&T`sA;7U+{Ha$+p&vMj|g?E-pJ1R}yyXoWzFbOC0oc(Ld0lg^mLORbl&#w=a{ zOP0FA_ecR$q3EY+q6Jl`NW2dA4fpZ7U@!x>hDo#-J@`?k$^jWYGS?Dy@j)j^MjM~N zV%N!EE&P(X#@|_Ti$BQSHgEpc9rI>ymlN0XIs76IUROAe)h+Vhck|2B+Lt#0-8|ky ztt6l;Ck@xab(<Ytn(MJhqKH!sPzCM`?JH0$w6AcEOg@siSMzD>t@}tIcrvkr)k&)K zP5#LdIXnDEd~*EToHZHS+qc^_W3BA^n}0gl`?u}pxOx00f1-Z$*>Z2;vMZ#;y7L~& z1K&!9KIMJ<g9|KnM6kNC5`zL(Kg2?hr<YM;F5E7%5f&%pr;~ZZ{NU>4p7=3m?Tjs* zI&Ye}PEIIh&%8JENrJ47V|9xBaz9%lhb{bE=U{mS)(|W;)6@{EYU)Zzh@63Vi)5DA z2N*h21B~V$s5d(?m;zx5guuxws?|C0V$MFL>$bEC$}-1lucX$Syf}oUhrG8#xHsi> z{7rcs3(<{6Oid9TmDG|OEIR4T0uKC`Q<xQ;p11;Px~y!Grj&%rM(4xRow%ZZK<|7} z<PrS)Ep6`XJ@{Sjd%gOO9K<4bxBDSy>X{Kawz(q*zRO}-wR@rc=(aNs9$EIvTb-M| z^m2oa<2x<h`=2d6x()3>>+&zYCwH_pf)~_N3o&^?BA_;KDw-dR6C=Y$u8rvDqX^N| zNk+XprXm#F2WsdEBejN@)h+Tf>5*WCgjDi~Tx{0avuQ8JKHH+nq<2o9v^C;S7J7TW z_+n6IMCKfM{X+C3FewArzXg5agziHAqlEGnMm4$`gu1er9}x-&&mdR?=}&tGl-NuV zxg&C4;HW88hg|+(Kg&7uS<@WP;CDYcDd%~c{IyOAFFXZk;$Tv80nNW=j0`jh)-z5@ z6o4d}QcE&M==co!m`|F|$9-I=G%P%&YwGH#NngR+AgPCD6aI$I=N6h+_}n4^#?1sC z3>~gXfg(J!=`R7|1#pOr5rx6w;mK;tf*gJ_lRqw&GWn^4pBF7JR-P|BrKA*{SL+pB zRjOg_&tUCm0b8KaHDLit<cN$#TcYFA98rLXj_vx()43pkgMkBN1z_R-{`ERv$A(^g z^5hyD%GX_$_p-U(AH83)<$SR0mQeziqIfIC1873wC!+D9gfA>W3BS+)N{|KGOp`)L z1z7qL(dHjaYziP`cVc2{H1#Y1ko!fa_^W+yxtr8|b71^4{GGEbRH<GV!+%$TGu2o6 zzNOY8N$L#Dd<a;qIV0n~20xku(Et0KQ9XHl(H6Obcb}57Vnv2hcI7m>V<WyqUT3e5 z9+tXVcv)($lF-_NWKU^tnik_gGnTz{#z6~*>nqZ3P%o(|?$o-esc2P+w!6@tf(G}n zXn9=rho5~W@BJ|0^0sZtMZTo&cZW~^vH16bkM1OodWrw{?6+Os`0gVAr7<Abi>9=f zja<_|dS@v~#a>>%od0e=LhyzI-jP0dZ9aIB9x0QgxdMfc>q^pMT!1&s1g|ZO$cjeX zG_+8s17;^8jwqDelOyBF#yi5#Iri_roRF=t&pz9~x9~+4aO?Z_um5<Cm3wJa-8(s- zK3M$6*~8z|#r>zTCF%nu9yNuRHJ7L<=yD}on=<636?j5LHXy>%8;cL0)@XsmCsgFD zg%p83(jlDbsAzCZs`}v2?B-K;w5-;;{l<fHUzz7=RPmMWivPMBsmm%4epB9XvC+l= zd31N!<heYH^;2IJkwvm8vLHX-m$)+o+d7&flLtn*yl{6odGOTUymNcWWRASM@Sg1z zBc5QGvIs<PYy$LFdrgXW3qB#6FT7KOK}*ujjsi1}QZEdWBoRW1<A^z!2=on^ZIwus z*!2Ms89WuH50L2H8hXB@Wgp~A?57sxe0b(;c1~uVT~C*+o;JSAJD-&M@uzrJ;g<U| zRx$3IucAN8G?FF^op_Q;u~(e*u2$h`$c<W-VdS-#2oh;eOp?|;hK<w~#-MC|GzR=+ z&ZJtqAUzJu^%Ni#L54a;G^YIfB3X2VBMvDCwCbg&Cc<v%@snYj(U{8e{Tj$$vcgBN zR;?@hZQ0=e=b0n7kG?x>;8cIsP=4#ys;+C-`cLcO_vKoqp1%KC_TWjYCi5ap%7H%L z*}AH~!2_-)y{O66YtSkXKqmTpU_*D%d=H{vSTA$p5Sgn)3pv1*iH<~wN=kZSx^QgL zqaoTD&Tz1ZsHnQ4**XDiYggN>zkF%^%&Bt+3|~5R>AK@5)-RvF;;nuQrx*1Yb>f>7 zBSxGYKH|iv;nSyP%$mRE?8Wz2WMqyTpEY~c`{z!qUz8zFocP&<iB~30xH5j+XC&X0 zm9GqK5QPajNkpwCyJb+zh>u{9s3NL4n23T(+5v5)?Pk;t=x;g&iO>j)-XT;1;Zne{ zKxzeENF)g(^fYqp^gldi<b>&e<oL_8l+XEMX5)w1%lvRYKgJs1pMx*Hu9THmcxzAI zGKn8yjVEoHEEkZUyc8PkRiV)wG{4}}X~K$P1)7a1)ggHB5y&rJnwy&oD-d1uqjE<6 z3xE9vcd6sSuPM^Y(sa$;q$yF-?ewIAlD<r;8&RN}E*0fRlIV$YW;ZM)pC(5&q)(Aq z3!#a#Y;$_1wp;Y{)plm5oBpC=dTrlb5(~u8Q|Wi4qxAlB@^2DSMSg~wyb^24q)heN zp=vNOm_w~3S3%;LCU>G#M4<SsIzG2ab6#|J_$N*0=I&dQv30@a19{yJG;YQMZ~ws5 zob7WLY<zYWi`g-vp7+iht=hIqo53p7Xx)A;+xu~cIvIVopMLha5f|jCE8gzWWnhEa z!&VQ=?TnANp84j&<kh2kHEH`|+vYvr+f}A~c;6;X+ctjP716gz=b=Q4zUr^?eN2ZT zF^GITl|)Qa9aK04HwcjsL7$$?sA7yN_Vu+=?$KXuU8#HWg%_&()S`mO^Jm+CK`WBF z=cJWdV!h`HkBLIx;h5%=zFHtyErWa0aFRxzq>@&Peyt?!3sqtixtkVHD~&z~NZKa_ zI0NuOh?suNc9|HMLZi}Ct-Pq-dD5KOv89t~o?4LS(o>(AAzMxP8iQ26?(r%SVHhn4 zL(^GhH??1)G9Qbk2VWP2+WmudYd=1^dc*D|-MhXyWXPNU>E^}wQaEeG!ZxhqziiyV z@2wwoh_zx<Sj$8E`2JHT`ThfPhm{+z9C*ufW%Kgh9p+3NF>X%#zSDQ}FSc%FRd(-W z@!PiWTRYFPH%_1CThE^4+b>^YaD5;-@`p;Oz-JX{m=*$m8t4e2#(iB27;WR4njl&x zP~?&dG+Ct+El|8ru>}3#Atv+h3e<m$Mvu}@saRq{8E|A2OVW~&VkL{5sG|#|61`GF zEG-WTvcEEA?9^?$hfHeHX5;SJGgg)3o2z%OFuYwCIX^z3<D~w>#-+?kOAhKkb>U2Y zqUUQ9tCnBD8YaIfp-;M>v_k2ld?+QbB~Q5IKqLOk#T;7iT{c!ZqQs&vsJy7$G3X@B zlUV{zKq!=wawOL~QEAA=GQW*bmeL#G!S8^x5b$>jD#agYX$^$@r-{Y@9HHTGbo+29 zTzi8T4NaOIUdk?%tSw)9s>KRF-xHgp#p|7N@!-#RXFT{bH8!3ogbB!_spvJ6Qk|(t z(8rkgvuaE#{UX-sNh<IpCDFua{nD>V=q&7(4rWZQTgr_#QpBR|ncTJLNOwX?VnSjf zjmRJw<9_35#v29J+^~^FtX<3R3D#tJ^I62o9aAPS*WwVxIm)x9dFR%B=Eygm;=a?w zojX|k?p-X7xbsiGM|o}9<cTlVT~)thg0pgttA{#8Zk*NvrFbSSuth1gY+K-UmDQL^ z9)lP(g9~8HK7(3RRIKIgA#bonEd*~*Rr2IXpi>}ho3G+rLJExD1|?bS6lf4;#gh<S z(Gy1=qDVzWL`_{jXu#5?0|sUmr?qUEmfotBnm1t0>VbnYGozY4SFcr@AuVy|o`@>` zRR6%8L(zXPX7k{=mBir4Fu-a3$E+U3;O3SRTL^iK`vPs{ZKCX1VkP0AW2y3NHiR$R z#@}V{ZDTJMeXP8sbX>uEv2`oh+QMKIVVTreUM=sk9m4uMYJMi$E`lqABSrQw3c2X0 z(&eM#swp8+#7H4<qfK8xH(&%QB8n2~0_7MX6{vxdqhZ^KP&rQC<=w$*3~ls6t7dDr zF~3!V+k4nTdG!!hu3wWn^<Q~z<g$0)8ZfxcVnEnHUL>yqgbC6-E!LEwu|OyW!2qEq zl@)n>De1s4>0N1|q;%67Vi@c|C_2!R=u8ZR0b)lf#9BazK0StsFq4c$h>0+*qJBk; zgvFNr3D!l`k&<PR3a0@V94G;zH%O+YU?j}Heaq$zn?BcS?fa8vuW8zN`Ob{#Ti3OD zscDNfoz;HzYuBuo{7kQj;|HX7E&I&EL6hGZT=Rt&l1Io`_eAxkTtQ^HQSp{h@s>r! z8?MVfT8!L{TuB8La77G>QisS3U-O5{?GnF9lwF0);C;lcVbW__@Y6jwMsy&;cjH8) zQ;dwD!HVX=4K2_StP|B073E8*Pz5p(8iBQA{YRf}kh{&l+s>u0A!+TM_5PYHCARR? zs97y|b(?_cC2)NscwqrjbxjsM`MM1eNe>IRiF~?5ei8EcE;Kz+J5-!Yp4tAt{BIWU zUluI;@vEN~KWR&AT`CV|<CPYO(W5oR*WZXp%a26=dih&je2xUD6Q8hD9~G5v(5z@u z!-n+tqxq{0Q<l)zjWt^+O?v2X8MTDclnc<aFWm07L(w9Gg&%qx#!bj&r94BN&IXBs zKN8bf#h{fN7CPE=YSb$>rTmhfBL=4=)u@JE8r*k+yqY}#KJ17On5joeggzF05O1rc z1D>UvSTf)VOXR?SCws8=I_n(<vF`HRl((U2CditS2pt!M`gCJW(R1DKv7JxZ9xb4} zsPJP9-)S<*P<qj&rvj10>_Fwy6Z4J<hgYpWe2AYv@_x_u?Yj4D_gW8G?Y-`J{??Z3 zj~&}$?=x~#-`2fHzSUE=x>?FNWk0O$(qIWzcPSer(dS5B+7{bBkP_Lc;xFVZye?y9 zBtPbmW96%$kW(j&fgmyI1QxJ;BK~HXbPrx7{q0pbi#gayrBdnN82x7AZ-(J-et}k@ z>#v}{kz<v*Ac;eck?P2+jr+vGFN^~rYS#fKU*_*B;}F-EK__Mx&0p`tgr=?lO!}&r z;`fjzWUb=B92k@pJo0}SDe}a@vRkToA&SGHH@hW^p)z>%m^cy%0XU0wqvTiEGd&Il z%A<$&nVciv`RK&e4MQ9ICXD3mgWesJ-@D4tQ6su;JpajuAM72`dI_r!=eTAK6d~o} z-+}B-;J8re1>Z!i5d;Y)w{X0X>C@1LN38C8YTVS4K0yzrC?KH~_Ni_Fv&9PYb%({p zDKtO>gGIBS;c*zFadax0AL>1S;TvVR@{#w|2)VWQV3eHyWG_1t!+P@dHcu9RW`*|` zHctK$veQMnsC=pRh6R0A><i$pH>jY;KEPk3L{&v>l)ywrlA=mmf#y)D&5jcIl<5g) zEFC?n44lHcV6Oh)SPTHP7|Rc`mSg#Tz8`08S(}MGTO7D&B72SRg$hGo^ZS@Cx`&KY zEHbA9G__iFx~xiGF&z_pvSk@PE5T+tr%08$#S4Xz<wc*0xi*ik(8_cr^N2ERjtBtI zUXz~So1KYHPj*Cw+ajW=;(=b?ltc!_Y&1mgn$~l{q9fE1eRWy?`RixPMKyZKDgCyh zF<Oay*84@*@g?2Sudn_-j}2%?{n3-R6d&R@dq147WDG<5Zt#I=MGG5I1FwwcKlJ{A zdxGIFKmzm%HjruP62U!&-ZK1H^s<Xo0J^rE`a3l5n&-w*XE3n{mPzg<OsVRom_Hn6 zWN8yw4;H=j)>!`NTYf(`mqjkvumMw5{ELCd-Z}O?KaIC9d2g>6H*p06cg#ioagaR> zG2mb=PGnw8io+-s8^fO#&esCM$$8X5Y}B9N!5FA{nmJbg(yf1qq*GOMSRRLBuFofo zjHo2*-T>t_g|k4xx$ZN#*vmPWa`&B_(&})>a|d4ApHRKdtkl6HT7KMV?tsoW)lLOJ zf4F|~xhBco7iGM%UaL`Ib!?3{Ur*_=bk1vCF13GiF#iHP*t+<heG)2H)i5nsUplMF zdV~R|pcbY!ou21>JZR}`s|bBo(XjUxSue@9$rVY~wIG}5W(z`#Ptc_xcpK;*ah9%C z3l9<vC*|~C*Ap+EdiZs{OZ5<M#FhS(8iJ~no@!O~-&Tiz?aWRp{eoH{(jb+#SbY$Y zB4eXPP7nL2bdDKuy<I3B$?eh@FiP(nLR#ZKFU)xHoB7)gfBWTEZw(kY@8Iu;MtSNs z+%xdgAG}o^dzLR7pVVkwk$>}bb??4CZ;;Y<)N+?xZf3;3j&FXjV(p#|gD1ZATKK~K z?b@~J#EX9%<vIFFOvd=kmTAApZ?${1o076?Zp`$h6c@h(4y$RefxfBg@SPnsY7(~~ zb5YfYcj1y6oK{jM2`x)$dYa#KiFf7}9LkGX*omPvN>sZ=$q;LgP7oPDa8z`9w1RDY zC?wWxg_834?dvmV-5Njq(tGcZuRZ@}i@fO{J@;Vm$1MiPadZE(c+<8ilULdz`6J%H z+dA)}_r9LF^v9_qkI!G$ds^2z>(l1G*Qe-@XY02(x^3QxZw``<YbNbhYpS_;7Yseu zPIKtp`dA?%6C7Pwr)=5CgSalJ#2d1gyn|mPf-v&LJW<BSA~cA@{yV~Tj!%H(_}-~C zi^?hg^={A|HJ`iI`5>z&Jhlc6in!S31FjoodnTaI;GpPVOF+k$Dk22Z!BDC=x_#8J z^cxsG59ZEHEzV6^8RnRB;n2LMT)0-YyqLAc<`A)DHbf_aP`wz4BL9~(a=5O9?LHlx zmfCgVorQ0`*<w<^<CDH#G@xV4NqsKl^kkhHG@r9w?$?R$PH#MS8(I{+bE_@8u!`rc z<Fnn%&n;)^$A-xVVsGw0FwEPw>=!xUwB_hlNJJ#?bcbOO*7k3GoWh_Re!NjN)NKE6 zJqj)oIZGpFqUce`8FB1iS`-``yl1EXqelUfvK#P6!*7}@p*$h<t);AMKw0W&QveVe zfkCe%I)p1UMBoXBZ)#E)tUy!k6p?75!NT`I3rCcgyD$-tVrJ{m3--d$`zBG-pgu~< zDJMrJBZ+Qwxv9Gk>n)VjO^I#2{BjN#4KcbY)ysuRqK`6!x+<A%MOIeP4x^<~hCYyN zH0>LX^$yjIj^H=LEQ(l%Ru`cUa7Vx_MhNyIA5wS%rjAt)iZn<X+eCTBVOl$Smjmd> zRNz2yOVr_g+kC)iQUPAf4pJJxCeNIi?{(+nD1Vt>)Jy&nO_d846iGBrs7ec1Jhlpm z_bxaFyGbpS9S}8Id#j$d7zlEx2G}8&%H{a0RqzWM;%$(zubD;MEG6xdq6bx~3>Sek zhaH4V($?FNvpQ<OL;+S3-fP7;m>V&*07j&S_Mk0Iuu8pW?C9X!+^%f?SNsL`!;kC& zPgd*kG<MeehnKqH;{hL>Y-qA>Iz>dki$Rf+S~?37T!b_q=m4+8)LKGxzz~dSyA$## zQDVZJBvMRBS_eQguqu%@F(T_oMZW=dd~)!|G$RPiE3Mj3ZtuOcR$g3fay5AqVGU5p z0g#J8sg(|usMQ%Jqr_cgy3hJQLIrIsU;rdyC%*JZYJJfm7_x3%?xIDxO5geu>wBZ* z_tPh)%iqwwH}O{LZ-Ps^YIZT}rh{Pd;Qr~p8d^mpU%<qqC{3=M791jWQV0tnAA&qN z%7=(n($rK&^kOdcw_R)Mr-f9hpW#i$T;>G~EO@u)hOG)IXr>M-%*5<qmO20sE=CW) zukdfmVNxtuM6Ei6LRDB8(l`<5GaxM4FO8Ma!&*zTK8R=T)&ZncP(z<gDZDO9?ufp; z^x)CwK0GLITMe+?jb{&=y!SQm{i;0$=ZYR~J{Yz(_?Z0n+$l4PK7$0v(tzTh9W?V8 zjY)+}ja9BfiVG&NoX-+jSkIEgviM^q8WFiFlg`t^C6VBQCNyuP0~gEGT}`Vq?Mg*{ zmMOS^@Wz2E338$<g|>Q#rmUg(huz&o6FF^I<?t`^Xd^fAd2BA<G-;>Ol%~sR(!6lq zh<-zdd~i%Y9+}BPcd^vn%(Wr{1LteKMdd@(1)<+v;-|0t3=Pt=_#Wn0TO&rQfh_n+ z&7LYsDRzpFAWWXxK8#qDg{9TRXm1u~LHMquI2{4P^{gXaJav-H(F3`urqN3+LjP?! z{kN5cWv^yZqcuzHd6e)jk=YQ<=x{Enw)W3f!z!XfJtd*%_%9aB{-pkkCx&18$y?X4 zdFJ79Eoc8Z>q5f@r)S>ck8(E5oxNe>oLQUHn!Pil<dE5N;s!qZ?LPfR@t;TUJUQf@ z>BIZ<9>HpJ|BtR;yLjNn)vNE%DK|M2e7^x&VJiC9gQR3lF?9(EEE&q7gjKBaN8RY; zBa2S-NY@7D+4Ow-=&H#dse5)DiChr)Wnm9+D0=>FVS+<rq&GDwmNFG^dUWx!$h>hI z&8FuuY)P;<W`QT)-ppwTh)kxJe0j`S#HG@(t^>7ew3142X=ODLF=`x5T;%X?dA{=S z;g?(H)=!FB_XDfN`mEtUZQF4>wCK{62!!QL-gYpVRj5~PVQ^<Yi)Xh#$&Vgb$82B3 z-^A9mtoSZ(B_9U9DoLZYh?rQcMPG=hOBwhQt1|S2B?uC64=D|7B>7Z7fvL-!bUX^T zp=iFg#Dl^NmFSGLR51%sLIFo)vfSg`_Eq)khE#g##b	$$K|G-##kgg<a}7Wmh5J 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 literal 0 HcmV?d00001 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fa4be9f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +pub mod components { + pub use kayak_components::*; +} + +pub mod core { + pub use kayak_core::*; +} -- GitLab