all_bindings/
all_bindings.rs

1use bevy::prelude::*;
2use press_here::{
3    Add, And, AppExt, AxisBinding, AxisBindingBuilder, AxisVisualizer, Clamp, Deadzone, Divide,
4    Invert, MouseWheel, MouseY, Multiply, Normalize, Not, Pair, RateLimit, Remap, Smooth, Subtract,
5    Transformation, Trigger, TriggerBinding, WithCurve, WithTriggerBinding,
6};
7use std::time::Duration;
8
9fn main() {
10    // Any type that implements AxisBinding can be used as an axis binding. A lot of bindings can be nested and combined
11    // to create more complex behaviors.
12    let _super_complex_axis_binding = WithTriggerBinding(
13        Smooth::new(
14            Deadzone(
15                (
16                    Pair(KeyCode::KeyA, GamepadAxis::LeftStickX),
17                    Multiply(32.0, GamepadAxis::LeftStickX),
18                ),
19                0.1,
20            ),
21            0.2,
22        ),
23        MouseButton::Left,
24    );
25
26    // The same complex axis binding can be built using the AxisBindingBuilder trait methods. AxisBindingBuilder is
27    // implemented for all AxisBinding types, so all bindings have access to these combinator methods. Triggers have the
28    // equivalent TriggerBindingBuilder trait.
29    let _super_complex_axis_binding_built_different = (
30        Pair(KeyCode::KeyA, GamepadAxis::LeftStickX),
31        32.0.mult(GamepadAxis::LeftStickX),
32    )
33        .deadzone(0.1)
34        .smooth(0.2)
35        .with_trigger_binding(MouseButton::Left);
36
37    //
38    // Bindings:
39    //
40    App::new()
41        .add_plugins(DefaultPlugins)
42        //
43        // Axis bindings:
44        //
45        // Basic axis bindings
46        .add_axis::<EmptyAxis>(()) // Empty binding that always returns and will not contribute to the axis value.
47        .add_axis::<ConstantAxis>(1.0) // AxisBinding is implemented for f32. It's a constant binding that always returns the given value.
48        .add_axis::<KeyCodeAxis>(KeyCode::Space) // Binding that returns 1.0 when the specified key is pressed.
49        .add_axis::<MouseButtonAxis>(MouseButton::Left) // Binding that returns 1.0 when the specified mouse button is pressed.
50        .add_axis::<GamepadButtonAxis>(GamepadButton::South) // Binding that returns the value of the specified gamepad button.
51        .add_axis::<GamepadAxisAxis>(GamepadAxis::LeftStickX) // Binding that returns the value of the specified gamepad axis.
52        .add_axis::<MouseMovementAxis>(MouseY) // Binding that returns the mouse movement delta. Also works for MouseX.
53        .add_axis::<MouseWheelAxis>(MouseWheel::default()) // Binding that returns the mouse wheel scroll delta.
54        .add_axis::<BoxedAxis>(Box::new(KeyCode::KeyW) as Box<dyn AxisBinding>) // Box<dyn AxisBinding> also implements the AxisBinding trait.
55        // Axis combinators
56        .add_axis::<TupleAxis>((KeyCode::KeyW, GamepadAxis::LeftStickX)) // Tuple of AxisBindings. All active bindings are averaged.
57        .add_axis::<VecAxis>(vec![KeyCode::KeyW, KeyCode::ArrowUp]) // Vec of AxisBindings. All active bindings are averaged.
58        .add_axis::<PairAxis>(Pair(KeyCode::KeyS, KeyCode::KeyW)) // Pair combinator that uses the first binding for negative direction and the second for positive.
59        .add_axis::<WithTriggerAxis>(WithTriggerBinding(MouseY, MouseButton::Left)) // Axis that is only active when the trigger binding is active.
60        // Axis filters
61        .add_axis::<DeadzoneAxis>(Deadzone(GamepadAxis::LeftStickX, 0.2)) // Deadzone filter that ignores small input values.
62        .add_axis::<SmoothAxis>(Smooth::new(GamepadAxis::LeftStickX, 0.1)) // Smooth filter that smooths input values over time.
63        .add_axis::<NormalizeAxis>(Normalize(GamepadAxis::LeftStickX, GamepadAxis::LeftStickY)) // Constrain the first given axis to a unit circle when combined with the second axis.
64        .add_axis::<RateLimitAxis>(RateLimit::new(GamepadAxis::LeftStickX, 1.0)) // Rate limit filter that limits how quickly the axis value can change over time.
65        .add_axis::<ClampAxis>(Clamp(GamepadAxis::LeftStickX, -0.5, 0.5)) // Clamp filter that clamps the axis value to the given min and max.
66        // Axis modifiers
67        .add_axis::<MultiplyAxis>(Multiply(GamepadAxis::LeftStickX, 0.5)) // Modifier that multiplies two axis values together.
68        .add_axis::<DivideAxis>(Divide(GamepadAxis::LeftStickX, 2.0)) // Modifier that divides the first axis by the second axis.
69        .add_axis::<AddAxis>(Add(GamepadAxis::LeftStickX, 0.5)) // Modifier that adds two axis values together.
70        .add_axis::<SubtractAxis>(Subtract(GamepadAxis::LeftStickX, 0.5)) // Modifier that subtracts the second axis from the first axis.
71        .add_axis::<InvertAxis>(Invert(GamepadAxis::LeftStickX)) // Invert modifier that negates the axis value.
72        .add_axis::<WithCurveAxis>(WithCurve(GamepadAxis::LeftStickX, EaseFunction::BounceIn)) // Modifier that applies a curve to the axis value.
73        .add_axis::<TransformationAxis>(Transformation(GamepadAxis::LeftStickX, |v| v * v)) // Modifier that applies a custom transformation function to the axis value.
74        .add_axis::<RemapAxis>(Remap(GamepadAxis::LeftStickX, -1.0, 1.0, 0.0, 1.0)) // Modifier that remaps the axis value from one range to another.
75        //
76        // Triggers:
77        //
78        // Basic trigger bindings
79        .add_trigger::<EmptyTrigger>(()) // Empty trigger that is never active.
80        .add_trigger::<ConstantTrigger>(true) // Constant trigger that reflects the given boolean value.
81        .add_trigger::<KeyCodeTrigger>(KeyCode::Space) // Trigger that is active when the specified key is pressed.
82        .add_trigger::<MouseButtonTrigger>(MouseButton::Left) // Trigger that is active when the specified mouse button is pressed.
83        .add_trigger::<GamepadButtonTrigger>(GamepadButton::South) // Trigger that is active when the specified gamepad button is pressed.
84        .add_trigger::<BoxedTrigger>(Box::new(KeyCode::KeyW) as Box<dyn TriggerBinding>) // Box<dyn TriggerBinding> also implements the TriggerBinding trait.
85        // Trigger combinators
86        .add_trigger::<TupleTrigger>((KeyCode::KeyW, GamepadButton::South)) // Tuple of TriggerBindings. Active if any binding is active.
87        .add_trigger::<VecTrigger>(vec![KeyCode::KeyW, KeyCode::ArrowUp]) // Vec of TriggerBindings. Active if any binding is active.
88        .add_trigger::<AndTrigger>(And(KeyCode::KeyW, GamepadButton::South)) // Combinator that is only active if both bindings are active.
89        // Trigger modifiers
90        .add_trigger::<NotTrigger>(Not(KeyCode::KeyW)) // Modifier that inverts the trigger state.
91        .add_systems(
92            Update,
93            (
94                visualize_basic,
95                visualize_combinators,
96                visualize_filters,
97                visualize_modifiers,
98                draw_triggers,
99            ),
100        )
101        .add_systems(Startup, setup)
102        .insert_resource(ClearColor(Color::WHITE))
103        .run();
104}
105
106struct EmptyAxis;
107struct ConstantAxis;
108struct KeyCodeAxis;
109struct MouseButtonAxis;
110struct GamepadButtonAxis;
111struct GamepadAxisAxis;
112struct MouseMovementAxis;
113struct MouseWheelAxis;
114struct BoxedAxis;
115
116struct TupleAxis;
117struct VecAxis;
118struct PairAxis;
119struct WithTriggerAxis;
120
121struct DeadzoneAxis;
122struct SmoothAxis;
123struct NormalizeAxis;
124struct RateLimitAxis;
125struct ClampAxis;
126
127struct MultiplyAxis;
128struct DivideAxis;
129struct AddAxis;
130struct SubtractAxis;
131struct InvertAxis;
132struct WithCurveAxis;
133struct TransformationAxis;
134struct RemapAxis;
135
136struct EmptyTrigger;
137struct ConstantTrigger;
138struct KeyCodeTrigger;
139struct MouseButtonTrigger;
140struct GamepadButtonTrigger;
141struct BoxedTrigger;
142
143struct TupleTrigger;
144struct VecTrigger;
145struct AndTrigger;
146
147struct NotTrigger;
148
149#[allow(clippy::too_many_arguments)]
150fn visualize_basic(
151    mut empty: AxisVisualizer<EmptyAxis>,
152    mut constant: AxisVisualizer<ConstantAxis>,
153    mut keycode: AxisVisualizer<KeyCodeAxis>,
154    mut mouse_button: AxisVisualizer<MouseButtonAxis>,
155    mut gamepad_button: AxisVisualizer<GamepadButtonAxis>,
156    mut gamepad_axis: AxisVisualizer<GamepadAxisAxis>,
157    mut mouse_movement: AxisVisualizer<MouseMovementAxis>,
158    mut mouse_wheel: AxisVisualizer<MouseWheelAxis>,
159    mut boxed: AxisVisualizer<BoxedAxis>,
160) {
161    graph(&mut empty, 0, 0, SCALE);
162    graph(&mut constant, 1, 0, SCALE);
163    graph(&mut keycode, 2, 0, SCALE);
164    graph(&mut mouse_button, 3, 0, SCALE);
165    graph(&mut gamepad_button, 4, 0, SCALE);
166    graph(&mut gamepad_axis, 5, 0, SCALE);
167    graph(&mut mouse_movement, 6, 0, 1.0);
168    graph(&mut mouse_wheel, 7, 0, 1.0);
169    graph(&mut boxed, 8, 0, SCALE);
170}
171
172fn visualize_combinators(
173    mut tuple: AxisVisualizer<TupleAxis>,
174    mut vec: AxisVisualizer<VecAxis>,
175    mut pair: AxisVisualizer<PairAxis>,
176    mut with_trigger: AxisVisualizer<WithTriggerAxis>,
177) {
178    graph(&mut tuple, 0, 1, SCALE);
179    graph(&mut vec, 1, 1, SCALE);
180    graph(&mut pair, 2, 1, SCALE);
181    graph(&mut with_trigger, 3, 1, 1.0);
182}
183
184fn visualize_filters(
185    mut deadzone: AxisVisualizer<DeadzoneAxis>,
186    mut smooth: AxisVisualizer<SmoothAxis>,
187    mut normalize: AxisVisualizer<NormalizeAxis>,
188    mut rate_limit: AxisVisualizer<RateLimitAxis>,
189    mut clamp: AxisVisualizer<ClampAxis>,
190) {
191    graph(&mut deadzone, 0, 2, SCALE);
192    graph(&mut smooth, 1, 2, SCALE);
193    graph(&mut normalize, 2, 2, SCALE);
194    graph(&mut rate_limit, 3, 2, SCALE);
195    graph(&mut clamp, 4, 2, SCALE);
196}
197
198#[allow(clippy::too_many_arguments)]
199fn visualize_modifiers(
200    mut multiply: AxisVisualizer<MultiplyAxis>,
201    mut divide: AxisVisualizer<DivideAxis>,
202    mut add: AxisVisualizer<AddAxis>,
203    mut subtract: AxisVisualizer<SubtractAxis>,
204    mut invert: AxisVisualizer<InvertAxis>,
205    mut with_curve: AxisVisualizer<WithCurveAxis>,
206    mut transformation: AxisVisualizer<TransformationAxis>,
207    mut remap: AxisVisualizer<RemapAxis>,
208) {
209    graph(&mut multiply, 0, 3, SCALE);
210    graph(&mut divide, 1, 3, SCALE);
211    graph(&mut add, 2, 3, SCALE);
212    graph(&mut subtract, 3, 3, SCALE);
213    graph(&mut invert, 4, 3, SCALE);
214    graph(&mut with_curve, 5, 3, SCALE);
215    graph(&mut transformation, 6, 3, SCALE);
216    graph(&mut remap, 7, 3, SCALE);
217}
218
219#[allow(clippy::too_many_arguments)]
220fn draw_triggers(
221    mut gizmos: Gizmos,
222    empty: Res<Trigger<EmptyTrigger>>,
223    constant: Res<Trigger<ConstantTrigger>>,
224    keycode: Res<Trigger<KeyCodeTrigger>>,
225    mouse_button: Res<Trigger<MouseButtonTrigger>>,
226    gamepad_button: Res<Trigger<GamepadButtonTrigger>>,
227    boxed: Res<Trigger<BoxedTrigger>>,
228    tuple: Res<Trigger<TupleTrigger>>,
229    vec: Res<Trigger<VecTrigger>>,
230    and: Res<Trigger<AndTrigger>>,
231    not: Res<Trigger<NotTrigger>>,
232) {
233    draw_trigger(&mut gizmos, &empty, 0);
234    draw_trigger(&mut gizmos, &constant, 1);
235    draw_trigger(&mut gizmos, &keycode, 2);
236    draw_trigger(&mut gizmos, &mouse_button, 3);
237    draw_trigger(&mut gizmos, &gamepad_button, 4);
238    draw_trigger(&mut gizmos, &boxed, 5);
239    draw_trigger(&mut gizmos, &tuple, 6);
240    draw_trigger(&mut gizmos, &vec, 7);
241    draw_trigger(&mut gizmos, &and, 8);
242    draw_trigger(&mut gizmos, &not, 9);
243}
244
245fn setup(mut commands: Commands) {
246    commands.spawn(Camera2d);
247}
248
249const SCALE: f32 = 32.0;
250const MAX_COLUMNS: u32 = 9;
251const MAX_ROWS: u32 = 4;
252const TRIGGER_COUNT: u32 = 10;
253
254fn graph<A: Send + Sync + 'static>(visualizer: &mut AxisVisualizer<A>, x: i32, y: i32, scale: f32) {
255    let timespan = Duration::from_secs(5);
256    let margin = 32.0;
257    let size = 64.0;
258
259    let width = MAX_COLUMNS as f32 * (size + margin) - margin;
260    let height = MAX_ROWS as f32 * (size + margin) - margin;
261
262    let pos = Vec2::new(
263        -width / 2.0 + x as f32 * (size + margin) + size / 2.0,
264        height / 2.0 - y as f32 * (size + margin) - size / 2.0,
265    );
266    let color = Color::srgb(
267        x as f32 / MAX_COLUMNS as f32,
268        y as f32 / MAX_ROWS as f32,
269        1.0 - (x as f32 / MAX_COLUMNS as f32),
270    );
271
272    visualizer.graph_x(timespan, pos, scale, Vec2::splat(size), color);
273}
274
275fn draw_trigger<T: Send + Sync + 'static>(
276    gizmos: &mut Gizmos,
277    trigger: &Res<Trigger<T>>,
278    index: u32,
279) {
280    let is_pressed = trigger.pressed();
281    let color = if is_pressed {
282        Srgba::RED
283    } else {
284        Srgba::gray(0.4)
285    };
286
287    let size = 8.0;
288    let margin = 32.0;
289
290    let width = TRIGGER_COUNT as f32 * (size + margin) - margin;
291
292    let pos = Vec2::new(
293        -width / 2.0 + index as f32 * (size + margin) + size / 2.0,
294        -240.0,
295    );
296
297    gizmos.circle_2d(pos, size, color);
298}