diff --git a/Cargo.toml b/Cargo.toml index 8cea379..dee8426 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ env_logger = "0.10.1" [workspace] resolver = "2" -members = ["crate/*"] exclude = ["example/"] [[example]] diff --git a/example/basic/Cargo.lock b/example/basic/Cargo.lock index 553df5b..202f450 100644 --- a/example/basic/Cargo.lock +++ b/example/basic/Cargo.lock @@ -336,6 +336,8 @@ version = "0.1.0" dependencies = [ "bevy", "bevy_controls", + "strum", + "strum_macros", ] [[package]] @@ -1233,6 +1235,20 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "calloop" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +dependencies = [ + "bitflags 1.3.2", + "log", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] + [[package]] name = "cc" version = "1.0.83" @@ -1497,6 +1513,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.7.4", +] + [[package]] name = "downcast-rs" version = "1.2.0" @@ -2257,6 +2282,24 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.26.0" @@ -2379,6 +2422,20 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", ] [[package]] @@ -2899,12 +2956,31 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sctk-adwaita" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "serde" version = "1.0.196" @@ -2984,6 +3060,25 @@ dependencies = [ "serde", ] +[[package]] +name = "smithay-client-toolkit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.24.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + [[package]] name = "smol_str" version = "0.2.1" @@ -3009,6 +3104,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "strum" version = "0.25.0" @@ -3141,6 +3242,31 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiny-skia" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3417,6 +3543,57 @@ version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + [[package]] name = "wayland-scanner" version = "0.29.5" @@ -3428,6 +3605,17 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.67" @@ -3856,7 +4044,12 @@ dependencies = [ "percent-encoding", "raw-window-handle", "redox_syscall 0.3.5", + "sctk-adwaita", + "smithay-client-toolkit", "wasm-bindgen", + "wayland-client", + "wayland-commons", + "wayland-protocols", "wayland-scanner", "web-sys", "windows-sys 0.45.0", @@ -3883,6 +4076,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xcursor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + [[package]] name = "xi-unicode" version = "0.3.0" diff --git a/example/basic/Cargo.toml b/example/basic/Cargo.toml index 46eebb8..4e7728c 100644 --- a/example/basic/Cargo.toml +++ b/example/basic/Cargo.toml @@ -3,6 +3,14 @@ name = "basic" version = "0.1.0" edition = "2021" +[features] +default_font = [] +x11 = ["bevy/x11"] +wayland = ["bevy/wayland"] +windows = ["bevy/bevy_winit"] + [dependencies] bevy = "0.12.1" -bevy_controls = { path = "./../../crate/bevy_controls" } +bevy_controls = { path = "./../../" } +strum = "0.25.0" +strum_macros = "0.25.3" diff --git a/example/basic/asset/font/FiraCode/FiraCode-VariableFont_wght.ttf b/example/basic/asset/font/FiraCode/FiraCode-VariableFont_wght.ttf new file mode 100644 index 0000000..5655ed5 Binary files /dev/null and b/example/basic/asset/font/FiraCode/FiraCode-VariableFont_wght.ttf differ diff --git a/example/basic/asset/font/FiraCode/OFL.txt b/example/basic/asset/font/FiraCode/OFL.txt new file mode 100644 index 0000000..0e38b88 --- /dev/null +++ b/example/basic/asset/font/FiraCode/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014-2020 The Fira Code Project Authors (https://github.com/tonsky/FiraCode) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/example/basic/asset/font/FiraCode/README.txt b/example/basic/asset/font/FiraCode/README.txt new file mode 100644 index 0000000..e70b485 --- /dev/null +++ b/example/basic/asset/font/FiraCode/README.txt @@ -0,0 +1,67 @@ +Fira Code Variable Font +======================= + +This download contains Fira Code as both a variable font and static fonts. + +Fira Code is a variable font with this axis: + wght + +This means all the styles are contained in a single file: + FiraCode-VariableFont_wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Fira Code: + static/FiraCode-Light.ttf + static/FiraCode-Regular.ttf + static/FiraCode-Medium.ttf + static/FiraCode-SemiBold.ttf + static/FiraCode-Bold.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/example/basic/asset/font/FiraCode/static/FiraCode-Bold.ttf b/example/basic/asset/font/FiraCode/static/FiraCode-Bold.ttf new file mode 100644 index 0000000..9a3d65f Binary files /dev/null and b/example/basic/asset/font/FiraCode/static/FiraCode-Bold.ttf differ diff --git a/example/basic/asset/font/FiraCode/static/FiraCode-Light.ttf b/example/basic/asset/font/FiraCode/static/FiraCode-Light.ttf new file mode 100644 index 0000000..87ff012 Binary files /dev/null and b/example/basic/asset/font/FiraCode/static/FiraCode-Light.ttf differ diff --git a/example/basic/asset/font/FiraCode/static/FiraCode-Medium.ttf b/example/basic/asset/font/FiraCode/static/FiraCode-Medium.ttf new file mode 100644 index 0000000..7a9c38e Binary files /dev/null and b/example/basic/asset/font/FiraCode/static/FiraCode-Medium.ttf differ diff --git a/example/basic/asset/font/FiraCode/static/FiraCode-Regular.ttf b/example/basic/asset/font/FiraCode/static/FiraCode-Regular.ttf new file mode 100644 index 0000000..b8a44d2 Binary files /dev/null and b/example/basic/asset/font/FiraCode/static/FiraCode-Regular.ttf differ diff --git a/example/basic/asset/font/FiraCode/static/FiraCode-SemiBold.ttf b/example/basic/asset/font/FiraCode/static/FiraCode-SemiBold.ttf new file mode 100644 index 0000000..45cfed7 Binary files /dev/null and b/example/basic/asset/font/FiraCode/static/FiraCode-SemiBold.ttf differ diff --git a/example/basic/flake.lock b/example/basic/flake.lock new file mode 100644 index 0000000..d1417ee --- /dev/null +++ b/example/basic/flake.lock @@ -0,0 +1,85 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1707092692, + "narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "faf912b086576fd1a15fca610166c98d47bc667e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1707358215, + "narHash": "sha256-Nuhi8KEJ2e+2nTimSyEIPqN5eh7ECVWd+AnPXG6L+SY=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "dd917bb1b67fc049fd56fe6de70266a9ab74a4aa", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/example/basic/flake.nix b/example/basic/flake.nix new file mode 100644 index 0000000..79f9cd9 --- /dev/null +++ b/example/basic/flake.nix @@ -0,0 +1,52 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + }; + + outputs = { self, nixpkgs, flake-utils, rust-overlay }: + flake-utils.lib.eachDefaultSystem + (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + rustToolchain = (pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml); + nativeBuildInputs = with pkgs; [ rustToolchain pkg-config ]; + buildInputs = with pkgs; [ + openssl + alsa-lib + + vulkan-loader + + xorg.libX11 + xorg.libXrandr + xorg.libXcursor + xorg.libXi + + wayland + libxkbcommon + + udev + shellcheck + file + ]; + in + with pkgs; + { + devShells.default = mkShell { + inherit buildInputs nativeBuildInputs; + LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; + }; + } + ); + +} diff --git a/example/basic/rust-toolchain.toml b/example/basic/rust-toolchain.toml new file mode 100644 index 0000000..263cc78 --- /dev/null +++ b/example/basic/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = '1.72.0' diff --git a/example/basic/src/main.rs b/example/basic/src/main.rs index 2a778e4..cea81cb 100644 --- a/example/basic/src/main.rs +++ b/example/basic/src/main.rs @@ -1,3 +1,215 @@ -fn main() { +//! This example illustrates how to create +//! +//! It displays +//! +//! Yeah I know, now it's a bit ugly +use bevy::{ + diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, + ecs::schedule::States, + prelude::*, +}; +use bevy_controls::{ + contract::{ + Action, ActionInner, GameState, GameStateInner, InputsContainer, InputsContainerInner, + }, + plugin::ControlsPlugin, + resource::{Binding, ButtonCombination, Controls, InputType, Inputs, PlayerInputs}, +}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; +const FIRA_CODE_PATH: &str = "font/FiraCode/FiraCode-VariableFont_wght.ttf"; + +#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy)] +enum MyAction { + MoveForward, + MoveBack, + MoveLeft, + MoveRight, + Jump, + Interact, + Shoot, + Scope, } + +impl Action for MyAction {} + +impl ActionInner for MyAction {} + +#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default)] +enum MyGameState { + #[default] + InGame, +} + +impl GameState for MyGameState {} + +impl GameStateInner for MyGameState {} + +#[derive(Resource, Default, Clone)] +struct MyInputsContainer { + // When the game does not provide multiplayer, one field is enough + player_inputs: PlayerInputs, +} + +impl InputsContainer for MyInputsContainer {} + +impl InputsContainerInner for MyInputsContainer { + fn iter_inputs<'a>(&'a self) -> Box> + 'a> { + todo!() + } + + fn me<'a>(&'a self) -> Option<&'a PlayerInputs> { + Some(& self.player_inputs) + } + + fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs> { + Some(&mut self.player_inputs) + } +} + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins.set(AssetPlugin { + file_path: "asset".into(), + ..default() + }), + ControlsPlugin::::default(), + )) + .add_systems(Startup, (add_bindings, setup)) + .run(); +} + +// TODO: default bingings... +fn add_bindings(mut controls: ResMut>) { + controls.push( + MyAction::MoveForward, + Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::W))), + ); +} + +#[derive(Component)] +struct TextBlock; + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera2dBundle::default()); + + commands.spawn(( + TextBundle::from_sections([ + TextSection::new( + "Input: ", + TextStyle { + font: asset_server.load(FIRA_CODE_PATH), + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "A ", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "S ", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "W ", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "D ", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "\nAction: ", + TextStyle { + font: asset_server.load(FIRA_CODE_PATH), + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "MoveLeft", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "MoveBack", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "MoveForward", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + TextSection::new( + "MoveRight", + TextStyle { + font_size: 60.0, + ..default() + }, + ), + ]), + TextBlock, + )); +} + +// text sections indexes +const A_INDEX: usize = 2; +const S_INDEX: usize = 3; +const W_INDEX: usize = 4; +const D_INDEX: usize = 5; + +const MOVE_LEFT_INDEX: usize = 6; +const MOVE_BACK_INDEX: usize = 7; +const MOVE_FORWARD_INDEX: usize = 8; +const MOVE_RIGHT_INDEX: usize = 9; + +fn text_update_system( + mut query: Query<&mut Text, With>, + inputs_container: MyInputsContainer, +) { + for mut text in &mut query { + + let input_value = inputs_container.me().expect("This is bad").get(MyAction::MoveLeft); + if input_value.is_boolean() && input_value.to_boolean() { + text.sections[MOVE_LEFT_INDEX].style.color = Color::GOLD; + } else { + text.sections[MOVE_LEFT_INDEX].style.color = Color::GOLD; + } + } +} + +// fn text_update_system( +// diagnostics: Res, +// mut query: Query<&mut Text, With>, +// ) { +// for mut text in &mut query { +// if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) { +// if let Some(value) = fps.smoothed() { +// // Update the value of the second section +// text.sections[1].value = format!("{value:.2}"); +// } +// } +// } +// } diff --git a/script/fetures-clippy.sh b/script/fetures-clippy.sh index ce57420..a713549 100644 --- a/script/fetures-clippy.sh +++ b/script/fetures-clippy.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash CURDIR="$(realpath "$(dirname "$0")")" @@ -29,29 +29,6 @@ usage %s \n exit fi -generate_combinations() { - items="$1" - size=$2 - current=$3 - combination="$4" - - if [ "$current" -eq "$size" ]; then - echo "$combination" - return - fi - - # Split items into positional parameters - set -- $items - while [ $# -gt 0 ]; do - item=$1 - shift - new_combination="$combination $item" - remaining_items="$@" - generate_combinations "$remaining_items" $size $(($current + 1)) "$new_combination" - set -- "$item" $@ - done -} - CARGO_PATH="$1" if [ -z "$CARGO_PATH" ]; then @@ -63,20 +40,38 @@ if [ -d "$CARGO_PATH" ]; then CARGO_PATH="${CARGO_PATH}/Cargo.toml" fi -features=$(grep '^\[features\]' "$CARGO_PATH" -A99999999 | grep '^\[' -m 2 -B99999999 | grep -v '^\[' | cut -d '=' -f 1 | grep -v 'default') -num_features=$(echo "$features" | wc -w | tr -d ' ') +features=$(grep '^\[features\]' $1 -A99999999 | grep '^\[' -m 2 -B99999999 | grep -v '^\[' | cut -d '=' -f 1 | grep -v 'default') -i=1 -while [ $i -le $num_features ]; do + +generate_combinations() { + local items=($1) + local size=$2 + local current=$3 + local combination=$4 + + if [ $current -eq $size ]; then + echo "$combination" + return + fi + + for ((i = 0; i < ${#items[@]}; ++i)); do + new_combination="$combination ${items[$i]}" + remaining_items=("${items[@]:0:$i}" "${items[@]:$((i + 1))}") + generate_combinations "$(IFS=" "; echo "${remaining_items[*]}")" $size $((current + 1)) "$new_combination" + done +} + +for ((i = 1; i <= $(wc -w <<< "$features"); ++i)); do echo "Generating combinations of size $i" combos=$(generate_combinations "$features" $i 0 "") - echo "$combos" | while IFS= read -r line; do - combo=$(echo $line | xargs) # Trim + echo "$combos" | while read line + do + echo "Running clippy with features: $line" + combo=$(echo $combo | xargs) # Trim echo "Running clippy with features: $combo" if ! cargo clippy --features "$combo"; then echo "Error found in combination: $combo" exit 1 fi done - i=$((i + 1)) done diff --git a/src/contract.rs b/src/contract.rs index ea2cda5..7b04253 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,102 +1,170 @@ use std::hash::Hash; - -use bevy_ecs::{system::Resource, schedule::States}; -use strum::IntoEnumIterator; + +use bevy_ecs::{schedule::States, system::Resource}; #[cfg(feature = "inspector-egui")] use bevy_inspector_egui::inspector_options::InspectorOptionsType; +use strum::IntoEnumIterator; // #[cfg(all(feature = "reflect", feature = "serialize"))] // use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; -#[cfg(feature = "serialize")] -use serde::{Serialize, de::DeserializeOwned}; #[cfg(feature = "reflect")] -use bevy_reflect::{Reflect, TypePath, FromReflect}; +use bevy_reflect::{FromReflect, Reflect, TypePath}; +#[cfg(feature = "serialize")] +use serde::{de::DeserializeOwned, Serialize}; use crate::resource::PlayerInputs; -pub trait ActionInner: 'static + std::marker::Sync + std::marker::Send + PartialEq + Eq + Hash + IntoEnumIterator + Clone + Copy { } +pub trait ActionInner: + 'static + + std::marker::Sync + + std::marker::Send + + PartialEq + + Eq + + Hash + + IntoEnumIterator + + Clone + + Copy +{ +} -pub trait GameStateInner: 'static + std::marker::Sync + std::marker::Send + States { } +pub trait GameStateInner: 'static + std::marker::Sync + std::marker::Send + States + Default {} -pub trait InputsContainerInner: 'static + std::marker::Sync + std::marker::Send + Resource { +pub trait InputsContainerInner: + 'static + std::marker::Sync + std::marker::Send + Resource + Default +{ /// The method returning an iterator over references to the PlayerInputs. /// Note: the use of 'a for both the method's lifetime and the returned Iterator's lifetime. fn iter_inputs<'a>(&'a self) -> Box> + 'a>; /// The method returning an player inputs of this client. - fn me<'a>(&self) -> Option<&'a PlayerInputs>; + fn me<'a>(&'a self) -> Option<&'a PlayerInputs>; /// The method returning an mutable player inputs of this client. - fn me_mut<'a>(&self) -> Option<&'a mut PlayerInputs>; + fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs>; } #[cfg(feature = "serialize")] -pub trait SerializeImpl: Serialize + DeserializeOwned { } +pub trait SerializeImpl: Serialize + DeserializeOwned {} #[cfg(feature = "reflect")] -pub trait ReflectImpl: Reflect + TypePath + FromReflect { } +pub trait ReflectImpl: Reflect + TypePath + FromReflect {} #[cfg(feature = "inspector-egui")] -pub trait InspectorEguiImpl: InspectorOptionsType { } +pub trait InspectorEguiImpl: InspectorOptionsType {} // ===== Action ===== // SAFATY: inspector-egui includes reflect -#[cfg(all(not(feature = "serialize"), not(feature = "reflect"), not(feature = "inspector-egui")))] +#[cfg(all( + not(feature = "serialize"), + not(feature = "reflect"), + not(feature = "inspector-egui") +))] pub trait Action: ActionInner {} -#[cfg(all(feature = "serialize", not(feature = "reflect"), not(feature = "inspector-egui")))] +#[cfg(all( + feature = "serialize", + not(feature = "reflect"), + not(feature = "inspector-egui") +))] pub trait Action: ActionInner + SerializeImpl {} -#[cfg(all(not(feature = "serialize"), feature = "reflect", not(feature = "inspector-egui")))] +#[cfg(all( + not(feature = "serialize"), + feature = "reflect", + not(feature = "inspector-egui") +))] pub trait Action: ActionInner + ReflectImpl {} -#[cfg(all(feature = "serialize", feature = "reflect", not(feature = "inspector-egui")))] -pub trait Action: ActionInner + SerializeImpl + ReflectImpl{} +#[cfg(all( + feature = "serialize", + feature = "reflect", + not(feature = "inspector-egui") +))] +pub trait Action: ActionInner + SerializeImpl + ReflectImpl {} #[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))] -pub trait Action: ActionInner + ReflectImpl + InspectorEguiImpl {} +pub trait Action: ActionInner + ReflectImpl + InspectorEguiImpl {} #[cfg(all(feature = "serialize", feature = "inspector-egui"))] -pub trait Action: ActionInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {} +pub trait Action: ActionInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {} // ===== GameState ===== // SAFATY: inspector-egui includes reflect -#[cfg(all(not(feature = "serialize"), not(feature = "reflect"), not(feature = "inspector-egui")))] +#[cfg(all( + not(feature = "serialize"), + not(feature = "reflect"), + not(feature = "inspector-egui") +))] pub trait GameState: GameStateInner {} -#[cfg(all(feature = "serialize", not(feature = "reflect"), not(feature = "inspector-egui")))] +#[cfg(all( + feature = "serialize", + not(feature = "reflect"), + not(feature = "inspector-egui") +))] pub trait GameState: GameStateInner + SerializeImpl {} -#[cfg(all(not(feature = "serialize"), feature = "reflect", not(feature = "inspector-egui")))] +#[cfg(all( + not(feature = "serialize"), + feature = "reflect", + not(feature = "inspector-egui") +))] pub trait GameState: GameStateInner + ReflectImpl {} -#[cfg(all(feature = "serialize", feature = "reflect", not(feature = "inspector-egui")))] -pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl{} +#[cfg(all( + feature = "serialize", + feature = "reflect", + not(feature = "inspector-egui") +))] +pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl {} #[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))] -pub trait GameState: GameStateInner + ReflectImpl + InspectorEguiImpl {} +pub trait GameState: GameStateInner + ReflectImpl + InspectorEguiImpl {} #[cfg(all(feature = "serialize", feature = "inspector-egui"))] -pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {} - - +pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {} // ===== InputsContainer ===== // SAFATY: inspector-egui includes reflect -#[cfg(all(not(feature = "serialize"), not(feature = "reflect"), not(feature = "inspector-egui")))] +#[cfg(all( + not(feature = "serialize"), + not(feature = "reflect"), + not(feature = "inspector-egui") +))] pub trait InputsContainer: InputsContainerInner {} -#[cfg(all(feature = "serialize", not(feature = "reflect"), not(feature = "inspector-egui")))] +#[cfg(all( + feature = "serialize", + not(feature = "reflect"), + not(feature = "inspector-egui") +))] pub trait InputsContainer: InputsContainerInner + SerializeImpl {} -#[cfg(all(not(feature = "serialize"), feature = "reflect", not(feature = "inspector-egui")))] +#[cfg(all( + not(feature = "serialize"), + feature = "reflect", + not(feature = "inspector-egui") +))] pub trait InputsContainer: InputsContainerInner + ReflectImpl {} -#[cfg(all(feature = "serialize", feature = "reflect", not(feature = "inspector-egui")))] -pub trait InputsContainer: InputsContainerInner + SerializeImpl + ReflectImpl{} +#[cfg(all( + feature = "serialize", + feature = "reflect", + not(feature = "inspector-egui") +))] +pub trait InputsContainer: + InputsContainerInner + SerializeImpl + ReflectImpl +{ +} #[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))] -pub trait InputsContainer: InputsContainerInner + ReflectImpl + InspectorEguiImpl {} +pub trait InputsContainer: + InputsContainerInner + ReflectImpl + InspectorEguiImpl +{ +} #[cfg(all(feature = "serialize", feature = "inspector-egui"))] -pub trait InputsContainer: InputsContainerInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {} +pub trait InputsContainer: + InputsContainerInner + SerializeImpl + ReflectImpl + InspectorEguiImpl +{ +} // #[cfg(test)] // mod test { diff --git a/src/plugin.rs b/src/plugin.rs index e4fc72a..c2d3432 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -4,10 +4,20 @@ use bevy_input::{Input, keyboard::KeyCode, mouse::MouseButton}; use crate::{resource::*, contract::{Action, GameState, InputsContainer}}; -pub struct ControlsPlugin { - _action: std::marker::PhantomData, - _inputs_container: std::marker::PhantomData, - _game_state: std::marker::PhantomData, +pub struct ControlsPlugin { + _action: std::marker::PhantomData, + _inputs_container: std::marker::PhantomData, + _game_state: std::marker::PhantomData, +} + +impl Default for ControlsPlugin { + fn default() -> Self { + Self { + _action: std::marker::PhantomData, + _inputs_container: std::marker::PhantomData, + _game_state: std::marker::PhantomData, + } + } } impl< @@ -17,6 +27,8 @@ impl< > Plugin for ControlsPlugin { fn build(&self, app: &mut App) { app.init_resource::>() + .init_resource::() + .add_state::() .add_systems(Update, Self::save_input); #[cfg(feature = "reflect")] @@ -34,7 +46,7 @@ impl< fn save_input( keyboard_input: Res>, mouse_input: Res>, - lobby: ResMut, + mut lobby: ResMut, controls: Res>, game_state: Res>, ) { @@ -235,4 +247,4 @@ impl< // log::info!("without casting: {:?}", duration); // } -// } \ No newline at end of file +// } diff --git a/src/resource.rs b/src/resource.rs index f3de8f3..822843c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -80,6 +80,14 @@ impl PlayerInputs { *self.current.get(&action).unwrap() } + // TODO + // /// Returns current input value for action + // /// + // /// in any case faster that `get_many` method + // pub fn all_active(&self) -> InputValue { + // *self.current.iter().filter(|a| {a.}) + // } + /// Returns current input values for actions pub fn get_many(&self, actions: Vec) -> Vec { // SAFETY: action is always valid @@ -119,6 +127,8 @@ impl PlayerInputs { } /// Set input value to new + /// + /// notice: not recomended to use if you do not know what do you do pub fn forced_set(&mut self, action: A, value: impl Into) { // SAFETY: action is always valid // because we iterate over all actions in `default` method @@ -129,6 +139,8 @@ impl PlayerInputs { /// Set input value to new /// if it is not the same InputValue type return `Error()` + /// + /// notice: not recomended to use if you do not know what do you do pub fn set( &mut self, action: A, @@ -150,6 +162,8 @@ impl PlayerInputs { } /// Update current inputs + /// + /// notice: not recomended to use if you do not know what do you do pub fn forced_update(&mut self, inputs: Inputs) { *self.previous = self.current.clone().0; *self.current = inputs.0; @@ -158,6 +172,8 @@ impl PlayerInputs { /// Update current inputs /// if any input is not the same InputValue type return `Error()` /// ! remember that it work safely but slow + /// + /// notice: not recomended to use if you do not know what do you do pub fn update(&mut self, inputs: Inputs) -> Result<(), Box> { // SAFETY: action is always valid // because we iterate over all actions in `default` method @@ -222,6 +238,7 @@ common_traits_conditions! { Tap, } + #[derive(Clone)] pub struct ActivationOptions { /// Manner in which input is registered pub mode: ActivationMode, @@ -361,7 +378,7 @@ impl InputValue { matches!(self, InputValue::Float(_)) } - pub fn as_boolean(&self) -> bool { + pub fn to_boolean(&self) -> bool { match self { InputValue::Boolean(value) => *value, InputValue::Float(value) => *value > 0.0, @@ -371,6 +388,7 @@ impl InputValue { } common_traits_conditions! { + #[derive(Clone)] pub struct Bindings { /// List of bindings for action #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] @@ -450,7 +468,7 @@ impl Bindings { common_traits_conditions! { /// Contains all bindings for action /// and different activation options - #[derive(Default)] + #[derive(Default, Clone)] pub struct BindingConfig { #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] pub bindings: Bindings, @@ -470,22 +488,26 @@ impl BindingConfig { common_traits_conditions! { /// Contains all bindings for actions - #[derive(Resource)] + #[derive(Resource, Clone)] #[cfg_attr(feature = "reflect", reflect(Resource))] // TODO: Add delay for input - pub struct Controls(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap>); // TODO: change HashMap to Vec for faster iteration + pub struct Controls(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap>); // TODO: change HashMap to Vec for faster iteration } impl Default for Controls { fn default() -> Self { // TODO: default must create empty controls or fill empty / default BindingConfig on any actions - let controls = Self(HashMap::new()); + let mut controls = HashMap::new(); + + for key in A::iter() { + controls.insert(key, BindingConfig::default()); + } // If you see this error, you may add new action in menu_actions // or make sure that you have only one Menufor<'a> Action<'a> with the same name in the Menufor<'a> Action<'a>s - assert!(validate_hash_map(&controls.0)); + assert!(validate_hash_map(&controls)); - controls + Self(controls) } } @@ -498,6 +520,7 @@ impl Controls { /// Push new binding for action pub fn push(&mut self, action: A, binding: Binding) { // TODO: warning if config is not exist yet + // TODO: interface for [`ActivationOptions`] self.0 .entry(action) .or_insert_with(BindingConfig::default)