Fix initial Android support (#778)

* Add force touches, fix ui focus system and touch screen system

* Fix examples README. Update rodio with Android support. Add Android build CI

* Alter android metadata in root Cargo.toml
This commit is contained in:
David Ackerman 2020-11-03 21:32:48 +02:00 committed by GitHub
parent 562190f518
commit 7efb1b1887
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 204 additions and 106 deletions

View file

@ -85,6 +85,17 @@ jobs:
command: check
args: --target wasm32-unknown-unknown --no-default-features --features bevy_winit,x11,hdr,bevy_gltf
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Android targets
run: rustup target add aarch64-linux-android armv7-linux-androideabi
- name: Install Cargo APK
run: cargo install cargo-apk
- name: Build APK
run: cargo apk build --example bevy_android
clean:
runs-on: ubuntu-latest
steps:

View file

@ -30,21 +30,6 @@ default = [
"x11",
]
supported_android_features = [
# cpal is not supported yet
# "bevy_audio",
"bevy_dynamic_plugin",
"bevy_gilrs",
"bevy_gltf",
"bevy_wgpu",
"bevy_winit",
"render",
"png",
"hdr",
# "mp3",
"x11",
]
profiler = ["bevy_ecs/profiler", "bevy_diagnostic/profiler"]
wgpu_trace = ["bevy_wgpu/trace"]
@ -341,10 +326,6 @@ path = "examples/android/bevy_android.rs"
crate-type = ["cdylib"]
[package.metadata.android]
build_targets = [ "aarch64-linux-android", "armv7-linux-androideabi" ]
target_sdk_version = 28
min_sdk_version = 28
[[package.metadata.android.feature]]
name = "android.hardware.vulkan.level"
version = "1"
build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"]
target_sdk_version = 29
min_sdk_version = 16

View file

@ -22,7 +22,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
# other
anyhow = "1.0"
rodio = { version = "0.12", default-features = false }
rodio = { version = "0.13", default-features = false }
parking_lot = "0.11.0"
[features]

View file

@ -17,6 +17,7 @@ pub mod prelude {
},
keyboard::KeyCode,
mouse::MouseButton,
touch::{TouchInput, Touches},
Axis, Input,
};
}

View file

@ -1,20 +1,73 @@
use bevy_app::{EventReader, Events};
use bevy_ecs::{Local, Res, ResMut};
use bevy_math::Vec2;
use bevy_utils::{HashMap, HashSet};
use bevy_utils::HashMap;
use core::ops::DerefMut;
/// A touch input event
#[derive(Debug, Clone)]
/// Represents a touch event
///
/// Every time the user touches the screen, a new `Start` event with an unique
/// identifier for the finger is generated. When the finger is lifted, an `End`
/// event is generated with the same finger id.
///
/// After a `Start` event has been emitted, there may be zero or more `Move`
/// events when the finger is moved or the touch pressure changes.
///
/// The finger id may be reused by the system after an `End` event. The user
/// should assume that a new `Start` event received with the same id has nothing
/// to do with the old finger and is a new finger.
///
/// A `Cancelled` event is emitted when the system has canceled tracking this
/// touch, such as when the window loses focus, or on iOS if the user moves the
/// device against their face.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TouchInput {
pub phase: TouchPhase,
pub position: Vec2,
/// Describes how hard the screen was pressed. May be `None` if the platform
/// does not support pressure sensitivity.
///
/// ## Platform-specific
///
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
pub force: Option<ForceTouch>,
/// Unique identifier of a finger.
pub id: u64,
}
/// Describes the force of a touch event
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ForceTouch {
/// On iOS, the force is calibrated so that the same number corresponds to
/// roughly the same amount of pressure on the screen regardless of the
/// device.
Calibrated {
/// The force of the touch, where a value of 1.0 represents the force of
/// an average touch (predetermined by the system, not user-specific).
///
/// The force reported by Apple Pencil is measured along the axis of the
/// pencil. If you want a force perpendicular to the device, you need to
/// calculate this value using the `altitude_angle` value.
force: f64,
/// The maximum possible force for a touch.
///
/// The value of this field is sufficiently high to provide a wide
/// dynamic range for values of the `force` field.
max_possible_force: f64,
/// The altitude (in radians) of the stylus.
///
/// A value of 0 radians indicates that the stylus is parallel to the
/// surface. The value of this property is Pi/2 when the stylus is
/// perpendicular to the surface.
altitude_angle: Option<f64>,
},
/// If the platform reports the force as normalized, we have no way of
/// knowing how much pressure 1.0 corresponds to we know it's the maximum
/// amount of force, but as to how much force, you might either have to
/// press really really hard, or not hard at all, depending on the device.
Normalized(f64),
}
/// Describes touch-screen input state.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
@ -30,12 +83,15 @@ pub struct TouchSystemState {
touch_event_reader: EventReader<TouchInput>,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct Touch {
pub id: u64,
pub start_position: Vec2,
pub start_force: Option<ForceTouch>,
pub previous_position: Vec2,
pub previous_force: Option<ForceTouch>,
pub position: Vec2,
pub force: Option<ForceTouch>,
}
impl Touch {
@ -48,47 +104,69 @@ impl Touch {
}
}
#[derive(Default)]
impl From<&TouchInput> for Touch {
fn from(input: &TouchInput) -> Touch {
Touch {
id: input.id,
start_position: input.position,
start_force: input.force,
previous_position: input.position,
previous_force: input.force,
position: input.position,
force: input.force,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Touches {
active_touches: HashMap<u64, Touch>,
just_pressed: HashSet<u64>,
just_released: HashSet<u64>,
just_cancelled: HashSet<u64>,
pressed: HashMap<u64, Touch>,
just_pressed: HashMap<u64, Touch>,
just_released: HashMap<u64, Touch>,
just_cancelled: HashMap<u64, Touch>,
}
impl Touches {
pub fn iter(&self) -> impl Iterator<Item = &Touch> + '_ {
self.active_touches.values()
self.pressed.values()
}
pub fn get_pressed(&self, id: u64) -> Option<&Touch> {
self.pressed.get(&id)
}
pub fn just_pressed(&self, id: u64) -> bool {
self.just_pressed.contains(&id)
self.just_pressed.contains_key(&id)
}
pub fn iter_just_pressed(&self) -> impl Iterator<Item = &Touch> + '_ {
self.just_pressed
.iter()
.map(move |id| self.active_touches.get(id).unwrap())
.map(move |(id, _)| self.pressed.get(id).unwrap())
}
pub fn get_released(&self, id: u64) -> Option<&Touch> {
self.just_released.get(&id)
}
pub fn just_released(&self, id: u64) -> bool {
self.just_released.contains(&id)
self.just_released.contains_key(&id)
}
pub fn iter_just_released(&self) -> impl Iterator<Item = &Touch> + '_ {
self.just_released
.iter()
.map(move |id| self.active_touches.get(id).unwrap())
.map(move |(id, _)| self.pressed.get(id).unwrap())
}
pub fn just_cancelled(&self, id: u64) -> bool {
self.just_cancelled.contains(&id)
self.just_cancelled.contains_key(&id)
}
pub fn iter_just_cancelled(&self) -> impl Iterator<Item = &Touch> + '_ {
self.just_cancelled
.iter()
.map(move |id| self.active_touches.get(id).unwrap())
.map(move |(id, _)| self.pressed.get(id).unwrap())
}
}
@ -98,45 +176,30 @@ pub fn touch_screen_input_system(
mut touch_state: ResMut<Touches>,
touch_input_events: Res<Events<TouchInput>>,
) {
let touch_state = &mut *touch_state;
for released_id in touch_state.just_released.iter() {
touch_state.active_touches.remove(&released_id);
}
for cancelled_id in touch_state.just_cancelled.iter() {
touch_state.active_touches.remove(&cancelled_id);
}
let touch_state = touch_state.deref_mut();
touch_state.just_pressed.clear();
touch_state.just_cancelled.clear();
touch_state.just_released.clear();
for event in state.touch_event_reader.iter(&touch_input_events) {
let active_touch = touch_state.active_touches.get(&event.id);
match event.phase {
TouchPhase::Started => {
touch_state.active_touches.insert(
event.id,
Touch {
id: event.id,
start_position: event.position,
previous_position: event.position,
position: event.position,
},
);
touch_state.just_pressed.insert(event.id);
touch_state.pressed.insert(event.id, event.into());
touch_state.just_pressed.insert(event.id, event.into());
}
TouchPhase::Moved => {
let old_touch = active_touch.unwrap();
let mut new_touch = old_touch.clone();
let mut new_touch = touch_state.pressed.get(&event.id).cloned().unwrap();
new_touch.previous_position = new_touch.position;
new_touch.previous_force = new_touch.force;
new_touch.position = event.position;
touch_state.active_touches.insert(event.id, new_touch);
new_touch.force = event.force;
touch_state.pressed.insert(event.id, new_touch);
}
TouchPhase::Ended => {
touch_state.just_released.insert(event.id);
touch_state.just_released.insert(event.id, event.into());
touch_state.pressed.remove_entry(&event.id);
}
TouchPhase::Cancelled => {
touch_state.just_cancelled.insert(event.id);
touch_state.just_cancelled.insert(event.id, event.into());
touch_state.pressed.remove_entry(&event.id);
}
};
}

View file

@ -2,7 +2,7 @@ use crate::Node;
use bevy_app::{EventReader, Events};
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
use bevy_input::{mouse::MouseButton, Input};
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
use bevy_math::Vec2;
use bevy_transform::components::GlobalTransform;
use bevy_window::CursorMoved;
@ -43,6 +43,7 @@ pub fn ui_focus_system(
mut state: Local<State>,
mouse_button_input: Res<Input<MouseButton>>,
cursor_moved_events: Res<Events<CursorMoved>>,
touches_input: Res<Touches>,
mut node_query: Query<(
Entity,
&Node,
@ -54,8 +55,11 @@ pub fn ui_focus_system(
if let Some(cursor_moved) = state.cursor_moved_event_reader.latest(&cursor_moved_events) {
state.cursor_position = cursor_moved.position;
}
if let Some(touch) = touches_input.get_pressed(0) {
state.cursor_position = touch.position;
}
if mouse_button_input.just_released(MouseButton::Left) {
if mouse_button_input.just_released(MouseButton::Left) || touches_input.just_released(0) {
for (_entity, _node, _global_transform, interaction, _focus_policy) in node_query.iter_mut()
{
if let Some(mut interaction) = interaction {
@ -66,7 +70,8 @@ pub fn ui_focus_system(
}
}
let mouse_clicked = mouse_button_input.just_pressed(MouseButton::Left);
let mouse_clicked =
mouse_button_input.just_pressed(MouseButton::Left) || touches_input.just_released(0);
let mut hovered_entity = None;
{

View file

@ -1,7 +1,7 @@
use bevy_input::{
keyboard::{KeyCode, KeyboardInput},
mouse::MouseButton,
touch::{TouchInput, TouchPhase},
touch::{ForceTouch, TouchInput, TouchPhase},
ElementState,
};
use bevy_math::Vec2;
@ -39,6 +39,18 @@ pub fn convert_touch_input(touch_input: winit::event::Touch) -> TouchInput {
winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled,
},
position: Vec2::new(touch_input.location.x as f32, touch_input.location.y as f32),
force: touch_input.force.map(|f| match f {
winit::event::Force::Calibrated {
force,
max_possible_force,
altitude_angle,
} => ForceTouch::Calibrated {
force,
max_possible_force,
altitude_angle,
},
winit::event::Force::Normalized(x) => ForceTouch::Normalized(x),
}),
id: touch_input.id,
}
}

View file

@ -261,9 +261,15 @@ pub fn winit_runner(mut app: App) {
});
}
},
WindowEvent::Touch(touch) => {
WindowEvent::Touch(mut touch) => {
let mut touch_input_events =
app.resources.get_mut::<Events<TouchInput>>().unwrap();
let windows = app.resources.get_mut::<Windows>().unwrap();
// FIXME?: On Android window start is top while on PC/Linux/OSX on bottom
if cfg!(target_os = "android") {
let window_height = windows.get_primary().unwrap().height();
touch.location.y = window_height as f64 - touch.location.y;
}
touch_input_events.send(converters::convert_touch_input(touch));
}
_ => {}

View file

@ -2,7 +2,8 @@
These examples demonstrate the main features of Bevy and how to use them.
To run an example, use the command `cargo run --example <Example>`, and add the option `--features x11` or `--features wayland` to force the example to run on a specific window compositor, e.g.
```
```sh
cargo run --features wayland --example hello_world
```
@ -119,47 +120,61 @@ Example | File | Description
## WASM
#### pre-req
#### Pre-requirements
$ rustup target add wasm32-unknown-unknown
$ cargo install wasm-bindgen-cli
```sh
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
```
#### build & run
#### Build & run
Following is an example for `headless_wasm`. For other examples in wasm/ directory,
change the `headless_wasm` in the following commands **and edit** `examples/wasm/index.html`
to point to the correct `.js` file.
$ cargo build --example headless_wasm --target wasm32-unknown-unknown --no-default-features
$ wasm-bindgen --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/debug/examples/headless_wasm.wasm
```sh
cargo build --example headless_wasm --target wasm32-unknown-unknown --no-default-features
wasm-bindgen --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/debug/examples/headless_wasm.wasm
```
Then serve `examples/wasm` dir to browser. i.e.
$ basic-http-server examples/wasm
```sh
basic-http-server examples/wasm
```
## iOS
#### pre-req
#### Pre-requirements
$ rustup target add aarch64-apple-ios x86_64-apple-ios
$ cargo install cargo-lipo
```sh
rustup target add aarch64-apple-ios x86_64-apple-ios
cargo install cargo-lipo
```
#### build & run
#### Build & run
Using bash:
$ cd examples/ios
$ make run
```sh
cd examples/ios
make run
```
In an ideal world, this will boot up, install and run the app for the first
iOS simulator in your `xcrun simctl devices list`. If this fails, you can
specify the simulator device UUID via:
$ DEVICE_ID=${YOUR_DEVICE_ID} make run
```sh
DEVICE_ID=${YOUR_DEVICE_ID} make run
```
If you'd like to see xcode do stuff, you can run
$ open bevy_ios_example.xcodeproj/
```sh
open bevy_ios_example.xcodeproj/
```
which will open xcode. You then must push the zoom zoom play button and wait
for the magic.
@ -175,42 +190,46 @@ used for the `Makefile`.
## Android
#### pre-req
#### Pre-requirements
$ rustup target add aarch64-linux-android armv7-linux-androideabi
$ cargo install cargo-apk
```sh
rustup target add aarch64-linux-android armv7-linux-androideabi
cargo install cargo-apk
```
The Android SDK 29 must be installed, and the environment variable `ANDROID_SDK_ROOT` set to the root Android `sdk` folder.
The Android SDK must be installed, and the environment variable `ANDROID_SDK_ROOT` set to the root Android `sdk` folder.
When using `NDK (Side by side)`, the environment variable `ANDROID_NDK_ROOT` must also be set to one of the NDKs in `sdk\ndk\[NDK number]`.
#### build & run
#### Build & run
To run on a device setup for Android development, run:
$ cargo apk run --example bevy_android --features="supported_android_features" --no-default-features
```sh
cargo apk run --example bevy_android
```
:warning: At this time Bevy does not work in Android Emulator.
When using Bevy as a library, the following fields must be added to `Cargo.toml`:
[package.metadata.android]
build_targets = [ "aarch64-linux-android", "armv7-linux-androideabi" ]
target_sdk_version = 29
min_sdk_version = 29
[[package.metadata.android.feature]]
name = "android.hardware.vulkan.level"
version = "1"
```toml
[package.metadata.android]
build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"]
target_sdk_version = 29
min_sdk_version = 16
```
Please reference `cargo-apk` [README](https://crates.io/crates/cargo-apk) for other Android Manifest fields.
#### old phones
#### Old phones
Bevy by default requires Android API level 29 which is the [Play Store's minimum API to upload or update apps](https://developer.android.com/distribute/best-practices/develop/target-sdk). Users of older phones may want to use an older API when testing.
Bevy by default targets Android API level 29 in its examples which is the [Play Store's minimum API to upload or update apps](https://developer.android.com/distribute/best-practices/develop/target-sdk). Users of older phones may want to use an older API when testing.
To use a different API, the following fields must be updated in Cargo.toml:
[package.metadata.android]
target_sdk_version = >>API<<
min_sdk_version = >>API or less<<
```toml
[package.metadata.android]
target_sdk_version = >>API<<
min_sdk_version = >>API or less<<
```