Skip to content

Commit

Permalink
Android: make slint::android public
Browse files Browse the repository at this point in the history
Add a `backend-android-activity-05` feature that enables the
`slint::android` module
  • Loading branch information
ogoffart authored Feb 20, 2024
1 parent f53a051 commit ddebd64
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 42 deletions.
6 changes: 6 additions & 0 deletions api/rs/slint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ backend-linuxkms = ["i-slint-backend-selector/backend-linuxkms", "std"]
## windowing system. (Experimental)
backend-linuxkms-noseat = ["i-slint-backend-selector/backend-linuxkms-noseat", "std"]

## Use the backend based on the [android-activity](https://docs.rs/android-activity) crate. (Using it's native activity feature)
backend-android-activity-05 = ["i-slint-backend-android-activity/native-activity"]

[dependencies]
i-slint-core = { workspace = true }
slint-macros = { workspace = true }
Expand All @@ -177,6 +180,9 @@ log = { version = "0.4.17", optional = true }
# end even then wouldn't work because it can't load fonts
i-slint-renderer-femtovg = { workspace = true, optional = true }

[target.'cfg(target_os = "android")'.dependencies]
i-slint-backend-android-activity = { workspace = true, optional = true }

[dev-dependencies]
slint-build = { path = "../build" }
i-slint-backend-testing = { path = "../../../internal/backends/testing" }
Expand Down
127 changes: 127 additions & 0 deletions api/rs/slint/android.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial

//! Android backend.
//!
//! **Note:** This module is only available on Android with the "backend-android-activity-05" feature
//!
//! Slint uses the [android-activity crate](https://github.com/rust-mobile/android-activity) as a backend.
//!
//! For convenience, Slint re-exports the content of the [`android-activity`](https://docs.rs/android-activity) under `slint::android::android_activity`.
//!
//! As with every application using the android-activity crate, the entry point to your app will be the `android_main` function.
//! From that function, you can call [`slint::android::init`](init()) or [`slint::android::init_with_event_listener`](init_with_event_listener)
//!
//! # Example
//!
//! This is a basic example of an Android application.
//! Do not forget the `#[no_mangle]`
//!
//! ```rust
//! # #[cfg(target_os = "android")]
//! #[no_mangle]
//! fn android_main(app: slint::android::AndroidApp) {
//! slint::android::init(app).unwrap();
//!
//! // ... rest of your code ...
//! slint::slint!{
//! export component MainWindow inherits Window {
//! Text { text: "Hello World"; }
//! }
//! }
//! MainWindow::new().unwrap().run().unwrap();
//! }
//! ```
//!
//! That function must be in a `cdylib` library, and you should enable the "backend-android-activity-05"`
//! feature of the slint crate in your Cargo.toml:
//!
//! ```toml
//! [lib]
//! crate-type = ["cdylib"]
//!
//! [dependencies]
//! slint = { version = "1.5", features = ["backend-android-activity-05"] }
//! ```
//!
//! ## Building and Deploying
//!
//! To build and deploy your application, we suggest the usage of [cargo-apk](https://github.com/rust-mobile/cargo-apk),
//! a cargo subcommand that allows you to build, sign, and deploy Android APKs made in Rust.
//!
//! You can install it and use it with the following command:
//!
//! ```sh
//! cargo install cargo-apk
//! cargo apk run --target aarch64-linux-android --lib
//! ```
//!
//! Please ensure that you have the Android NDK and SDK installed and properly set up in your development environment for the above command to work as expected.
//! For detailed instructions on how to set up the Android NDK and SDK, please refer to the [Android Developer's guide](https://developer.android.com/studio/projects/install-ndk).
//! The `ANDROID_HOME` and `ANDROID_NDK_ROOT` environment variable need to be set to the right path.
/// Re-export of the android-activity crate.
#[cfg(all(target_os = "android", feature = "backend-android-activity-05"))]
pub use i_slint_backend_android_activity::android_activity;

#[cfg(not(all(target_os = "android", feature = "backend-android-activity-05")))]
/// Re-export of the [android-activity](https://docs.rs/android-activity) crate.
pub mod android_activity {
#[doc(hidden)]
pub struct AndroidApp;
#[doc(hidden)]
pub struct PollEvent<'a>(&'a ());
}

/// Re-export of AndroidApp from the [android-activity](https://docs.rs/android-activity) crate.
#[doc(no_inline)]
pub use android_activity::AndroidApp;

use crate::platform::SetPlatformError;

/// Initializes the Android backend.
///
/// **Note:** This function is only available on Android with the "backend-android-activity-05" feature
///
/// This function must be called from the `android_main` function before any call to Slint that needs a backend.
///
/// See the [module documentation](self) for an example on how to create Android application.
///
/// See also [`init_with_event_listener`]
pub fn init(app: android_activity::AndroidApp) -> Result<(), SetPlatformError> {
crate::platform::set_platform(Box::new(i_slint_backend_android_activity::AndroidPlatform::new(
app,
)))
}

/// Similar to [`init()`], which allow to listen to android-activity's event
///
/// **Note:** This function is only available on Android with the "backend-android-activity-05" feature
///
/// The listener argument is a function that takes a [`android_activity::PollEvent`](https://docs.rs/android-activity/latest/android_activity/enum.PollEvent.html)
///
/// # Example
///
/// ```rust
/// # #[cfg(target_os = "android")]
/// #[no_mangle]
/// fn android_main(app: slint::android_activity::AndroidApp) {
/// slint::android_init_with_event_listener(
/// app,
/// |event| { eprintln!("got event {event:?}") }
/// ).unwrap();
///
/// // ... rest of your application ...
///
/// }
/// ```
///
/// Check out the [module documentation](self) for a more complete example on how to write an android application
pub fn init_with_event_listener(
app: android_activity::AndroidApp,
listener: impl Fn(&android_activity::PollEvent<'_>) + 'static,
) -> Result<(), SetPlatformError> {
crate::platform::set_platform(Box::new(
i_slint_backend_android_activity::AndroidPlatform::new_with_event_listener(app, listener),
))
}
3 changes: 3 additions & 0 deletions api/rs/slint/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ pub mod platform {
}
}

#[cfg(any(doc, all(target_os = "android", feature = "backend-android-activity-05")))]
pub mod android;

/// Helper type that helps checking that the generated code is generated for the right version
#[doc(hidden)]
#[allow(non_camel_case_types)]
Expand Down
20 changes: 11 additions & 9 deletions examples/printerdemo/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,27 @@ path = "main.rs"
name = "printerdemo"

[dependencies]
slint = { path = "../../../api/rs/slint" }
slint = { path = "../../../api/rs/slint", features = ["backend-android-activity-05"] }

chrono = { version = "0.4", default-features = false, features = ["clock", "std"]}

[target.'cfg(target_os = "android")'.dependencies]
i-slint-backend-android-activity = { workspace = true, features = ["native-activity"] }

[build-dependencies]
slint-build = { path = "../../../api/rs/build" }

# Remove the `#wasm#` to uncomment the wasm build.
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
web-sys = { version = "0.3", features=["console"] }
console_error_panic_hook = "0.1.5"

# Remove the `#wasm#` to uncomment the wasm or android build.
# This is commented out by default because we don't want to build it as a library by default
# The CI has a script that does sed "s/#wasm# //" to generate the wasm build.

#wasm# [lib]
#wasm# path = "main.rs"
#wasm# crate-type = ["cdylib"]

#wasm# [target.'cfg(target_arch = "wasm32")'.dependencies]
#wasm# wasm-bindgen = { version = "0.2" }
#wasm# web-sys = { version = "0.3", features=["console"] }
#wasm# console_error_panic_hook = "0.1.5"




7 changes: 2 additions & 5 deletions examples/printerdemo/rust/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,7 @@ pub fn main() {

#[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();
fn android_main(app: slint::android::AndroidApp) {
slint::android::init(app).unwrap();
main()
}
5 changes: 1 addition & 4 deletions examples/todo/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ path = "main.rs"
name = "todo"

[dependencies]
slint = { path = "../../../api/rs/slint", features = ["serde"] }
slint = { path = "../../../api/rs/slint", features = ["serde", "backend-android-activity-05"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[target.'cfg(target_os = "android")'.dependencies]
i-slint-backend-android-activity = { workspace = true, features = ["native-activity"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
console_error_panic_hook = "0.1.5"
Expand Down
46 changes: 22 additions & 24 deletions examples/todo/rust/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,30 +106,28 @@ pub fn main() {

#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: i_slint_backend_android_activity::AndroidApp) {
use i_slint_backend_android_activity::android_activity::{MainEvent, PollEvent};
slint::platform::set_platform(Box::new(
i_slint_backend_android_activity::AndroidPlatform::new_with_event_listener(app, |event| {
match event {
PollEvent::Main(MainEvent::SaveState { saver, .. }) => {
STATE.with(|state| -> Option<()> {
let todo_state = SerializedState::save(state.borrow().as_ref()?);
saver.store(&serde_json::to_vec(&todo_state).ok()?);
Some(())
});
}
PollEvent::Main(MainEvent::Resume { loader, .. }) => {
STATE.with(|state| -> Option<()> {
let bytes: Vec<u8> = loader.load()?;
let todo_state: SerializedState = serde_json::from_slice(&bytes).ok()?;
todo_state.restore(state.borrow().as_ref()?);
Some(())
});
}
_ => {}
};
}),
))
fn android_main(app: slint::android::AndroidApp) {
use slint::android::android_activity::{MainEvent, PollEvent};
slint::android::init_with_event_listener(app, |event| {
match event {
PollEvent::Main(MainEvent::SaveState { saver, .. }) => {
STATE.with(|state| -> Option<()> {
let todo_state = SerializedState::save(state.borrow().as_ref()?);
saver.store(&serde_json::to_vec(&todo_state).ok()?);
Some(())
});
}
PollEvent::Main(MainEvent::Resume { loader, .. }) => {
STATE.with(|state| -> Option<()> {
let bytes: Vec<u8> = loader.load()?;
let todo_state: SerializedState = serde_json::from_slice(&bytes).ok()?;
todo_state.restore(state.borrow().as_ref()?);
Some(())
});
}
_ => {}
};
})
.unwrap();
main();
}
Expand Down

0 comments on commit ddebd64

Please sign in to comment.