From b8828105cf6231e249da279851285aecd2970b4b Mon Sep 17 00:00:00 2001 From: Jasper De Sutter Date: Wed, 6 May 2020 15:27:49 +0200 Subject: [PATCH] add android NDK event loop (#1556) * add android NDK event loop * add Android build documentation & cargo-apk to CI Co-authored-by: David Craven --- .github/workflows/ci.yml | 34 +- CHANGELOG.md | 1 + Cargo.toml | 6 +- README.md | 19 + src/platform/android.rs | 37 +- src/platform_impl/android/ffi.rs | 122 ----- src/platform_impl/android/mod.rs | 859 +++++++++++++++++-------------- 7 files changed, 532 insertions(+), 546 deletions(-) delete mode 100644 src/platform_impl/android/ffi.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45984456..b98dbc56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,20 +37,21 @@ jobs: - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } + - { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } - { target: aarch64-apple-ios, os: macos-latest, } # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web # doesn't currently work on Linux. - - { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web } - - { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web } + - { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web } + - { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web } env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" FEATURES: ${{ format(',{0}', matrix.platform.features ) }} - WEB: ${{ matrix.platform.web }} + CMD: ${{ matrix.platform.cmd }} runs-on: ${{ matrix.platform.os }} steps: @@ -70,6 +71,9 @@ jobs: - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') run: sudo apt-get update && sudo apt-get install gcc-multilib + - name: Install cargo-apk + if: contains(matrix.platform.target, 'android') + run: cargo install cargo-apk - name: Install cargo-web continue-on-error: true if: contains(matrix.platform.target, 'wasm32') @@ -78,29 +82,35 @@ jobs: - name: Check documentation shell: bash if: matrix.platform.target != 'wasm32-unknown-unknown' - run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES - name: Build shell: bash - run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Build tests shell: bash - run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Run tests shell: bash - if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES + if: ( + !contains(matrix.platform.target, 'android') && + !contains(matrix.platform.target, 'ios') && + !contains(matrix.platform.target, 'wasm32')) + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Build with serde enabled shell: bash - run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES - name: Build tests with serde enabled shell: bash - run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES - name: Run tests with serde enabled shell: bash - if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + if: ( + !contains(matrix.platform.target, 'android') && + !contains(matrix.platform.target, 'ios') && + !contains(matrix.platform.target, 'wasm32')) + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c79649..4ef3d12c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. +- On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. # 0.22.1 (2020-04-16) diff --git a/Cargo.toml b/Cargo.toml index 0b6f833f..cb466f9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,10 @@ bitflags = "1" image = "0.23" simple_logger = "1" -[target.'cfg(target_os = "android")'.dependencies.android_glue] -version = "0.2" +[target.'cfg(target_os = "android")'.dependencies] +ndk = "0.1.0" +ndk-sys = "0.1.0" +ndk-glue = "0.1.0" [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.3" diff --git a/README.md b/README.md index 39376225..95aad64a 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,22 @@ Building a binary will yield a `.js` file. In order to use it in an HTML file, y the element of the `` element (in the example you would retrieve it via `document.getElementById("my_id")`). More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html). - Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created. + +#### Android + +This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation. + +Running on an Android device needs a dynamic system library, add this to Cargo.toml: +```toml +[[example]] +name = "request_redraw_threaded" +crate-type = ["cdylib"] +``` + +And add this to the example file to add the native activity glue: +```rust +#[cfg(target_os = "android")] +ndk_glue::ndk_glue!(main); +``` + +And run the application with `cargo apk run --example request_redraw_threaded` diff --git a/src/platform/android.rs b/src/platform/android.rs index dafb7a39..b4e99176 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,32 +1,39 @@ #![cfg(any(target_os = "android"))] -use crate::{EventLoop, Window, WindowBuilder}; -use std::os::raw::c_void; +use crate::{ + event_loop::{EventLoop, EventLoopWindowTarget}, + window::{Window, WindowBuilder}, +}; +use ndk::configuration::Configuration; +use ndk_glue::Rect; /// Additional methods on `EventLoop` that are specific to Android. -pub trait EventLoopExtAndroid { - /// Makes it possible for glutin to register a callback when a suspend event happens on Android - fn set_suspend_callback(&self, cb: Option ()>>); -} +pub trait EventLoopExtAndroid {} -impl EventLoopExtAndroid for EventLoop { - fn set_suspend_callback(&self, cb: Option ()>>) { - self.event_loop.set_suspend_callback(cb); - } -} +impl EventLoopExtAndroid for EventLoop {} + +/// Additional methods on `EventLoopWindowTarget` that are specific to Android. +pub trait EventLoopWindowTargetExtAndroid {} /// Additional methods on `Window` that are specific to Android. pub trait WindowExtAndroid { - fn native_window(&self) -> *const c_void; + fn content_rect(&self) -> Rect; + + fn config(&self) -> Configuration; } impl WindowExtAndroid for Window { - #[inline] - fn native_window(&self) -> *const c_void { - self.window.native_window() + fn content_rect(&self) -> Rect { + self.window.content_rect() + } + + fn config(&self) -> Configuration { + self.window.config() } } +impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} + /// Additional methods on `WindowBuilder` that are specific to Android. pub trait WindowBuilderExtAndroid {} diff --git a/src/platform_impl/android/ffi.rs b/src/platform_impl/android/ffi.rs deleted file mode 100644 index 93a59b82..00000000 --- a/src/platform_impl/android/ffi.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![allow(dead_code)] -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] - -use libc; -use std::os::raw; - -#[link(name = "android")] -#[link(name = "EGL")] -#[link(name = "GLESv2")] -extern "C" {} - -/** - ** asset_manager.h - **/ -pub type AAssetManager = raw::c_void; - -/** - ** native_window.h - **/ -pub type ANativeWindow = raw::c_void; - -extern "C" { - pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t; - pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t; -} - -/** - ** native_activity.h - **/ -pub type JavaVM = (); -pub type JNIEnv = (); -pub type jobject = *const libc::c_void; - -pub type AInputQueue = (); // FIXME: wrong -pub type ARect = (); // FIXME: wrong - -#[repr(C)] -pub struct ANativeActivity { - pub callbacks: *mut ANativeActivityCallbacks, - pub vm: *mut JavaVM, - pub env: *mut JNIEnv, - pub clazz: jobject, - pub internalDataPath: *const libc::c_char, - pub externalDataPath: *const libc::c_char, - pub sdkVersion: libc::int32_t, - pub instance: *mut libc::c_void, - pub assetManager: *mut AAssetManager, - pub obbPath: *const libc::c_char, -} - -#[repr(C)] -pub struct ANativeActivityCallbacks { - pub onStart: extern "C" fn(*mut ANativeActivity), - pub onResume: extern "C" fn(*mut ANativeActivity), - pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t), - pub onPause: extern "C" fn(*mut ANativeActivity), - pub onStop: extern "C" fn(*mut ANativeActivity), - pub onDestroy: extern "C" fn(*mut ANativeActivity), - pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int), - pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue), - pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue), - pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect), - pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity), - pub onLowMemory: extern "C" fn(*mut ANativeActivity), -} - -/** - ** looper.h - **/ -pub type ALooper = (); - -#[link(name = "android")] -extern "C" { - pub fn ALooper_forThread() -> *const ALooper; - pub fn ALooper_acquire(looper: *const ALooper); - pub fn ALooper_release(looper: *const ALooper); - pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper; - pub fn ALooper_pollOnce( - timeoutMillis: libc::c_int, - outFd: *mut libc::c_int, - outEvents: *mut libc::c_int, - outData: *mut *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_pollAll( - timeoutMillis: libc::c_int, - outFd: *mut libc::c_int, - outEvents: *mut libc::c_int, - outData: *mut *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_wake(looper: *const ALooper); - pub fn ALooper_addFd( - looper: *const ALooper, - fd: libc::c_int, - ident: libc::c_int, - events: libc::c_int, - callback: ALooper_callbackFunc, - data: *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int; -} - -pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0; - -pub const ALOOPER_POLL_WAKE: libc::c_int = -1; -pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2; -pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3; -pub const ALOOPER_POLL_ERROR: libc::c_int = -4; - -pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0; -pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1; -pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2; -pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3; -pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4; - -pub type ALooper_callbackFunc = - extern "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index cf189374..ce26994b 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -1,450 +1,519 @@ #![cfg(target_os = "android")] -extern crate android_glue; - -mod ffi; - -use std::{ - cell::RefCell, - collections::VecDeque, - fmt, - os::raw::c_void, - sync::mpsc::{channel, Receiver}, -}; - use crate::{ - error::{ExternalError, NotSupportedError}, - events::{Touch, TouchPhase}, - window::MonitorHandle as RootMonitorHandle, - CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, - WindowAttributes, WindowEvent, WindowId as RootWindowId, + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error, event, + event_loop::{self, ControlFlow}, + monitor, window, +}; +use ndk::{ + configuration::Configuration, + event::{InputEvent, MotionAction}, + looper::{ForeignLooper, Poll, ThreadLooper}, +}; +use ndk_glue::{Event, Rect}; +use std::{ + collections::VecDeque, + sync::{Arc, Mutex, RwLock}, + time::{Duration, Instant}, }; -use raw_window_handle::{android::AndroidHandle, RawWindowHandle}; -use CreationError::OsError; -pub(crate) use crate::icon::NoIcon as PlatformIcon; - -pub type OsError = std::io::Error; - -pub struct EventLoop { - event_rx: Receiver, - suspend_callback: RefCell ()>>>, +lazy_static! { + static ref CONFIG: RwLock = RwLock::new(Configuration::new()); } -#[derive(Clone)] -pub struct EventLoopProxy; +enum EventSource { + Callback, + InputQueue, + User, +} -impl EventLoop { - pub fn new() -> EventLoop { - let (tx, rx) = channel(); - android_glue::add_sender(tx); - EventLoop { - event_rx: rx, - suspend_callback: Default::default(), +fn poll(poll: Poll) -> Option { + match poll { + Poll::Event { data, .. } => match data as usize { + 0 => Some(EventSource::Callback), + 1 => Some(EventSource::InputQueue), + _ => unreachable!(), + }, + Poll::Timeout => None, + Poll::Wake => Some(EventSource::User), + Poll::Callback => unreachable!(), + } +} + +pub struct EventLoop { + window_target: event_loop::EventLoopWindowTarget, + user_queue: Arc>>, +} + +impl EventLoop { + pub fn new() -> Self { + Self { + window_target: event_loop::EventLoopWindowTarget { + p: EventLoopWindowTarget { + _marker: std::marker::PhantomData, + }, + _marker: std::marker::PhantomData, + }, + user_queue: Default::default(), } } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb + pub fn run(self, mut event_handler: F) -> ! + where + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let mut cf = ControlFlow::default(); + let mut first_event = None; + let mut start_cause = event::StartCause::Init; + let looper = ThreadLooper::for_thread().unwrap(); + let mut running = false; + + loop { + event_handler( + event::Event::NewEvents(start_cause), + self.window_target(), + &mut cf, + ); + + let mut redraw = false; + let mut resized = false; + + match first_event.take() { + Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { + Event::WindowCreated => { + event_handler(event::Event::Resumed, self.window_target(), &mut cf); + } + Event::WindowResized => resized = true, + Event::WindowRedrawNeeded => redraw = true, + Event::WindowDestroyed => { + event_handler(event::Event::Suspended, self.window_target(), &mut cf); + } + Event::Pause => running = false, + Event::Resume => running = true, + Event::ConfigChanged => { + let am = ndk_glue::native_activity().asset_manager(); + let config = Configuration::from_asset_manager(&am); + let old_scale_factor = MonitorHandle.scale_factor(); + *CONFIG.write().unwrap() = config; + let scale_factor = MonitorHandle.scale_factor(); + if (scale_factor - old_scale_factor).abs() < f64::EPSILON { + let mut size = MonitorHandle.size(); + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::ScaleFactorChanged { + new_inner_size: &mut size, + scale_factor, + }, + }; + event_handler(event, self.window_target(), &mut cf); + } + } + _ => {} + }, + Some(EventSource::InputQueue) => { + if let Some(input_queue) = ndk_glue::input_queue().as_ref() { + while let Some(event) = input_queue.get_event() { + println!("event {:?}", event); + if let Some(event) = input_queue.pre_dispatch(event) { + let window_id = window::WindowId(WindowId); + let device_id = event::DeviceId(DeviceId); + match &event { + InputEvent::MotionEvent(motion_event) => { + let phase = match motion_event.action() { + MotionAction::Down => Some(event::TouchPhase::Started), + MotionAction::Up => Some(event::TouchPhase::Ended), + MotionAction::Move => Some(event::TouchPhase::Moved), + MotionAction::Cancel => { + Some(event::TouchPhase::Cancelled) + } + _ => None, // TODO mouse events + }; + let pointer = motion_event.pointer_at_index(0); + let location = PhysicalPosition { + x: pointer.x() as _, + y: pointer.y() as _, + }; + + if let Some(phase) = phase { + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Touch(event::Touch { + device_id, + phase, + location, + id: 0, + force: None, + }), + }; + event_handler(event, self.window_target(), &mut cf); + } + } + InputEvent::KeyEvent(_) => {} // TODO + }; + input_queue.finish_event(event, true); + } + } + } + } + Some(EventSource::User) => { + let mut user_queue = self.user_queue.lock().unwrap(); + while let Some(event) = user_queue.pop_front() { + event_handler( + event::Event::UserEvent(event), + self.window_target(), + &mut cf, + ); + } + } + None => {} + } + + event_handler( + event::Event::MainEventsCleared, + self.window_target(), + &mut cf, + ); + + if resized && running { + let size = MonitorHandle.size(); + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Resized(size), + }; + event_handler(event, self.window_target(), &mut cf); + } + + if redraw && running { + let event = event::Event::RedrawRequested(window::WindowId(WindowId)); + event_handler(event, self.window_target(), &mut cf); + } + + event_handler( + event::Event::RedrawEventsCleared, + self.window_target(), + &mut cf, + ); + + match cf { + ControlFlow::Exit => panic!(), + ControlFlow::Poll => { + start_cause = event::StartCause::Poll; + } + ControlFlow::Wait => { + first_event = poll(looper.poll_all().unwrap()); + start_cause = event::StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + } + } + ControlFlow::WaitUntil(instant) => { + let start = Instant::now(); + let duration = if instant <= start { + Duration::default() + } else { + instant - start + }; + first_event = poll(looper.poll_all_timeout(duration).unwrap()); + start_cause = if first_event.is_some() { + event::StartCause::WaitCancelled { + start, + requested_resume: Some(instant), + } + } else { + event::StartCause::ResumeTimeReached { + start, + requested_resume: instant, + } + } + } + } + } + } + + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { + &self.window_target } - #[inline] pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle } - pub fn poll_events(&mut self, mut callback: F) - where - F: FnMut(::Event), - { - while let Ok(event) = self.event_rx.try_recv() { - let e = match event { - android_glue::Event::EventMotion(motion) => { - let scale_factor = MonitorHandle.scale_factor(); - let location = LogicalPosition::from_physical( - (motion.x as f64, motion.y as f64), - scale_factor, - ); - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Touch(Touch { - phase: match motion.action { - android_glue::MotionAction::Down => TouchPhase::Started, - android_glue::MotionAction::Move => TouchPhase::Moved, - android_glue::MotionAction::Up => TouchPhase::Ended, - android_glue::MotionAction::Cancel => TouchPhase::Cancelled, - }, - location, - force: None, // TODO - id: motion.pointer_id as u64, - device_id: DEVICE_ID, - }), - }) - } - android_glue::Event::InitWindow => { - // The activity went to foreground. - if let Some(cb) = self.suspend_callback.borrow().as_ref() { - (*cb)(false); - } - Some(Event::Resumed) - } - android_glue::Event::TermWindow => { - // The activity went to background. - if let Some(cb) = self.suspend_callback.borrow().as_ref() { - (*cb)(true); - } - Some(Event::Suspended) - } - android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => { - // Activity Orientation changed or resized. - let native_window = unsafe { android_glue::native_window() }; - if native_window.is_null() { - None - } else { - let scale_factor = MonitorHandle.scale_factor(); - let physical_size = MonitorHandle.size(); - let size = LogicalSize::from_physical(physical_size, scale_factor); - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Resized(size), - }) - } - } - android_glue::Event::WindowRedrawNeeded => { - // The activity needs to be redrawn. - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Redraw, - }) - } - android_glue::Event::Wake => Some(Event::Awakened), - _ => None, - }; + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(self.primary_monitor()); + v + } - if let Some(event) = e { - callback(event); - } + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + queue: self.user_queue.clone(), + looper: ForeignLooper::for_thread().expect("called from event loop thread"), } } - - pub fn set_suspend_callback(&self, cb: Option ()>>) { - *self.suspend_callback.borrow_mut() = cb; - } - - pub fn run_forever(&mut self, mut callback: F) - where - F: FnMut(::Event) -> ::ControlFlow, - { - // Yeah that's a very bad implementation. - loop { - let mut control_flow = ::ControlFlow::Continue; - self.poll_events(|e| { - if let ::ControlFlow::Break = callback(e) { - control_flow = ::ControlFlow::Break; - } - }); - if let ::ControlFlow::Break = control_flow { - break; - } - ::std::thread::sleep(::std::time::Duration::from_millis(5)); - } - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy - } } -impl EventLoopProxy { - pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> { - android_glue::wake_event_loop(); +pub struct EventLoopProxy { + queue: Arc>>, + looper: ForeignLooper, +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { + self.queue.lock().unwrap().push_back(event); + self.looper.wake(); Ok(()) } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + queue: self.queue.clone(), + looper: self.looper.clone(), + } + } +} + +pub struct EventLoopWindowTarget { + _marker: std::marker::PhantomData, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId; impl WindowId { - pub unsafe fn dummy() -> Self { + pub fn dummy() -> Self { WindowId } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId; impl DeviceId { - pub unsafe fn dummy() -> Self { + pub fn dummy() -> Self { DeviceId } } -pub struct Window { - native_window: *const c_void, -} - -#[derive(Clone)] -pub struct MonitorHandle; - -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[derive(Debug)] - struct MonitorHandle { - name: Option, - dimensions: PhysicalSize, - position: PhysicalPosition, - scale_factor: f64, - } - - let monitor_id_proxy = MonitorHandle { - name: self.name(), - dimensions: self.size(), - position: self.outer_position(), - scale_factor: self.scale_factor(), - }; - - monitor_id_proxy.fmt(f) - } -} - -impl MonitorHandle { - #[inline] - pub fn name(&self) -> Option { - Some("Primary".to_string()) - } - - #[inline] - pub fn size(&self) -> PhysicalSize { - unsafe { - let window = android_glue::native_window(); - ( - ffi::ANativeWindow_getWidth(window) as f64, - ffi::ANativeWindow_getHeight(window) as f64, - ) - .into() - } - } - - #[inline] - pub fn outer_position(&self) -> PhysicalPosition { - // Android assumes single screen - (0, 0).into() - } - - #[inline] - pub fn scale_factor(&self) -> f64 { - 1.0 - } -} - -#[derive(Clone, Default)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowBuilderAttributes; -#[derive(Clone, Default)] -pub struct PlatformSpecificHeadlessBuilderAttributes; + +pub struct Window; impl Window { - pub fn new( - _: &EventLoop, - win_attribs: WindowAttributes, + pub fn new( + _el: &EventLoopWindowTarget, + _window_attrs: window::WindowAttributes, _: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - let native_window = unsafe { android_glue::native_window() }; - if native_window.is_null() { - return Err(OsError(format!("Android's native window is null"))); - } - - android_glue::set_multitouch(true); - - Ok(Window { - native_window: native_window as *const _, - }) + ) -> Result { + // FIXME this ignores requested window attributes + Ok(Self) } - #[inline] - pub fn native_window(&self) -> *const c_void { - self.native_window - } - - #[inline] - pub fn set_title(&self, _: &str) { - // N/A - } - - #[inline] - pub fn show(&self) { - // N/A - } - - #[inline] - pub fn hide(&self) { - // N/A - } - - #[inline] - pub fn outer_position(&self) -> Option> { - // N/A - None - } - - #[inline] - pub fn inner_position(&self) -> Option> { - // N/A - None - } - - #[inline] - pub fn set_outer_position(&self, _position: LogicalPosition) { - // N/A - } - - #[inline] - pub fn set_min_inner_size(&self, _dimensions: Option>) { - // N/A - } - - #[inline] - pub fn set_max_inner_size(&self, _dimensions: Option>) { - // N/A - } - - #[inline] - pub fn set_resizable(&self, _resizable: bool) { - // N/A - } - - #[inline] - pub fn inner_size(&self) -> Option> { - if self.native_window.is_null() { - None - } else { - let scale_factor = self.scale_factor(); - let physical_size = self.current_monitor().size(); - Some(LogicalSize::from_physical(physical_size, scale_factor)) - } - } - - #[inline] - pub fn outer_size(&self) -> Option> { - self.inner_size() - } - - #[inline] - pub fn set_inner_size(&self, _size: LogicalSize) { - // N/A - } - - #[inline] - pub fn scale_factor(&self) -> f64 { - self.current_monitor().scale_factor() - } - - #[inline] - pub fn set_cursor_icon(&self, _: CursorIcon) { - // N/A - } - - #[inline] - pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - #[inline] - pub fn hide_cursor(&self, _hide: bool) { - // N/A - } - - #[inline] - pub fn set_cursor_position( - &self, - _position: LogicalPosition, - ) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - #[inline] - pub fn set_minimized(&self, _minimized: bool) { - unimplemented!() - } - - #[inline] - pub fn set_maximized(&self, _maximized: bool) { - // N/A - // Android has single screen maximized apps so nothing to do - } - - #[inline] - pub fn fullscreen(&self) -> Option { - // N/A - // Android has single screen maximized apps so nothing to do - None - } - - #[inline] - pub fn set_fullscreen(&self, _monitor: Option) { - // N/A - // Android has single screen maximized apps so nothing to do - } - - #[inline] - pub fn set_decorations(&self, _decorations: bool) { - // N/A - } - - #[inline] - pub fn set_always_on_top(&self, _always_on_top: bool) { - // N/A - } - - #[inline] - pub fn set_window_icon(&self, _icon: Option<::Icon>) { - // N/A - } - - #[inline] - pub fn set_ime_position(&self, _spot: LogicalPosition) { - // N/A - } - - #[inline] - pub fn current_monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: MonitorHandle, - } - } - - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb - } - - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - #[inline] pub fn id(&self) -> WindowId { WindowId } - #[inline] - pub fn raw_window_handle(&self) -> RawWindowHandle { - let handle = AndroidHandle { - a_native_window: self.native_window, - ..WindowsHandle::empty() + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(MonitorHandle); + v + } + + pub fn current_monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: MonitorHandle, + } + } + + pub fn scale_factor(&self) -> f64 { + MonitorHandle.scale_factor() + } + + pub fn request_redraw(&self) { + // TODO + } + + pub fn inner_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) + } + + pub fn outer_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) + } + + pub fn set_outer_position(&self, _position: Position) { + // no effect + } + + pub fn inner_size(&self) -> PhysicalSize { + self.outer_size() + } + + pub fn set_inner_size(&self, _size: Size) { + panic!("Cannot set window size on Android"); + } + + pub fn outer_size(&self) -> PhysicalSize { + MonitorHandle.size() + } + + pub fn set_min_inner_size(&self, _: Option) {} + + pub fn set_max_inner_size(&self, _: Option) {} + + pub fn set_title(&self, _title: &str) {} + + pub fn set_visible(&self, _visibility: bool) {} + + pub fn set_resizable(&self, _resizeable: bool) {} + + pub fn set_minimized(&self, _minimized: bool) {} + + pub fn set_maximized(&self, _maximized: bool) {} + + pub fn set_fullscreen(&self, _monitor: Option) { + panic!("Cannot set fullscreen on Android"); + } + + pub fn fullscreen(&self) -> Option { + None + } + + pub fn set_decorations(&self, _decorations: bool) {} + + pub fn set_always_on_top(&self, _always_on_top: bool) {} + + pub fn set_window_icon(&self, _window_icon: Option) {} + + pub fn set_ime_position(&self, _position: Position) {} + + pub fn set_cursor_icon(&self, _: window::CursorIcon) {} + + pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + pub fn set_cursor_visible(&self, _: bool) {} + + pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() { + unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ } + } else { + panic!("native window null"); }; - RawWindowHandle::Android(handle) + let mut handle = raw_window_handle::android::AndroidHandle::empty(); + handle.a_native_window = a_native_window; + raw_window_handle::RawWindowHandle::Android(handle) + } + + pub fn config(&self) -> Configuration { + CONFIG.read().unwrap().clone() + } + + pub fn content_rect(&self) -> Rect { + ndk_glue::content_rect() } } -unsafe impl Send for Window {} -unsafe impl Sync for Window {} +#[derive(Default, Clone, Debug)] +pub struct OsError; -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); +use std::fmt::{self, Display, Formatter}; +impl Display for OsError { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { + write!(fmt, "Android OS Error") + } +} + +pub(crate) use crate::icon::NoIcon as PlatformIcon; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct MonitorHandle; + +impl MonitorHandle { + pub fn name(&self) -> Option { + Some("Android Device".to_owned()) + } + + pub fn size(&self) -> PhysicalSize { + if let Some(native_window) = ndk_glue::native_window().as_ref() { + let width = native_window.width() as _; + let height = native_window.height() as _; + PhysicalSize::new(width, height) + } else { + PhysicalSize::new(0, 0) + } + } + + pub fn position(&self) -> PhysicalPosition { + (0, 0).into() + } + + pub fn scale_factor(&self) -> f64 { + let config = CONFIG.read().unwrap(); + config + .density() + .map(|dpi| dpi as f64 / 160.0) + .unwrap_or(1.0) + } + + pub fn video_modes(&self) -> impl Iterator { + let size = self.size().into(); + let mut v = Vec::new(); + // FIXME this is not the real refresh rate + // (it is guarunteed to support 32 bit color though) + v.push(monitor::VideoMode { + video_mode: VideoMode { + size, + bit_depth: 32, + refresh_rate: 60, + monitor: self.clone(), + }, + }); + v.into_iter() + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct VideoMode { + size: (u32, u32), + bit_depth: u16, + refresh_rate: u16, + monitor: MonitorHandle, +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: self.monitor.clone(), + } + } +}