Compare commits

1 Commits

Author SHA1 Message Date
yukkop 036ef889e8 build: examples target directory 2024-02-17 22:53:04 +01:00
32 changed files with 3950 additions and 12744 deletions
Generated Executable → Regular
+1801 -1970
View File
File diff suppressed because it is too large Load Diff
-25
View File
@@ -1,25 +0,0 @@
# v0.1.0
- [ ] debug log for trigered actions
- [ ] core
- [x] single keys
- [ ] chords
- [x] contracts
- [x] loby
- [x] actions
- [x] game state
- [ ] serialization & deserialization with ignoring imutable values
- [x] way to define default controlls
- [ ] layers for controlls: if on (shift + space) action enable, ignore on (space) action
- [ ] warnings on actions collision
- [ ] examples
- [ ] basic example
- [ ] serialization example
- [ ] action options work
- [ ] deley
- [ ] tap / hold
# v0.2.0
- [ ] layers for controlls: user posibility give priority to controls
- [ ] controller support
- [ ] touch pad support
- [ ] posibility support more that one game state
Executable → Regular
+9 -11
View File
@@ -5,24 +5,22 @@ edition = "2021"
[features]
default = []
logical-keyboard = []
serialize = ["bevy_input/serialize", "serde"]
reflect = ["dep:bevy_reflect"]
inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
[dependencies]
bevy-inspector-egui = { version = "0.37", optional = true, default-features = false }
bevy_app = "0.19.1"
bevy_derive = "0.19.1"
bevy_ecs = "0.19.1"
bevy_input = { version = "0.19.1", features = ["keyboard", "mouse"] }
bevy_state = "0.19.1"
bevy_platform = "0.19.1"
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"], optional = true }
bevy-inspector-egui = { version = "0.22.1", optional = true }
bevy_app = "0.12.1"
bevy_derive = "0.12.1"
bevy_ecs = "0.12.1"
bevy_input = "0.12.1"
bevy_reflect = { version = "0.12.1", optional = true }
bevy_utils = "0.12.1"
log = "0.4.20"
serde = { version = "1.0.194", optional = true }
strum = "0.26.2"
strum_macros = "0.26.2"
strum = "0.25.0"
strum_macros = "0.25.3"
[dev-dependencies]
env_logger = "0.10.1"
+1370 -3086
View File
File diff suppressed because it is too large Load Diff
+7 -18
View File
@@ -7,30 +7,19 @@ edition = "2021"
target-dir = "../../../../target"
[features]
x11 = ["bevy/x11"]
wayland = ["bevy/wayland"]
x11 = ["bevy/x11", "bevy/bevy_winit"]
wayland = ["bevy/wayland", "bevy/bevy_winit"]
windows = ["bevy/bevy_winit"]
[dependencies]
bevy = { version = "0.19.1", default-features = false, features = [
"std",
"default_app",
bevy = { version = "0.12.1", default-features = false, features = [
"bevy_asset",
"bevy_text",
"bevy_state",
"bevy_ui",
"bevy_ui_render",
"bevy_window",
"bevy_winit",
"dynamic_linking",
"default_font",
]}
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"] }
bevy_controls = { path = "./../../", features = [
"logical-keyboard",
]}
bevy_controls = { path = "./../../" }
bevy_controls_derive = { path = "./../../../bevy_controls_derive" }
strum_macros = "0.26.2"
strum = "0.26.2"
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
strum = "0.25.0"
strum_macros = "0.25.3"
@@ -1,6 +0,0 @@
Basic example
to run
```bash
cargo run --features '<x11|wayland|windows>'
```
+12 -9
View File
@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1788316716,
"narHash": "sha256-bc7rSpXIdn9QWGNqfWcPZWOhEVF8NoeAZkWq0XWnf/k=",
"lastModified": 1707092692,
"narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3ed67ec0a4d3c7ab4ae1f04f8ee8df07bfa506a2",
"rev": "faf912b086576fd1a15fca610166c98d47bc667e",
"type": "github"
},
"original": {
@@ -43,16 +43,19 @@
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1788456718,
"narHash": "sha256-TxHC0sBjV2pmsBLKAX02JTXlye/EOLt6Do+WNtGofGw=",
"lastModified": 1707358215,
"narHash": "sha256-Nuhi8KEJ2e+2nTimSyEIPqN5eh7ECVWd+AnPXG6L+SY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "0b20a18e84d9164464739189c2b2a1b00f6f0e4b",
"rev": "dd917bb1b67fc049fd56fe6de70266a9ab74a4aa",
"type": "github"
},
"original": {
+5 -4
View File
@@ -6,6 +6,7 @@
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
};
@@ -26,10 +27,10 @@
vulkan-loader
libx11
libxrandr
libxcursor
libxi
xorg.libX11
xorg.libXrandr
xorg.libXcursor
xorg.libXi
wayland
libxkbcommon
+1 -1
View File
@@ -1,2 +1,2 @@
[toolchain]
channel = '1.95.0'
channel = '1.72.0'
+163 -191
View File
@@ -2,60 +2,55 @@
//!
//! It displays
//!
//! Yeah I know, that's kind of weird that it displays
//! But it does.........
//! Yeah I know, now it's a bit ugly
// TODO strum redefine in bevy_controls
use bevy::prelude::*;
use bevy::input::keyboard::Key;
use bevy::{ecs::schedule::States, prelude::*};
use bevy_controls::{
contract::InputsContainer,
plugin::ControlsPlugin,
resource::{
ActivationMode, ActivationOptions, AxisName, Binding, BindingCondition, BindingConfig,
Bindings, ButtonCombination, Controls, InputType, InputValue, Keyboard, MouseInput, OptionsMode,
PlayerActions,
contract::{
Action, ActionInner, GameState, GameStateInner, InputsContainer, InputsContainerInner,
},
plugin::ControlsPlugin,
resource::{Binding, ButtonCombination, Controls, InputType, InputValue, Keyboard, PlayerInputs},
};
use bevy_controls_derive::{Action, GameState};
use strum_macros::EnumIter;
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, Action)]
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, bevy_controls_derive::Action)]
enum MyAction {
Forward,
Back,
Left,
Right,
Up,
Down,
MouseHorizontal,
MouseVertical,
}
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default, GameState)]
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default)]
enum MyGameState {
#[default]
InGame,
Menu,
}
impl GameState for MyGameState {}
impl GameStateInner for MyGameState {}
#[derive(Resource, Default, Clone, Debug)]
struct MyInputsContainer {
// When the game does not provide multiplayer, one field is enough
player_inputs: PlayerActions<MyAction>,
player_inputs: PlayerInputs<MyAction>,
}
impl InputsContainer<MyAction> for MyInputsContainer {
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<MyAction>> + 'a> {
impl InputsContainer<MyAction> for MyInputsContainer {}
impl InputsContainerInner<MyAction> for MyInputsContainer {
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<MyAction>> + 'a> {
todo!()
}
fn me<'a>(&'a self) -> Option<&'a PlayerActions<MyAction>> {
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<MyAction>> {
Some(&self.player_inputs)
}
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<MyAction>> {
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs<MyAction>> {
Some(&mut self.player_inputs)
}
}
@@ -66,198 +61,175 @@ struct JumpsCount(i32);
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
transparent: true,
decorations: false,
#[cfg(target_os = "macos")]
composite_alpha_mode: CompositeAlphaMode::PostMultiplied,
..default()
}),
..default()
}),
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::new(
Controls::<MyAction, MyGameState>::new()
// the first control instance I will describe in detail
.with(
MyAction::Left,
BindingConfig::new(Bindings::new(vec![
// `KeyCode` binds physical key location, so this is the US-layout A position.
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::KeyA),
)))
.with_condition(BindingCondition::InGameState(MyGameState::InGame)),
// `Key` binds logical text, so this activates when the current layout produces h.
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::Key(Key::Character("h".into())),
)))
.with_condition(BindingCondition::InGameState(MyGameState::InGame)),
]))
.with_activation_options(
ActivationOptions::new(ActivationMode::Tap, OptionsMode::Immutable).with_delay(0.05),
),
)
// the rest will be simplified
.with(
MyAction::Back,
BindingConfig::from_vec(vec![
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyS))),
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("j".into())))),
]),
)
.with(
MyAction::Forward,
BindingConfig::from_vec(vec![
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyW))),
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("k".into())))),
]),
)
.with(
MyAction::Right,
BindingConfig::from_vec(vec![
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyD))),
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("l".into())))),
]),
)
.with(
MyAction::Up,
BindingConfig::from_bind(Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(
KeyCode::Space,
)))),
)
.with(
MyAction::MouseHorizontal,
BindingConfig::from_bind(Binding::from_single(InputType::Mouse(MouseInput::Axis(
AxisName::Horizontal,
)))),
)
.with(
MyAction::MouseVertical,
BindingConfig::from_bind(Binding::from_single(InputType::Mouse(MouseInput::Axis(
AxisName::Vertical,
)))),
)
.with(
MyAction::Down,
BindingConfig::from_bind(Binding::from_single(
InputType::Keyboard(Keyboard::KeyCode(KeyCode::ShiftLeft)),
//InputType::Keyboard(Keyboard::KeyCode(KeyCode::Space)),
)),
)
.build(),
),
DefaultPlugins,
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::default(),
))
.insert_resource(ClearColor(Color::NONE))
.init_resource::<JumpsCount>()
.add_systems(Startup, setup)
.add_systems(Startup, (add_bindings, setup))
.add_systems(Update, text_update_system)
.run();
}
#[derive(Component, Clone, Copy)]
enum TextSpanKind {
MoveLeft,
MoveBack,
MoveForward,
MoveRight,
Jump,
DeltaX,
DeltaY,
// TODO: default bingings...
fn add_bindings(mut controls: ResMut<Controls<MyAction, MyGameState>>) {
controls.force_push(
MyAction::Left,
// this way input will work only on latin layout only on `A`
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::A),
))),
);
controls.force_push(
MyAction::Left,
// this way input will work only on any layout on same button, 35 is `H` for `us` layout or
// `О` for `ru` layout
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(35)),
))),
);
controls.force_push(
MyAction::Back,
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::S),
))),
);
controls.force_push(
MyAction::Back,
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(36)),
))),
);
controls.force_push(
MyAction::Forward,
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::W),
))),
);
controls.force_push(
MyAction::Forward,
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(37)),
))),
);
controls.force_push(
MyAction::Right,
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::D),
))),
);
controls.force_push(
MyAction::Right,
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(38)),
))),
);
controls.force_push(
MyAction::Up,
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::Space),
))),
);
}
#[derive(Component)]
struct TextBlock;
const TEXT_SIZE: f32 = 30.;
fn text_span(text: &str, kind: TextSpanKind) -> (TextSpan, TextFont, TextColor, TextSpanKind) {
(
TextSpan::new(text),
TextFont {
font_size: FontSize::Px(TEXT_SIZE),
..default()
},
TextColor(Color::WHITE),
kind,
)
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.spawn(Camera2dBundle::default());
commands
.spawn((
Text::new(
"Info: Important that WASD use physical key positions and HJKL use logical characters from current layout\nAction: ",
commands.spawn((
TextBundle::from_sections([
TextSection::new(
"\nAction: ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextFont {
font_size: FontSize::Px(TEXT_SIZE),
..default()
},
TextColor(Color::WHITE),
BackgroundColor(Color::BLACK),
))
.with_children(|parent| {
parent.spawn(text_span("Left (H/A) ", TextSpanKind::MoveLeft));
parent.spawn(text_span("Back (J/S) ", TextSpanKind::MoveBack));
parent.spawn(text_span("Forward (K/W) ", TextSpanKind::MoveForward));
parent.spawn(text_span("Right (L/D) ", TextSpanKind::MoveRight));
parent.spawn(text_span("Up (Space)", TextSpanKind::Jump));
parent.spawn(text_span("(MouseDeltaX)", TextSpanKind::DeltaX));
parent.spawn(text_span("(MouseDeltaY)", TextSpanKind::DeltaY));
});
TextSection::new(
"Left (H/A) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Back (J/S) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Forward (K/W) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Right (L/D) ",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Up (Space)",
TextStyle {
font_size: TEXT_SIZE,
..default()
},
),
]),
TextBlock,
));
}
// text sections indexes
const MOVE_LEFT_INDEX: usize = 1;
const MOVE_BACK_INDEX: usize = 2;
const MOVE_FORWARD_INDEX: usize = 3;
const MOVE_RIGHT_INDEX: usize = 4;
const JUMP_INDEX: usize = 5;
fn text_update_system(
mut query: Query<(&TextSpanKind, &mut TextSpan, &mut TextColor)>,
mut query: Query<&mut Text, With<TextBlock>>,
inputs_container: Res<MyInputsContainer>,
mut jumps_count: ResMut<JumpsCount>,
) {
for (text_span_kind, mut text_span, mut text_color) in &mut query {
for text in query.iter_mut() {
let player_inputs = inputs_container.me().expect("This is bad");
match *text_span_kind {
TextSpanKind::MoveLeft => {
update_text_color(&mut text_color, player_inputs.get(MyAction::Left))
}
TextSpanKind::MoveBack => {
update_text_color(&mut text_color, player_inputs.get(MyAction::Back))
}
TextSpanKind::MoveForward => {
update_text_color(&mut text_color, player_inputs.get(MyAction::Forward));
}
TextSpanKind::MoveRight => {
update_text_color(&mut text_color, player_inputs.get(MyAction::Right));
}
TextSpanKind::Jump => {
if player_inputs
.get_just_pressed(MyAction::Up)
.unwrap_or(false)
{
**jumps_count += 1;
text_span.0 = format!("Up (Space) - {}", **jumps_count);
}
if player_inputs
.get_just_pressed(MyAction::Down)
.unwrap_or(false)
{
**jumps_count -= 1;
text_span.0 = format!("Up (Space) - {}", **jumps_count);
}
}
TextSpanKind::DeltaX => {
let x = player_inputs.get(MyAction::MouseHorizontal).to_float();
text_span.0 = format!("(MouseDeltaX) - {}", x);
}
TextSpanKind::DeltaY => {
let y = player_inputs.get(MyAction::MouseVertical).to_float();
text_span.0 = format!("(MouseDeltaY) - {}", y);
}
let mut text = update_text(text, player_inputs.get(MyAction::Left), MOVE_LEFT_INDEX);
text = update_text(text, player_inputs.get(MyAction::Back), MOVE_BACK_INDEX);
text = update_text(
text,
player_inputs.get(MyAction::Forward),
MOVE_FORWARD_INDEX,
);
text = update_text(text, player_inputs.get(MyAction::Right), MOVE_RIGHT_INDEX);
if player_inputs
.get_just_pressed(MyAction::Up)
.unwrap_or(false)
{
**jumps_count += 1;
text.sections[JUMP_INDEX].value = format!("Up (Space) - {}", **jumps_count);
}
}
}
fn update_text_color(text_color: &mut TextColor, input_value: InputValue) {
fn update_text(mut text: Mut<Text>, input_value: InputValue, index: usize) -> Mut<Text> {
if input_value.is_boolean() && input_value.to_boolean() {
text_color.0 = Color::linear_rgb(0.855, 0.647, 0.125);
text.sections[index].style.color = Color::GOLD;
} else {
text_color.0 = Color::WHITE;
text.sections[index].style.color = Color::WHITE;
}
text
}
File diff suppressed because it is too large Load Diff
@@ -1,34 +0,0 @@
[package]
name = "rebinding"
version = "0.1.0"
edition = "2021"
[build]
target-dir = "../../../../target"
[features]
x11 = ["bevy/x11"]
wayland = ["bevy/wayland"]
windows = ["bevy/bevy_winit"]
[dependencies]
bevy = { version = "0.19.1", default-features = false, features = [
"std",
"default_app",
"bevy_asset",
"bevy_text",
"bevy_state",
"bevy_ui",
"bevy_ui_render",
"bevy_window",
"bevy_winit",
"default_font",
] }
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"] }
bevy_controls = { path = "./../../" }
bevy_controls_derive = { path = "./../../../bevy_controls_derive" }
strum = "0.26.2"
strum_macros = "0.26.2"
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
@@ -1,18 +0,0 @@
# Rebinding example
Run from this directory with one platform feature:
```bash
cargo run --features x11
cargo run --features wayland
cargo run --features windows
```
Controls:
- `1`-`4`: select Left, Back, Forward, or Right.
- `R`: capture next key and add it to selected action.
- `C`: clear selected action bindings.
- `Escape`: cancel key capture.
Default WASD bindings and captured keys use physical `KeyCode` positions.
@@ -1,208 +0,0 @@
use bevy::{
ecs::message::MessageReader,
input::{keyboard::KeyboardInput, ButtonState},
prelude::*,
};
use bevy_controls::{
contract::InputsContainer,
plugin::ControlsPlugin,
resource::{Binding, BindingConfig, Bindings, Controls, InputType, Keyboard, PlayerActions},
};
use bevy_controls_derive::{Action, GameState};
use strum_macros::EnumIter;
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, Action)]
enum MovementAction {
Left,
Back,
Forward,
Right,
}
impl MovementAction {
const fn name(self) -> &'static str {
match self {
Self::Left => "Left",
Self::Back => "Back",
Self::Forward => "Forward",
Self::Right => "Right",
}
}
}
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default, GameState)]
enum DemoState {
#[default]
Playing,
}
#[derive(Resource, Default, Clone, Debug)]
struct MovementInputs(PlayerActions<MovementAction>);
impl InputsContainer<MovementAction> for MovementInputs {
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<MovementAction>> + 'a> {
Box::new(std::iter::once(&self.0))
}
fn me<'a>(&'a self) -> Option<&'a PlayerActions<MovementAction>> {
Some(&self.0)
}
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<MovementAction>> {
Some(&mut self.0)
}
}
#[derive(Resource)]
struct RebindingUi {
selected: MovementAction,
capturing: bool,
}
impl Default for RebindingUi {
fn default() -> Self {
Self {
selected: MovementAction::Left,
capturing: false,
}
}
}
#[derive(Component)]
struct RebindingText;
const TEXT_SIZE: f32 = 30.;
fn physical_binding(key_code: KeyCode) -> Binding<DemoState> {
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(key_code)))
}
fn controls() -> Controls<MovementAction, DemoState> {
Controls::new()
.with(
MovementAction::Left,
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyA)]).customizable()),
)
.with(
MovementAction::Back,
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyS)]).customizable()),
)
.with(
MovementAction::Forward,
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyW)]).customizable()),
)
.with(
MovementAction::Right,
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyD)]).customizable()),
)
.build()
}
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Bevy Controls Rebinding".into(),
..default()
}),
..default()
}),
ControlsPlugin::<MovementAction, MovementInputs, DemoState>::new(controls()),
))
.init_resource::<RebindingUi>()
.add_systems(Startup, setup)
.add_systems(Update, (handle_rebinding, update_text))
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.spawn((
Text::new(""),
TextFont {
font_size: FontSize::Px(TEXT_SIZE),
..default()
},
TextColor(Color::WHITE),
BackgroundColor(Color::BLACK),
RebindingText,
));
}
fn handle_rebinding(
mut controls: ResMut<Controls<MovementAction, DemoState>>,
mut ui: ResMut<RebindingUi>,
keys: Res<ButtonInput<KeyCode>>,
mut keyboard_events: MessageReader<KeyboardInput>,
) {
if ui.capturing {
for event in keyboard_events.read() {
if event.state != ButtonState::Pressed {
continue;
}
if event.key_code == KeyCode::Escape {
ui.capturing = false;
return;
}
let binding = Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(event.key_code)));
controls.push_binding(ui.selected, binding);
ui.capturing = false;
return;
}
return;
}
for _ in keyboard_events.read() {}
if keys.just_pressed(KeyCode::Digit1) {
ui.selected = MovementAction::Left;
}
if keys.just_pressed(KeyCode::Digit2) {
ui.selected = MovementAction::Back;
}
if keys.just_pressed(KeyCode::Digit3) {
ui.selected = MovementAction::Forward;
}
if keys.just_pressed(KeyCode::Digit4) {
ui.selected = MovementAction::Right;
}
if keys.just_pressed(KeyCode::KeyC) {
controls.clear_bindings(ui.selected);
}
if keys.just_pressed(KeyCode::KeyR) {
ui.capturing = true;
}
}
fn update_text(
mut text: Single<&mut Text, With<RebindingText>>,
ui: Res<RebindingUi>,
controls: Res<Controls<MovementAction, DemoState>>,
) {
let bindings = controls.get(ui.selected).map_or_else(
|| String::from(" (none)"),
|config| {
config
.bindings
.iter()
.map(|binding| format!(" {binding:?}"))
.collect::<Vec<_>>()
.join("\n")
},
);
let capture_status = if ui.capturing {
"Waiting for a key. Escape cancels."
} else {
"Ready. Press R to add a binding."
};
text.0 = format!(
"Keyboard rebinding demo\n\nSelected action: {}\n{}\n\nBindings:\n{}\n\n1-4 select action | R capture | C clear | Escape cancel",
ui.selected.name(),
capture_status,
bindings,
);
}
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "bevy_controls_macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
#bevy_controls = { path = "./../bevy_controls" }
syn = { version = "1.0", features = [ "full", "extra-traits" ] }
quote = "1.0"
[dev-dependencies]
env_logger = "0.10.1"
-114
View File
@@ -1,114 +0,0 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use std::collections::HashMap;
use quote::quote;
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
punctuated::Punctuated,
ItemTrait, LitStr, Token, TypeParamBound,
};
// #[cfg_feature_trait_bounds {
// "serialize" => Serialize + DeserializeOwned,
// "reflect" => Reflect + TypePath + FromReflect,
// "inspector-egui" => InspectorOptionsType,
// }]
type RuleKey = LitStr;
#[derive(Debug)]
struct FeatureTraitBoundMap(HashMap<RuleKey, Punctuated<TypeParamBound, Token![+]>>);
impl Parse for FeatureTraitBoundMap {
fn parse(input: ParseStream) -> Result<Self> {
let mut rules = HashMap::new();
loop {
let condition: RuleKey = input.parse()?;
let _arrow = input.parse::<Token![=>]>()?;
let supertraits = {
let mut supertraits = Punctuated::new();
loop {
supertraits.push_value(input.parse()?);
if !input.peek(Token![+]) {
break;
}
supertraits.push_punct(input.parse()?);
if input.peek(Token![,]) {
break;
}
}
supertraits
};
let _comma = input.parse::<Token![,]>();
rules.insert(condition, supertraits);
if input.is_empty() {
break;
}
}
Ok(FeatureTraitBoundMap(rules))
}
}
#[proc_macro_attribute]
pub fn cfg_feature_trait_bounds(attr: TokenStream, input: TokenStream) -> TokenStream {
let _rules = parse_macro_input!(attr as FeatureTraitBoundMap);
let _input = parse_macro_input!(input as ItemTrait);
dbg!(std::env::vars().map(|(key, _)| key).collect::<Vec<_>>());
// let code = quote! {
// trait #name: #rules {}
// };
//
// dbg!(code);
TokenStream::new()
}
#[proc_macro_attribute]
pub fn supertraits(_attrs: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemTrait);
let mut bounds = Vec::new();
if cfg!(feature = "serialize") {
bounds.push(quote! { serde::Serialize });
bounds.push(quote! { serde::de::DeserializeOwned });
};
if cfg!(feature = "reflect") {
bounds.push(quote! { bevy_reflect::Reflect });
bounds.push(quote! { bevy_reflect::TypePath });
bounds.push(quote! { bevy_reflect::FromReflect });
};
if cfg!(feature = "inspector-egui") {
bounds.push(quote! { bevy_inspector_egui::inspector_options::InspectorOptionsType });
};
let ItemTrait {
ident,
vis,
items,
generics,
supertraits: bounds_old,
..
} = input;
let bounds_old = bounds_old.iter().collect::<Vec<_>>();
let out = quote! {
#vis trait #ident #generics: #(#bounds_old)+* + #(#bounds)+* { #(#items)* }
};
// dbg!(&out);
// dbg!(out.to_string());
TokenStream::from(out)
}
+1 -1
View File
@@ -7,7 +7,7 @@ log "enable logging"
while [ "$#" -gt 0 ]; do
case $1 in
-h|--help|help)
-h|-?|--help|help)
HELP=1
;;
-*)
+228 -62
View File
@@ -1,82 +1,248 @@
use std::hash::Hash;
use bevy_ecs::{component::Mutable, prelude::Resource};
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 = "reflect")]
use bevy_reflect::{FromReflect, Reflect, TypePath};
use bevy_state::state::{FreelyMutableState, States};
#[cfg(feature = "serialize")]
use serde::{de::DeserializeOwned, Serialize};
use strum::IntoEnumIterator;
use crate::resource::PlayerActions;
use crate::resource::PlayerInputs;
macro_rules! define_contracts {
($($feature_bounds:tt)*) => {
pub trait Action:
'static
+ std::marker::Sync
+ std::marker::Send
+ PartialEq
+ Eq
+ Hash
+ IntoEnumIterator
+ Clone
+ Copy
+ std::fmt::Debug
$($feature_bounds)*
{
}
pub trait GameState:
'static
+ std::marker::Sync
+ std::marker::Send
+ States
+ Default
+ std::fmt::Debug
+ FreelyMutableState
$($feature_bounds)*
{
}
pub trait InputsContainer<A: Action>:
'static
+ std::marker::Sync
+ std::marker::Send
+ Resource<Mutability = Mutable>
+ Default
+ std::fmt::Debug
$($feature_bounds)*
{
/// The method returning an iterator over references to the PlayerInputs.
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<A>> + 'a>;
/// The method returning an player inputs of this client.
fn me<'a>(&'a self) -> Option<&'a PlayerActions<A>>;
/// The method returning an mutable player inputs of this client.
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<A>>;
}
};
pub trait ActionInner:
'static
+ std::marker::Sync
+ std::marker::Send
+ PartialEq
+ Eq
+ Hash
+ IntoEnumIterator
+ Clone
+ Copy
+ std::fmt::Debug
{
}
#[cfg(all(not(feature = "serialize"), not(feature = "reflect")))]
define_contracts!();
#[cfg(all(feature = "serialize", not(feature = "reflect")))]
define_contracts!(+ Serialize + DeserializeOwned);
pub trait GameStateInner:
'static + std::marker::Sync + std::marker::Send + States + Default + std::fmt::Debug
{
}
pub trait InputsContainerInner<A: Action>:
'static + std::marker::Sync + std::marker::Send + Resource + Default + std::fmt::Debug
{
/// 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<dyn Iterator<Item = &'a PlayerInputs<A>> + 'a>;
/// The method returning an player inputs of this client.
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<A>>;
/// The method returning an mutable player inputs of this client.
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs<A>>;
}
#[cfg(feature = "serialize")]
pub trait SerializeImpl: Serialize + DeserializeOwned {}
#[cfg(feature = "reflect")]
pub trait ReflectImpl: Reflect + TypePath + FromReflect {}
#[cfg(feature = "inspector-egui")]
pub trait InspectorEguiImpl: InspectorOptionsType {}
// ===== Action =====
// SAFETY: inspector-egui includes reflect
#[cfg(all(
feature = "reflect",
not(feature = "serialize"),
not(feature = "reflect"),
not(feature = "inspector-egui")
))]
define_contracts!(+ Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed);
pub trait Action: ActionInner {}
#[cfg(all(
feature = "reflect",
feature = "serialize",
not(feature = "reflect"),
not(feature = "inspector-egui")
))]
define_contracts!(+ Serialize + DeserializeOwned + Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed);
#[cfg(all(feature = "inspector-egui", not(feature = "serialize")))]
define_contracts!(+ Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed + InspectorOptionsType);
#[cfg(all(feature = "inspector-egui", feature = "serialize"))]
define_contracts!(+ Serialize + DeserializeOwned + Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed + InspectorOptionsType);
pub trait Action: ActionInner + SerializeImpl {}
#[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(not(feature = "serialize"), feature = "inspector-egui"))]
pub trait Action: ActionInner + ReflectImpl + InspectorEguiImpl {}
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
pub trait Action: ActionInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {}
// ===== GameState =====
// SAFETY: inspector-egui includes reflect
#[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")
))]
pub trait GameState: GameStateInner + SerializeImpl {}
#[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(not(feature = "serialize"), feature = "inspector-egui"))]
pub trait GameState: GameStateInner + ReflectImpl + InspectorEguiImpl {}
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {}
// ===== InputsContainer =====
// SAFETY: inspector-egui includes reflect
#[cfg(all(
not(feature = "serialize"),
not(feature = "reflect"),
not(feature = "inspector-egui")
))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> {}
#[cfg(all(
feature = "serialize",
not(feature = "reflect"),
not(feature = "inspector-egui")
))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + SerializeImpl {}
#[cfg(all(
not(feature = "serialize"),
feature = "reflect",
not(feature = "inspector-egui")
))]
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + ReflectImpl {}
#[cfg(all(
feature = "serialize",
feature = "reflect",
not(feature = "inspector-egui")
))]
pub trait InputsContainer<A: Action>:
InputsContainerInner<A> + SerializeImpl + ReflectImpl
{
}
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
pub trait InputsContainer<A: Action>:
InputsContainerInner<A> + ReflectImpl + InspectorEguiImpl
{
}
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
pub trait InputsContainer<A: Action>:
InputsContainerInner<A> + SerializeImpl + ReflectImpl + InspectorEguiImpl
{
}
// #[cfg(test)]
// mod test {
// use bevy_ecs::{entity::Entity, schedule::States};
// use bevy_utils::{HashMap, prelude::default};
// use strum_macros::EnumIter;
// #[cfg(feature = "serialize")]
// use serde::{Serialize, Deserialize};
// use crate::{hashmap, resource::PlayerInputs, common_traits_conditions};
// common_traits_conditions! {
// #[derive(Debug, Clone, PartialEq)]
// #[cfg_attr(feature = "reflect", reflect(Resource))]
// pub struct Lobby {
// pub me: PlayerId,
// pub players: HashMap<PlayerId, Player>,
// }
// }
// impl Default for Lobby {
// fn default() -> Self {
// Self {
// me: PlayerId::Host,
// players: hashmap! {
// PlayerId::Host => Player::default()
// },
// }
// }
// }
// common_traits_conditions! {
// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
// pub enum PlayerId {
// /// Host or alone
// Host,
// /// Client
// Client(()),
// }
// #[derive(Default, Debug, PartialEq, Clone)]
// pub struct Player {
// /// Client do not need to know about other clients
// #[cfg_attr(feature = "serialize", serde(skip))]
// pub inputs: PlayerInputs<Action>,
// #[cfg_attr(feature = "serialize", serde(skip))]
// pub entity: Option<Entity>,
// }
// }
// #[derive(Debug, PartialEq, Clone, States, Default, Hash, Eq)]
// pub enum GameState {
// #[default]
// LevelEditor,
// InGame,
// }
// impl super::GameState for GameState {}
// // TODO: it is just plug, it must be a trait
// /// Actions that can be performed by player
// #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, EnumIter)]
// pub enum Action {
// /// Move forward
// LeverEditorForward,
// /// Move backward
// LevelEditorBackward,
// /// Move left
// LevelEditorLeft,
// /// Move right
// LevelEditorRight,
// LevelEditorFly,
// }
// impl super::Action for Action {}
// }
+2
View File
@@ -2,3 +2,5 @@ pub mod contract;
pub mod plugin;
pub mod resource;
mod util;
pub use util::*;
+214 -200
View File
@@ -1,108 +1,38 @@
use bevy_app::{App, Plugin, PreUpdate};
use bevy_derive::{Deref, DerefMut};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{
message::MessageReader,
prelude::Resource,
system::{In, IntoSystem, Res, ResMut},
schedule::State,
system::{Res, ResMut},
};
use bevy_input::{
keyboard::KeyCode,
mouse::{MouseButton, MouseMotion, MouseScrollUnit, MouseWheel},
ButtonInput,
};
#[cfg(feature = "logical-keyboard")]
use bevy_input::{
keyboard::{Key, KeyboardInput},
ButtonState,
};
use bevy_state::app::AppExtStates;
use bevy_state::state::State;
use std::collections::HashMap;
#[cfg(feature = "logical-keyboard")]
use std::collections::HashSet;
use bevy_input::{keyboard::{KeyCode, ScanCode}, mouse::MouseButton, Input};
use crate::{
contract::{Action, GameState, InputsContainer},
resource::*,
};
pub struct ControlsPlugin<A: Action, Ic: InputsContainer<A>, Gs: GameState> {
pub struct ControlsPlugin<A, Ic, Gs> {
_action: std::marker::PhantomData<A>,
_inputs_container: std::marker::PhantomData<Ic>,
_game_state: std::marker::PhantomData<Gs>,
controls: Controls<A, Gs>,
}
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Default for ControlsPlugin<A, Ic, Gs> {
impl<A, Ic, Gs> Default for ControlsPlugin<A, Ic, Gs> {
fn default() -> Self {
Self {
_action: std::marker::PhantomData,
_inputs_container: std::marker::PhantomData,
_game_state: std::marker::PhantomData,
controls: Controls::<A, Gs>::default(),
}
}
}
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
pub fn new(controls: Controls<A, Gs>) -> Self {
Self {
_action: std::marker::PhantomData,
_inputs_container: std::marker::PhantomData,
_game_state: std::marker::PhantomData,
controls,
}
}
}
#[derive(Resource, Default, Debug, Deref, DerefMut)]
struct TrigeredInputs(HashMap<InputType, InputValue>);
#[cfg(feature = "logical-keyboard")]
#[derive(Resource, Default, Debug, Deref, DerefMut)]
struct PressedLogicalKeys(HashSet<Key>);
#[cfg(not(feature = "reflect"))]
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin<A, Ic, Gs> {
fn build(&self, app: &mut App) {
app
.insert_resource(self.controls.clone())
.init_resource::<Controls<A, Gs>>()
.init_resource::<Ic>()
.init_resource::<TrigeredInputs>()
.insert_state::<Gs>(Gs::default());
#[cfg(feature = "logical-keyboard")]
app.init_resource::<PressedLogicalKeys>();
app.add_systems(
PreUpdate,
Self::collect_inputs.pipe(Self::fill_players_inputs),
);
}
}
#[cfg(feature = "reflect")]
impl<A, Ic, Gs> Plugin for ControlsPlugin<A, Ic, Gs>
where
A: Action + bevy_reflect::FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed,
Ic: InputsContainer<A>,
Gs:
GameState + bevy_reflect::FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed,
{
fn build(&self, app: &mut App) {
app
.insert_resource(self.controls.clone())
.init_resource::<Ic>()
.init_resource::<TrigeredInputs>()
.insert_state::<Gs>(Gs::default());
#[cfg(feature = "logical-keyboard")]
app.init_resource::<PressedLogicalKeys>();
app.add_systems(
PreUpdate,
Self::collect_inputs.pipe(Self::fill_players_inputs),
);
.add_state::<Gs>()
.add_systems(Update, Self::save_input);
#[cfg(feature = "reflect")]
app.register_type::<Controls<A, Gs>>();
@@ -110,139 +40,223 @@ where
}
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
/// Collects input events from various input devices
/// (keyboard, mouse buttons, mouse motion, and scroll)
/// and stores them in a `TrigeredInputs` structure.
///
/// This function is separated from filling inputs to allow for more complex input handling
/// in the future, particularly in scenarios where the order of input events becomes significant.
///
fn collect_inputs(
keyboard_button: Res<ButtonInput<KeyCode>>,
#[cfg(feature = "logical-keyboard")] mut keyboard_evr: MessageReader<KeyboardInput>,
#[cfg(feature = "logical-keyboard")] mut pressed_logical_keys: ResMut<PressedLogicalKeys>,
mouse_buttons: Res<ButtonInput<MouseButton>>,
mut scroll_evr: MessageReader<MouseWheel>,
mut motion_evr: MessageReader<MouseMotion>,
// mut collected_inputs: ResMut<TrigeredInputs>,
) -> TrigeredInputs {
let mut collected_inputs = TrigeredInputs(HashMap::new());
#[cfg(feature = "logical-keyboard")]
for event in keyboard_evr.read() {
match event.state {
ButtonState::Pressed => {
pressed_logical_keys.insert(event.logical_key.clone());
}
ButtonState::Released => {
pressed_logical_keys.remove(&event.logical_key);
}
}
}
// Collect pressed keyboard buttons
for key in keyboard_button.get_pressed() {
collected_inputs.insert(
InputType::Keyboard(Keyboard::KeyCode(*key)),
InputValue::Boolean(true),
);
}
#[cfg(feature = "logical-keyboard")]
for key in pressed_logical_keys.iter() {
collected_inputs.insert(
InputType::Keyboard(Keyboard::Key(key.clone())),
InputValue::Boolean(true),
);
}
// buttons.get_just_released()
// Collect pressed mouse buttons
for button in mouse_buttons.get_pressed() {
collected_inputs.insert(
InputType::Mouse(MouseInput::Button(*button)),
InputValue::Boolean(true),
);
}
// Collect mouse motion events
for ev in motion_evr.read() {
collected_inputs.insert(
InputType::Mouse(MouseInput::Axis(AxisName::Horizontal)),
InputValue::Float(ev.delta.x),
);
collected_inputs.insert(
InputType::Mouse(MouseInput::Axis(AxisName::Vertical)),
InputValue::Float(ev.delta.y),
);
}
// Collect mouse scroll events
for ev in scroll_evr.read() {
match ev.unit {
// Handle scroll in line units
MouseScrollUnit::Line => {
collected_inputs.insert(
InputType::Mouse(MouseInput::Wheel(AxisName::Horizontal)),
InputValue::Float(ev.x),
);
collected_inputs.insert(
InputType::Mouse(MouseInput::Wheel(AxisName::Vertical)),
InputValue::Float(ev.y),
);
}
// Handle scroll in pixel units (not accepted in this game)
MouseScrollUnit::Pixel => {
panic!("I'm do not accept you to use touch pad in my game");
}
}
}
collected_inputs
}
/// Fills the player's inputs based on the collected inputs and the current control bindings.
///
/// This function takes in the current inputs, updates them according to the specified control
/// bindings, and then stores the updated inputs in the input container. It only updates the
/// inputs if they satisfy the binding conditions, such as being in a specific game state.
///
fn fill_players_inputs(
In(collected_inputs): In<TrigeredInputs>,
/// Process all hard inputs and bindings to update [`PlayerInputs`]
fn save_input(
keyboard_input_keycode: Res<Input<KeyCode>>,
keyboard_input_scancode: Res<Input<ScanCode>>,
mouse_input: Res<Input<MouseButton>>,
mut inputs_container: ResMut<Ic>,
controls: Res<Controls<A, Gs>>,
game_state: Res<State<Gs>>,
) {
// Check if the input container is accessible
if let Some(inputs) = inputs_container.me_mut() {
for (action, config) in controls.iter() {
let mut action_value = InputValue::Empty;
// Iterate over the control bindings and collect all action-binding pairs
for binding in config.bindings.iter() {
// Skip any binding with mismatched condition
if binding
.conditions
.iter()
.any(|condition| matches!(condition, BindingCondition::InGameState(state) if *state != *game_state.get()))
{
continue;
}
match &binding.input {
ButtonCombination::Single(input_type) => {
if let Some(&value) = collected_inputs.get(&*input_type) {
action_value = action_value.merge(value);
'bindings_loop: for binding in config.bindings.iter() {
for condition in &binding.conditions {
match condition {
BindingCondition::InGameState(state) => {
if *state != *game_state.get() {
continue 'bindings_loop;
}
}
}
}
}
inputs.forced_set(*action, action_value);
log::trace!("action binding {:?} {:?} in condition", action, binding);
match &binding.input {
ButtonCombination::Single(button) => match button {
InputType::Keyboard(Keyboard::KeyCode(key)) => {
log::trace!("presed?: {:?}", keyboard_input_keycode.pressed(*key));
log::trace!("active");
if keyboard_input_keycode.pressed(*key) {
inputs.forced_set(*action, true);
break; // when on binding trigered no sence check another
}
inputs.forced_set(*action, false); // FIXME: this is happening on every pass so too frequently
}
InputType::Keyboard(Keyboard::ScanCode(code)) => {
log::trace!("presed?: {:?}", keyboard_input_scancode.pressed(*code));
log::trace!("active");
if keyboard_input_scancode.pressed(*code) {
inputs.forced_set(*action, true);
break; // when on binding trigered no sence check another
}
inputs.forced_set(*action, false); // FIXME: this is happening on every pass so too frequently
}
InputType::Mouse(input) => match input {
MouseInput::Axis(_axis) => {
todo!();
}
MouseInput::Button(button) => {
if !mouse_input.get_pressed().any(|b| b == button) {
continue 'bindings_loop;
}
}
MouseInput::Wheel(_axis) => {
todo!();
}
},
},
ButtonCombination::Chord(buttons) => {
// If any button in chord is not pressed we skip this `binding`
for button in buttons {
match button {
InputType::Keyboard(Keyboard::KeyCode(key)) => {
if !keyboard_input_keycode.pressed(*key) {
continue 'bindings_loop;
}
}
InputType::Keyboard(Keyboard::ScanCode(code)) => {
if !keyboard_input_scancode.pressed(*code) {
continue 'bindings_loop;
}
}
InputType::Mouse(input) => match input {
MouseInput::Axis(_axis) => {
todo!();
}
MouseInput::Button(button) => {
if !mouse_input.get_pressed().any(|b| b == button) {
continue 'bindings_loop;
}
}
MouseInput::Wheel(_axis) => {
todo!();
}
},
}
}
log::trace!("active");
// TODO: should be [`Chord`](ButtonCombination::Chord) only [`Boolean`](InputValue::Boolean) type
inputs.forced_set(*action, true);
break; // when on binding trigered no sence check another
}
}
}
}
} else {
log::warn!("cannot find me in inputs container");
log::error!("cannot find me in inputs container")
}
}
}
// #[cfg(test)]
// mod performance_test {
// use crate::{ util::test::{enable_loggings, measure_time, Times}, resource::PlayerInputs};
// use super::Controls;
// use std::time::Duration;
// /// Test for execution time for [`Controls`] get
// ///
// /// Example:
// /// ```
// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::controls_get --exact --nocapture
// /// ```
// #[test]
// fn controls_get() {
// enable_loggings();
// let controls = Controls::default();
// let duration = measure_time(
// || {
// controls.get(Action::LeverEditorForward);
// controls.get(Action::LevelEditorBackward);
// controls.get(Action::LevelEditorLeft);
// controls.get(Action::LevelEditorRight);
// },
// Times::default(),
// );
// log::info!("time: {:?}", duration);
// }
// /// Test for execution speed for [`PlayerInputs`] get
// fn player_inputs_get() -> Duration {
// enable_loggings();
// let inputs = PlayerInputs::default();
// let duration = measure_time(
// || {
// inputs.get(Action::LeverEditorForward);
// inputs.get(Action::LevelEditorBackward);
// inputs.get(Action::LevelEditorLeft);
// inputs.get(Action::LevelEditorRight);
// },
// Times::default(),
// );
// duration
// }
// /// Test for execution speed for [`PlayerInputs`] get_many
// fn player_inputs_get_many() -> Duration {
// enable_loggings();
// let inputs = PlayerInputs::default();
// let duration = measure_time(
// || {
// inputs.get_many(vec![
// Action::LeverEditorForward,
// Action::LevelEditorBackward,
// Action::LevelEditorLeft,
// Action::LevelEditorRight,
// ]);
// },
// Times::default(),
// );
// duration
// }
// /// Test for execution speed for [`PlayerInputs`] get and get_many
// ///
// /// Example:
// /// ```
// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::compare_player_inputs_get_and_get_many --exact --nocapture
// /// ```
// #[test]
// fn compare_player_inputs_get_and_get_many() {
// let get = player_inputs_get();
// let get_many = player_inputs_get_many();
// log::info!("get: {:?}", get);
// log::info!("get_many: {:?}", get_many);
// }
// /// Test for execution speed for [`InputValue`] casting
// #[test]
// fn action_casting() {
// enable_loggings();
// let inputs = PlayerInputs::default();
// let duration = measure_time(
// || {
// let _ = (inputs.get(Action::LeverEditorForward).as_boolean() as i8
// - inputs.get(Action::LevelEditorBackward).as_boolean() as i8)
// as f32;
// },
// 10000000.into(),
// );
// log::info!("with casting: {:?}", duration);
// let inputs = PlayerInputs::default();
// let duration = measure_time(
// || {
// let _ = inputs.get(Action::LeverEditorForward);
// let _ = inputs.get(Action::LevelEditorBackward);
// },
// 10000000.into(),
// );
// log::info!("without casting: {:?}", duration);
// }
// }
+56 -240
View File
@@ -1,15 +1,13 @@
use std::error;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::Resource;
#[cfg(feature = "logical-keyboard")]
use bevy_input::keyboard::Key;
use bevy_input::{keyboard::KeyCode, mouse::MouseButton};
use bevy_ecs::system::Resource;
use bevy_input::{keyboard::{KeyCode, ScanCode}, mouse::MouseButton};
#[cfg(feature = "inspector-egui")]
use bevy_inspector_egui::prelude::*;
use bevy_platform::collections::HashMap;
#[cfg(all(feature = "reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_utils::HashMap;
use log::warn;
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
@@ -46,27 +44,27 @@ where
common_traits_conditions! {
/// Struct that contains user's inputs corresponding to actions
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
pub struct Actions<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
pub struct Inputs<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
/// Resource that contains current user inputs
#[derive(Debug, PartialEq, Clone)]
pub struct PlayerActions<A: Action> {
pub struct PlayerInputs<A: Action> {
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
current: Actions<A>,
current: Inputs<A>,
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
previous: Actions<A>,
previous: Inputs<A>,
}
}
impl<A: Action> Default for PlayerActions<A> {
impl<A: Action> Default for PlayerInputs<A> {
fn default() -> Self {
PlayerActions {
current: Actions(
PlayerInputs {
current: Inputs(
A::iter()
.map(|action| (action, InputValue::Empty))
.collect(),
),
previous: Actions(
previous: Inputs(
A::iter()
.map(|action| (action, InputValue::Empty))
.collect(),
@@ -75,7 +73,7 @@ impl<A: Action> Default for PlayerActions<A> {
}
}
impl<A: Action> PlayerActions<A> {
impl<A: Action> PlayerInputs<A> {
/// Returns current input value for action
///
/// in any case faster that `get_many` method
@@ -121,11 +119,15 @@ impl<A: Action> PlayerActions<A> {
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
/// If input has not same type as `InputValue` on previous frame return `Error()`
pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> {
return Ok(
self.current.get(&action).unwrap().to_boolean()
&& !self.previous.get(&action).unwrap().to_boolean(),
);
//Err("This input is not boolean".into())
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
if let InputValue::Boolean(previous) = *self.previous.get(&action).unwrap() {
return Ok(current && !previous);
}
return Err("Previous input is not boolean".into());
}
Err("This input is not boolean".into())
}
/// Set input value to new
@@ -166,7 +168,7 @@ impl<A: Action> PlayerActions<A> {
/// Update current inputs
///
/// notice: not recomended to use if you do not know what do you do
pub fn forced_update(&mut self, inputs: Actions<A>) {
pub fn forced_update(&mut self, inputs: Inputs<A>) {
*self.previous = self.current.clone().0;
*self.current = inputs.0;
}
@@ -176,7 +178,7 @@ impl<A: Action> PlayerActions<A> {
/// ! 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: Actions<A>) -> Result<(), Box<dyn error::Error>> {
pub fn update(&mut self, inputs: Inputs<A>) -> Result<(), Box<dyn error::Error>> {
// SAFETY: action is always valid
// because we iterate over all actions in `default` method
for (action, value) in inputs.0.iter() {
@@ -194,56 +196,48 @@ impl<A: Action> PlayerActions<A> {
}
}
// TODO: add option on exlude or not exclude if used
common_traits_conditions! {
/// Keyboard input identified by physical location or logical meaning.
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum Keyboard {
KeyCode(KeyCode),
#[cfg(feature = "logical-keyboard")]
Key(Key),
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
#[derive(Debug, PartialEq, Clone)]
pub enum InputType {
Keyboard(Keyboard),
Mouse(MouseInput),
// TODO: Gamepad(GamepadButtonType),
// TODO: Touch screen
// TODO: TouchPad inputs
}
}
// TODO: Gamepad(GamepadButtonType),
// TODO: Touch screen
}
common_traits_conditions! {
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
#[derive(Debug, PartialEq, Clone)]
pub enum Keyboard {
KeyCode(KeyCode),
ScanCode(ScanCode),
}
#[derive(Debug, PartialEq, Clone)]
pub enum MouseInput {
Button(MouseButton),
Axis(AxisName),
Wheel(AxisName)
// TODO: CursorPosition
// TODO: TouchPad inputs
}
/// Name of axis
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
#[derive(Debug, PartialEq, Clone)]
pub enum AxisName {
Horizontal,
Vertical,
}
/// Represents a binding that can be changed by the player
/// default: [`Immutable`](OptionsMode::Immutable)
#[derive(Debug, PartialEq, Clone, Default)]
#[derive(Debug, PartialEq, Clone)]
pub enum OptionsMode {
/// Represents a binding that cannot be changed as it is crucial to the game logic
#[default] // I think this is more safe
Immutable,
/// Represents a binding that the player can customize
Customizable,
}
/// Represents the manner in which input is registered
/// default: [`Tap`](ActivationMode::Tap)
#[derive(Debug, PartialEq, Clone, Default)]
#[derive(Debug, PartialEq, Clone)]
pub enum ActivationMode {
/// for<'a> Action<'a> is triggered when the button is pressed
Hold,
@@ -251,7 +245,6 @@ common_traits_conditions! {
/// and cannot be triggered again until the button is released
/// you don't need to release all buttons in chord
/// you can release only last pressed button
#[default] // I think this is more safe
Tap,
}
@@ -269,8 +262,8 @@ common_traits_conditions! {
impl Default for ActivationOptions {
fn default() -> Self {
Self {
mode: ActivationMode::default(),
option: OptionsMode::default(),
mode: ActivationMode::Tap, // Because it is more safe
option: OptionsMode::Immutable,
delay: 0.05, // 20 times per second; 14 - world record?
}
}
@@ -297,21 +290,10 @@ common_traits_conditions! {
pub enum ButtonCombination {
/// Single button press
Single(InputType),
// /// Chord is a combination of buttons that must be pressed at the same time
// Chord(Vec<InputType>),
/// Chord is a combination of buttons that must be pressed at the same time
Chord(Vec<InputType>),
}
}
impl ButtonCombination {
pub fn len(&self) -> usize {
use crate::resource::ButtonCombination::*;
match self {
Single(_) => 1, // TODO: Chord
}
}
}
common_traits_conditions! {
#[derive(Debug, PartialEq, Clone)]
pub enum BindingCondition<Gs: GameState> {
/// During specific game state
@@ -346,20 +328,6 @@ impl<Gs: GameState> Binding<Gs> {
}
}
pub fn from_single(input: InputType) -> Self {
Self {
input: ButtonCombination::Single(input),
conditions: Vec::new(),
}
}
// pub fn from_chord(input: Vec<InputType>) -> Self {
// Self {
// input: ButtonCombination::Chord(input),
// conditions: Vec::new(),
// }
// }
pub fn with_condition(mut self, condition: BindingCondition<Gs>) -> Self {
self.conditions.push(condition);
self
@@ -427,50 +395,6 @@ impl InputValue {
InputValue::Empty => false,
}
}
pub fn to_float(&self) -> f32 {
match self {
InputValue::Boolean(value) => {
if *value {
1.
} else {
0.
}
}
InputValue::Float(value) => *value,
InputValue::Empty => 0.,
}
}
/// Merges another input value into this one.
///
/// `Empty` acts as an identity value. Boolean inputs are combined using
/// logical OR. For other non-empty values, the existing value is preserved.
///
/// This is used when multiple control bindings contribute to the same action.
pub(crate) fn merge(self, other: Self) -> Self {
match (self, other) {
// Empty doesn't affect an existing value
(Self::Empty, rhs) => rhs,
(lhs, Self::Empty) => lhs,
// Multiple boolean bindings combine with OR
(Self::Boolean(lhs), Self::Boolean(rhs)) => Self::Boolean(lhs || rhs),
// For floats choose max
// TODO: maybe user want to chose multiply same inputs of chose max
(Self::Float(lhs), Self::Float(rhs)) => {
if rhs.abs() > lhs.abs() {
Self::Float(rhs)
} else {
Self::Float(lhs)
}
}
(lhs, _) if !lhs.is_empty() => lhs,
(_, rhs) => rhs,
}
}
}
common_traits_conditions! {
@@ -488,44 +412,20 @@ impl<Gs: GameState> Default for Bindings<Gs> {
fn default() -> Self {
Self {
list: Vec::new(),
options: OptionsMode::default(),
options: OptionsMode::Immutable,
}
}
}
impl<Gs: GameState> Bindings<Gs> {
pub fn new(bindings: Vec<Binding<Gs>>) -> Self {
let mut result = Self {
list: Vec::with_capacity(bindings.len()),
..Default::default()
};
// To avoid duplicates
for binding in bindings {
result.force_push(binding);
pub fn new(bindings: Vec<Binding<Gs>>, options: OptionsMode) -> Self {
Self {
list: bindings,
options,
}
result
}
pub fn customizable(mut self) -> Self {
self.options = OptionsMode::Customizable;
self
}
pub fn immutable(mut self) -> Self {
self.options = OptionsMode::Immutable;
self
}
pub fn with_option_mode(mut self, options: OptionsMode) -> Self {
self.options = options;
self
}
pub fn force_push(&mut self, binding: Binding<Gs>) {
if self.list.contains(&binding) {
return;
}
self.list.push(binding);
}
@@ -534,7 +434,7 @@ impl<Gs: GameState> Bindings<Gs> {
warn!("You try to push binding to immutable bindings");
return;
}
self.force_push(binding);
self.list.push(binding);
}
pub fn clear(&mut self) {
@@ -592,60 +492,22 @@ common_traits_conditions! {
}
impl<Gs: GameState> BindingConfig<Gs> {
pub fn new(bindings: Bindings<Gs>) -> Self {
pub fn new(bindings: Bindings<Gs>, activation: ActivationOptions) -> Self {
Self {
bindings,
..Default::default()
activation,
}
}
pub fn from_vec(bindings: Vec<Binding<Gs>>) -> Self {
Self {
bindings: Bindings::new(bindings),
..Default::default()
}
}
pub fn from_bind(binding: Binding<Gs>) -> Self {
Self {
bindings: Bindings::new(vec![binding]),
..Default::default()
}
}
pub fn with_activation_options(mut self, activation: ActivationOptions) -> Self {
self.activation = activation;
self
}
}
#[derive(Deref, DerefMut)]
pub struct ControlsBuilder<A: Action, Gs: GameState>(Controls<A, Gs>);
impl<A: Action, Gs: GameState> ControlsBuilder<A, Gs> {
///
pub fn with(mut self, action: A, config: BindingConfig<Gs>) -> Self {
self.insert(action, config);
self
}
///
pub fn build(mut self) -> Controls<A, Gs> {
// 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(&**self));
std::mem::take(&mut self) // TODO
}
}
common_traits_conditions! {
/// Contains all bindings for actions
#[derive(Resource, Clone, Deref, DerefMut)]
#[derive(Resource, Clone)]
#[cfg_attr(feature = "reflect", reflect(Resource))]
// TODO: Add delay for input
pub struct Controls<A: Action, Gs: GameState>(
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
// FIXME: change HashMap to Vec for faster iteration
// TODO: change HashMap to Vec for faster iteration
HashMap<A, BindingConfig<Gs>>
);
}
@@ -668,11 +530,6 @@ impl<A: Action, Gs: GameState> Default for Controls<A, Gs> {
}
impl<A: Action, Gs: GameState> Controls<A, Gs> {
/// New [`Controls`] instance
pub fn new() -> ControlsBuilder<A, Gs> {
ControlsBuilder(Controls(HashMap::new()))
}
/// Returns bindings for action
pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> {
self.0.get(&action)
@@ -691,7 +548,7 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
}
/// Push new binding for action
pub fn push_binding(&mut self, action: A, binding: Binding<Gs>) {
pub fn push(&mut self, action: A, binding: Binding<Gs>) {
// TODO: warning if config is not exist yet
// TODO: interface for [`ActivationOptions`]
self
@@ -703,7 +560,7 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
}
/// Remove all bindings for action
pub fn clear_bindings(&mut self, action: A) {
pub fn remove(&mut self, action: A) {
// TODO: warning if config is not exist yet
self
.0
@@ -713,10 +570,9 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
.clear();
}
/// Remove binding for action
pub fn remove_binding(&mut self, action: A, binding: Binding<Gs>) {
if let Some(config) = self.0.get_mut(&action) {
config.bindings.retain(|b| *b != binding);
config.bindings.retain(|b| *b == binding);
}
}
@@ -724,43 +580,3 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
self.0.iter()
}
}
#[cfg(all(
test,
feature = "logical-keyboard",
not(feature = "serialize"),
not(feature = "reflect"),
not(feature = "inspector-egui")
))]
mod tests {
use super::*;
use bevy_state::state::States;
#[test]
fn keyboard_variants_are_distinct() {
let physical = InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyH));
let logical = InputType::Keyboard(Keyboard::Key(Key::Character("h".into())));
assert_ne!(physical, logical);
}
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default)]
enum TestState {
#[default]
Playing,
}
impl crate::contract::GameState for TestState {}
#[test]
fn bindings_reject_duplicates() {
let binding: Binding<TestState> =
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyH)));
let mut bindings = Bindings::new(vec![binding.clone(), binding.clone()]).customizable();
bindings.push(binding.clone());
bindings.force_push(binding);
assert_eq!(bindings.iter().count(), 1);
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
/// Creates a [`HashMap`](std::collections::HashMap) from a list of key-value pairs.
/// Creates a [`HashMap`](bevy_utils::HashMap) from a list of key-value pairs.
///
/// Example:
/// ```ignore
@@ -9,7 +9,7 @@
#[macro_export]
macro_rules! hashmap {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
let mut map = HashMap::new();
$(
map.insert($key, $val);
)*
+17
View File
@@ -6,7 +6,24 @@ edition = "2021"
[lib]
proc-macro = true
# [features]
# default = []
# serialize = ["bevy_input/serialize", "serde"]
# reflect = ["dep:bevy_reflect"]
# inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
[dependencies]
# bevy-inspector-egui = { version = "0.22.1", optional = true }
# bevy_app = "0.12.1"
# bevy_derive = "0.12.1"
# bevy_ecs = "0.12.1"
# bevy_input = "0.12.1"
# bevy_reflect = { version = "0.12.1", optional = true }
# bevy_utils = "0.12.1"
# log = "0.4.20"
# serde = { version = "1.0.194", optional = true }
# strum = "0.25.0"
# strum_macros = "0.25.3"
bevy_controls = { path = "./../bevy_controls" }
syn = "1.0"
quote = "1.0"
+11 -13
View File
@@ -1,27 +1,25 @@
extern crate proc_macro;
use bevy_controls::contract::{Action, ActionInner};
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(Action)]
pub fn action(input: TokenStream) -> TokenStream {
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl bevy_controls::contract::Action for #name {}
};
gen.into()
}
#[proc_macro_derive(GameState)]
pub fn game_state(input: TokenStream) -> TokenStream {
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl bevy_controls::contract::GameState for #name {}
impl Action for #name {}
impl ActionInner for #name {}
};
gen.into()
}
-47
View File
@@ -1,47 +0,0 @@
{ inputs, flake, self, pkgs }:
let
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
runtimeLibraries = with pkgs; [
alsa-lib
vulkan-loader
udev
libx11
libxcursor
libxi
libxrandr
libxkbcommon
wayland
];
in {
default = pkgs.mkShell {
buildInputs = with pkgs; [
rustToolchain
stdenv.cc
git
pkg-config
shellcheck
file
openssl
] ++ runtimeLibraries;
shellHook = ''
export RUST_SRC_PATH="${rustToolchain}/lib/rustlib/src/rust/library"
if [ -d /run/opengl-driver/lib ]; then
export LD_LIBRARY_PATH="/run/opengl-driver/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
fi
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibraries}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
if git_root=$(${pkgs.git}/bin/git rev-parse --show-toplevel 2>/dev/null); then
export LOCAL_DIR="$git_root"
else
export LOCAL_DIR="$PWD"
fi
printf '%s\n' "bevy_controls dev shell"
printf '%s\n' "Library: cargo check && cargo test"
printf '%s\n' "Example: cd crate/bevy_controls/example/basic && cargo run --features x11"
'';
};
}
Generated
+16 -1234
View File
File diff suppressed because it is too large Load Diff
+29 -19
View File
@@ -1,28 +1,38 @@
{
description = "Bevy controls library and examples";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
hearth = {
url = "git+https://gitea.hectic-lab.com/hinterland/hearth.git";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
};
outputs = { self, hearth, ... }@inputs:
let
flake = builtins.warn "Using the `flake` variable may produce a large source closure." ./.;
lib = import ./lib { inherit inputs flake self; };
in
hearth.lib.forAllSystemsWithPkgs lib.composedOverlays ({ system, pkgs }: {
legacyPackages.${system} = pkgs;
packages.${system} = import ./package { inherit self inputs flake pkgs; };
devShells.${system} = import ./devshell { inherit self inputs flake pkgs; };
}) // {
inherit lib;
};
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
shellcheck
file
];
in
with pkgs;
{
devShells.default = mkShell {
inherit buildInputs nativeBuildInputs;
};
}
);
}
-28
View File
@@ -1,28 +0,0 @@
<h1 id="v0.1.0">v0.1.0</h1>
<ul class="task-list">
<li><label><input type="checkbox" checked="" />contracts</label>
<ul class="task-list">
<li><label><input type="checkbox" checked="" />loby<br />
</label></li>
<li><label><input type="checkbox" checked="" />actions<br />
</label></li>
<li><label><input type="checkbox" checked="" />game state<br />
</label></li>
</ul></li>
<li><label><input type="checkbox" checked="" />way to define default
controlls</label></li>
<li><label><input type="checkbox" />layers for controlls: if on (shift +
space) action enable, ignore on (space) action</label></li>
<li><label><input type="checkbox" />warnings on actions
collision</label></li>
<li><label><input type="checkbox" />basic example</label></li>
</ul>
<h1 id="v0.2.0">v0.2.0</h1>
<ul class="task-list">
<li><label><input type="checkbox" />layers for controlls: user
posibility give priority to controls</label></li>
<li><label><input type="checkbox" />controller support</label></li>
<li><label><input type="checkbox" />touch pad support</label></li>
<li><label><input type="checkbox" />posibility support more that one
game state</label></li>
</ul>
-10
View File
@@ -1,10 +0,0 @@
{ self, flake, inputs }:
let
overlays = with inputs; [
hearth.overlays.default
(import rust-overlay)
];
in {
nixpkgs-lib = inputs.nixpkgs.lib;
composedOverlays = overlays;
}
-38
View File
@@ -1,38 +0,0 @@
{ pkgs }:
let
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml;
rustPlatform = pkgs.makeRustPlatform {
rustc = rustToolchain;
cargo = rustToolchain;
};
in rustPlatform.buildRustPackage {
pname = "bevy-controls";
version = "0.1.0";
src = ../..;
cargoLock.lockFile = ../../Cargo.lock;
cargoBuildFlags = [ "--workspace" ];
cargoTestFlags = [ "--workspace" ];
nativeBuildInputs = with pkgs; [
pkg-config
];
buildInputs = with pkgs; [
openssl
alsa-lib
vulkan-loader
udev
libx11
libxcursor
libxi
libxrandr
libxkbcommon
wayland
];
installPhase = ''
mkdir -p "$out"
touch "$out/workspace-built"
'';
}
-5
View File
@@ -1,5 +0,0 @@
{ pkgs, ... }:
{
bevy-controls = import ./bevy-controls { inherit pkgs; };
default = import ./bevy-controls { inherit pkgs; };
}
Executable → Regular
+1 -2
View File
@@ -1,3 +1,2 @@
[toolchain]
channel = '1.95.0'
components = ["rustfmt", "clippy", "rust-src"]
channel = '1.72.0'