diff --git a/.gitignore b/.gitignore index 088ba6ba7d345b76aa2b8dc021dd25e1323189b3..afb05e53627ef9e7c2178feed46908502555e61b 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 0000000000000000000000000000000000000000..4b9e0ca7c5ebf99adaca5c433e566899d60944a0 --- /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 0000000000000000000000000000000000000000..1ffcf7e7de740ee391bff8892cd6eaffecf43563 --- /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 0000000000000000000000000000000000000000..9322b73836bd23de91d7daaa90e3ffd807513802 --- /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 0000000000000000000000000000000000000000..f0ed588de77ad2ad5f58925e4a8cbfabb9e6bbcd --- /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 0000000000000000000000000000000000000000..2d0c1db51dc1a20ef30678ae681a8f951ee14106 --- /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 0000000000000000000000000000000000000000..e4e6621f259008610d897e843f8268c625ae51c0 --- /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 0000000000000000000000000000000000000000..c5f8e11b1bfb03c0dce3d49c79410e528b273348 --- /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 0000000000000000000000000000000000000000..051005c4963194bfc8fc6eb797147c92d81b2503 --- /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 0000000000000000000000000000000000000000..222f9839359376c87164dc92f5293db512cfc5a9 --- /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 0000000000000000000000000000000000000000..8c5d8575be447550679ac7c756a99292185d64af --- /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 0000000000000000000000000000000000000000..df4e649349c713520cbd6d0882bb958df250c4b3 --- /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 0000000000000000000000000000000000000000..7a4424cdbbe7f29e72b778830d29aae73886ea24 --- /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 0000000000000000000000000000000000000000..eeb405ab60d8c6d6d48ab722c9cf0caaa31a33f1 --- /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 0000000000000000000000000000000000000000..a15bb07e81734b5b7794e40cbc0172d927a447c2 --- /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 0000000000000000000000000000000000000000..bfe438125d34f0c313bd981f756e130738d0707d --- /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 0000000000000000000000000000000000000000..6b2691eaada2b655a96baf2ea4b49af9d739391f --- /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 0000000000000000000000000000000000000000..5a2b1ec6afcc802661957767a5269cc4db9732c4 --- /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 0000000000000000000000000000000000000000..d04383c8aa4f42ecc40b632f7a5f29d83ac1e7e8 --- /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 0000000000000000000000000000000000000000..edce5b8932df92bc3ebf014d05cec6394a121975 --- /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 0000000000000000000000000000000000000000..2b553164966ee4fa8287c1ea9a93345544715129 --- /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 0000000000000000000000000000000000000000..7a4424cdbbe7f29e72b778830d29aae73886ea24 --- /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 0000000000000000000000000000000000000000..eeb405ab60d8c6d6d48ab722c9cf0caaa31a33f1 --- /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 0000000000000000000000000000000000000000..d96b6d0c87385e833398f902ea3835465962098f --- /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 0000000000000000000000000000000000000000..e356d38dd34ab007fa251c4b179d900b8656d25c --- /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 0000000000000000000000000000000000000000..37aeb58243aa976470811d76a83d949f8754ca34 --- /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 0000000000000000000000000000000000000000..043f0c546c58bca1278d9f13d765b9f146b1914d --- /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 0000000000000000000000000000000000000000..523ed83e5983a1a9323a1683f601d1504cc0a8ab --- /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 0000000000000000000000000000000000000000..6d9ba6526f170e2f47d404cf9097e5581f17f3d5 --- /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 0000000000000000000000000000000000000000..0032221714c236b71c256a3f3b923a6e1515de11 --- /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 0000000000000000000000000000000000000000..86833ba5ae799e4fd16fbd0d1789cae2b3d9024d --- /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 0000000000000000000000000000000000000000..dedfde9891893b0c6eeb1009784d64ecc66d366c --- /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 0000000000000000000000000000000000000000..cdd43adbcc41cc49dcf80afe3af9f0b5e67fee4c --- /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 0000000000000000000000000000000000000000..4f8fea6fe0dedaa63025911fbfea63bdc272c724 --- /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 0000000000000000000000000000000000000000..31167baa8b0d4dca42593a6e21de0231991ce020 --- /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 0000000000000000000000000000000000000000..bd1ad1f895e568a3bb8ef37ecd3c68364e202994 --- /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 0000000000000000000000000000000000000000..d5883f67dfc6f053a100564d11dc27df49fa6219 --- /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 0000000000000000000000000000000000000000..2972b9473ffa462d91f5ea25021ecc51dbf440d0 --- /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 0000000000000000000000000000000000000000..9e72b31390ce26c450d96de9655ed159dc0a5355 --- /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 0000000000000000000000000000000000000000..8fd1a9802f100fbe3278f1df8e0fe7e36aabf30f --- /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 0000000000000000000000000000000000000000..3abf6e4b91a1320de1bf3a72b0d9b929bd350440 --- /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 0000000000000000000000000000000000000000..1f1eb406add78c7fc40e27691abdd060d8716e45 --- /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 0000000000000000000000000000000000000000..34079bcc18ff2f0b476db53211570a431fc3ba87 --- /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 0000000000000000000000000000000000000000..b71a107f2f4f7e529d7953a2326871a18c3316a2 --- /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 0000000000000000000000000000000000000000..74cd09b3f76340ca262933350cc84a0fcf7f92c1 --- /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 0000000000000000000000000000000000000000..837a5e6145f6ca947ef8d11e824f885226284248 --- /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 0000000000000000000000000000000000000000..e363ea592505460321d5968ea98ca9d9652147bd --- /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 0000000000000000000000000000000000000000..59f251ecbf8c322d0a44f8e043955d5157c4b746 --- /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 0000000000000000000000000000000000000000..73ad0d8d8aa726d2518b9962414c421951dc738f --- /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 0000000000000000000000000000000000000000..704cc010bed46b1df679c313a6a6bf89807d1391 --- /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 0000000000000000000000000000000000000000..eb75d0b435e68a96ed89707ef33303c681247d90 --- /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 0000000000000000000000000000000000000000..1c540547861fa4e6b2f8202ca7ecea1f3e80c1a4 --- /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 0000000000000000000000000000000000000000..d75533a9d49f4e20fb5b41fae137eec3cfa2ac40 --- /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 0000000000000000000000000000000000000000..b9e2fe4bfb0f332852746a4f9a4fda9453223be7 --- /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 0000000000000000000000000000000000000000..3e63d8701992694cb94cea79cb767e678d462c81 --- /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 0000000000000000000000000000000000000000..f4e3b702d4fa4eb6e8f3c418332e8f3e7481fcd6 --- /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 0000000000000000000000000000000000000000..aab52d906fa2b84b60b9aa27be530440a3c97962 --- /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 0000000000000000000000000000000000000000..79436b77847607641ead408d6144d56d548ac08b --- /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 0000000000000000000000000000000000000000..2bed82802396a4971c6a7b678c0fcbce6819365f --- /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 0000000000000000000000000000000000000000..09f8903e9eb757c0a80b715e86e58d9296a477c3 --- /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 Binary files /dev/null and b/kayak_font/resources/Roboto-Regular.ttf differ diff --git a/kayak_font/src/color_flags.rs b/kayak_font/src/color_flags.rs new file mode 100644 index 0000000000000000000000000000000000000000..7322912b4e0c849822302ba5bcc85f374b1af552 --- /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 0000000000000000000000000000000000000000..c963de3a0ae6a100704608a867c4f173b6fb0778 --- /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 0000000000000000000000000000000000000000..806d47e1169949f0bc3d50c3c2b92b2f8bd9d837 --- /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 0000000000000000000000000000000000000000..07499ca10ba58fe9523356b58c8b10e5d9f8b379 --- /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 0000000000000000000000000000000000000000..8429e93d4e4fa611b56a998517245cd3676abb84 --- /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 0000000000000000000000000000000000000000..077bc44911cf62a3f6db4483a20852c31a446495 --- /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 0000000000000000000000000000000000000000..7500fccbbf88b412570d39a17ab2517b3c213e20 --- /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 0000000000000000000000000000000000000000..61cbbe89dea590befa760dc7b36d9ea46451a277 --- /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 0000000000000000000000000000000000000000..c1cd976de29931814a94af5857e10d47bbf65f0a --- /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 0000000000000000000000000000000000000000..4c8607ca5ee2ae16ae07e9ac8914d18bbb59a615 --- /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 0000000000000000000000000000000000000000..5e8e35fc6e0a8cbc7f0da38abb0c571a0039a86c --- /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 0000000000000000000000000000000000000000..8707f9acbe7899b9d3173147ff3e927b76b78136 --- /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 0000000000000000000000000000000000000000..75d7cccc218b6da20316f5068594358dc9b1fa02 --- /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 0000000000000000000000000000000000000000..bc5c1ad9a04775e78349608ea68ca35390162608 --- /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 0000000000000000000000000000000000000000..5cedf71fc2f739d7c361a75ad9b8a674b8108558 --- /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 0000000000000000000000000000000000000000..ed2c17fac8195413bb8e3e848a9a33e540390b24 --- /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 0000000000000000000000000000000000000000..db5093c63243fa731c745bfd83d6454fa6f4614b --- /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 0000000000000000000000000000000000000000..55c82f3f44f0d6f323412c60d132bcbee13e4dd9 --- /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 0000000000000000000000000000000000000000..dcedd3dffbd299ec280bf2cac86171f869e12d9e --- /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 0000000000000000000000000000000000000000..aa2650084fcc8a1091c5e42084606cd9cc26f6cf --- /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 0000000000000000000000000000000000000000..fffe931c335ee681fa763b4305381bb724f29c72 --- /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 0000000000000000000000000000000000000000..96d0c703d7621ead8ad5486a71f92e408038bbb9 --- /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 0000000000000000000000000000000000000000..3f1c9db7ba677d7a7e15e0080f8a9d30b2e74551 --- /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 Binary files /dev/null and b/resources/Roboto-Regular.ttf differ diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..fa4be9fa97d4c8007dbddaf2eb4795472191c51c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +pub mod components { + pub use kayak_components::*; +} + +pub mod core { + pub use kayak_core::*; +}