#![doc = include_str!("README.md")]
#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
#![cfg_attr(not(target_os = "android"), allow(rustdoc::broken_intra_doc_links))]
#![cfg(target_os = "android")]
mod androidwindowadapter;
mod javahelper;
#[cfg(all(not(feature = "aa-06"), feature = "aa-05"))]
pub use android_activity_05 as android_activity;
#[cfg(feature = "aa-06")]
pub use android_activity_06 as android_activity;
pub use android_activity::AndroidApp;
use android_activity::PollEvent;
use androidwindowadapter::AndroidWindowAdapter;
use core::ops::ControlFlow;
use i_slint_core::api::{EventLoopError, PlatformError};
use i_slint_core::platform::{Clipboard, WindowAdapter};
use i_slint_renderer_skia::SkiaRendererExt;
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
thread_local! {
static CURRENT_WINDOW: RefCell<Weak<AndroidWindowAdapter>> = RefCell::new(Default::default());
}
pub struct AndroidPlatform {
app: AndroidApp,
window: Rc<AndroidWindowAdapter>,
event_listener: Option<Box<dyn Fn(&PollEvent<'_>)>>,
}
impl AndroidPlatform {
pub fn new(app: AndroidApp) -> Self {
let window = AndroidWindowAdapter::new(app.clone());
CURRENT_WINDOW.set(Rc::downgrade(&window));
Self { app, window, event_listener: None }
}
pub fn new_with_event_listener(
app: AndroidApp,
listener: impl Fn(&PollEvent<'_>) + 'static,
) -> Self {
let mut this = Self::new(app);
this.event_listener = Some(Box::new(listener));
this
}
}
impl i_slint_core::platform::Platform for AndroidPlatform {
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
Ok(self.window.clone())
}
fn run_event_loop(&self) -> Result<(), PlatformError> {
loop {
let mut timeout = i_slint_core::platform::duration_until_next_timer_update();
if self.window.window.has_active_animations() {
let frame_duration = std::time::Duration::from_millis(10);
timeout = Some(match timeout {
Some(x) => x.min(frame_duration),
None => frame_duration,
})
}
let mut r = Ok(ControlFlow::Continue(()));
self.app.poll_events(timeout, |e| {
i_slint_core::platform::update_timers_and_animations();
r = self.window.process_event(&e);
if let Some(event_listener) = &self.event_listener {
event_listener(&e)
}
});
if r?.is_break() {
break;
}
if self.window.pending_redraw.take() {
self.window.do_render()?;
}
}
Ok(())
}
fn new_event_loop_proxy(&self) -> Option<Box<dyn i_slint_core::platform::EventLoopProxy>> {
Some(Box::new(AndroidEventLoopProxy {
event_queue: self.window.event_queue.clone(),
waker: self.app.create_waker(),
}))
}
fn set_clipboard_text(&self, text: &str, clipboard: Clipboard) {
if clipboard == Clipboard::DefaultClipboard {
self.window
.java_helper
.set_clipboard(text)
.unwrap_or_else(|e| javahelper::print_jni_error(&self.app, e));
}
}
fn clipboard_text(&self, clipboard: Clipboard) -> Option<String> {
if clipboard == Clipboard::DefaultClipboard {
Some(
self.window
.java_helper
.get_clipboard()
.unwrap_or_else(|e| javahelper::print_jni_error(&self.app, e)),
)
} else {
None
}
}
}
enum Event {
Quit,
Other(Box<dyn FnOnce() + Send + 'static>),
}
type EventQueue = Arc<Mutex<Vec<Event>>>;
struct AndroidEventLoopProxy {
event_queue: EventQueue,
waker: android_activity::AndroidAppWaker,
}
impl i_slint_core::platform::EventLoopProxy for AndroidEventLoopProxy {
fn quit_event_loop(&self) -> Result<(), EventLoopError> {
self.event_queue.lock().unwrap().push(Event::Quit);
self.waker.wake();
Ok(())
}
fn invoke_from_event_loop(
&self,
event: Box<dyn FnOnce() + Send>,
) -> Result<(), EventLoopError> {
self.event_queue.lock().unwrap().push(Event::Other(event));
self.waker.wake();
Ok(())
}
}