#![allow(clippy::needless_pass_by_value)]
use bevy::prelude::*;
use bevy_archie::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(ControllerPlugin::default())
.add_systems(Startup, setup)
.add_systems(Update, (handle_remap_ui, handle_remap_events))
.run();
}
#[derive(Component)]
struct RemapActionButton(GameAction);
#[derive(Component)]
struct CurrentBindingText(GameAction);
#[derive(Component)]
struct StatusText;
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands
.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
padding: UiRect::all(Val::Px(40.0)),
row_gap: Val::Px(15.0),
..default()
},
BackgroundColor(Color::srgb(0.1, 0.1, 0.15)),
))
.with_children(|parent| {
parent.spawn((
Text::new("Controller Remapping"),
TextFont {
font_size: 32.0,
..default()
},
TextColor(Color::WHITE),
));
parent.spawn((
Text::new("Click an action to remap it, then press a button on your controller"),
TextFont {
font_size: 18.0,
..default()
},
TextColor(Color::srgb(0.6, 0.6, 0.6)),
StatusText,
));
let actions = [
GameAction::Confirm,
GameAction::Cancel,
GameAction::Primary,
GameAction::Secondary,
GameAction::LeftShoulder,
GameAction::RightShoulder,
];
for action in actions {
spawn_remap_row(parent, action);
}
parent
.spawn((
Node {
margin: UiRect::top(Val::Px(20.0)),
padding: UiRect::axes(Val::Px(20.0), Val::Px(10.0)),
justify_content: JustifyContent::Center,
border_radius: BorderRadius::all(Val::Px(5.0)),
..default()
},
BackgroundColor(Color::srgb(0.5, 0.2, 0.2)),
Button,
))
.with_children(|btn| {
btn.spawn((
Text::new("Reset to Defaults"),
TextFont {
font_size: 16.0,
..default()
},
TextColor(Color::WHITE),
));
});
});
}
fn spawn_remap_row(parent: &mut ChildSpawnerCommands, action: GameAction) {
parent
.spawn(Node {
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center,
column_gap: Val::Px(20.0),
..default()
})
.with_children(|row: &mut ChildSpawnerCommands| {
row.spawn((
Text::new(action.display_name()),
TextFont {
font_size: 20.0,
..default()
},
TextColor(Color::WHITE),
Node {
width: Val::Px(150.0),
..default()
},
));
row.spawn((
Text::new("[A]"),
TextFont {
font_size: 18.0,
..default()
},
TextColor(Color::srgb(0.7, 0.7, 0.7)),
CurrentBindingText(action),
Node {
width: Val::Px(100.0),
..default()
},
));
row.spawn((
Node {
padding: UiRect::axes(Val::Px(15.0), Val::Px(8.0)),
justify_content: JustifyContent::Center,
border_radius: BorderRadius::all(Val::Px(5.0)),
..default()
},
BackgroundColor(Color::srgb(0.2, 0.3, 0.5)),
Button,
RemapActionButton(action),
))
.with_children(|btn: &mut ChildSpawnerCommands| {
btn.spawn((
Text::new("Remap"),
TextFont {
font_size: 14.0,
..default()
},
TextColor(Color::WHITE),
));
});
});
}
fn handle_remap_ui(
mut remap_events: MessageWriter<StartRemapEvent>,
action_map: Res<ActionMap>,
config: Res<ControllerConfig>,
interaction_query: Query<(&Interaction, &RemapActionButton), Changed<Interaction>>,
mut binding_query: Query<(&mut Text, &CurrentBindingText)>,
) {
for (interaction, remap_button) in interaction_query.iter() {
if *interaction == Interaction::Pressed {
remap_events.write(StartRemapEvent::new(remap_button.0));
}
}
let layout = config.layout();
for (mut text, binding_text) in &mut binding_query {
if let Some(button) = action_map.primary_gamepad_button(binding_text.0) {
let button_name = layout.button_name(button);
**text = format!("[{button_name}]");
} else {
**text = "[None]".to_string();
}
}
}
fn handle_remap_events(
mut events: MessageReader<RemapEvent>,
mut status_query: Query<&mut Text, With<StatusText>>,
) {
for event in events.read() {
let message = match event {
RemapEvent::Success { action, button } => {
format!("Remapped {action:?} to {button:?}")
}
RemapEvent::Cancelled { action } => {
format!("Cancelled remapping {action:?}")
}
RemapEvent::TimedOut { action } => {
format!("Timed out remapping {action:?}")
}
RemapEvent::Conflict {
action,
conflicting_action,
..
} => {
format!("Conflict: {action:?} is already bound to {conflicting_action:?}")
}
};
for mut text in &mut status_query {
text.0.clone_from(&message);
}
}
}