use crate::window::AndroidWindow;
use blinc_platform::{
ControlFlow, Event, EventLoop, LifecycleEvent, PlatformError, Window, WindowEvent, WindowId,
};
#[cfg(target_os = "android")]
use android_activity::{AndroidApp, MainEvent, PollEvent};
#[cfg(target_os = "android")]
use ndk::looper::ForeignLooper;
#[cfg(target_os = "android")]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_os = "android")]
use std::sync::Arc;
#[cfg(target_os = "android")]
use std::time::Duration;
#[cfg(target_os = "android")]
use tracing::{debug, info, warn};
#[cfg(target_os = "android")]
#[derive(Clone)]
pub struct AndroidWakeProxy {
looper: ForeignLooper,
wake_requested: Arc<AtomicBool>,
}
#[cfg(target_os = "android")]
impl AndroidWakeProxy {
pub fn new() -> Option<Self> {
ForeignLooper::for_thread().map(|looper| Self {
looper,
wake_requested: Arc::new(AtomicBool::new(false)),
})
}
pub fn wake(&self) {
self.wake_requested.store(true, Ordering::SeqCst);
self.looper.wake();
}
pub fn take_wake_request(&self) -> bool {
self.wake_requested.swap(false, Ordering::SeqCst)
}
}
#[cfg(not(target_os = "android"))]
#[derive(Clone)]
pub struct AndroidWakeProxy;
#[cfg(not(target_os = "android"))]
impl AndroidWakeProxy {
pub fn new() -> Option<Self> {
None
}
pub fn wake(&self) {}
pub fn take_wake_request(&self) -> bool {
false
}
}
#[cfg(target_os = "android")]
pub struct AndroidEventLoop {
app: AndroidApp,
wake_proxy: Option<AndroidWakeProxy>,
}
#[cfg(target_os = "android")]
impl AndroidEventLoop {
pub fn new(app: AndroidApp) -> Self {
let wake_proxy = AndroidWakeProxy::new();
if wake_proxy.is_none() {
warn!("Failed to create AndroidWakeProxy - animations may not wake event loop");
}
Self { app, wake_proxy }
}
pub fn wake_proxy(&self) -> Option<AndroidWakeProxy> {
self.wake_proxy.clone()
}
}
#[cfg(target_os = "android")]
impl EventLoop for AndroidEventLoop {
type Window = AndroidWindow;
fn run<F>(self, mut handler: F) -> Result<(), PlatformError>
where
F: FnMut(Event, &Self::Window) -> ControlFlow + 'static,
{
let mut window: Option<AndroidWindow> = None;
let mut should_exit = false;
while !should_exit {
self.app
.poll_events(Some(Duration::from_millis(16)), |event| match event {
PollEvent::Main(main_event) => match main_event {
MainEvent::InitWindow { .. } => {
info!("Android: Native window initialized");
if let Some(native) = self.app.native_window() {
let w = native.width();
let h = native.height();
info!("Android: Window size {}x{}", w, h);
window = Some(AndroidWindow::new(native));
}
}
MainEvent::TerminateWindow { .. } => {
info!("Android: Native window terminated");
window = None;
}
MainEvent::WindowResized { .. } => {
if let Some(ref win) = window {
let (width, height) = win.size();
info!("Android: Window resized to {}x{}", width, height);
let flow = handler(
Event::Window(
WindowId::PRIMARY,
WindowEvent::Resized { width, height },
),
win,
);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
}
MainEvent::GainedFocus => {
debug!("Android: Gained focus");
if let Some(ref win) = window {
win.set_focused(true);
let flow = handler(
Event::Window(WindowId::PRIMARY, WindowEvent::Focused(true)),
win,
);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
}
MainEvent::LostFocus => {
debug!("Android: Lost focus");
if let Some(ref win) = window {
win.set_focused(false);
let flow = handler(
Event::Window(WindowId::PRIMARY, WindowEvent::Focused(false)),
win,
);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
}
MainEvent::Resume { .. } => {
info!("Android: Resumed");
if let Some(ref win) = window {
let flow = handler(Event::Lifecycle(LifecycleEvent::Resumed), win);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
}
MainEvent::Pause => {
info!("Android: Paused");
if let Some(ref win) = window {
let flow =
handler(Event::Lifecycle(LifecycleEvent::Suspended), win);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
}
MainEvent::Destroy => {
info!("Android: Destroyed");
if let Some(ref win) = window {
win.set_running(false);
let flow = handler(
Event::Window(WindowId::PRIMARY, WindowEvent::CloseRequested),
win,
);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
should_exit = true;
}
MainEvent::LowMemory => {
warn!("Android: Low memory");
if let Some(ref win) = window {
let flow =
handler(Event::Lifecycle(LifecycleEvent::LowMemory), win);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
}
_ => {}
},
_ => {}
});
let wake_requested = self
.wake_proxy
.as_ref()
.map(|p| p.take_wake_request())
.unwrap_or(false);
if let Some(ref win) = window {
if win.is_focused() || wake_requested {
let flow = handler(Event::Frame(WindowId::PRIMARY), win);
if flow == ControlFlow::Exit {
should_exit = true;
}
}
}
}
info!("Android: Event loop exiting");
Ok(())
}
}
#[cfg(not(target_os = "android"))]
#[derive(Default)]
pub struct AndroidEventLoop {
_private: (),
}
#[cfg(not(target_os = "android"))]
impl AndroidEventLoop {
pub fn new() -> Self {
Self::default()
}
}
#[cfg(not(target_os = "android"))]
impl EventLoop for AndroidEventLoop {
type Window = AndroidWindow;
fn run<F>(self, _handler: F) -> Result<(), PlatformError>
where
F: FnMut(Event, &Self::Window) -> ControlFlow + 'static,
{
Err(PlatformError::Unsupported(
"Android platform only available on Android".to_string(),
))
}
}