Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android: make slint::android public #4616

Merged
merged 3 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-export the content of the [`android-activity`](https://docs.rs/android-activity) under `slint::android::android_activity`.
//!
//! As 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
Loading