// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 #![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> = RefCell::new(Default::default()); } pub struct AndroidPlatform { app: AndroidApp, window: Rc, event_listener: Option)>>, } impl AndroidPlatform { /// Instantiate a new Android backend given the [`android_activity::AndroidApp`] /// /// Pass the returned value to [`slint::platform::set_platform()`](`i_slint_core::platform::set_platform()`) /// /// # Example /// ``` /// #[cfg(target_os = "android")] /// #[no_mangle] /// fn android_main(app: i_slint_backend_android_activity::AndroidApp) { /// slint::platform::set_platform(Box::new( /// i_slint_backend_android_activity::AndroidPlatform::new(app), /// )) /// .unwrap(); /// // ... your slint application ... /// } /// ``` pub fn new(app: AndroidApp) -> Self { let window = AndroidWindowAdapter::new(app.clone()); CURRENT_WINDOW.set(Rc::downgrade(&window)); Self { app, window, event_listener: None } } /// Instantiate a new Android backend given the [`android_activity::AndroidApp`] /// and a function to process the events. /// /// This is the same as [`AndroidPlatform::new()`], but it allow you to get notified /// of events. /// /// Pass the returned value to [`slint::platform::set_platform()`](`i_slint_core::platform::set_platform()`) /// /// # Example /// ``` /// #[cfg(target_os = "android")] /// #[no_mangle] /// fn android_main(app: i_slint_backend_android_activity::AndroidApp) { /// slint::platform::set_platform(Box::new( /// i_slint_backend_android_activity::AndroidPlatform::new_with_event_listener( /// app, /// |event| { eprintln!("got event {event:?}") } /// ), /// )) /// .unwrap(); /// // ... your slint application ... /// } /// ``` 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, 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() { // FIXME: we should not hardcode a value here 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 matches!(r.map_err(|e| PlatformError::from(e.to_string()))?, ControlFlow::Break(())) { return Ok(()); } if self.window.pending_redraw.take() { self.window.do_render()?; } } } fn new_event_loop_proxy(&self) -> Option> { 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 { 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), } type EventQueue = Arc>>; 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, ) -> Result<(), EventLoopError> { self.event_queue.lock().unwrap().push(Event::Other(event)); self.waker.wake(); Ok(()) } }