diff --git a/Cargo.lock b/Cargo.lock index 89a3eb7..ef48ccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,7 +103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg-if", "libc", ] @@ -125,7 +125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.8.0", + "bitflags 2.9.0", "cc", "cesu8", "jni", @@ -136,7 +136,7 @@ dependencies = [ "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -147,9 +147,9 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "android_log-sys" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" [[package]] name = "android_system_properties" @@ -160,6 +160,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + [[package]] name = "approx" version = "0.5.1" @@ -390,7 +396,7 @@ dependencies = [ "bevy_tasks", "bevy_utils", "bevy_window", - "bitflags 2.8.0", + "bitflags 2.9.0", "blake3", "crossbeam-channel", "derive_more", @@ -437,7 +443,6 @@ dependencies = [ "bevy_reflect", "bevy_transform", "bevy_utils", - "cpal", "rodio", ] @@ -489,7 +494,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.8.0", + "bitflags 2.9.0", "derive_more", "nonmax", "radsort", @@ -536,7 +541,7 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", - "bitflags 2.8.0", + "bitflags 2.9.0", "concurrent-queue", "derive_more", "disqualified", @@ -678,7 +683,7 @@ dependencies = [ "bevy_math", "bevy_reflect", "bevy_utils", - "bitflags 2.8.0", + "bitflags 2.9.0", "bytemuck", "derive_more", "futures-lite", @@ -748,6 +753,20 @@ dependencies = [ "bevy_winit", ] +[[package]] +name = "bevy_kira_audio" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c929e59bffeb04011aab93880623163c0f8c31c7e99d752d74ba935a53546731" +dependencies = [ + "anyhow", + "bevy", + "kira", + "parking_lot", + "thiserror 2.0.12", + "uuid", +] + [[package]] name = "bevy_log" version = "0.15.3" @@ -807,7 +826,7 @@ dependencies = [ "bevy_reflect", "bevy_transform", "bevy_utils", - "bitflags 2.8.0", + "bitflags 2.9.0", "bytemuck", "derive_more", "hexasphere", @@ -843,7 +862,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.8.0", + "bitflags 2.9.0", "bytemuck", "derive_more", "fixedbitset 0.5.7", @@ -1017,7 +1036,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.8.0", + "bitflags 2.9.0", "bytemuck", "derive_more", "fixedbitset 0.5.7", @@ -1239,7 +1258,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cexpr", "clang-sys", "itertools", @@ -1291,25 +1310,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" dependencies = [ "serde", ] [[package]] name = "blake3" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" +checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "memmap2", ] [[package]] @@ -1348,9 +1366,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" dependencies = [ "bytemuck_derive", ] @@ -1380,9 +1398,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "calloop" @@ -1390,19 +1408,19 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "log", "polling", "rustix", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "cc" -version = "1.2.15" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", @@ -1621,7 +1639,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "fontdb", "log", "rangemap", @@ -1803,9 +1821,9 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encase" @@ -1816,7 +1834,7 @@ dependencies = [ "const_panic", "encase_derive", "glam", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1839,6 +1857,15 @@ dependencies = [ "syn", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1847,9 +1874,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" dependencies = [ "serde", "typeid", @@ -2118,6 +2145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" dependencies = [ "bytemuck", + "mint", "rand", "serde", ] @@ -2191,7 +2219,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "gpu-alloc-types", ] @@ -2201,7 +2229,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -2212,7 +2240,7 @@ checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", - "thiserror", + "thiserror 1.0.69", "windows 0.58.0", ] @@ -2222,7 +2250,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "gpu-descriptor-types", "hashbrown 0.15.2", ] @@ -2233,7 +2261,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -2338,7 +2366,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "inotify-sys", "libc", ] @@ -2373,9 +2401,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" @@ -2388,7 +2416,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -2435,6 +2463,22 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kira" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a9f9dff5e262540b628b00d5e1a772270a962d6869f8695bb24832ff3b394" +dependencies = [ + "cpal", + "glam", + "mint", + "paste", + "ringbuf", + "send_wrapper", + "symphonia", + "triple_buffer", +] + [[package]] name = "ktx2" version = "0.3.0" @@ -2489,9 +2533,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "libc", - "redox_syscall 0.5.9", + "redox_syscall 0.5.10", ] [[package]] @@ -2580,7 +2624,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block", "core-graphics-types", "foreign-types", @@ -2605,6 +2649,21 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mint" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" + +[[package]] +name = "mobile" +version = "0.1.0" +dependencies = [ + "bevy", + "bevy_kira_audio", + "tempo-trainer", +] + [[package]] name = "naga" version = "23.1.0" @@ -2613,7 +2672,7 @@ checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", "bit-set 0.8.0", - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", @@ -2623,7 +2682,7 @@ dependencies = [ "rustc-hash", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -2642,7 +2701,7 @@ dependencies = [ "regex", "regex-syntax 0.8.5", "rustc-hash", - "thiserror", + "thiserror 1.0.69", "tracing", "unicode-ident", ] @@ -2653,12 +2712,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "jni-sys", "log", "ndk-sys 0.5.0+25.2.9519653", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2667,13 +2726,13 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "jni-sys", "log", "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2706,7 +2765,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -2820,7 +2879,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "libc", "objc2", @@ -2836,7 +2895,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "objc2", "objc2-core-location", @@ -2860,7 +2919,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "objc2", "objc2-foundation", @@ -2902,7 +2961,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "dispatch", "libc", @@ -2927,7 +2986,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "objc2", "objc2-foundation", @@ -2939,7 +2998,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "objc2", "objc2-foundation", @@ -2962,7 +3021,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "objc2", "objc2-cloud-kit", @@ -2994,7 +3053,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "objc2", "objc2-core-location", @@ -3088,7 +3147,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.9", + "redox_syscall 0.5.10", "smallvec", "windows-targets 0.52.6", ] @@ -3119,18 +3178,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", @@ -3156,9 +3215,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" @@ -3214,9 +3273,9 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", "syn", @@ -3224,18 +3283,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -3248,9 +3307,9 @@ checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" [[package]] name = "quote" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -3366,11 +3425,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -3423,6 +3482,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "ringbuf" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "rodio" version = "0.19.0" @@ -3431,7 +3499,7 @@ checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb" dependencies = [ "cpal", "lewton", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3441,7 +3509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.8.0", + "bitflags 2.9.0", "serde", "serde_derive", ] @@ -3464,7 +3532,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -3473,9 +3541,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rustybuzz" @@ -3483,7 +3551,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "bytemuck", "libm", "smallvec", @@ -3505,9 +3573,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -3558,9 +3626,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -3638,7 +3706,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -3670,11 +3738,82 @@ dependencies = [ "zeno", ] +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-ogg", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" -version = "2.0.98" +version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", @@ -3721,6 +3860,7 @@ name = "tempo-trainer" version = "0.1.0" dependencies = [ "bevy", + "log", ] [[package]] @@ -3738,7 +3878,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -3752,6 +3901,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -3773,9 +3933,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -3890,6 +4050,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "triple_buffer" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7a7d39da903eaef0d0fd14aae8c8c36cdd7dc1d5a251f88c84b676e8dc0a14" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "ttf-parser" version = "0.20.0" @@ -3914,9 +4083,9 @@ dependencies = [ [[package]] name = "typeid" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "unicode-bidi" @@ -3938,9 +4107,9 @@ checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-linebreak" @@ -3985,6 +4154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "getrandom", + "rand", "serde", ] @@ -4146,7 +4316,7 @@ checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" dependencies = [ "arrayvec", "bit-vec 0.8.0", - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg_aliases 0.1.1", "document-features", "indexmap", @@ -4158,7 +4328,7 @@ dependencies = [ "raw-window-handle", "rustc-hash", "smallvec", - "thiserror", + "thiserror 1.0.69", "wgpu-hal", "wgpu-types", ] @@ -4173,7 +4343,7 @@ dependencies = [ "arrayvec", "ash", "bit-set 0.8.0", - "bitflags 2.8.0", + "bitflags 2.9.0", "block", "bytemuck", "cfg_aliases 0.1.1", @@ -4200,7 +4370,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash", "smallvec", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types", @@ -4214,7 +4384,7 @@ version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "js-sys", "web-sys", ] @@ -4733,7 +4903,7 @@ checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" dependencies = [ "android-activity", "atomic-waker", - "bitflags 2.8.0", + "bitflags 2.9.0", "block2", "bytemuck", "calloop", @@ -4816,7 +4986,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "dlib", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 880263a..51a054e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,50 @@ name = "tempo-trainer" version = "0.1.0" edition = "2024" +[workspace] +members = ["mobile"] + [dependencies] -bevy = "0.15.3" +bevy = { version = "0.15", default-features = false, features = [ + "animation", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_gilrs", + "bevy_gizmos", + "bevy_gltf", + "bevy_mesh_picking_backend", + "bevy_pbr", + "bevy_picking", + "bevy_render", + "bevy_scene", + "bevy_sprite", + "bevy_sprite_picking_backend", + "bevy_state", + "bevy_text", + "bevy_ui", + "bevy_ui_picking_backend", + "bevy_window", + "bevy_winit", + "custom_cursor", + "default_font", + "hdr", + "multi_threaded", + "png", + "smaa_luts", + "sysinfo_plugin", + "tonemapping_luts", + "webgl2", + "x11", + "bevy_audio", + "vorbis", +] } + +## This greatly improves WGPU's performance due to its heavy use of trace! calls +log = { version = "0.4", features = [ + "max_level_debug", + "release_max_level_warn", +] } [profile.web] inherits = "release" diff --git a/build/android/res/mipmap-mdpi/icon.png b/build/android/res/mipmap-mdpi/icon.png new file mode 100644 index 0000000..63a5cd3 Binary files /dev/null and b/build/android/res/mipmap-mdpi/icon.png differ diff --git a/build/icon_1024x1024.png b/build/icon_1024x1024.png new file mode 100644 index 0000000..e1a7be5 Binary files /dev/null and b/build/icon_1024x1024.png differ diff --git a/build/macos/AppIcon.iconset/icon_128x128.png b/build/macos/AppIcon.iconset/icon_128x128.png new file mode 100644 index 0000000..a31ad87 Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_128x128.png differ diff --git a/build/macos/AppIcon.iconset/icon_128x128@2x.png b/build/macos/AppIcon.iconset/icon_128x128@2x.png new file mode 100644 index 0000000..8c780b8 Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_128x128@2x.png differ diff --git a/build/macos/AppIcon.iconset/icon_16x16.png b/build/macos/AppIcon.iconset/icon_16x16.png new file mode 100644 index 0000000..20e53e8 Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_16x16.png differ diff --git a/build/macos/AppIcon.iconset/icon_16x16@2x.png b/build/macos/AppIcon.iconset/icon_16x16@2x.png new file mode 100644 index 0000000..c4f2b8f Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_16x16@2x.png differ diff --git a/build/macos/AppIcon.iconset/icon_256x256.png b/build/macos/AppIcon.iconset/icon_256x256.png new file mode 100644 index 0000000..8c780b8 Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_256x256.png differ diff --git a/build/macos/AppIcon.iconset/icon_256x256@2x.png b/build/macos/AppIcon.iconset/icon_256x256@2x.png new file mode 100644 index 0000000..a16a1aa Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_256x256@2x.png differ diff --git a/build/macos/AppIcon.iconset/icon_32x32.png b/build/macos/AppIcon.iconset/icon_32x32.png new file mode 100644 index 0000000..c4f2b8f Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_32x32.png differ diff --git a/build/macos/AppIcon.iconset/icon_32x32@2x.png b/build/macos/AppIcon.iconset/icon_32x32@2x.png new file mode 100644 index 0000000..250577f Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_32x32@2x.png differ diff --git a/build/macos/AppIcon.iconset/icon_512x512.png b/build/macos/AppIcon.iconset/icon_512x512.png new file mode 100644 index 0000000..a16a1aa Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_512x512.png differ diff --git a/build/macos/AppIcon.iconset/icon_512x512@2x.png b/build/macos/AppIcon.iconset/icon_512x512@2x.png new file mode 100644 index 0000000..e1a7be5 Binary files /dev/null and b/build/macos/AppIcon.iconset/icon_512x512@2x.png differ diff --git a/build/macos/create_icns.sh b/build/macos/create_icns.sh new file mode 100755 index 0000000..f71ed47 --- /dev/null +++ b/build/macos/create_icns.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +rm -rf AppIcon.iconset/* +mkdir -p AppIcon.iconset +sips -z 16 16 ../icon_1024x1024.png --out AppIcon.iconset/icon_16x16.png +sips -z 32 32 ../icon_1024x1024.png --out AppIcon.iconset/icon_16x16@2x.png +sips -z 32 32 ../icon_1024x1024.png --out AppIcon.iconset/icon_32x32.png +sips -z 64 64 ../icon_1024x1024.png --out AppIcon.iconset/icon_32x32@2x.png +sips -z 128 128 ../icon_1024x1024.png --out AppIcon.iconset/icon_128x128.png +sips -z 256 256 ../icon_1024x1024.png --out AppIcon.iconset/icon_128x128@2x.png +sips -z 256 256 ../icon_1024x1024.png --out AppIcon.iconset/icon_256x256.png +sips -z 512 512 ../icon_1024x1024.png --out AppIcon.iconset/icon_256x256@2x.png +sips -z 512 512 ../icon_1024x1024.png --out AppIcon.iconset/icon_512x512.png +cp ../icon_1024x1024.png AppIcon.iconset/icon_512x512@2x.png +iconutil -c icns AppIcon.iconset +mkdir -p src/Game.app/Contents/Resources +mv AppIcon.icns src/Game.app/Contents/Resources/ diff --git a/build/macos/create_icns_linux.sh b/build/macos/create_icns_linux.sh new file mode 100755 index 0000000..d538ef0 --- /dev/null +++ b/build/macos/create_icns_linux.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +rm -rf AppIcon.iconset/* +mkdir -p AppIcon.iconset +convert ../icon_1024x1024.png -resize 16x16 AppIcon.iconset/icon_16x16.png +convert ../icon_1024x1024.png -resize 32x32 AppIcon.iconset/icon_16x16@2x.png +convert ../icon_1024x1024.png -resize 32x32 AppIcon.iconset/icon_32x32.png +convert ../icon_1024x1024.png -resize 64x64 AppIcon.iconset/icon_32x32@2x.png +convert ../icon_1024x1024.png -resize 128x128 AppIcon.iconset/icon_128x128.png +convert ../icon_1024x1024.png -resize 256x256 AppIcon.iconset/icon_128x128@2x.png +convert ../icon_1024x1024.png -resize 256x256 AppIcon.iconset/icon_256x256.png +convert ../icon_1024x1024.png -resize 512x512 AppIcon.iconset/icon_256x256@2x.png +convert ../icon_1024x1024.png -resize 512x512 AppIcon.iconset/icon_512x512.png +cp ../icon_1024x1024.png AppIcon.iconset/icon_512x512@2x.png +png2icns ./AppIcon.icns AppIcon.iconset/icon_16x16.png AppIcon.iconset/icon_32x32.png AppIcon.iconset/icon_128x128.png AppIcon.iconset/icon_256x256.png AppIcon.iconset/icon_512x512.png +mkdir -p src/Game.app/Contents/Resources +mv AppIcon.icns src/Game.app/Contents/Resources/ diff --git a/build/macos/src/Game.app/Contents/Info.plist b/build/macos/src/Game.app/Contents/Info.plist new file mode 100644 index 0000000..3609a04 --- /dev/null +++ b/build/macos/src/Game.app/Contents/Info.plist @@ -0,0 +1,29 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + BevyGame + CFBundleExecutable + bevy_game + CFBundleIconFile + AppIcon.icns + CFBundleIdentifier + your.domain.bevy-game + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Bevy Game + CFBundlePackageType + APPL + CFBundleShortVersionString + + 0.1.0 + CFBundleSupportedPlatforms + + MacOSX + + + diff --git a/build/macos/src/Game.app/Contents/Resources/AppIcon.icns b/build/macos/src/Game.app/Contents/Resources/AppIcon.icns new file mode 100644 index 0000000..a46cb58 Binary files /dev/null and b/build/macos/src/Game.app/Contents/Resources/AppIcon.icns differ diff --git a/build/web/sound.js b/build/web/sound.js new file mode 100644 index 0000000..b2ab677 --- /dev/null +++ b/build/web/sound.js @@ -0,0 +1,62 @@ +// Insert hack to make sound autoplay on Chrome as soon as the user interacts with the tab: +// https://developers.google.com/web/updates/2018/11/web-audio-autoplay#moving-forward + +// the following function keeps track of all AudioContexts and resumes them on the first user +// interaction with the page. If the function is called and all contexts are already running, +// it will remove itself from all event listeners. +(function () { + // An array of all contexts to resume on the page + const audioContextList = []; + + // An array of various user interaction events we should listen for + const userInputEventNames = [ + "click", + "contextmenu", + "auxclick", + "dblclick", + "mousedown", + "mouseup", + "pointerup", + "touchend", + "keydown", + "keyup", + ]; + + // A proxy object to intercept AudioContexts and + // add them to the array for tracking and resuming later + self.AudioContext = new Proxy(self.AudioContext, { + construct(target, args) { + const result = new target(...args); + audioContextList.push(result); + return result; + }, + }); + + // To resume all AudioContexts being tracked + function resumeAllContexts(_event) { + let count = 0; + + audioContextList.forEach((context) => { + if (context.state !== "running") { + context.resume(); + } else { + count++; + } + }); + + // If all the AudioContexts have now resumed then we unbind all + // the event listeners from the page to prevent unnecessary resume attempts + // Checking count > 0 ensures that the user interaction happens AFTER the game started up + if (count > 0 && count === audioContextList.length) { + userInputEventNames.forEach((eventName) => { + document.removeEventListener(eventName, resumeAllContexts); + }); + } + } + + // We bind the resume function for each user interaction + // event on the page + userInputEventNames.forEach((eventName) => { + document.addEventListener(eventName, resumeAllContexts); + }); +})(); diff --git a/build/web/styles.css b/build/web/styles.css new file mode 100644 index 0000000..b0a0359 --- /dev/null +++ b/build/web/styles.css @@ -0,0 +1,53 @@ +body, html { + height: 100%; +} + +body { + background-color: lightgray; + margin: 0; + display: flex; + justify-content: center; + align-items: center; +} + +.game-container { + width: 800px; + height: 600px; + display: flex; + justify-content: center; + align-items: center; +} + +.lds-dual-ring { + display: inline-block; + position: absolute; + left: 0; + right: 0; + margin: auto; + width: 80px; + height: 80px; +} + +.lds-dual-ring:after { + content: " "; + display: block; + width: 64px; + height: 64px; + border-radius: 50%; + border: 6px solid #fff; + border-color: #fff transparent #fff transparent; + animation: lds-dual-ring 1.2s linear infinite; +} + +@keyframes lds-dual-ring { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +#bevy { + z-index: 2; +} diff --git a/build/windows/icon.ico b/build/windows/icon.ico new file mode 100644 index 0000000..8c7afbf Binary files /dev/null and b/build/windows/icon.ico differ diff --git a/build/windows/icon.rc b/build/windows/icon.rc new file mode 100644 index 0000000..61c0aef --- /dev/null +++ b/build/windows/icon.rc @@ -0,0 +1 @@ +app_icon ICON "icon.ico" diff --git a/build/windows/installer/.gitignore b/build/windows/installer/.gitignore new file mode 100644 index 0000000..4ded7c4 --- /dev/null +++ b/build/windows/installer/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/obj/ diff --git a/build/windows/installer/InstallDirUi.wxs b/build/windows/installer/InstallDirUi.wxs new file mode 100644 index 0000000..4815975 --- /dev/null +++ b/build/windows/installer/InstallDirUi.wxs @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/windows/installer/Installer.sln b/build/windows/installer/Installer.sln new file mode 100644 index 0000000..a79b034 --- /dev/null +++ b/build/windows/installer/Installer.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Installer", "Installer.wixproj", "{340293B0-F46C-46A0-88D8-4BB2F3465C53}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|Any CPU.ActiveCfg = Debug|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|Any CPU.Build.0 = Debug|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|ARM64.Build.0 = Debug|ARM64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x64.ActiveCfg = Debug|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x64.Build.0 = Debug|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x86.ActiveCfg = Debug|x86 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Debug|x86.Build.0 = Debug|x86 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|Any CPU.ActiveCfg = Release|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|Any CPU.Build.0 = Release|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|ARM64.ActiveCfg = Release|ARM64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|ARM64.Build.0 = Release|ARM64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x64.ActiveCfg = Release|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x64.Build.0 = Release|x64 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x86.ActiveCfg = Release|x86 + {340293B0-F46C-46A0-88D8-4BB2F3465C53}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/build/windows/installer/Installer.wixproj b/build/windows/installer/Installer.wixproj new file mode 100644 index 0000000..4c3ca90 --- /dev/null +++ b/build/windows/installer/Installer.wixproj @@ -0,0 +1,29 @@ + + + + none + installer + + + + + + + + + AssetsDirectory + INSTALLFOLDER + false + + + + + + + CreditsDirectory + INSTALLFOLDER + false + + + + diff --git a/build/windows/installer/Package.en-us.wxl b/build/windows/installer/Package.en-us.wxl new file mode 100644 index 0000000..83e657b --- /dev/null +++ b/build/windows/installer/Package.en-us.wxl @@ -0,0 +1,6 @@ + + + + diff --git a/build/windows/installer/Package.wxs b/build/windows/installer/Package.wxs new file mode 100644 index 0000000..305bc1f --- /dev/null +++ b/build/windows/installer/Package.wxs @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/windows/installer/global.json b/build/windows/installer/global.json new file mode 100644 index 0000000..9f70a2c --- /dev/null +++ b/build/windows/installer/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "7.0.102", + "rollForward": "latestFeature" + } +} diff --git a/mobile/.cargo/config.toml b/mobile/.cargo/config.toml new file mode 100644 index 0000000..19a6340 --- /dev/null +++ b/mobile/.cargo/config.toml @@ -0,0 +1,5 @@ +# Flag to notify the compiler we're building for the iOS simulator from an Apple silicon mac +# This needs some workarounds for now +# See https://github.com/bevyengine/bevy/pull/10178 - remove if it's not needed anymore. +[target.aarch64-apple-ios-sim] +rustflags = ["--cfg=ios_simulator"] diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..b446ed7 --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1,3 @@ +mobile.xcodeproj/xcuserdata/ +mobile.xcodeproj/project.xcworkspace/ +build/ diff --git a/mobile/Cargo.toml b/mobile/Cargo.toml new file mode 100644 index 0000000..176e414 --- /dev/null +++ b/mobile/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "mobile" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "mobile" +crate-type = ["staticlib", "cdylib"] + +[dependencies] +tempo-trainer = { path = ".." } +bevy = { version = "0.15", default-features = false, features = [ + "android-native-activity", +] } + +# Needed for Android +bevy_kira_audio = { version = "0.22.0", features = ["android_shared_stdcxx"] } + +# See https://github.com/bevyengine/bevy/pull/12052 +[target.aarch64-apple-ios-sim.dependencies] +bevy = { version = "0.15", default-features = false, features = [ + "ios_simulator", +] } + +[package.metadata.android] +package = "me.nikl.bevygame" # ToDo +apk_name = "BevyGame" # ToDo same as GAME_OSX_APP_NAME in release workflow +assets = "../assets" +strip = "strip" +resources = "../build/android/res" +build_targets = ["aarch64-linux-android"] + +[package.metadata.android.sdk] +target_sdk_version = 33 + +[package.metadata.android.application] +icon = "@mipmap/icon" +label = "Bevy Game" # ToDo + +[package.metadata.android.signing.release] +path = "/home/hatoo/.android/debug.keystore" +keystore_password = "android" diff --git a/mobile/Makefile b/mobile/Makefile new file mode 100644 index 0000000..4fb6908 --- /dev/null +++ b/mobile/Makefile @@ -0,0 +1,29 @@ +.PHONY: xcodebuild run install boot-sim generate clean + +DEVICE = ${DEVICE_ID} +ifndef DEVICE_ID + DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'unavailable' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})") +endif + +run: install + # Todo: change the bundle identifier (then remove this comment :P) + xcrun simctl launch --console $(DEVICE) me.nikl.bevygame + +boot-sim: + xcrun simctl boot $(DEVICE) || true + +install: xcodebuild-simulator boot-sim + xcrun simctl install $(DEVICE) build/Build/Products/Debug-iphonesimulator/mobile.app + +xcodebuild-simulator: + IOS_TARGETS=x86_64-apple-ios xcodebuild -scheme mobile -configuration Debug -derivedDataPath build -destination "id=$(DEVICE)" + +xcodebuild-iphone: + IOS_TARGETS=aarch64-apple-ios xcodebuild -scheme mobile -configuration Debug -derivedDataPath build -arch arm64 + +xcodebuild-iphone-release: + IOS_TARGETS=aarch64-apple-ios xcodebuild -scheme mobile -configuration Release -derivedDataPath build -arch arm64 + +clean: + rm -r build + cargo clean diff --git a/mobile/build_rust_deps.sh b/mobile/build_rust_deps.sh new file mode 100644 index 0000000..af8e0a7 --- /dev/null +++ b/mobile/build_rust_deps.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# based on https://github.com/mozilla/glean/blob/main/build-scripts/xc-universal-binary.sh + +set -eux + +PATH=$PATH:$HOME/.cargo/bin + +RELFLAG= +if [[ "$CONFIGURATION" != "Debug" ]]; then + RELFLAG="--profile dist" +fi + +set -euvx + +if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then + # Assume we're in Xcode, which means we're probably cross-compiling. + # In this case, we need to add an extra library search path for build scripts and proc-macros, + # which run on the host instead of the target. + # (macOS Big Sur does not have linkable libraries in /usr/lib/.) + export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}" +fi + +# add homebrew bin path, as it's the most commonly used package manager on macOS +# this is needed for cmake on apple arm processors as it's not available by default +export PATH="$PATH:/opt/homebrew/bin" + +IS_SIMULATOR=0 +if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then + IS_SIMULATOR=1 +fi + +for arch in $ARCHS; do + case "$arch" in + x86_64) + if [ $IS_SIMULATOR -eq 0 ]; then + echo "Building for x86_64, but not a simulator build. What's going on?" >&2 + exit 2 + fi + + # Intel iOS simulator + export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios" + cargo rustc --crate-type staticlib --lib $RELFLAG --target x86_64-apple-ios + ;; + + arm64) + if [ $IS_SIMULATOR -eq 0 ]; then + # Hardware iOS targets + cargo rustc --crate-type staticlib --lib $RELFLAG --target aarch64-apple-ios + else + # M1 iOS simulator -- currently in Nightly only and requires to build `libstd` + cargo rustc --crate-type staticlib --lib $RELFLAG --target aarch64-apple-ios-sim + fi + esac +done diff --git a/mobile/ios-src/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/ios-src/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..5a60908 --- /dev/null +++ b/mobile/ios-src/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "icon_1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios-src/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png b/mobile/ios-src/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png new file mode 100644 index 0000000..a79c37d Binary files /dev/null and b/mobile/ios-src/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png differ diff --git a/mobile/ios-src/Assets.xcassets/Contents.json b/mobile/ios-src/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/mobile/ios-src/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios-src/Info.plist b/mobile/ios-src/Info.plist new file mode 100644 index 0000000..f815a6b --- /dev/null +++ b/mobile/ios-src/Info.plist @@ -0,0 +1,35 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.1 + CFBundleIconName + AppIcon + CFBundleVersion + 0.1.1 + UILaunchStoryboardName + LaunchScreen + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/mobile/ios-src/LaunchScreen.storyboard b/mobile/ios-src/LaunchScreen.storyboard new file mode 100644 index 0000000..324de1d --- /dev/null +++ b/mobile/ios-src/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios-src/bindings.h b/mobile/ios-src/bindings.h new file mode 100644 index 0000000..a705f03 --- /dev/null +++ b/mobile/ios-src/bindings.h @@ -0,0 +1 @@ +void main_rs(void); diff --git a/mobile/ios-src/main.m b/mobile/ios-src/main.m new file mode 100644 index 0000000..7d152fb --- /dev/null +++ b/mobile/ios-src/main.m @@ -0,0 +1,6 @@ +#import "bindings.h" + +int main() { + main_rs(); + return 0; +} diff --git a/mobile/manifest.yaml b/mobile/manifest.yaml new file mode 100644 index 0000000..4be78a5 --- /dev/null +++ b/mobile/manifest.yaml @@ -0,0 +1,11 @@ +android: + gradle: true + # this assets configuration is currently only used without gradle! + #assets: + # - "../assets/*" + icon: "ios-src/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png" + manifest: + package: "me.nikl.bevygame" # Todo + version_code: 3 # Todo you should start at 1 + application: + label: "Bevy game" # Todo diff --git a/mobile/mobile.xcodeproj/project.pbxproj b/mobile/mobile.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4a377e4 --- /dev/null +++ b/mobile/mobile.xcodeproj/project.pbxproj @@ -0,0 +1,469 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 134866208A035F8615C99114 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96A1E5B62F48B379829E8A0D /* Metal.framework */; }; + 2469A4292A6F9AC200ACF4EF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2469A4282A6F9AC200ACF4EF /* Assets.xcassets */; }; + 2469A42B2A6FAC7000ACF4EF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2469A42A2A6FAC7000ACF4EF /* LaunchScreen.storyboard */; }; + 2604C99FAB5A8322EDCABB9F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE71FBCAA714DB4F42459106 /* UIKit.framework */; }; + 442540D056ADB9AE61A0A590 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F1B41978FA53999AA836D0F /* Security.framework */; }; + 55892F1396056740E1AF9685 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AF7DE91055EBD05ED77E57F9 /* main.m */; }; + 55B7188F81C3C4183F81D3AE /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A39528EB2CCB182F5328223A /* libc++.tbd */; }; + 57CD6306253C7A940098CD4A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57CD6305253C7A940098CD4A /* AudioToolbox.framework */; }; + 57CD630E253C80EC0098CD4A /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 57CD630A253C7F5F0098CD4A /* assets */; }; + 6ADF1AB92CCDA73A00AF5F8E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 26BF2C4863C966DABAB40DC8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8DBF1E2B5C613DA41701F6D9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D08AEBE0B1A9C9A7B8C7B33F; + remoteInfo = cargo_ios; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 160DB77300A3F1806F024D47 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; + 2469A4282A6F9AC200ACF4EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "ios-src/Assets.xcassets"; sourceTree = SOURCE_ROOT; }; + 2469A42A2A6FAC7000ACF4EF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ios-src/LaunchScreen.storyboard; sourceTree = ""; }; + 55EAC02897847195D2F44C15 /* mobile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mobile.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 57CD6305253C7A940098CD4A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 57CD630A253C7F5F0098CD4A /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../assets; sourceTree = ""; }; + 6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 8EE7F1E3B0303533925D7E33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 96A1E5B62F48B379829E8A0D /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 9F1B41978FA53999AA836D0F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + A39528EB2CCB182F5328223A /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + AF7DE91055EBD05ED77E57F9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + FE71FBCAA714DB4F42459106 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D5A822CB2D6847BA8800BE4C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6ADF1AB92CCDA73A00AF5F8E /* QuartzCore.framework in Frameworks */, + 442540D056ADB9AE61A0A590 /* Security.framework in Frameworks */, + 134866208A035F8615C99114 /* Metal.framework in Frameworks */, + 2604C99FAB5A8322EDCABB9F /* UIKit.framework in Frameworks */, + 55B7188F81C3C4183F81D3AE /* libc++.tbd in Frameworks */, + 57CD6306253C7A940098CD4A /* AudioToolbox.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 321F7D6A765B38E746C35105 /* Products */ = { + isa = PBXGroup; + children = ( + 55EAC02897847195D2F44C15 /* mobile.app */, + ); + name = Products; + sourceTree = ""; + }; + 4F1D6F28B8A5D1927AB0ADED /* ios-src */ = { + isa = PBXGroup; + children = ( + 2469A4282A6F9AC200ACF4EF /* Assets.xcassets */, + 57CD630A253C7F5F0098CD4A /* assets */, + 160DB77300A3F1806F024D47 /* bindings.h */, + 8EE7F1E3B0303533925D7E33 /* Info.plist */, + AF7DE91055EBD05ED77E57F9 /* main.m */, + ); + path = "ios-src"; + sourceTree = ""; + }; + 8F2E3E6040EAD2EC9F3FA530 = { + isa = PBXGroup; + children = ( + 2469A42A2A6FAC7000ACF4EF /* LaunchScreen.storyboard */, + 4F1D6F28B8A5D1927AB0ADED /* ios-src */, + EB028409C2D0655412DA6E44 /* Frameworks */, + 321F7D6A765B38E746C35105 /* Products */, + ); + sourceTree = ""; + }; + EB028409C2D0655412DA6E44 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6ADF1AB82CCDA73A00AF5F8E /* QuartzCore.framework */, + 57CD6305253C7A940098CD4A /* AudioToolbox.framework */, + A39528EB2CCB182F5328223A /* libc++.tbd */, + 96A1E5B62F48B379829E8A0D /* Metal.framework */, + 9F1B41978FA53999AA836D0F /* Security.framework */, + FE71FBCAA714DB4F42459106 /* UIKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXLegacyTarget section */ + D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */ = { + isa = PBXLegacyTarget; + buildArgumentsString = build_rust_deps.sh; + buildConfigurationList = AA00A0CFDB11F37F2BA3FC2E /* Build configuration list for PBXLegacyTarget "cargo_ios" */; + buildPhases = ( + FE045B3D04D57B713A565FF8 /* Sources */, + ); + buildToolPath = /bin/sh; + buildWorkingDirectory = .; + dependencies = ( + ); + name = cargo_ios; + passBuildSettingsInEnvironment = 1; + productName = cargo_ios; + }; +/* End PBXLegacyTarget section */ + +/* Begin PBXNativeTarget section */ + 3BDB8152E4962373181B4FE5 /* mobile */ = { + isa = PBXNativeTarget; + buildConfigurationList = E714A1AEAAE517C348B5BD27 /* Build configuration list for PBXNativeTarget "mobile" */; + buildPhases = ( + 9F13800790AD9DBC2BC0F116 /* Sources */, + D5A822CB2D6847BA8800BE4C /* Frameworks */, + 57CD630D253C80E60098CD4A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 19D4B9C22ADC6705B5132B4C /* PBXTargetDependency */, + ); + name = mobile; + productName = mobile; + productReference = 55EAC02897847195D2F44C15 /* mobile.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8DBF1E2B5C613DA41701F6D9 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1240; + }; + buildConfigurationList = 9D43D41707A5C30B227B83F9 /* Build configuration list for PBXProject "mobile" */; + compatibilityVersion = "Xcode 10.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 8F2E3E6040EAD2EC9F3FA530; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3BDB8152E4962373181B4FE5 /* mobile */, + D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 57CD630D253C80E60098CD4A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2469A42B2A6FAC7000ACF4EF /* LaunchScreen.storyboard in Resources */, + 2469A4292A6F9AC200ACF4EF /* Assets.xcassets in Resources */, + 57CD630E253C80EC0098CD4A /* assets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 9F13800790AD9DBC2BC0F116 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 55892F1396056740E1AF9685 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FE045B3D04D57B713A565FF8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 19D4B9C22ADC6705B5132B4C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D08AEBE0B1A9C9A7B8C7B33F /* cargo_ios */; + targetProxy = 26BF2C4863C966DABAB40DC8 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 4AD7BC6FDD56FF18FA6DA7D7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 5B14EC4ADC81FBF1F8CF20E9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LANGUAGE_STANDARD = "c++11"; + CLANG_CXX_LIBRARY = "libc++"; + CODE_SIGN_IDENTITY = ""; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "ios-src/", + ); + INFOPLIST_FILE = "ios-src/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = ( + "$(inherited)", + "../target/aarch64-apple-ios/dist", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = ( + "$(inherited)", + "../target/aarch64-apple-ios-sim/dist", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = ( + "$(inherited)", + "../target/x86_64-apple-ios/dist", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lmobile", + "-lc++abi", + ); + PRODUCT_BUNDLE_IDENTIFIER /* Todo */ = me.nikl.bevygame; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 79E3C28F06346EA58420A93D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8265913A25816D964A847F1B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.rust.cargo-ios"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A2D5B73DD30D562B6F366526 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LANGUAGE_STANDARD = "c++11"; + CLANG_CXX_LIBRARY = "libc++"; + CODE_SIGN_IDENTITY = ""; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "ios-src/", + ); + INFOPLIST_FILE = "ios-src/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = ( + "$(inherited)", + "../target/aarch64-apple-ios/debug", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = ( + "$(inherited)", + "../target/aarch64-apple-ios-sim/debug", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = ( + "$(inherited)", + "../target/x86_64-apple-ios/debug", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lmobile", + "-lc++abi", + ); + PRODUCT_BUNDLE_IDENTIFIER /* Todo */ = me.nikl.bevygame; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FEA9B18D9236F9F6DC6DF799 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.rust.cargo-ios"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 9D43D41707A5C30B227B83F9 /* Build configuration list for PBXProject "mobile" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AD7BC6FDD56FF18FA6DA7D7 /* Debug */, + 79E3C28F06346EA58420A93D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + AA00A0CFDB11F37F2BA3FC2E /* Build configuration list for PBXLegacyTarget "cargo_ios" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8265913A25816D964A847F1B /* Debug */, + FEA9B18D9236F9F6DC6DF799 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + E714A1AEAAE517C348B5BD27 /* Build configuration list for PBXNativeTarget "mobile" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2D5B73DD30D562B6F366526 /* Debug */, + 5B14EC4ADC81FBF1F8CF20E9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8DBF1E2B5C613DA41701F6D9 /* Project object */; +} diff --git a/mobile/mobile.xcodeproj/xcshareddata/xcschemes/mobile.xcscheme b/mobile/mobile.xcodeproj/xcshareddata/xcschemes/mobile.xcscheme new file mode 100644 index 0000000..495aaf6 --- /dev/null +++ b/mobile/mobile.xcodeproj/xcshareddata/xcschemes/mobile.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/src/lib.rs b/mobile/src/lib.rs new file mode 100644 index 0000000..cee805c --- /dev/null +++ b/mobile/src/lib.rs @@ -0,0 +1,23 @@ +use bevy::prelude::*; +use bevy::window::{PresentMode, WindowMode}; +use bevy::winit::WinitSettings; +use tempo_trainer::GamePlugin; + +#[bevy_main] +fn main() { + App::new() + .insert_resource(WinitSettings::mobile()) + .add_plugins(( + DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + resizable: false, + mode: WindowMode::BorderlessFullscreen(MonitorSelection::Current), + present_mode: PresentMode::AutoVsync, + ..default() + }), + ..default() + }), + GamePlugin, + )) + .run(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..27da579 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1170 @@ +use std::collections::VecDeque; + +use bevy::utils::{Duration, Instant}; + +use bevy::{ + color::palettes::basic::*, + diagnostic::{DiagnosticsStore, EntityCountDiagnosticsPlugin, FrameTimeDiagnosticsPlugin}, + prelude::*, + render::{camera::ScalingMode, mesh::CircleMeshBuilder}, +}; + +const CIRCLE_SIZE: f32 = 400.0; +const BINS: usize = 16; + +const BAR_HEIGHT_MULTIPLIER: f32 = 4000.0; + +#[derive(Component)] +struct StatusText; + +#[derive(Component)] +struct ClockMarker; + +#[derive(Resource)] +struct LastTick(Instant); + +#[derive(Resource)] +struct Division(u32); + +struct Delta { + delta: f64, + division: usize, + // 0 to 2pi + theta: f64, +} + +#[derive(Resource)] +// delta and nearest disvision +struct TapDeltas(VecDeque); + +#[derive(Resource, Default)] +struct Mute { + tick_mute: bool, + tap_mute: bool, +} + +#[derive(Resource)] +struct HideClock(bool); + +#[derive(Component)] +struct Clock; + +#[derive(Resource)] +struct AudioHandles { + handles: Vec>, + tick: usize, + tap: usize, +} + +impl AudioHandles { + fn tick(&self) -> &Handle { + &self.handles[self.tick] + } + + fn tap(&self) -> &Handle { + &self.handles[self.tap] + } +} + +#[derive(Resource)] +struct ClockResource { + mesh_legend: Handle, + material_legend: Handle, + mesh_delta: Handle, + material_delta: Handle, + mesh_precision: Handle, + material_precision: Handle, +} + +#[derive(Component)] +enum Index { + Tick, + Tap, +} + +#[derive(Component)] +enum IndexButton { + TickIncrement, + TickDecrement, + TapIncrement, + TapDecrement, +} + +#[derive(Component)] +struct Statistics; + +pub struct GamePlugin; + +impl Plugin for GamePlugin { + fn build(&self, app: &mut App) { + app.add_plugins((FrameTimeDiagnosticsPlugin, EntityCountDiagnosticsPlugin)) + .insert_resource(Time::::from_duration(from_bpm(90.0))) + .insert_resource(LastTick(Instant::now())) + .insert_resource(Division(1)) + .insert_resource(TapDeltas(VecDeque::new())) + .insert_resource(Mute::default()) + .insert_resource(HideBarChart(false)) + .insert_resource(HideClock(false)) + .add_systems(Startup, setup) + .add_systems(FixedUpdate, metronome) + .add_systems( + Update, + ( + control, + clock, + set_status_text, + set_bins, + set_clock_legend, + diagnostics_text_update_system, + hide_bar_chart, + hide_clock, + button_system, + set_audio_indices, + set_statistics, + set_clock_delta, + ), + ) + // Set tap sound before tap + .add_systems(Update, (index_button_system, tap).chain()); + } +} + +fn bpm(time: &Time) -> f32 { + 60.0 / time.timestep().as_secs_f32() +} + +fn from_bpm(bpm: f32) -> Duration { + Duration::from_secs_f32(60.0 / bpm) +} + +const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); +const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35); + +#[derive(Component, Clone, Copy)] +enum ButtonKind { + BpmUp1, + BpmDown1, + BpmUp10, + BpmDown10, + DivisionUp1, + DivisionDown1, + TapMute, + TickMute, + HideClock, + HideBarChart, +} + +impl ButtonKind { + fn label(&self) -> &str { + match self { + ButtonKind::BpmUp1 => "BPM+1", + ButtonKind::BpmDown1 => "BPM-1", + ButtonKind::BpmUp10 => "BPM+10", + ButtonKind::BpmDown10 => "BPM-10", + ButtonKind::DivisionUp1 => "Div+", + ButtonKind::DivisionDown1 => "Div-", + ButtonKind::TapMute => "Tap Mute", + ButtonKind::TickMute => "Tick Mute", + ButtonKind::HideClock => "Clock", + ButtonKind::HideBarChart => "Chart", + } + } +} + +#[derive(Component)] +struct BarChart; + +#[derive(Resource)] +struct HideBarChart(bool); + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + asset_server: Res, +) { + commands.insert_resource(AudioHandles { + handles: vec![ + asset_server.load("sounds/c4.ogg"), + asset_server.load("sounds/c5.ogg"), + asset_server.load("sounds/808sd.ogg"), + asset_server.load("sounds/808cb.ogg"), + asset_server.load("sounds/808cp.ogg"), + ], + tap: 0, + tick: 1, + }); + + commands.insert_resource(ClockResource { + mesh_legend: meshes.add(Mesh::from(Circle { radius: 16.0 })), + material_legend: materials.add(Color::linear_rgb(0.1, 0.3, 0.1)), + mesh_delta: meshes.add(Mesh::from(Circle { radius: 12.0 })), + material_delta: materials.add(Color::linear_rgb(0.1, 0.1, 0.3)), + mesh_precision: meshes.add(Mesh::from(Rectangle { + half_size: Vec2::new(0.5, 0.5), + })), + material_precision: materials.add(Color::linear_rgb(0.0, 0.0, 0.0)), + }); + + commands.spawn(( + Camera2d, + Projection::Orthographic(OrthographicProjection { + scaling_mode: ScalingMode::AutoMin { + min_width: 1200.0, + min_height: 1200.0, + }, + ..OrthographicProjection::default_2d() + }), + )); + + commands + .spawn((Clock, Transform::default(), Visibility::Hidden)) + .with_children(|commands| { + commands.spawn(( + Mesh2d(meshes.add(CircleMeshBuilder { + circle: Circle::new(CIRCLE_SIZE), + resolution: 128, + })), + MeshMaterial2d(materials.add(Color::linear_rgb(0.4, 0.4, 0.4))), + Transform::from_xyz(0.0, 0.0, 0.0), + )); + + commands.spawn(( + Mesh2d(meshes.add(CircleMeshBuilder { + circle: Circle::new(CIRCLE_SIZE + 4.0), + resolution: 128, + })), + MeshMaterial2d(materials.add(Color::linear_rgb(0.1, 0.1, 0.1))), + Transform::from_xyz(0.0, 0.0, -1.0), + )); + + commands.spawn(( + ClockMarker, + Mesh2d(meshes.add(Mesh::from(Circle::new(CIRCLE_SIZE / 8.0)))), + MeshMaterial2d(materials.add(Color::BLACK)), + Transform::from_xyz(0.0, 0.0, 1.0), + )); + }); + + commands.spawn( + Node { + position_type: PositionType::Absolute, + display: Display::Flex, + flex_direction: FlexDirection::Row, + top: Val::Px(24.0), + left: Val::Px(0.0), + ..Default::default() + }, + ).with_children(|commands| { + commands.spawn(( + Node { + display: Display::Flex, + flex_direction: FlexDirection::Column, + margin: UiRect { + left: Val::Px(12.0), + ..Default::default() + }, + ..Default::default() + }, + )).with_children(|commands| { + commands.spawn(( + StatusText, + Text::new(""), + )); + commands.spawn(( + Node { + border: UiRect::all(Val::Px(2.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + margin: UiRect { + top: Val::Px(4.0), + ..Default::default() + }, + ..Default::default() + }, + BorderColor(Color::BLACK), + BackgroundColor(NORMAL_BUTTON), + )).with_children(|commands| { + commands.spawn(( + Button, + IndexButton::TickDecrement, + Node { + position_type: PositionType::Absolute, + left: Val::Px(0.0), + height: Val::Percent(100.0), + width: Val::Percent(50.0), + ..default() + }, + )).with_children(|commands| { + commands.spawn(( + Text::new("-"), + Node { + position_type: PositionType::Absolute, + left: Val::Px(12.0), + ..default() + } + )); + }); + commands.spawn(( + Button, + IndexButton::TickIncrement, + Node { + position_type: PositionType::Absolute, + right: Val::Px(0.0), + height: Val::Percent(100.0), + width: Val::Percent(50.0), + ..default() + }, + )).with_children(|commands| { + commands.spawn(( + Text::new("+"), + Node { + position_type: PositionType::Absolute, + right: Val::Px(12.0), + ..default() + } + )); + }); + commands.spawn(( + Index::Tick, + Text::new(""), + )); + }); + commands.spawn(( + Node { + border: UiRect::all(Val::Px(2.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + margin: UiRect { + top: Val::Px(2.0), + ..Default::default() + }, + ..Default::default() + }, + BorderColor(Color::BLACK), + BackgroundColor(NORMAL_BUTTON), + )).with_children(|commands| { + commands.spawn(( + Button, + IndexButton::TapDecrement, + Node { + position_type: PositionType::Absolute, + left: Val::Px(0.0), + height: Val::Percent(100.0), + width: Val::Percent(50.0), + ..default() + }, + )).with_children(|commands| { + commands.spawn(( + Text::new("-"), + Node { + position_type: PositionType::Absolute, + left: Val::Px(12.0), + ..default() + } + )); + }); + commands.spawn(( + Button, + IndexButton::TapIncrement, + Node { + position_type: PositionType::Absolute, + right: Val::Px(0.0), + height: Val::Percent(100.0), + width: Val::Percent(50.0), + ..default() + }, + )).with_children(|commands| { + commands.spawn(( + Text::new("+"), + Node { + position_type: PositionType::Absolute, + right: Val::Px(12.0), + ..default() + } + )); + }); + commands.spawn(( + Index::Tap, + Text::new(""), + )); + }); + } + ); + + #[cfg(not(target_os = "android"))] + commands.spawn( + ( + Node { + margin: UiRect { + left: Val::Px(12.0), + ..default() + }, + ..default() + }, + Statistics, + Text::new("Statistics:"), + ) + ); + + #[cfg(not(target_os = "android"))] + commands.spawn(( + Text::new( + "up/down: BPM +-1\nleft/right: BPM +-10\n[/]: Division +-1\nn: Tap Mute\nm: Tick Mute\n,: Hide Clock", + ), + Node { + margin: UiRect { + left: Val::Px(12.0), + ..Default::default() + }, + ..Default::default() + }, + )); + }); + + // Bar chart + + commands + .spawn(( + BarChart, + Visibility::Visible, + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + justify_self: JustifySelf::Center, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + )) + .with_children(|commands| { + commands + .spawn(Node { + display: Display::Flex, + justify_self: JustifySelf::Center, + flex_direction: FlexDirection::Row, + width: Val::Percent(80.0), + height: Val::Percent(100.0), + ..Default::default() + }) + .with_children(|commands| { + for (f, height, label) in [ + (0.0, 4.0, "0"), + (1.0, 3.0, "1/60"), + (1.5, 2.0, "1.5/60"), + (2.0, 1.0, "2/60"), + ] { + commands.spawn(( + Node { + position_type: PositionType::Absolute, + width: Val::Percent(100.0), + height: Val::Px(f / 60.0 * BAR_HEIGHT_MULTIPLIER + height / 2.0), + bottom: Val::Percent(50.0), + border: UiRect { + top: Val::Px(height), + ..default() + }, + ..default() + }, + BorderColor(Color::BLACK), + )); + commands.spawn(( + Node { + position_type: PositionType::Absolute, + width: Val::Percent(100.0), + height: Val::Px(f / 60.0 * BAR_HEIGHT_MULTIPLIER + height / 2.0), + top: Val::Percent(50.0), + border: UiRect { + bottom: Val::Px(height), + ..default() + }, + ..default() + }, + BorderColor(Color::BLACK), + )); + + commands + .spawn(( + Node { + position_type: PositionType::Absolute, + left: Val::Px(-12.0), + height: Val::Px(f / 60.0 * BAR_HEIGHT_MULTIPLIER), + width: Val::Percent(100.0), + bottom: Val::Percent(50.0), + ..default() + }, + // BackgroundColor(Color::linear_rgba(0.0, 1.0, 0.0, 0.3)), + )) + .with_children(|commands| { + commands.spawn(( + Node { + position_type: PositionType::Absolute, + right: Val::Percent(100.0), + bottom: Val::Percent(100.0), + ..default() + }, + Text::new(label), + TextFont { + font_size: 10.3, + ..Default::default() + }, + )); + }); + } + + for i in 0..BINS { + commands + .spawn(Node { + margin: UiRect { + left: Val::Px(4.0), + right: Val::Px(4.0), + ..default() + }, + flex_grow: 1.0, + flex_basis: Val::Px(0.0), + justify_content: JustifyContent::Center, + justify_self: JustifySelf::Center, + align_items: AlignItems::Center, + ..default() + }) + .with_children(|commands| { + commands + .spawn(( + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + justify_content: JustifyContent::Center, + justify_self: JustifySelf::Center, + align_items: AlignItems::Center, + ..default() + }, + // BackgroundColor(Color::linear_rgba(0.0, 1.0, 0.0, 0.3)), + )) + .with_children(|commands| { + commands.spawn(( + BinBar, + BinIndex(i), + Visibility::Inherited, + Node { + position_type: PositionType::Absolute, + width: Val::Percent(100.0), + height: Val::Px(100.0), + top: Val::Percent(50.0), + bottom: Val::DEFAULT, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + BackgroundColor(Color::linear_rgb(0.0, 0.0, 1.0)), + )); + commands.spawn(( + BinIndex(i), + Text::new("1.23"), + TextFont { + font_size: 10.3, + ..Default::default() + }, + )); + }); + }); + } + }); + }); + + #[cfg(not(target_os = "android"))] + commands.spawn(( + DiagnosticsText, + Text::new(""), + Node { + position_type: PositionType::Absolute, + top: Val::Px(0.0), + right: Val::Px(0.0), + ..default() + }, + )); + + // UI Buttons + + let mut node = commands.spawn(Node { + position_type: PositionType::Absolute, + width: Val::Percent(100.), + bottom: Val::Px(4.0), + display: Display::Flex, + flex_direction: FlexDirection::Row, + flex_wrap: FlexWrap::Wrap, + ..default() + }); + + node.with_children(|parent| { + for button_kind in &[ + ButtonKind::BpmDown10, + ButtonKind::BpmDown1, + ButtonKind::BpmUp1, + ButtonKind::BpmUp10, + ButtonKind::DivisionDown1, + ButtonKind::DivisionUp1, + ButtonKind::TapMute, + ButtonKind::TickMute, + ButtonKind::HideBarChart, + ButtonKind::HideClock, + ] { + let kind = *button_kind; + parent + .spawn(( + Button, + kind, + Node { + // width: Val::Px(105.0), + // height: Val::Px(48.0), + border: UiRect::all(Val::Px(2.0)), + // horizontally center child text + justify_content: JustifyContent::Center, + // vertically center child text + align_items: AlignItems::Center, + margin: UiRect { + left: Val::Px(2.0), + right: Val::Px(2.0), + ..Default::default() + }, + flex_grow: 1.0, + ..default() + }, + BorderColor(Color::BLACK), + BorderRadius::all(Val::Px(4.0)), + BackgroundColor(NORMAL_BUTTON), + )) + .with_child(( + Text::new(kind.label()), + TextColor(Color::srgb(0.9, 0.9, 0.9)), + )); + } + }); +} + +#[allow(clippy::too_many_arguments)] +fn tap( + mut commands: Commands, + audio_handles: Res, + keyboard_input: Res>, + game_pad: Query<&Gamepad>, + buttons: Res>, + touches: Res, + last_tick: Res, + timer: Res>, + division: Res, + mut tap_deltas: ResMut, + mute: Res, +) { + if keyboard_input.get_just_pressed().count() > 0 + || buttons.get_just_pressed().count() > 0 + || touches.any_just_pressed() + || game_pad + .iter() + .any(|game_pad| game_pad.get_just_pressed().count() > 0) + { + if !mute.tap_mute { + commands.spawn(( + AudioPlayer::new(audio_handles.tap().clone()), + PlaybackSettings::DESPAWN, + )); + } + + let now = Instant::now(); + let time_step = timer.timestep(); + let time_step_div = time_step / division.0; + + let last_tick = last_tick.0; + let next_tick = last_tick + time_step; + + let from_last = (now - last_tick).as_secs_f64(); + let from_next = (next_tick - now).as_secs_f64(); + + let delta_from_last = from_last % time_step_div.as_secs_f64(); + let delta_from_next = from_next % time_step_div.as_secs_f64(); + + let (delta, division) = if delta_from_last < delta_from_next { + let division = (from_last / time_step_div.as_secs_f64()) as usize; + (delta_from_last, division) + } else { + let division = (division.0 as usize + - (from_next / time_step_div.as_secs_f64()) as usize) + % division.0 as usize; + (-delta_from_next, division) + }; + + tap_deltas.0.push_front(Delta { + delta, + division, + theta: from_last / time_step.as_secs_f64() * 2.0 * std::f64::consts::PI, + }); + while tap_deltas.0.len() > BINS { + tap_deltas.0.pop_back(); + } + } +} + +fn metronome( + mut commands: Commands, + audio_handles: Res, + mut last_tick: ResMut, + mute: Res, +) { + if !mute.tick_mute { + commands.spawn(( + AudioPlayer::new(audio_handles.tick().clone()), + PlaybackSettings::DESPAWN, + )); + } + last_tick.0 = Instant::now(); +} + +fn control( + mut timer: ResMut>, + mut division: ResMut, + mut mute: ResMut, + mut hide_clock: ResMut, + keyboard_input: Res>, +) { + if keyboard_input.just_pressed(KeyCode::ArrowUp) { + let next_bpm = bpm(&timer).round() as u32 + 1; + timer.set_timestep(from_bpm(next_bpm as f32)); + } + + if keyboard_input.just_pressed(KeyCode::ArrowRight) { + let next_bpm = bpm(&timer).round() as u32 + 10; + timer.set_timestep(from_bpm(next_bpm as f32)); + } + + if keyboard_input.just_pressed(KeyCode::ArrowDown) { + let current_bpm = bpm(&timer).round() as u32; + + if current_bpm > 1 { + let next_bpm = current_bpm - 1; + timer.set_timestep(from_bpm(next_bpm as f32)); + } + } + + if keyboard_input.just_pressed(KeyCode::ArrowLeft) { + let current_bpm = bpm(&timer).round() as u32; + + let next_bpm = if current_bpm > 10 { + current_bpm - 10 + } else { + 1 + }; + + timer.set_timestep(from_bpm(next_bpm as f32)); + } + + if keyboard_input.just_pressed(KeyCode::BracketLeft) && division.0 > 1 { + division.0 -= 1; + } + + if keyboard_input.just_pressed(KeyCode::BracketRight) { + division.0 += 1; + } + + if keyboard_input.just_pressed(KeyCode::KeyN) { + mute.tap_mute = !mute.tap_mute; + } + + if keyboard_input.just_pressed(KeyCode::KeyM) { + mute.tick_mute = !mute.tick_mute; + } + + if keyboard_input.just_pressed(KeyCode::Comma) { + hide_clock.0 = !hide_clock.0; + } +} + +fn set_status_text( + timer: Res>, + division: Res, + mute: Res, + mut query: Query<&mut Text, With>, +) { + if timer.is_changed() || division.is_changed() || mute.is_changed() { + for mut text in &mut query { + text.0 = format!( + "BPM: {}\n1 / {}\nTick Mute: {}\nTap Mute: {}", + bpm(&timer).round() as u32, + division.0, + mute.tick_mute, + mute.tap_mute + ); + } + } +} + +fn clock( + last_tick: Res, + timer: Res>, + mut query: Query<&mut Transform, With>, +) { + let now = Instant::now(); + let time_step = timer.timestep(); + let delta = (now - last_tick.0).as_secs_f64() / time_step.as_secs_f64(); + + let angle = 2.0 * std::f32::consts::PI * delta as f32; + + for mut transform in &mut query { + transform.translation = + Vec3::new(angle.sin() * CIRCLE_SIZE, angle.cos() * CIRCLE_SIZE, 1.0); + } +} + +#[derive(Component)] +struct BinIndex(usize); + +#[derive(Component)] +struct BinBar; + +fn set_bins( + mut query_bar: Query< + (&BinIndex, &mut Node, &mut BackgroundColor, &mut Visibility), + With, + >, + mut query_text: Query<(&BinIndex, &mut Text)>, + tap_deltas: Res, +) { + if tap_deltas.is_changed() { + for (BinIndex(index), mut node, mut color, mut visibility) in &mut query_bar { + if let Some(Delta { delta, .. }) = tap_deltas.0.get(*index) { + let height = delta.abs() as f32 * BAR_HEIGHT_MULTIPLIER; + node.height = Val::Px(height); + node.position_type = PositionType::Absolute; + + if *delta >= 0.0 { + color.0 = Color::linear_rgba(1.0, 0.0, 0.0, 0.6); + node.top = Val::DEFAULT; + node.bottom = Val::Percent(50.0); + } else { + color.0 = Color::linear_rgba(0.0, 0.0, 1.0, 0.6); + node.bottom = Val::DEFAULT; + node.top = Val::Percent(50.0); + } + + *visibility = Visibility::Inherited; + } else { + *visibility = Visibility::Hidden; + } + } + + for (BinIndex(index), mut text) in &mut query_text { + if let Some(Delta { + delta, division, .. + }) = tap_deltas.0.get(*index) + { + text.0 = format!("[{}]{:+.1}", division, delta * 1000.0); + } else { + text.0 = "".to_string(); + } + } + } +} + +fn hide_bar_chart( + mut bar_chart: Query<&mut Visibility, With>, + hide_bar_chart: Res, +) { + if hide_bar_chart.is_changed() { + for mut visibility in &mut bar_chart { + if hide_bar_chart.0 { + *visibility = Visibility::Hidden; + } else { + *visibility = Visibility::Visible; + } + } + } +} + +fn hide_clock(mut clock: Query<&mut Visibility, With>, hide_clock: Res) { + if hide_clock.is_changed() { + for mut visibility in &mut clock { + if hide_clock.0 { + *visibility = Visibility::Hidden; + } else { + *visibility = Visibility::Visible; + } + } + } +} + +#[derive(Component)] +struct ClockLegend; + +fn set_clock_legend( + mut commands: Commands, + query: Query>, + parent: Query>, + division: Res, + clock_resource: Res, + timer: Res>, +) { + if division.is_changed() || timer.is_changed() { + for e in query.iter() { + commands.entity(e).despawn_recursive(); + } + + let division = division.0; + let tick = timer.timestep().as_secs_f32(); + + for parent in &parent { + commands.entity(parent).with_children(|commands| { + for i in 0..division { + let angle = 2.0 * std::f32::consts::PI * (i as f32 / division as f32); + let x = angle.sin() * CIRCLE_SIZE; + let y = angle.cos() * CIRCLE_SIZE; + + commands.spawn(( + ClockLegend, + Mesh2d(clock_resource.mesh_legend.clone()), + MeshMaterial2d(clock_resource.material_legend.clone()), + Transform::from_xyz(x, y, 3.0), + )); + + let t = tick / division as f32 * i as f32; + + for delta in [ + -1.0 / 60.0, + 1.0 / 60.0, + -1.5 / 60.0, + 1.5 / 60.0, + -2.0 / 60.0, + 2.0 / 60.0, + ] { + let theta = (t + delta) / tick * 2.0 * std::f32::consts::PI; + + let mut transform = Transform::from_scale(Vec3::new( + 6.0, + 96.0 * (-delta.abs() * 60.0).exp(), + 1.0, + )) + .with_translation(Vec3::new( + 0.0, + CIRCLE_SIZE, + 8.0, + )); + transform.rotate_around(Vec3::ZERO, Quat::from_rotation_z(theta)); + + commands.spawn(( + ClockLegend, + Mesh2d(clock_resource.mesh_precision.clone()), + MeshMaterial2d(clock_resource.material_precision.clone()), + transform, + )); + } + } + }); + } + } +} + +#[derive(Component)] +struct ClockDelta; + +fn set_clock_delta( + mut commands: Commands, + query: Query>, + tap_deltas: Res, + parent: Query>, + clock_resource: Res, +) { + if tap_deltas.is_changed() { + for e in query.iter() { + commands.entity(e).despawn_recursive(); + } + + for parent in &parent { + commands.entity(parent).with_children(|commands| { + for Delta { theta, .. } in tap_deltas.0.iter() { + let x = theta.sin() as f32 * CIRCLE_SIZE; + let y = theta.cos() as f32 * CIRCLE_SIZE; + + commands.spawn(( + ClockDelta, + Mesh2d(clock_resource.mesh_delta.clone()), + MeshMaterial2d(clock_resource.material_delta.clone()), + Transform::from_xyz(x, y, 4.0), + )); + } + }); + } + } +} + +#[derive(Component)] +struct DiagnosticsText; + +fn diagnostics_text_update_system( + diagnostics: Res, + mut query: Query<&mut Text, With>, +) { + if diagnostics.is_changed() { + let fps = if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) { + if let Some(value) = fps.smoothed() { + format!("{value:.2}") + } else { + "N/A".to_string() + } + } else { + "N/A".to_string() + }; + + let entity_count = if let Some(entity_count) = + diagnostics.get(&EntityCountDiagnosticsPlugin::ENTITY_COUNT) + { + if let Some(value) = entity_count.value() { + format!("{value:.0}") + } else { + "N/A".to_string() + } + } else { + "N/A".to_string() + }; + + for mut span in &mut query { + **span = format!("entity_count: {entity_count} FPS: {fps}"); + } + } +} + +#[allow(clippy::type_complexity)] +fn button_system( + mut interaction_query: Query< + ( + &Interaction, + &mut BackgroundColor, + &mut BorderColor, + &ButtonKind, + ), + (Changed, With