From 08680b27ae8dfbb3fe91b277225adc64a76adbcf Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Tue, 26 Nov 2024 00:38:03 -0500 Subject: [PATCH] Out with dear imgui, in with egui --- Cargo.lock | 1492 +++++++++++++++++++++++++++++++------ Cargo.toml | 11 +- src/app.rs | 164 ---- src/app/common.rs | 264 ------- src/app/game.rs | 408 ---------- src/app/input.rs | 220 ------ src/application.rs | 315 ++++++++ src/main.rs | 7 +- src/window.rs | 157 ++++ src/window/game.rs | 174 +++++ src/window/game_screen.rs | 194 +++++ src/window/input.rs | 133 ++++ 12 files changed, 2241 insertions(+), 1298 deletions(-) delete mode 100644 src/app.rs delete mode 100644 src/app/common.rs delete mode 100644 src/app/game.rs delete mode 100644 src/app/input.rs create mode 100644 src/application.rs create mode 100644 src/window.rs create mode 100644 src/window/game.rs create mode 100644 src/window/game_screen.rs create mode 100644 src/window/input.rs diff --git a/Cargo.lock b/Cargo.lock index c4ac34f..4990382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,21 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "x11rb", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -177,12 +192,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -192,6 +201,182 @@ dependencies = [ "libloading", ] +[[package]] +name = "ashpd" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -213,29 +398,29 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.87", ] [[package]] name = "bit-set" -version = "0.8.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" [[package]] name = "bitflags" @@ -264,6 +449,19 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -287,9 +485,15 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.8.0" @@ -366,12 +570,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chlorine" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e10e7569f6ca78ef7664d7d651115172d4875c4410c050306bccde856a99a49" - [[package]] name = "clang-sys" version = "1.8.1" @@ -414,7 +612,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -424,33 +622,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] -name = "cocoa" -version = "0.25.0" +name = "clipboard-win" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation 0.9.4", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.9.4", - "core-graphics-types", - "libc", - "objc", + "error-code", ] [[package]] @@ -469,6 +646,37 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "combine" version = "4.6.7" @@ -592,39 +800,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "d3d12" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" +dependencies = [ + "bitflags 2.6.0", + "libloading", + "winapi", +] + [[package]] name = "dasp_sample" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "dlib" version = "0.5.2" @@ -655,12 +864,164 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +[[package]] +name = "ecolor" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" +dependencies = [ + "ahash", + "emath", + "epaint", + "log", + "nohash-hasher", +] + +[[package]] +name = "egui-wgpu" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00fd5d06d8405397e64a928fa0ef3934b3c30273ea7603e3dc4627b1f7a1a82" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "thiserror", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9c430f4f816340e8e8c1b20eec274186b1be6bc4c7dfc467ed50d57abc36c6" +dependencies = [ + "ahash", + "arboard", + "egui", + "log", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_extras" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3c1f5cd8dfe2ade470a218696c66cf556fcfd701e7830fa2e9f4428292a2a1" +dependencies = [ + "ahash", + "egui", + "enum-map", + "log", + "mime_guess2", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "emath" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "epaint" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" + [[package]] name = "equivalent" version = "1.0.1" @@ -677,6 +1038,39 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + [[package]] name = "fnv" version = "1.0.7" @@ -701,7 +1095,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -710,6 +1104,89 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -784,9 +1261,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "glow" -version = "0.14.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ "js-sys", "slotmap", @@ -824,14 +1301,15 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.27.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" dependencies = [ "log", "presser", "thiserror", - "windows 0.58.0", + "winapi", + "windows 0.52.0", ] [[package]] @@ -870,6 +1348,21 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.6.0", + "com", + "libc", + "libloading", + "thiserror", + "widestring", + "winapi", +] + [[package]] name = "heck" version = "0.5.0" @@ -882,6 +1375,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hexf-parse" version = "0.2.1" @@ -898,50 +1397,142 @@ dependencies = [ ] [[package]] -name = "imgui" -version = "0.12.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8addafa5cecf0515812226e806913814e02ce38d10215778082af5174abe5669" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "imgui-sys", - "mint", - "parking_lot", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "imgui-sys" -version = "0.12.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead193f9f4b60398e8b8f4ab1483e2321640d87aeebdaa3e5f44c55633ccd804" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "cc", - "cfg-if", - "chlorine", - "mint", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "imgui-wgpu" -version = "0.25.0" -source = "git+https://github.com/SupernaviX/imgui-wgpu-rs?rev=5bb8673#5bb8673e256b6b5ad497a3baa2dc151545025f12" +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "bytemuck", - "imgui", - "log", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", "smallvec", - "wgpu", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "imgui-winit-support" -version = "0.13.0" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7fcccfa9efab56c94274c0fec9939bb14149342b49e6a425883a5b7dda6a3f" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "imgui", - "winit", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -990,15 +1581,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1108,6 +1690,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "litrs" version = "0.4.1" @@ -1163,6 +1751,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.29.0" @@ -1178,23 +1775,33 @@ dependencies = [ "paste", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess2" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "mint" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" - [[package]] name = "naga" -version = "23.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", "bit-set", @@ -1211,29 +1818,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "native-dialog" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e7038885d2aeab236bd60da9e159a5967b47cde3292da3b15ff1bec27c039f" -dependencies = [ - "ascii", - "block", - "cocoa", - "core-foundation 0.9.4", - "dirs-next", - "objc", - "objc-foundation", - "objc_id", - "once_cell", - "raw-window-handle 0.5.2", - "thiserror", - "versions", - "wfd", - "which", - "winapi", -] - [[package]] name = "ndk" version = "0.8.0" @@ -1259,7 +1843,7 @@ dependencies = [ "log", "ndk-sys 0.6.0+11769913", "num_enum", - "raw-window-handle 0.6.2", + "raw-window-handle", "thiserror", ] @@ -1297,8 +1881,15 @@ dependencies = [ "cfg-if", "cfg_aliases 0.2.1", "libc", + "memoffset", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -1326,7 +1917,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1365,7 +1956,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1377,17 +1968,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -1591,15 +2171,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "oboe" version = "0.6.1" @@ -1638,6 +2209,16 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "owned_ttf_parser" version = "0.25.0" @@ -1647,6 +2228,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1699,7 +2286,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1708,6 +2295,23 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -1729,12 +2333,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "pollster" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" @@ -1792,18 +2411,42 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1837,17 +2480,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.11.1" @@ -1883,6 +2515,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rfd" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8" +dependencies = [ + "ashpd", + "block2", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "pollster 0.3.0", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rtrb" version = "0.3.1" @@ -1992,7 +2646,18 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -2011,15 +2676,16 @@ dependencies = [ "cc", "clap", "cpal", + "egui", + "egui-wgpu", + "egui-winit", + "egui_extras", "gilrs", - "imgui", - "imgui-wgpu", - "imgui-winit-support", - "itertools 0.13.0", - "native-dialog", + "itertools", "num-derive", "num-traits", - "pollster", + "pollster 0.4.0", + "rfd", "rtrb", "rubato", "thread-priority", @@ -2028,6 +2694,15 @@ dependencies = [ "winit", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -2077,6 +2752,17 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.2.2" @@ -2095,6 +2781,12 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2119,6 +2811,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.87" @@ -2130,6 +2833,30 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -2156,7 +2883,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2198,6 +2925,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "toml_datetime" version = "0.6.8" @@ -2222,14 +2959,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] [[package]] name = "transpose" @@ -2247,6 +2999,32 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-ident" version = "1.0.13" @@ -2271,6 +3049,36 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2295,16 +3103,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "versions" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c73a36bc44e3039f51fbee93e39f41225f6b17b380eb70cc2aab942df06b34dd" -dependencies = [ - "itertools 0.11.0", - "nom", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -2343,7 +3141,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -2377,7 +3175,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2518,20 +3316,28 @@ dependencies = [ ] [[package]] -name = "wfd" -version = "0.1.7" +name = "webbrowser" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e713040b67aae5bf1a0ae3e1ebba8cc29ab2b90da9aa1bff6e09031a8a41d7a8" +checksum = "2e5f07fb9bc8de2ddfe6b24a71a75430673fd679e568c48b52716cef1cfae923" dependencies = [ - "libc", - "winapi", + "block2", + "core-foundation 0.10.0", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", ] [[package]] name = "wgpu" -version = "23.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ab52f2d3d18b70d5ab8dd270a1cff3ebe6dbe4a7d13c1cc2557138a9777fdc" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ "arrayvec", "cfg_aliases 0.1.1", @@ -2541,7 +3347,7 @@ dependencies = [ "naga", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "smallvec", "static_assertions", "wasm-bindgen", @@ -2554,9 +3360,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "23.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c68e7b6322a03ee5b83fcd92caeac5c2a932f6457818179f4652ad2a9c065" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec", @@ -2569,7 +3375,7 @@ dependencies = [ "once_cell", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "rustc-hash", "smallvec", "thiserror", @@ -2579,9 +3385,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "23.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6e7266b869de56c7e3ed72a954899f71d14fec6cc81c102b7530b92947601b" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", "arrayvec", @@ -2589,14 +3395,15 @@ dependencies = [ "bit-set", "bitflags 2.6.0", "block", - "bytemuck", "cfg_aliases 0.1.1", "core-graphics-types", + "d3d12", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", + "hassle-rs", "js-sys", "khronos-egl", "libc", @@ -2610,7 +3417,7 @@ dependencies = [ "parking_lot", "profiling", "range-alloc", - "raw-window-handle 0.6.2", + "raw-window-handle", "renderdoc-sys", "rustc-hash", "smallvec", @@ -2618,15 +3425,14 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "windows 0.58.0", - "windows-core 0.58.0", + "winapi", ] [[package]] name = "wgpu-types" -version = "23.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" dependencies = [ "bitflags 2.6.0", "js-sys", @@ -2634,16 +3440,10 @@ dependencies = [ ] [[package]] -name = "which" -version = "4.4.2" +name = "widestring" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -2676,6 +3476,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.54.0" @@ -2698,6 +3508,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.54.0" @@ -2729,7 +3548,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2740,7 +3559,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2751,7 +3570,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2762,7 +3581,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2802,6 +3621,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3028,7 +3856,7 @@ dependencies = [ "orbclient", "percent-encoding", "pin-project", - "raw-window-handle 0.6.2", + "raw-window-handle", "redox_syscall 0.4.1", "rustix", "sctk-adwaita", @@ -3059,6 +3887,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "x11-dl" version = "2.21.0" @@ -3097,6 +3937,16 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "xkbcommon-dl" version = "0.4.2" @@ -3122,12 +3972,100 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-util", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.87", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -3139,5 +4077,91 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "zvariant" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.87", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.87", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 775d3a4..2db1fb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,19 +9,20 @@ bitflags = "2" bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" } +egui = "0.29" +egui_extras = "0.29" +egui-winit = "0.29" +egui-wgpu = { version = "0.29", features = ["winit"] } gilrs = "0.11" -imgui = { version = "0.12", features = ["tables-api"] } -imgui-wgpu = { git = "https://github.com/SupernaviX/imgui-wgpu-rs", rev = "5bb8673" } -imgui-winit-support = "0.13" itertools = "0.13" -native-dialog = "0.7" num-derive = "0.4" num-traits = "0.2" pollster = "0.4" +rfd = "0.15" rtrb = "0.3" rubato = "0.16" thread-priority = "1" -wgpu = "23.0" +wgpu = "22.1" winit = "0.30" [target.'cfg(windows)'.dependencies] diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index 5213243..0000000 --- a/src/app.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::{collections::HashMap, fmt::Debug, thread}; - -use game::GameWindow; -use gilrs::{EventType, Gilrs}; -use input::InputWindow; -use winit::{ - application::ApplicationHandler, - event::{Event, WindowEvent}, - event_loop::{ActiveEventLoop, EventLoopProxy}, - window::WindowId, -}; - -use crate::{ - controller::ControllerManager, - emulator::{EmulatorClient, SimId}, - input::MappingProvider, -}; - -mod common; -mod game; -mod input; - -pub struct App { - windows: HashMap>, - client: EmulatorClient, - mappings: MappingProvider, - controllers: ControllerManager, - proxy: EventLoopProxy, - player_2_window: Option, -} - -impl App { - pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { - let mappings = MappingProvider::new(); - let controllers = ControllerManager::new(client.clone(), &mappings); - { - let mappings = mappings.clone(); - let proxy = proxy.clone(); - thread::spawn(|| process_gamepad_input(mappings, proxy)); - } - Self { - windows: HashMap::new(), - client, - mappings, - controllers, - proxy, - player_2_window: None, - } - } -} - -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window = GameWindow::new( - event_loop, - SimId::Player1, - self.client.clone(), - self.proxy.clone(), - ); - self.windows.insert(window.id(), Box::new(window)); - } - - fn window_event( - &mut self, - event_loop: &ActiveEventLoop, - window_id: WindowId, - event: WindowEvent, - ) { - if let WindowEvent::KeyboardInput { event, .. } = &event { - self.controllers.handle_key_event(event); - } - let Some(window) = self.windows.get_mut(&window_id) else { - return; - }; - window.handle_event(event_loop, &Event::WindowEvent { window_id, event }); - } - - fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { - match event { - UserEvent::OpenInputWindow => { - let window = - InputWindow::new(event_loop, self.mappings.clone(), self.proxy.clone()); - self.windows.insert(window.id(), Box::new(window)); - } - UserEvent::OpenPlayer2Window => { - if self.player_2_window.is_some() { - return; - } - let window = GameWindow::new( - event_loop, - SimId::Player2, - self.client.clone(), - self.proxy.clone(), - ); - self.player_2_window = Some(window.id()); - self.windows.insert(window.id(), Box::new(window)); - } - UserEvent::Close(window_id) => { - if self.player_2_window == Some(window_id) { - self.player_2_window.take(); - } - self.windows.remove(&window_id); - } - UserEvent::GamepadEvent(event) => { - self.controllers.handle_gamepad_event(&event); - } - } - } - - fn device_event( - &mut self, - event_loop: &ActiveEventLoop, - device_id: winit::event::DeviceId, - event: winit::event::DeviceEvent, - ) { - for window in self.windows.values_mut() { - window.handle_event( - event_loop, - &Event::DeviceEvent { - device_id, - event: event.clone(), - }, - ); - } - } - - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - for window in self.windows.values_mut() { - window.handle_event(event_loop, &Event::AboutToWait); - } - } -} - -pub trait AppWindow { - fn id(&self) -> WindowId; - fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event); -} - -#[derive(Debug)] -pub enum UserEvent { - OpenInputWindow, - OpenPlayer2Window, - Close(WindowId), - GamepadEvent(gilrs::Event), -} - -fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { - let Ok(mut gilrs) = Gilrs::new() else { - eprintln!("could not connect gamepad listener"); - return; - }; - while let Some(event) = gilrs.next_event_blocking(None) { - if event.event == EventType::Connected { - let Some(gamepad) = gilrs.connected_gamepad(event.id) else { - continue; - }; - mappings.map_gamepad(SimId::Player1, &gamepad); - } - if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { - // main thread has closed! we done - return; - } - } -} diff --git a/src/app/common.rs b/src/app/common.rs deleted file mode 100644 index 396acb2..0000000 --- a/src/app/common.rs +++ /dev/null @@ -1,264 +0,0 @@ -use std::{ - ops::{Deref, DerefMut}, - sync::Arc, - time::Instant, -}; - -use imgui::{FontSource, MouseCursor, SuspendedContext, WindowToken}; -use imgui_wgpu::{Renderer, RendererConfig}; -use imgui_winit_support::WinitPlatform; -use pollster::block_on; -#[cfg(target_os = "windows")] -use winit::platform::windows::{CornerPreference, WindowAttributesExtWindows as _}; -use winit::{ - dpi::{LogicalSize, PhysicalSize, Size}, - event_loop::ActiveEventLoop, - window::{Window, WindowAttributes}, -}; - -pub struct WindowStateBuilder<'a> { - event_loop: &'a ActiveEventLoop, - attributes: WindowAttributes, -} -impl<'a> WindowStateBuilder<'a> { - pub fn new(event_loop: &'a ActiveEventLoop) -> Self { - let attributes = Window::default_attributes(); - #[cfg(target_os = "windows")] - let attributes = attributes.with_corner_preference(CornerPreference::DoNotRound); - Self { - event_loop, - attributes, - } - } - - pub fn with_title>(self, title: T) -> Self { - Self { - attributes: self.attributes.with_title(title), - ..self - } - } - - pub fn with_inner_size>(self, size: S) -> Self { - Self { - attributes: self.attributes.with_inner_size(size), - ..self - } - } - - pub fn build(self) -> WindowState { - WindowState::new(self.event_loop, self.attributes) - } -} - -#[derive(Debug)] -pub struct WindowState { - pub device: wgpu::Device, - pub queue: Arc, - pub window: Arc, - pub surface_desc: wgpu::SurfaceConfiguration, - pub surface: wgpu::Surface<'static>, - pub hidpi_factor: f64, - pub minimized: bool, -} - -impl WindowState { - fn new(event_loop: &ActiveEventLoop, attributes: WindowAttributes) -> Self { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let window = Arc::new(event_loop.create_window(attributes).unwrap()); - - let size = window.inner_size(); - let hidpi_factor = window.scale_factor(); - let surface = instance.create_surface(window.clone()).unwrap(); - - let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - - let (device, queue) = - block_on(adapter.request_device(&wgpu::DeviceDescriptor::default(), None)).unwrap(); - let queue = Arc::new(queue); - - // Set up swap chain - let surface_desc = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - desired_maximum_frame_latency: 2, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], - }; - - surface.configure(&device, &surface_desc); - - Self { - device, - queue, - window, - surface_desc, - surface, - hidpi_factor, - minimized: false, - } - } - - pub fn logical_size(&self) -> LogicalSize { - PhysicalSize::new(self.surface_desc.width, self.surface_desc.height) - .to_logical(self.hidpi_factor) - } - - pub fn handle_resize(&mut self, size: &PhysicalSize) { - if size.width > 0 && size.height > 0 { - self.minimized = false; - self.surface_desc.width = size.width; - self.surface_desc.height = size.height; - self.surface.configure(&self.device, &self.surface_desc); - } else { - self.minimized = true; - } - } -} - -pub struct ImguiState { - pub context: ContextGuard, - pub platform: WinitPlatform, - pub renderer: Renderer, - pub clear_color: wgpu::Color, - pub last_frame: Instant, - pub last_cursor: Option, -} -impl ImguiState { - pub fn new(window: &WindowState) -> Self { - let mut context_guard = ContextGuard::new(); - let mut context = context_guard.lock().unwrap(); - - let mut platform = imgui_winit_support::WinitPlatform::new(&mut context); - platform.attach_window( - context.io_mut(), - &window.window, - imgui_winit_support::HiDpiMode::Default, - ); - context.set_ini_filename(None); - - let font_size = (16.0 * window.hidpi_factor) as f32; - context.io_mut().font_global_scale = (1.0 / window.hidpi_factor) as f32; - - context.fonts().add_font(&[FontSource::TtfData { - data: include_bytes!("../../assets/selawk.ttf"), - size_pixels: font_size, - config: Some(imgui::FontConfig { - oversample_h: 1, - pixel_snap_h: true, - size_pixels: font_size, - ..Default::default() - }), - }]); - - let style = context.style_mut(); - style.use_light_colors(); - - // - // Set up dear imgui wgpu renderer - // - let renderer_config = RendererConfig { - texture_format: window.surface_desc.format, - ..Default::default() - }; - - let renderer = Renderer::new(&mut context, &window.device, &window.queue, renderer_config); - - let last_frame = Instant::now(); - let last_cursor = None; - - drop(context); - Self { - context: context_guard, - platform, - renderer, - clear_color: wgpu::Color::BLACK, - last_frame, - last_cursor, - } - } -} - -pub struct ContextGuard { - value: Option, -} - -impl ContextGuard { - fn new() -> Self { - Self { - value: Some(SuspendedContext::create()), - } - } - - pub fn lock(&mut self) -> Option> { - let sus = self.value.take()?; - match sus.activate() { - Ok(ctx) => Some(ContextLock { - ctx: Some(ctx), - holder: self, - }), - Err(sus) => { - self.value = Some(sus); - None - } - } - } -} - -pub struct ContextLock<'a> { - ctx: Option, - holder: &'a mut ContextGuard, -} - -impl<'a> Deref for ContextLock<'a> { - type Target = imgui::Context; - fn deref(&self) -> &Self::Target { - self.ctx.as_ref().unwrap() - } -} - -impl<'a> DerefMut for ContextLock<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.ctx.as_mut().unwrap() - } -} - -impl<'a> Drop for ContextLock<'a> { - fn drop(&mut self) { - self.holder.value = self.ctx.take().map(|c| c.suspend()) - } -} - -pub trait UiExt { - fn fullscreen_window(&self) -> Option>; - fn right_align_text>(&self, text: T, space: f32); -} - -impl UiExt for imgui::Ui { - fn fullscreen_window(&self) -> Option> { - self.window("fullscreen") - .position([0.0, 0.0], imgui::Condition::Always) - .size(self.io().display_size, imgui::Condition::Always) - .flags(imgui::WindowFlags::NO_DECORATION) - .begin() - } - - fn right_align_text>(&self, text: T, space: f32) { - let width = self.calc_text_size(text.as_ref())[0]; - let [left, y] = self.cursor_pos(); - let right = left + space; - self.set_cursor_pos([right - width, y]); - self.text(text); - } -} diff --git a/src/app/game.rs b/src/app/game.rs deleted file mode 100644 index 432b677..0000000 --- a/src/app/game.rs +++ /dev/null @@ -1,408 +0,0 @@ -use std::time::Instant; -use wgpu::util::DeviceExt as _; -use winit::{ - dpi::LogicalSize, - event::{Event, WindowEvent}, - event_loop::{ActiveEventLoop, EventLoopProxy}, - window::WindowId, -}; - -use crate::{ - emulator::{EmulatorClient, EmulatorCommand, SimId}, - graphics::TextureSink, -}; - -use super::{ - common::{ImguiState, WindowState, WindowStateBuilder}, - AppWindow, UserEvent, -}; - -pub struct GameWindow { - window: WindowState, - imgui: ImguiState, - pipeline: wgpu::RenderPipeline, - bind_group: wgpu::BindGroup, - sim_id: SimId, - client: EmulatorClient, - proxy: EventLoopProxy, - paused_due_to_minimize: bool, -} - -impl GameWindow { - pub fn new( - event_loop: &ActiveEventLoop, - sim_id: SimId, - client: EmulatorClient, - proxy: EventLoopProxy, - ) -> Self { - let title = if sim_id == SimId::Player2 { - "Shrooms VB (Player 2)" - } else { - "Shrooms VB" - }; - let window = WindowStateBuilder::new(event_loop) - .with_title(title) - .with_inner_size(LogicalSize::new(384, 244)) - .build(); - let device = &window.device; - - let (sink, texture_view) = TextureSink::new(device, window.queue.clone()); - client.send_command(EmulatorCommand::SetRenderer(sim_id, sink)); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); - let colors = Colors { - left: [1.0, 0.0, 0.0, 1.0], - right: [0.0, 0.7734375, 0.9375, 1.0], - }; - let color_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("colors"), - contents: bytemuck::bytes_of(&colors), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }); - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("texture bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("bind group"), - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: color_buf.as_entire_binding(), - }, - ], - }); - - let shader = device.create_shader_module(wgpu::include_wgsl!("../anaglyph.wgsl")); - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("render pipeline layout"), - bind_group_layouts: &[&texture_bind_group_layout], - push_constant_ranges: &[], - }); - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("render pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Bgra8UnormSrgb, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let imgui = ImguiState::new(&window); - - Self { - window, - imgui, - pipeline: render_pipeline, - bind_group, - sim_id, - client, - proxy, - paused_due_to_minimize: false, - } - } - - fn draw(&mut self, event_loop: &ActiveEventLoop) { - let window = &mut self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - let mut new_size = None; - - let now = Instant::now(); - context.io_mut().update_delta_time(now - imgui.last_frame); - imgui.last_frame = now; - - let frame = match window.surface.get_current_texture() { - Ok(frame) => frame, - Err(e) => { - if !self.window.minimized { - eprintln!("dropped frame: {e:?}"); - } - return; - } - }; - imgui - .platform - .prepare_frame(context.io_mut(), &window.window) - .expect("Failed to prepare frame"); - let ui = context.new_frame(); - let mut menu_height = 0.0; - ui.main_menu_bar(|| { - menu_height = ui.window_size()[1]; - ui.menu("ROM", || { - if ui.menu_item("Open ROM") { - let rom = native_dialog::FileDialog::new() - .add_filter("Virtual Boy ROMs", &["vb", "vbrom"]) - .show_open_single_file() - .unwrap(); - if let Some(path) = rom { - self.client - .send_command(EmulatorCommand::LoadGame(self.sim_id, path)); - } - } - if ui.menu_item("Quit") { - event_loop.exit(); - } - }); - ui.menu("Emulation", || { - let has_game = self.client.has_game(self.sim_id); - if self.client.is_running(self.sim_id) { - if ui.menu_item_config("Pause").enabled(has_game).build() { - self.client.send_command(EmulatorCommand::Pause); - } - } else if ui.menu_item_config("Resume").enabled(has_game).build() { - self.client.send_command(EmulatorCommand::Resume); - } - if ui.menu_item_config("Reset").enabled(has_game).build() { - self.client - .send_command(EmulatorCommand::Reset(self.sim_id)); - } - }); - ui.menu("Video", || { - let current_dims = window.logical_size(); - for scale in 1..=4 { - let label = format!("x{scale}"); - let dims = LogicalSize::new(384 * scale, 224 * scale + 20); - let selected = dims == current_dims; - if ui.menu_item_config(label).selected(selected).build() { - if let Some(size) = window.window.request_inner_size(dims) { - window.handle_resize(&size); - new_size = Some(size); - } - } - } - }); - ui.menu("Audio", || { - let p1_enabled = self.client.is_audio_enabled(SimId::Player1); - let p2_enabled = self.client.is_audio_enabled(SimId::Player2); - if ui.menu_item_config("Player 1").selected(p1_enabled).build() { - self.client - .send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled)); - } - if ui.menu_item_config("Player 2").selected(p2_enabled).build() { - self.client - .send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled)); - } - }); - ui.menu("Input", || { - if ui.menu_item("Bind Inputs") { - self.proxy.send_event(UserEvent::OpenInputWindow).unwrap(); - } - }); - ui.menu("Multiplayer", || { - if self.sim_id == SimId::Player1 - && !self.client.has_player_2() - && ui.menu_item("Open Player 2") - { - self.client - .send_command(EmulatorCommand::StartSecondSim(None)); - self.proxy.send_event(UserEvent::OpenPlayer2Window).unwrap(); - } - if self.client.has_player_2() { - let linked = self.client.are_sims_linked(); - if linked && ui.menu_item("Unlink") { - self.client.send_command(EmulatorCommand::Unlink); - } - if !linked && ui.menu_item("Link") { - self.client.send_command(EmulatorCommand::Link); - } - } - }); - }); - - let mut encoder: wgpu::CommandEncoder = window - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - if imgui.last_cursor != ui.mouse_cursor() { - imgui.last_cursor = ui.mouse_cursor(); - imgui.platform.prepare_render(ui, &window.window); - } - - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(imgui.clear_color), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - // Draw the game - rpass.set_pipeline(&self.pipeline); - let window_width = window.surface_desc.width as f32; - let window_height = window.surface_desc.height as f32; - let menu_height = menu_height * window.hidpi_factor as f32; - let ((x, y), (width, height)) = - compute_game_bounds(window_width, window_height, menu_height); - rpass.set_viewport(x, y, width, height, 0.0, 1.0); - rpass.set_bind_group(0, &self.bind_group, &[]); - rpass.draw(0..6, 0..1); - - // Draw the menu on top of the game - rpass.set_viewport(0.0, 0.0, window_width, window_height, 0.0, 1.0); - imgui - .renderer - .render(context.render(), &window.queue, &window.device, &mut rpass) - .expect("Rendering failed"); - - drop(rpass); - - if let Some(size) = new_size { - imgui.platform.handle_event::( - context.io_mut(), - &window.window, - &Event::WindowEvent { - window_id: window.window.id(), - event: WindowEvent::Resized(size), - }, - ); - } - - window.queue.submit(Some(encoder.finish())); - - frame.present(); - } -} - -impl AppWindow for GameWindow { - fn id(&self) -> WindowId { - self.window.window.id() - } - - fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event) { - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::Resized(size) => { - self.window.handle_resize(size); - if self.window.minimized { - if self.client.is_running(self.sim_id) { - self.client.send_command(EmulatorCommand::Pause); - self.paused_due_to_minimize = true; - } - } else if self.paused_due_to_minimize { - self.client.send_command(EmulatorCommand::Resume); - self.paused_due_to_minimize = false; - } - } - WindowEvent::CloseRequested => { - if self.sim_id == SimId::Player2 { - self.client.send_command(EmulatorCommand::StopSecondSim); - self.proxy.send_event(UserEvent::Close(self.id())).unwrap(); - } else { - event_loop.exit(); - } - } - WindowEvent::RedrawRequested => self.draw(event_loop), - _ => (), - }, - Event::AboutToWait => { - self.window.window.request_redraw(); - } - _ => (), - } - let window = &self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - imgui - .platform - .handle_event(context.io_mut(), &window.window, event); - } -} - -fn compute_game_bounds( - window_width: f32, - window_height: f32, - menu_height: f32, -) -> ((f32, f32), (f32, f32)) { - let available_width = window_width; - let available_height = window_height - menu_height; - - let width = available_width.min(available_height * 384.0 / 224.0); - let height = available_height.min(available_width * 224.0 / 384.0); - let x = (available_width - width) / 2.0; - let y = menu_height + (available_height - height) / 2.0; - ((x, y), (width, height)) -} - -#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -struct Colors { - left: [f32; 4], - right: [f32; 4], -} diff --git a/src/app/input.rs b/src/app/input.rs deleted file mode 100644 index 8147f5c..0000000 --- a/src/app/input.rs +++ /dev/null @@ -1,220 +0,0 @@ -use std::time::Instant; - -use winit::{ - dpi::LogicalSize, - event::{Event, KeyEvent, WindowEvent}, - event_loop::{ActiveEventLoop, EventLoopProxy}, -}; - -use crate::{ - emulator::{SimId, VBKey}, - input::MappingProvider, -}; - -use super::{ - common::{ImguiState, UiExt, WindowState, WindowStateBuilder}, - AppWindow, UserEvent, -}; - -pub struct InputWindow { - window: WindowState, - imgui: ImguiState, - mappings: MappingProvider, - proxy: EventLoopProxy, - now_binding: Option<(SimId, VBKey)>, -} - -const KEY_NAMES: [(VBKey, &str); 14] = [ - (VBKey::LU, "Up"), - (VBKey::LD, "Down"), - (VBKey::LL, "Left"), - (VBKey::LR, "Right"), - (VBKey::SEL, "Select"), - (VBKey::STA, "Start"), - (VBKey::B, "B"), - (VBKey::A, "A"), - (VBKey::LT, "L-Trigger"), - (VBKey::RT, "R-Trigger"), - (VBKey::RU, "R-Up"), - (VBKey::RD, "R-Down"), - (VBKey::RL, "R-Left"), - (VBKey::RR, "R-Right"), -]; - -impl InputWindow { - pub fn new( - event_loop: &ActiveEventLoop, - mappings: MappingProvider, - proxy: EventLoopProxy, - ) -> Self { - let window = WindowStateBuilder::new(event_loop) - .with_title("Bind Inputs") - .with_inner_size(LogicalSize::new(600, 400)) - .build(); - let imgui = ImguiState::new(&window); - Self { - window, - imgui, - mappings, - now_binding: None, - proxy, - } - } - - fn draw(&mut self) { - let window = &mut self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - - let now = Instant::now(); - context.io_mut().update_delta_time(now - imgui.last_frame); - imgui.last_frame = now; - - let frame = match window.surface.get_current_texture() { - Ok(frame) => frame, - Err(e) => { - if !self.window.minimized { - eprintln!("dropped frame: {e:?}"); - } - return; - } - }; - imgui - .platform - .prepare_frame(context.io_mut(), &window.window) - .expect("Failed to prepare frame"); - let ui = context.new_frame(); - - let mut render_key_bindings = |sim_id: SimId| { - let mappings = self.mappings.for_sim(sim_id); - if let Some(table) = ui.begin_table("controls", 2) { - let binding_names = { - let mapping = mappings.read().unwrap(); - mapping.keyboard_mapping_names() - }; - ui.table_next_row(); - - for (key, name) in KEY_NAMES { - let binding = binding_names.get(&key).map(|s| s.as_str()); - ui.table_next_column(); - let [space, _] = ui.content_region_avail(); - ui.group(|| { - ui.right_align_text(name, space * 0.20); - ui.same_line(); - let label_text = if self.now_binding == Some((sim_id, key)) { - "Press any input" - } else { - binding.unwrap_or("") - }; - let label = format!("{}##{}", label_text, name); - if ui.button_with_size(label, [space * 0.60, 0.0]) { - self.now_binding = Some((sim_id, key)); - } - }); - ui.same_line(); - if ui.button(format!("Clear##{name}")) { - let mut mapping = mappings.write().unwrap(); - mapping.clear_keyboard_mappings(key); - } - } - - table.end(); - } - }; - - if let Some(window) = ui.fullscreen_window() { - if let Some(tabs) = ui.tab_bar("tabs") { - if let Some(tab) = ui.tab_item("Player 1") { - render_key_bindings(SimId::Player1); - tab.end(); - } - if let Some(tab) = ui.tab_item("Player 2") { - render_key_bindings(SimId::Player2); - tab.end(); - } - tabs.end(); - } - window.end(); - } - let mut encoder: wgpu::CommandEncoder = window - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - if imgui.last_cursor != ui.mouse_cursor() { - imgui.last_cursor = ui.mouse_cursor(); - imgui.platform.prepare_render(ui, &window.window); - } - - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(imgui.clear_color), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - // Draw the game - imgui - .renderer - .render(context.render(), &window.queue, &window.device, &mut rpass) - .expect("Rendering failed"); - - drop(rpass); - - window.queue.submit(Some(encoder.finish())); - - frame.present(); - } - - fn try_bind_key(&mut self, event: &KeyEvent) { - if !event.state.is_pressed() { - return; - } - let Some((sim_id, vb)) = self.now_binding.take() else { - return; - }; - let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); - mappings.add_keyboard_mapping(vb, event.physical_key); - } -} - -impl AppWindow for InputWindow { - fn id(&self) -> winit::window::WindowId { - self.window.window.id() - } - - fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event) { - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::Resized(size) => self.window.handle_resize(size), - WindowEvent::CloseRequested => { - self.proxy.send_event(UserEvent::Close(self.id())).unwrap() - } - WindowEvent::KeyboardInput { event, .. } => self.try_bind_key(event), - WindowEvent::RedrawRequested => self.draw(), - _ => (), - }, - Event::AboutToWait => { - self.window.window.request_redraw(); - } - _ => (), - } - - let window = &self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - imgui - .platform - .handle_event(context.io_mut(), &window.window, event); - } -} diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 0000000..024caaa --- /dev/null +++ b/src/application.rs @@ -0,0 +1,315 @@ +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + num::NonZero, + sync::Arc, +}; + +use winit::event_loop::EventLoopProxy; + +use crate::{emulator::EmulatorClient, window::WindowManager}; + +pub struct Application { + client: EmulatorClient, + proxy: EventLoopProxy, + state: Option, +} + +impl Application { + pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { + Self { + client, + proxy, + state: None, + } + } +} + +impl winit::application::ApplicationHandler for Application { + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + self.state = Some(AppState::new( + self.client.clone(), + self.proxy.clone(), + event_loop, + )); + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + self.state + .as_mut() + .unwrap() + .window_event(event_loop, window_id, event); + } + + fn device_event( + &mut self, + _event_loop: &winit::event_loop::ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + self.state.as_mut().unwrap().device_event(event) + } + + fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) { + self.state.as_mut().unwrap().user_event(event); + } +} + +struct AppState { + painter: egui_wgpu::winit::Painter, + ctx: egui::Context, + viewports: HashMap, + viewports_by_window: HashMap, + focused: Option, + screen: WindowManager, +} +impl AppState { + fn new( + client: EmulatorClient, + proxy: EventLoopProxy, + event_loop: &winit::event_loop::ActiveEventLoop, + ) -> Self { + let mut painter = egui_wgpu::winit::Painter::new( + egui_wgpu::WgpuConfiguration::default(), + 1, + None, + false, + true, + ); + let ctx = egui::Context::default(); + ctx.style_mut(|s| { + s.wrap_mode = Some(egui::TextWrapMode::Extend); + s.visuals.menu_rounding = Default::default(); + }); + ctx.set_embed_viewports(false); + { + let proxy = proxy.clone(); + ctx.set_request_repaint_callback(move |info| { + proxy.send_event(UserEvent::RepaintRequested(info)).unwrap(); + }); + } + + let mut screen = WindowManager::new(client, proxy); + let root_viewport = ViewportState::new( + egui::ViewportId::ROOT, + &ctx, + event_loop, + screen.initial_viewport(), + &mut painter, + ); + screen.init_renderer(painter.render_state().as_ref().unwrap()); + + let mut viewports_by_window = HashMap::new(); + viewports_by_window.insert(root_viewport.window.id(), egui::ViewportId::ROOT); + let mut viewports = HashMap::new(); + viewports.insert(egui::ViewportId::ROOT, root_viewport); + + Self { + painter, + ctx, + viewports, + viewports_by_window, + focused: Some(egui::ViewportId::ROOT), + screen, + } + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + let Some(&viewport_id) = self.viewports_by_window.get(&window_id) else { + return; + }; + let Some(viewport) = self.viewports.get_mut(&viewport_id) else { + panic!("Unrecognized viewport"); + }; + viewport.on_window_event(&event); + + match event { + winit::event::WindowEvent::KeyboardInput { event, .. } => { + self.screen.handle_key_event(event); + } + winit::event::WindowEvent::RedrawRequested => { + pollster::block_on( + self.painter + .set_window(viewport_id, Some(viewport.window.clone())), + ) + .unwrap(); + let cb = viewport.viewport_ui_cb.clone(); + let mut input = viewport.state.take_egui_input(&viewport.window); + input.viewports = self + .viewports + .iter() + .map(|(k, v)| (*k, v.info.clone())) + .collect(); + let output = self.ctx.run(input, |ctx| { + if let Some(cb) = cb.as_deref() { + cb(ctx) + } else { + self.screen.show(ctx) + } + }); + let clipped_primitives = + self.ctx.tessellate(output.shapes, output.pixels_per_point); + self.painter.paint_and_update_textures( + viewport_id, + output.pixels_per_point, + [0.0, 0.0, 0.0, 0.0], + &clipped_primitives, + &output.textures_delta, + false, + ); + + let mut live_viewports = HashSet::default(); + for (id, output) in output.viewport_output { + live_viewports.insert(id); + let viewport = match self.viewports.entry(id) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => { + let mut v = ViewportState::new( + id, + &self.ctx, + event_loop, + output.builder, + &mut self.painter, + ); + v.viewport_ui_cb = output.viewport_ui_cb; + self.viewports_by_window.insert(v.window.id(), id); + e.insert(v) + } + }; + egui_winit::process_viewport_commands( + &self.ctx, + &mut viewport.info, + output.commands, + &viewport.window, + &mut HashSet::default(), + ); + if viewport.info.close_requested() { + live_viewports.remove(&id); + } + } + self.viewports.retain(|k, v| { + if live_viewports.contains(k) { + return true; + } + self.viewports_by_window.remove(&v.window.id()); + false + }); + self.painter.gc_viewports(&live_viewports); + if !self.viewports.contains_key(&egui::ViewportId::ROOT) { + event_loop.exit(); + } + } + winit::event::WindowEvent::Resized(size) => { + let (Some(width), Some(height)) = + (NonZero::new(size.width), NonZero::new(size.height)) + else { + return; + }; + self.painter.on_window_resized(viewport_id, width, height); + } + winit::event::WindowEvent::Focused(new_focused) => { + self.focused = new_focused.then_some(viewport_id); + } + winit::event::WindowEvent::CloseRequested => { + if viewport_id == egui::ViewportId::ROOT { + event_loop.exit(); + } else if let Some(viewport) = self.viewports.get_mut(&viewport_id) { + viewport.info.events.push(egui::ViewportEvent::Close); + self.ctx.request_repaint_of(viewport_id); + self.ctx.request_repaint_of(egui::ViewportId::ROOT); + } + } + _ => {} + } + } + + fn device_event(&mut self, event: winit::event::DeviceEvent) { + if let winit::event::DeviceEvent::MouseMotion { delta } = event { + let Some(viewport) = self + .focused + .as_ref() + .and_then(|id| self.viewports.get_mut(id)) + else { + return; + }; + viewport.state.on_mouse_motion(delta); + } + } + + fn user_event(&mut self, event: UserEvent) { + match event { + UserEvent::GamepadEvent(event) => self.screen.handle_gamepad_event(event), + UserEvent::RepaintRequested(info) => { + let Some(viewport) = self.viewports.get(&info.viewport_id) else { + return; + }; + viewport.window.request_redraw(); + } + } + } +} + +struct ViewportState { + info: egui::ViewportInfo, + state: egui_winit::State, + viewport_ui_cb: Option>, + window: Arc, +} + +impl ViewportState { + fn new( + id: egui::ViewportId, + ctx: &egui::Context, + event_loop: &winit::event_loop::ActiveEventLoop, + viewport: egui::ViewportBuilder, + painter: &mut egui_wgpu::winit::Painter, + ) -> Self { + let mut info = egui::ViewportInfo::default(); + let window = Arc::new(egui_winit::create_window(ctx, event_loop, &viewport).unwrap()); + egui_winit::update_viewport_info(&mut info, ctx, &window, true); + + pollster::block_on(painter.set_window(id, Some(window.clone()))).unwrap(); + let state = egui_winit::State::new( + ctx.clone(), + id, + event_loop, + Some(window.scale_factor() as f32), + event_loop.system_theme(), + painter.max_texture_side(), + ); + Self { + info, + state, + viewport_ui_cb: None, + window, + } + } + + fn on_window_event(&mut self, event: &winit::event::WindowEvent) { + let response = self.state.on_window_event(&self.window, event); + if response.repaint { + self.window.request_redraw(); + } + egui_winit::update_viewport_info( + &mut self.info, + self.state.egui_ctx(), + &self.window, + false, + ); + } +} + +#[derive(Debug)] +pub enum UserEvent { + GamepadEvent(gilrs::Event), + RepaintRequested(egui::RequestRepaintInfo), +} diff --git a/src/main.rs b/src/main.rs index 8c47f3b..411c0f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,19 @@ use std::{path::PathBuf, process}; use anyhow::Result; -use app::App; +use application::Application; use clap::Parser; use emulator::EmulatorBuilder; use thread_priority::{ThreadBuilder, ThreadPriority}; use winit::event_loop::{ControlFlow, EventLoop}; -mod app; +mod application; mod audio; mod controller; mod emulator; mod graphics; mod input; +mod window; #[derive(Parser)] struct Args { @@ -56,6 +57,6 @@ fn main() -> Result<()> { let event_loop = EventLoop::with_user_event().build().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let proxy = event_loop.create_proxy(); - event_loop.run_app(&mut App::new(client, proxy))?; + event_loop.run_app(&mut Application::new(client, proxy))?; Ok(()) } diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..53dd01c --- /dev/null +++ b/src/window.rs @@ -0,0 +1,157 @@ +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + thread, +}; + +use egui::{Context, ViewportBuilder, ViewportId}; +use game::GameWindow; +use game_screen::GameScreen; +use gilrs::{EventType, Gilrs}; +use input::InputWindow; +use winit::{event::KeyEvent, event_loop::EventLoopProxy}; + +use crate::{ + application::UserEvent, + controller::ControllerManager, + emulator::{EmulatorClient, EmulatorCommand, SimId}, + input::MappingProvider, +}; + +mod game; +mod game_screen; +mod input; + +pub struct WindowManager { + p1: GameWindow, + p2: Arc>, + client: EmulatorClient, + input: ChildWindow, + controllers: ControllerManager, +} +impl WindowManager { + pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { + let mappings = MappingProvider::new(); + let controllers = ControllerManager::new(client.clone(), &mappings); + { + let mappings = mappings.clone(); + thread::spawn(|| process_gamepad_input(mappings, proxy)); + } + let input = ChildWindow::new(InputWindow::new(mappings)); + let p1 = GameWindow::new(client.clone(), SimId::Player1, input.open.clone()); + let p2 = GameWindow::new(client.clone(), SimId::Player2, input.open.clone()); + Self { + p1, + p2: Arc::new(Mutex::new(p2)), + client, + input, + controllers, + } + } + + pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) { + GameScreen::init_pipeline(render_state); + self.p2.lock().unwrap().init_renderer(render_state); + self.p1.init_renderer(render_state); + } + + pub fn initial_viewport(&self) -> ViewportBuilder { + self.p1.initial_viewport() + } + + pub fn show(&mut self, ctx: &Context) { + self.p1.show(ctx); + self.input.show(ctx); + if self.client.has_player_2() { + let (viewport_id, viewport) = { + let p2 = self.p2.lock().unwrap(); + (p2.viewport_id(), p2.initial_viewport()) + }; + let client = self.client.clone(); + let p2 = self.p2.clone(); + ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| { + p2.lock().unwrap().show(ctx); + if ctx.input(|i| i.viewport().close_requested()) { + client.send_command(EmulatorCommand::StopSecondSim); + } + }); + } + } + + pub fn handle_key_event(&mut self, event: winit::event::KeyEvent) { + self.controllers.handle_key_event(&event); + self.input.handle_key_event(&event); + } + + pub fn handle_gamepad_event(&mut self, event: gilrs::Event) { + self.controllers.handle_gamepad_event(&event); + } +} + +fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { + let Ok(mut gilrs) = Gilrs::new() else { + eprintln!("could not connect gamepad listener"); + return; + }; + while let Some(event) = gilrs.next_event_blocking(None) { + if event.event == EventType::Connected { + let Some(gamepad) = gilrs.connected_gamepad(event.id) else { + continue; + }; + mappings.map_gamepad(SimId::Player1, &gamepad); + } + if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { + // main thread has closed! we done + return; + } + } +} + +trait AppWindow { + fn viewport_id(&self) -> ViewportId; + fn initial_viewport(&self) -> ViewportBuilder; + fn show(&mut self, ctx: &Context); + fn handle_key_event(&mut self, event: &KeyEvent) { + let _ = event; + } +} + +struct ChildWindow { + pub open: Arc, + window: Arc>, +} +impl ChildWindow { + fn new(window: T) -> Self { + Self { + open: Arc::new(AtomicBool::new(false)), + window: Arc::new(Mutex::new(window)), + } + } + + fn show(&self, ctx: &Context) { + if !self.open.load(Ordering::Relaxed) { + return; + } + let (viewport_id, viewport) = { + let window = self.window.lock().unwrap(); + (window.viewport_id(), window.initial_viewport()) + }; + let open = self.open.clone(); + let window = self.window.clone(); + ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| { + window.lock().unwrap().show(ctx); + if ctx.input(|i| i.viewport().close_requested()) { + open.store(false, Ordering::Relaxed); + ctx.request_repaint(); + } + }); + } + + fn handle_key_event(&self, event: &KeyEvent) { + if self.open.load(Ordering::Relaxed) { + self.window.lock().unwrap().handle_key_event(event); + } + } +} diff --git a/src/window/game.rs b/src/window/game.rs new file mode 100644 index 0000000..b3c3b7d --- /dev/null +++ b/src/window/game.rs @@ -0,0 +1,174 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; +use egui::{ + menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder, + ViewportCommand, ViewportId, WidgetText, +}; + +use super::{game_screen::GameScreen, AppWindow}; + +pub struct GameWindow { + client: EmulatorClient, + sim_id: SimId, + input_window_open: Arc, + screen: Option, +} + +impl GameWindow { + pub fn new(client: EmulatorClient, sim_id: SimId, input_window_open: Arc) -> Self { + Self { + client, + sim_id, + input_window_open, + screen: None, + } + } + + pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) { + let (screen, sink) = GameScreen::init(render_state); + self.client + .send_command(EmulatorCommand::SetRenderer(self.sim_id, sink)); + self.screen = Some(screen) + } + + fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) { + ui.menu_button("ROM", |ui| { + if ui.button("Open ROM").clicked() { + let rom = rfd::FileDialog::new() + .add_filter("Virtual Boy ROMs", &["vb", "vbrom"]) + .pick_file(); + if let Some(path) = rom { + self.client + .send_command(EmulatorCommand::LoadGame(SimId::Player1, path)); + } + ui.close_menu(); + } + if ui.button("Quit").clicked() { + ctx.send_viewport_cmd(ViewportCommand::Close); + } + }); + ui.menu_button("Emulation", |ui| { + let has_game = self.client.has_game(self.sim_id); + if self.client.is_running(self.sim_id) { + if ui.add_enabled(has_game, Button::new("Pause")).clicked() { + self.client.send_command(EmulatorCommand::Pause); + ui.close_menu(); + } + } else if ui.add_enabled(has_game, Button::new("Resume")).clicked() { + self.client.send_command(EmulatorCommand::Resume); + ui.close_menu(); + } + if ui.add_enabled(has_game, Button::new("Reset")).clicked() { + self.client + .send_command(EmulatorCommand::Reset(self.sim_id)); + ui.close_menu(); + } + }); + ui.menu_button("Video", |ui| { + let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap()); + let current_dims = current_dims.max - current_dims.min; + + for scale in 1..=4 { + let label = format!("x{scale}"); + let scale = scale as f32; + let dims = (384.0 * scale, 224.0 * scale + 20.0).into(); + if ui + .selectable_button((current_dims - dims).length() < 1.0, label) + .clicked() + { + ctx.send_viewport_cmd(ViewportCommand::InnerSize(dims)); + ui.close_menu(); + } + } + }); + ui.menu_button("Audio", |ui| { + let p1_enabled = self.client.is_audio_enabled(SimId::Player1); + let p2_enabled = self.client.is_audio_enabled(SimId::Player2); + if ui.selectable_button(p1_enabled, "Player 1").clicked() { + self.client + .send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled)); + ui.close_menu(); + } + if ui.selectable_button(p2_enabled, "Player 2").clicked() { + self.client + .send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled)); + ui.close_menu(); + } + }); + ui.menu_button("Input", |ui| { + if ui.button("Bind Inputs").clicked() { + self.input_window_open.store(true, Ordering::Relaxed); + ui.close_menu(); + } + }); + ui.menu_button("Multiplayer", |ui| { + if self.sim_id == SimId::Player1 + && !self.client.has_player_2() + && ui.button("Open Player 2").clicked() + { + self.client + .send_command(EmulatorCommand::StartSecondSim(None)); + ui.close_menu(); + } + if self.client.has_player_2() { + let linked = self.client.are_sims_linked(); + if linked && ui.button("Unlink").clicked() { + self.client.send_command(EmulatorCommand::Unlink); + ui.close_menu(); + } + if !linked && ui.button("Link").clicked() { + self.client.send_command(EmulatorCommand::Link); + ui.close_menu(); + } + } + }); + } +} + +impl AppWindow for GameWindow { + fn viewport_id(&self) -> ViewportId { + match self.sim_id { + SimId::Player1 => ViewportId::ROOT, + SimId::Player2 => ViewportId::from_hash_of("Player2"), + } + } + + fn initial_viewport(&self) -> ViewportBuilder { + ViewportBuilder::default() + .with_title("Shrooms VB") + .with_inner_size((384.0, 244.0)) + } + + fn show(&mut self, ctx: &Context) { + TopBottomPanel::top("menubar") + .exact_height(20.0) + .show(ctx, |ui| { + menu::bar(ui, |ui| { + self.show_menu(ctx, ui); + }); + }); + CentralPanel::default().show(ctx, |ui| { + if let Some(screen) = self.screen.as_ref() { + ui.add(screen); + } + }); + } +} + +trait UiExt { + fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response; +} + +impl UiExt for Ui { + fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response { + self.style_mut().visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT; + self.style_mut().visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT; + self.style_mut().visuals.widgets.active.bg_fill = Color32::TRANSPARENT; + let mut selected = selected; + self.checkbox(&mut selected, text) + } +} diff --git a/src/window/game_screen.rs b/src/window/game_screen.rs new file mode 100644 index 0000000..2a00520 --- /dev/null +++ b/src/window/game_screen.rs @@ -0,0 +1,194 @@ +use std::sync::Arc; + +use egui::Widget; +use wgpu::{util::DeviceExt as _, BindGroup, BindGroupLayout, RenderPipeline}; + +use crate::graphics::TextureSink; + +pub struct GameScreen { + bind_group: Arc, +} + +impl GameScreen { + pub fn init_pipeline(render_state: &egui_wgpu::RenderState) { + let device = &render_state.device; + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("texture bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let shader = device.create_shader_module(wgpu::include_wgsl!("../anaglyph.wgsl")); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("render pipeline layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("render pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Bgra8Unorm, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + render_state + .renderer + .write() + .callback_resources + .insert(SharedGameScreenResources { + pipeline: render_pipeline, + bind_group_layout, + }); + } + + pub fn init(render_state: &egui_wgpu::RenderState) -> (Self, TextureSink) { + let device = &render_state.device; + let queue = &render_state.queue; + + let (sink, texture_view) = TextureSink::new(device, queue.clone()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); + let colors = Colors { + left: [1.0, 0.0, 0.0, 1.0], + right: [0.0, 0.7734375, 0.9375, 1.0], + }; + + let color_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("colors"), + contents: bytemuck::bytes_of(&colors), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let renderer = render_state.renderer.read(); + let resources: &SharedGameScreenResources = renderer.callback_resources.get().unwrap(); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bind group"), + layout: &resources.bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: color_buf.as_entire_binding(), + }, + ], + }); + + ( + Self { + bind_group: Arc::new(bind_group), + }, + sink, + ) + } +} + +impl Widget for &GameScreen { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + let response = ui.allocate_rect(ui.clip_rect(), egui::Sense::hover()); + let callback = egui_wgpu::Callback::new_paint_callback( + response.rect, + GameScreenCallback { + bind_group: self.bind_group.clone(), + }, + ); + ui.painter().add(callback); + response + } +} + +struct GameScreenCallback { + bind_group: Arc, +} + +impl egui_wgpu::CallbackTrait for GameScreenCallback { + fn paint( + &self, + _info: egui::PaintCallbackInfo, + render_pass: &mut wgpu::RenderPass<'static>, + callback_resources: &egui_wgpu::CallbackResources, + ) { + let resources: &SharedGameScreenResources = callback_resources.get().unwrap(); + // TODO: maintain aspect ratio + render_pass.set_pipeline(&resources.pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(0..6, 0..1); + } +} + +struct SharedGameScreenResources { + pipeline: RenderPipeline, + bind_group_layout: BindGroupLayout, +} + +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct Colors { + left: [f32; 4], + right: [f32; 4], +} diff --git a/src/window/input.rs b/src/window/input.rs new file mode 100644 index 0000000..b2bc956 --- /dev/null +++ b/src/window/input.rs @@ -0,0 +1,133 @@ +use egui::{ + Button, CentralPanel, Context, Label, Layout, TopBottomPanel, Ui, ViewportBuilder, ViewportId, +}; +use egui_extras::{Column, TableBuilder}; + +use crate::{ + emulator::{SimId, VBKey}, + input::MappingProvider, +}; + +use super::AppWindow; + +pub struct InputWindow { + mappings: MappingProvider, + now_binding: Option<(SimId, VBKey)>, + active_tab: InputTab, +} + +const KEY_NAMES: [(VBKey, &str); 14] = [ + (VBKey::LU, "Up"), + (VBKey::LD, "Down"), + (VBKey::LL, "Left"), + (VBKey::LR, "Right"), + (VBKey::SEL, "Select"), + (VBKey::STA, "Start"), + (VBKey::B, "B"), + (VBKey::A, "A"), + (VBKey::LT, "L-Trigger"), + (VBKey::RT, "R-Trigger"), + (VBKey::RU, "R-Up"), + (VBKey::RD, "R-Down"), + (VBKey::RL, "R-Left"), + (VBKey::RR, "R-Right"), +]; + +impl InputWindow { + pub fn new(mappings: MappingProvider) -> Self { + Self { + mappings, + now_binding: None, + active_tab: InputTab::Player1, + } + } + + fn render_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { + let mappings = self.mappings.for_sim(sim_id); + let binding_names = { + let mapping = mappings.read().unwrap(); + mapping.keyboard_mapping_names() + }; + TableBuilder::new(ui) + .column(Column::remainder()) + .column(Column::remainder()) + .cell_layout(Layout::left_to_right(egui::Align::Center)) + .body(|mut body| { + for keys in KEY_NAMES.chunks_exact(2) { + body.row(20.0, |mut row| { + for (key, name) in keys { + let binding = binding_names.get(key).map(|s| s.as_str()); + row.col(|ui| { + let size = ui.available_size_before_wrap(); + let width = size.x; + let height = size.y; + ui.add_sized((width * 0.2, height), Label::new(*name)); + let label_text = if self.now_binding == Some((sim_id, *key)) { + "Press any input" + } else { + binding.unwrap_or("") + }; + if ui + .add_sized((width * 0.6, height), Button::new(label_text)) + .clicked() + { + self.now_binding = Some((sim_id, *key)) + } + if ui + .add_sized(ui.available_size(), Button::new("Clear")) + .clicked() + { + let mut mapping = mappings.write().unwrap(); + mapping.clear_keyboard_mappings(*key); + } + }); + } + }); + } + }); + } +} + +impl AppWindow for InputWindow { + fn viewport_id(&self) -> ViewportId { + ViewportId::from_hash_of("input") + } + + fn initial_viewport(&self) -> ViewportBuilder { + ViewportBuilder::default() + .with_title("Bind Inputs") + .with_inner_size((600.0, 400.0)) + } + + fn show(&mut self, ctx: &Context) { + TopBottomPanel::top("options").show(ctx, |ui| { + ui.horizontal(|ui| { + ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1"); + ui.selectable_value(&mut self.active_tab, InputTab::Player2, "Player 2"); + }); + }); + CentralPanel::default().show(ctx, |ui| { + match self.active_tab { + InputTab::Player1 => self.render_key_bindings(ui, SimId::Player1), + InputTab::Player2 => self.render_key_bindings(ui, SimId::Player2), + }; + }); + } + + fn handle_key_event(&mut self, event: &winit::event::KeyEvent) { + if !event.state.is_pressed() { + return; + } + let Some((sim_id, vb)) = self.now_binding.take() else { + return; + }; + let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); + mappings.add_keyboard_mapping(vb, event.physical_key); + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum InputTab { + Player1, + Player2, +}