Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic touch support #334

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ path = "examples/input/keyboard_input.rs"
name = "keyboard_input_events"
path = "examples/input/keyboard_input_events.rs"

[[example]]
name = "touch_input"
path = "examples/input/touch_input.rs"

[[example]]
name = "touch_input_events"
path = "examples/input/touch_input_events.rs"

[[example]]
name = "scene"
path = "examples/scene/scene.rs"
Expand Down
12 changes: 11 additions & 1 deletion crates/bevy_input/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ mod input;
pub mod keyboard;
pub mod mouse;
pub mod system;
pub mod touch;

pub use input::*;

pub mod prelude {
pub use crate::{keyboard::KeyCode, mouse::MouseButton, Input};
pub use crate::{keyboard::KeyCode, mouse::MouseButton, touch::Finger, Input};
}

use bevy_app::prelude::*;
use keyboard::{keyboard_input_system, KeyCode, KeyboardInput};
use mouse::{mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion, MouseWheel};
use touch::{touch_finger_input_system, Finger, TouchFingerInput, TouchMotion};

use bevy_ecs::IntoQuerySystem;

Expand All @@ -25,6 +27,9 @@ impl Plugin for InputPlugin {
.add_event::<MouseButtonInput>()
.add_event::<MouseMotion>()
.add_event::<MouseWheel>()
.add_event::<Finger>()
.add_event::<TouchFingerInput>()
.add_event::<TouchMotion>()
.init_resource::<Input<KeyCode>>()
.add_system_to_stage(
bevy_app::stage::EVENT_UPDATE,
Expand All @@ -34,6 +39,11 @@ impl Plugin for InputPlugin {
.add_system_to_stage(
bevy_app::stage::EVENT_UPDATE,
mouse_button_input_system.system(),
)
.init_resource::<Input<Finger>>()
.add_system_to_stage(
bevy_app::stage::EVENT_UPDATE,
touch_finger_input_system.system(),
);
}
}
49 changes: 49 additions & 0 deletions crates/bevy_input/src/touch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use super::keyboard::ElementState;
use crate::Input;
use bevy_app::prelude::{EventReader, Events};
use bevy_ecs::{Local, Res, ResMut};
use bevy_math::Vec2;

/// A finger on a touch screen device
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct Finger(pub u64);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think I would prefer it if we didn't call this finger. I think TouchId is a more generic term


/// A finger pressed event
#[derive(Debug, Clone)]
pub struct TouchFingerInput {
pub finger: Finger,
pub state: ElementState,
pub position: Vec2,
}

/// A finer motion event
#[derive(Debug, Clone)]
pub struct TouchMotion {
pub finger: Finger,
pub position: Vec2,
}

/// State used by the mouse button input system
#[derive(Default)]
pub struct TouchFingerInputState {
touch_finger_input_event_reader: EventReader<TouchFingerInput>,
}

/// Updates the Input<Finger> resource with the latest TouchFingerInput events
pub fn touch_finger_input_system(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As nice as this api is to consume, I think we should remove Input<Finger> for now. Touch events might need their own abstraction because they have more state to track and TouchId might be unstable / hard to know ahead of time.

mut state: Local<TouchFingerInputState>,
mut touch_finger_input: ResMut<Input<Finger>>,
touch_finger_input_events: Res<Events<TouchFingerInput>>,
) {
touch_finger_input.update();
for event in state
.touch_finger_input_event_reader
.iter(&touch_finger_input_events)
{
match event.state {
ElementState::Pressed => touch_finger_input.press(event.finger),
ElementState::Released => touch_finger_input.release(event.finger),
}
}
}
10 changes: 10 additions & 0 deletions crates/bevy_winit/src/converters.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use bevy_input::{
keyboard::{ElementState, KeyCode, KeyboardInput},
mouse::MouseButton,
touch::{Finger, TouchFingerInput},
};
use bevy_math::Vec2;

pub fn convert_keyboard_input(keyboard_input: &winit::event::KeyboardInput) -> KeyboardInput {
KeyboardInput {
Expand Down Expand Up @@ -192,3 +194,11 @@ pub fn convert_virtual_key_code(virtual_key_code: winit::event::VirtualKeyCode)
winit::event::VirtualKeyCode::Cut => KeyCode::Cut,
}
}

pub fn convert_touch_input(state: ElementState, touch: &winit::event::Touch) -> TouchFingerInput {
TouchFingerInput {
finger: Finger(touch.id),
state,
position: Vec2::new(touch.location.x as f32, touch.location.y as f32),
}
}
63 changes: 61 additions & 2 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ mod converters;
mod winit_config;
mod winit_windows;
use bevy_input::{
keyboard::KeyboardInput,
keyboard::{ElementState, KeyboardInput},
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
touch::{Finger, TouchFingerInput, TouchMotion},
};
pub use winit_config::*;
pub use winit_windows::*;
Expand All @@ -17,7 +18,7 @@ use bevy_window::{
use event::Event;
use winit::{
event,
event::{DeviceEvent, WindowEvent},
event::{DeviceEvent, Touch, TouchPhase, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};

Expand Down Expand Up @@ -192,6 +193,64 @@ pub fn winit_runner(mut app: App) {
});
}
},
WindowEvent::Touch(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its much more legible to compress these into a single match arm, then have a second match inside:

WindowEvent::Touch(touch) => match touch.phase {
    TouchPhase::Started => {
        let mut touch_finger_input_events =
            app.resources.get_mut::<Events<TouchFingerInput>>().unwrap();
        touch_finger_input_events.send(converters::convert_touch_input(
            ElementState::Pressed,
            &touch,
        ));
    }
    TouchPhase::Ended => {
        let mut touch_finger_input_events =
            app.resources.get_mut::<Events<TouchFingerInput>>().unwrap();
        touch_finger_input_events.send(converters::convert_touch_input(
            ElementState::Released,
            &touch,
        ));
    }
    TouchPhase::Cancelled => {
        let mut touch_finger_input_events =
            app.resources.get_mut::<Events<TouchFingerInput>>().unwrap();
        touch_finger_input_events.send(converters::convert_touch_input(
            ElementState::Released,
            &touch,
        ));
    }
    TouchPhase::Moved => {
        let mut touch_finger_moved_events =
            app.resources.get_mut::<Events<TouchMotion>>().unwrap();
        touch_finger_moved_events.send(TouchMotion {
            finger: Finger(touch.id),
            position: Vec2::new(touch.location.x as f32, touch.location.y as f32),
        });
    }
},

touch
@
Touch {
phase: TouchPhase::Started,
..
},
) => {
let mut touch_finger_input_events =
app.resources.get_mut::<Events<TouchFingerInput>>().unwrap();
touch_finger_input_events.send(converters::convert_touch_input(
ElementState::Pressed,
&touch,
));
}
WindowEvent::Touch(
touch
@
Touch {
phase: TouchPhase::Ended,
..
},
) => {
let mut touch_finger_input_events =
app.resources.get_mut::<Events<TouchFingerInput>>().unwrap();
touch_finger_input_events.send(converters::convert_touch_input(
ElementState::Released,
&touch,
));
}
WindowEvent::Touch(
touch
@
Touch {
phase: TouchPhase::Cancelled,
..
},
) => {
let mut touch_finger_input_events =
app.resources.get_mut::<Events<TouchFingerInput>>().unwrap();
touch_finger_input_events.send(converters::convert_touch_input(
ElementState::Released,
&touch,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should be mapping Cancelled to Released. The Input abstraction as it stands doesn't map well to touch. I think either we extend Input or create a different abstraction for touch... I think I personally would prefer the latter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. We should mirror the winit TouchPhase enum in the bevy events.

));
}
WindowEvent::Touch(Touch {
phase: TouchPhase::Moved,
id,
location,
..
}) => {
let mut touch_finger_moved_events =
app.resources.get_mut::<Events<TouchMotion>>().unwrap();
touch_finger_moved_events.send(TouchMotion {
finger: Finger(id),
position: Vec2::new(location.x as f32, location.y as f32),
});
}
_ => {}
},
event::Event::DeviceEvent { ref event, .. } => {
Expand Down
23 changes: 23 additions & 0 deletions examples/input/touch_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use bevy::prelude::*;

fn main() {
App::build()
.add_default_plugins()
.add_system(touch_system.system())
.run();
}

// This system prints messages when you use the touchscreen
fn touch_system(finger_input: Res<Input<Finger>>) {
if finger_input.pressed(Finger(0)) {
println!("finger 0 pressed");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure this works on all platforms? For example on iOS I was getting huge numbers, not 0 and 1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think we should assume that a "touch id" is an opaque identifier.

}

if finger_input.just_pressed(Finger(0)) {
println!("finger 0 just pressed");
}

if finger_input.just_released(Finger(0)) {
println!("finger 0 just released");
}
}
36 changes: 36 additions & 0 deletions examples/input/touch_input_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use bevy::{
input::touch::{TouchFingerInput, TouchMotion},
prelude::*,
};

fn main() {
App::build()
.add_default_plugins()
.init_resource::<State>()
.add_system(print_touch_events_system.system())
.run();
}

#[derive(Default)]
struct State {
touch_finger_event_reader: EventReader<TouchFingerInput>,
touch_motion_event_reader: EventReader<TouchMotion>,
}

/// This system prints out all touch events as they come in
fn print_touch_events_system(
mut state: ResMut<State>,
touch_finger_input_events: Res<Events<TouchFingerInput>>,
touch_motion_events: Res<Events<TouchMotion>>,
) {
for event in state
.touch_finger_event_reader
.iter(&touch_finger_input_events)
{
println!("{:?}", event);
}

for event in state.touch_motion_event_reader.iter(&touch_motion_events) {
println!("{:?}", event);
}
}