bevy_touch_stick/
lib.rs

1//! Virtual touch screen analog joysticks for the Bevy game engine.
2//!
3//! see the examples for more detailed usage
4//!
5//! ## Example
6//!```rust
7//! use bevy::prelude::*;
8//! use bevy_touch_stick::prelude::*;
9//!
10//!#[derive(Default, Reflect, Hash, Clone, PartialEq, Eq)]
11//! enum Stick {
12//!     #[default]
13//!     Left,
14//!     Right,
15//! }
16//!
17//! fn main () {
18//!     App::new().add_systems(Startup, spawn_joystick).run()
19//! }
20//!
21//!  fn spawn_joystick(mut commands: Commands) {
22//!     commands.spawn((
23//!          BackgroundColor(Color::BLUE),
24//!          TouchStickUiBundle {
25//!              stick: TouchStick {
26//!                  id: Stick::Right,
27//!                  stick_type: TouchStickType::Dynamic,
28//!                  ..default()
29//!              },
30//!              style: Style {
31//!                  width: Val::Px(150.),
32//!                  height: Val::Px(150.),
33//!                  position_type: PositionType::Absolute,
34//!                  right: Val::Px(35.),
35//!                  bottom: Val::Percent(15.),
36//!                  ..default()
37//!              },
38//!              ..default()
39//!          }
40//!      ));
41//! }
42//!```
43//!
44use bevy::{prelude::*, reflect::TypePath, ui::UiSystem};
45use std::{hash::Hash, marker::PhantomData};
46
47mod behavior;
48#[cfg(feature = "gamepad_mapping")]
49mod gamepad;
50mod input;
51mod ui;
52
53/// Commonly used exports from this crate
54pub mod prelude {
55    #[cfg(feature = "gamepad_mapping")]
56    pub use crate::TouchStickGamepadMapping;
57    pub use crate::{TouchStick, TouchStickPlugin, TouchStickType, TouchStickUiBundle};
58}
59
60#[cfg(feature = "gamepad_mapping")]
61use crate::gamepad::GamepadMappingPlugin;
62#[cfg(feature = "gamepad_mapping")]
63pub use crate::gamepad::TouchStickGamepadMapping;
64
65pub use crate::{
66    behavior::TouchStickType,
67    ui::{TouchStickInteractionArea, TouchStickUiBundle, TouchStickUiKnob, TouchStickUiOutline},
68};
69use crate::{
70    input::{
71        send_drag_events_from_mouse, send_drag_events_from_touch, update_sticks_from_drag_events,
72        DragEvent,
73    },
74    ui::TouchStickUiPlugin,
75};
76
77/// Pure data, independent of `bevy_ui`
78#[derive(Component, Clone, Debug, Reflect)]
79#[reflect(Component, Default)]
80pub struct TouchStick<S: StickIdType> {
81    /// Type used for identifying this [`TouchStick`]
82    pub id: S,
83    /// What drag event sequence is currently affecting this [`TouchStick`]
84    pub drag_id: Option<u64>,
85    /// Axes values less than `dead_zone` will not send [`TouchStickEvent`]s
86    pub dead_zone: f32,
87    /// Last `drag_position` of [`TouchStick`] only applies too [`TouchStickType::Dynamic`]
88    ///
89    /// `Vec2::ZERO` if node is released
90    pub base_position: Vec2,
91    /// The screen position where the drag was started
92    pub drag_start: Vec2,
93    /// The screen position where the drag is currently at
94    pub drag_position: Vec2,
95    /// Value with maximum magnitude 1
96    pub value: Vec2,
97    /// In input space (y-down)
98    pub interactable_zone: Rect,
99    /// In input space, how far to drag before reaching max activation
100    pub radius: f32,
101    /// Defines the positioning behavior of the [`TouchStick`]
102    pub stick_type: TouchStickType,
103}
104
105impl<S: StickIdType> Default for TouchStick<S> {
106    fn default() -> Self {
107        Self {
108            id: default(),
109            drag_id: None,
110            dead_zone: 0.,
111            base_position: default(),
112            drag_start: default(),
113            drag_position: default(),
114            value: default(),
115            interactable_zone: Rect {
116                min: Vec2::MIN,
117                max: Vec2::MAX,
118            },
119            radius: 75.,
120            stick_type: default(),
121        }
122    }
123}
124
125impl<S: StickIdType> From<S> for TouchStick<S> {
126    fn from(id: S) -> Self {
127        Self::new(id)
128    }
129}
130
131impl<S: StickIdType> TouchStick<S> {
132    /// Creates a new [`TouchStick`] with the given id.
133    pub fn new(id: S) -> Self {
134        Self { id, ..default() }
135    }
136}
137
138/// Plugin holding [`TouchStick`] functionality
139pub struct TouchStickPlugin<S> {
140    _marker: PhantomData<S>,
141}
142
143impl<S> Default for TouchStickPlugin<S> {
144    fn default() -> Self {
145        Self { _marker: default() }
146    }
147}
148
149impl<S: StickIdType> Plugin for TouchStickPlugin<S> {
150    fn build(&self, app: &mut bevy::prelude::App) {
151        app.register_type::<TouchStickInteractionArea>()
152            .register_type::<TouchStick<S>>()
153            .register_type::<TouchStickType>()
154            .register_type::<TouchStickEventType>()
155            .add_event::<TouchStickEvent<S>>()
156            .add_event::<DragEvent>()
157            .add_plugins(TouchStickUiPlugin::<S>::default())
158            .add_systems(
159                PreUpdate,
160                (
161                    // todo: resolve ambiguity
162                    send_drag_events_from_touch.before(update_sticks_from_drag_events::<S>),
163                    send_drag_events_from_mouse.before(update_sticks_from_drag_events::<S>),
164                ),
165            )
166            .add_systems(PreUpdate, update_sticks_from_drag_events::<S>)
167            .add_systems(
168                PostUpdate,
169                map_input_zones_from_ui_nodes::<S>.before(UiSystem::Layout),
170            );
171
172        #[cfg(feature = "gamepad_mapping")]
173        app.add_plugins(GamepadMappingPlugin::<S>::default());
174    }
175}
176
177/// Type definition for [`TouchStick`] identifier
178pub trait StickIdType:
179    Hash + Sync + Send + Clone + Default + Reflect + FromReflect + TypePath + 'static
180{
181}
182
183impl<S: Hash + Sync + Send + Clone + Default + Reflect + FromReflect + TypePath + 'static>
184    StickIdType for S
185{
186}
187
188fn map_input_zones_from_ui_nodes<S: StickIdType>(
189    mut interaction_areas: Query<
190        (&mut TouchStick<S>, &GlobalTransform, &Node),
191        With<TouchStickInteractionArea>,
192    >,
193) {
194    for (mut touch_stick, transform, node) in &mut interaction_areas {
195        let pos = transform.translation().truncate();
196        let size = node.size();
197        let interaction_area = Rect::from_center_size(pos, size);
198        touch_stick.interactable_zone = interaction_area;
199    }
200}
201
202/// What action the [`TouchStick`] is experiencing
203#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
204#[reflect]
205pub enum TouchStickEventType {
206    /// [`TouchStick`] was activated
207    Press,
208    /// [`TouchStick`] was moved
209    Drag,
210    /// [`TouchStick`] was deactivated
211    Release,
212}
213
214/// Event sent whenever the [`TouchStick`] is interacted.
215#[derive(Event)]
216pub struct TouchStickEvent<S: StickIdType> {
217    /// Identification for joystick that sent this event
218    id: S,
219    /// What interaction did this [`TouchStick`] experience
220    event: TouchStickEventType,
221    /// [`TouchStick`]
222    value: Vec2,
223}
224
225impl<S: StickIdType> TouchStickEvent<S> {
226    /// Returns the id for the stick that sent the event
227    pub fn id(&self) -> S {
228        self.id.clone()
229    }
230
231    /// Value of the joystick, maximum length 1
232    pub fn value(&self) -> Vec2 {
233        self.value
234    }
235
236    /// Return the Type of Joystick Event
237    pub fn get_type(&self) -> TouchStickEventType {
238        self.event
239    }
240}