diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3b78cbde935..8f52bb93f02 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -678,6 +678,27 @@ jobs: - name: Build usecases demo run: cargo apk build -p usecases --target aarch64-linux-android --lib + android_cpp: + needs: files-changed + if: ${{ needs.files-changed.outputs.internal == 'true' || needs.files-changed.outputs.api_cpp == 'true' || needs.files-changed.outputs.examples == 'true' }} + runs-on: ubuntu-latest + env: + CARGO_INCREMENTAL: false + CARGO_PROFILE_DEV_DEBUG: 0 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/install-linux-dependencies + - uses: ./.github/actions/setup-rust + with: + target: aarch64-linux-android + - name: Build Slint and usecases demo for Android (CMake cross-compile) + run: | + cmake -B build-android -S . \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-26 \ + -DSLINT_BUILD_EXAMPLES=ON + cmake --build build-android --target usecases + test-figma-inspector: needs: files-changed if: ${{ needs.files-changed.outputs.figma_inspector == 'true' }} diff --git a/Cargo.lock b/Cargo.lock index 0c26c49ee20..0bfe25f1956 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10103,6 +10103,7 @@ dependencies = [ "cbindgen", "esp-backtrace", "esp-println", + "i-slint-backend-android-activity", "i-slint-backend-selector", "i-slint-backend-testing", "i-slint-common", diff --git a/REUSE.toml b/REUSE.toml index 53aa0e61e52..7db93717628 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -325,3 +325,15 @@ path = ["examples/bevy/bevy-hosts-slint/**.gltf"] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright (c) 2024 e-verse" SPDX-License-Identifier = "MIT" + +[[annotations]] +path = ["examples/**/android/**/gradle-wrapper.properties"] +precedence = "aggregate" +SPDX-FileCopyrightText = "Copyright © SixtyFPS GmbH " +SPDX-License-Identifier = "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0" + +[[annotations]] +path = ["demos/**/android/**/gradle-wrapper.properties"] +precedence = "aggregate" +SPDX-FileCopyrightText = "Copyright © SixtyFPS GmbH " +SPDX-License-Identifier = "MIT" diff --git a/api/cpp/CMakeLists.txt b/api/cpp/CMakeLists.txt index b13947bfa04..6fd64b85c89 100644 --- a/api/cpp/CMakeLists.txt +++ b/api/cpp/CMakeLists.txt @@ -24,6 +24,39 @@ endif () list(PREPEND CMAKE_MODULE_PATH ${Corrosion_SOURCE_DIR}/cmake) find_package(Rust 1.92 REQUIRED MODULE) +# When building for Android (NDK toolchain sets CMAKE_SYSTEM_NAME to "Android"), +# auto-configure the Rust target, enable the android backend, and set sensible defaults. +# This block must run before the feature options are defined so that the forced cache values +# are picked up by define_cargo_feature / define_cargo_dependent_feature. +if(ANDROID) + # Map Android ABI to Rust target triple + if(ANDROID_ABI STREQUAL "arm64-v8a") + set(Rust_CARGO_TARGET "aarch64-linux-android" CACHE STRING "" FORCE) + elseif(ANDROID_ABI STREQUAL "armeabi-v7a") + set(Rust_CARGO_TARGET "armv7-linux-androideabi" CACHE STRING "" FORCE) + elseif(ANDROID_ABI STREQUAL "x86_64") + set(Rust_CARGO_TARGET "x86_64-linux-android" CACHE STRING "" FORCE) + elseif(ANDROID_ABI STREQUAL "x86") + set(Rust_CARGO_TARGET "i686-linux-android" CACHE STRING "" FORCE) + else() + message(FATAL_ERROR "Unsupported ANDROID_ABI: ${ANDROID_ABI}") + endif() + + # Android requires a shared library + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) + + # Android requires the skia renderer and cannot use desktop backends + set(SLINT_FEATURE_RENDERER_SKIA ON CACHE BOOL "" FORCE) + set(SLINT_FEATURE_BACKEND_WINIT OFF CACHE BOOL "" FORCE) + set(SLINT_FEATURE_BACKEND_QT OFF CACHE BOOL "" FORCE) + set(SLINT_FEATURE_RENDERER_FEMTOVG OFF CACHE BOOL "" FORCE) + + # Default to Material style on Android + if(NOT SLINT_STYLE) + set(SLINT_STYLE "material" CACHE STRING "" FORCE) + endif() +endif() + option(BUILD_SHARED_LIBS "Build Slint as shared library" ON) option(SLINT_FEATURE_COMPILER "Enable support for compiling .slint files to C++ ahead of time" ON) add_feature_info(SLINT_FEATURE_COMPILER SLINT_FEATURE_COMPILER "Enable support for compiling .slint files to C++ ahead of time") @@ -306,6 +339,41 @@ if (SLINT_BUILD_RUNTIME) ) endif() + # Propagate Android NDK/SDK paths to the Rust build so that the android-activity + # backend can compile Java sources and produce DEX bytecode. + if(ANDROID) + if(DEFINED ENV{ANDROID_NDK_ROOT}) + set_property( + TARGET slint_cpp + APPEND + PROPERTY CORROSION_ENVIRONMENT_VARIABLES + "ANDROID_NDK_ROOT=$ENV{ANDROID_NDK_ROOT}" + ) + elseif(CMAKE_ANDROID_NDK) + set_property( + TARGET slint_cpp + APPEND + PROPERTY CORROSION_ENVIRONMENT_VARIABLES + "ANDROID_NDK_ROOT=${CMAKE_ANDROID_NDK}" + ) + endif() + if(DEFINED ENV{ANDROID_HOME}) + set_property( + TARGET slint_cpp + APPEND + PROPERTY CORROSION_ENVIRONMENT_VARIABLES + "ANDROID_HOME=$ENV{ANDROID_HOME}" + ) + elseif(CMAKE_ANDROID_SDK) + set_property( + TARGET slint_cpp + APPEND + PROPERTY CORROSION_ENVIRONMENT_VARIABLES + "ANDROID_HOME=${CMAKE_ANDROID_SDK}" + ) + endif() + endif() + if(SLINT_FEATURE_RENDERER_SKIA OR SLINT_FEATURE_RENDERER_SKIA_OPENGL OR SLINT_FEATURE_RENDERER_SKIA_VULKAN) find_program(CLANGCC clang) find_program(CLANGCXX clang++) diff --git a/api/cpp/Cargo.toml b/api/cpp/Cargo.toml index bd2632ee52e..4cc681fd0c5 100644 --- a/api/cpp/Cargo.toml +++ b/api/cpp/Cargo.toml @@ -51,6 +51,7 @@ std = [ "i-slint-core/default", "i-slint-core/image-default-formats", "i-slint-backend-selector", + "i-slint-backend-selector/backend-android-activity", "i-slint-renderer-software?/std", "i-slint-core/raw-window-handle-06", "i-slint-backend-selector/raw-window-handle-06", @@ -74,6 +75,9 @@ esp-backtrace = { version = "0.17.0", features = ["panic-handler", "println"], o esp-println = { version = "0.15.0", default-features = false, features = ["auto", "log-04"], optional = true } unicode-segmentation = { workspace = true } +[target.'cfg(target_os = "android")'.dependencies] +i-slint-backend-android-activity = { workspace = true, features = ["native-activity", "aa-06"] } + [build-dependencies] anyhow = "1.0" cbindgen = { workspace = true } diff --git a/api/cpp/include/private/slint_callbacks.h b/api/cpp/include/private/slint_callbacks.h index 7a9466cddf0..20c46ed39f2 100644 --- a/api/cpp/include/private/slint_callbacks.h +++ b/api/cpp/include/private/slint_callbacks.h @@ -3,10 +3,28 @@ #pragma once #include +#include #include "private/slint_properties_internal.h" namespace slint::private_api { +namespace detail { +// Custom apply implementation to replace std::apply. +// NDK r27's libc++ rejects std::apply with const tuple references in some configurations. +template +decltype(auto) apply_impl(F &&f, Tuple &&t, std::index_sequence) +{ + return std::forward(f)(std::get(std::forward(t))...); +} +template +decltype(auto) apply(F &&f, Tuple &&t) +{ + return apply_impl( + std::forward(f), std::forward(t), + std::make_index_sequence>> {}); +} +} // namespace detail + /// A Callback stores a function pointer with no parameters and no return value. /// It's possible to set that pointer via set_handler() and it can be invoked via call(). This is /// used to implement callbacks in the `.slint` language. @@ -33,9 +51,9 @@ struct Callback cbindgen_private::slint_callback_set_handler( &inner, [](void *user_data, const void *arg, void *ret) { + Tuple args = *reinterpret_cast(arg); *reinterpret_cast(ret) = - std::apply(*reinterpret_cast(user_data), - *reinterpret_cast(arg)); + detail::apply(*reinterpret_cast(user_data), std::move(args)); }, new F(std::move(binding)), [](void *user_data) { delete reinterpret_cast(user_data); }); @@ -77,8 +95,8 @@ struct Callback cbindgen_private::slint_callback_set_handler( &inner, [](void *user_data, const void *arg, void *) { - std::apply(*reinterpret_cast(user_data), - *reinterpret_cast(arg)); + Tuple args = *reinterpret_cast(arg); + detail::apply(*reinterpret_cast(user_data), std::move(args)); }, new F(std::move(binding)), [](void *user_data) { delete reinterpret_cast(user_data); }); diff --git a/api/cpp/include/private/slint_timer.h b/api/cpp/include/private/slint_timer.h index 2886077b641..8dde8fc6efc 100644 --- a/api/cpp/include/private/slint_timer.h +++ b/api/cpp/include/private/slint_timer.h @@ -125,7 +125,7 @@ struct Timer } private: - uint64_t id = 0; + uintptr_t id = 0; }; } // namespace slint diff --git a/api/cpp/lib.rs b/api/cpp/lib.rs index 70d6505802d..4aed2b99d97 100644 --- a/api/cpp/lib.rs +++ b/api/cpp/lib.rs @@ -38,6 +38,22 @@ pub use i_slint_backend_testing; #[cfg(feature = "slint-interpreter")] pub use slint_interpreter; +#[cfg(target_os = "android")] +mod android { + unsafe extern "C" { + fn slint_main(); + } + + #[unsafe(no_mangle)] + fn android_main(app: i_slint_backend_android_activity::AndroidApp) { + i_slint_core::platform::set_platform(alloc::boxed::Box::new( + i_slint_backend_android_activity::AndroidPlatform::new(app), + )) + .unwrap(); + unsafe { slint_main() }; + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_windowrc_init(out: *mut WindowAdapterRcOpaque) { assert_eq!( diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 1ec98d90347..f58ae694b39 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -12,6 +12,7 @@ endif() if (TARGET Slint::slint-compiler) add_subdirectory(printerdemo/cpp/) + add_subdirectory(usecases/cpp/) endif() if (SLINT_FEATURE_INTERPRETER) add_subdirectory(printerdemo/cpp_interpreted/) diff --git a/demos/usecases/cpp/CMakeLists.txt b/demos/usecases/cpp/CMakeLists.txt index 23842bb294c..3bdd636963d 100644 --- a/demos/usecases/cpp/CMakeLists.txt +++ b/demos/usecases/cpp/CMakeLists.txt @@ -8,8 +8,14 @@ if (NOT TARGET Slint::Slint) find_package(Slint REQUIRED) endif() -set(SLINT_STYLE "cosmic-light" CACHE STRING "Style for demo" FORCE) +if(NOT SLINT_STYLE) + set(SLINT_STYLE "cosmic-light" CACHE STRING "Style for demo" FORCE) +endif() -add_executable(usecases main.cpp) +if(ANDROID) + add_library(usecases SHARED main.cpp) +else() + add_executable(usecases main.cpp) +endif() target_link_libraries(usecases PRIVATE Slint::Slint) slint_target_sources(usecases ../ui/app.slint) diff --git a/demos/usecases/cpp/android/build.gradle.kts b/demos/usecases/cpp/android/build.gradle.kts new file mode 100644 index 00000000000..79f84849029 --- /dev/null +++ b/demos/usecases/cpp/android/build.gradle.kts @@ -0,0 +1,38 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +plugins { + id("com.android.application") version "8.7.0" +} + +android { + namespace = "dev.slint.usecases" + compileSdk = 35 + + defaultConfig { + applicationId = "dev.slint.usecases" + // API 26 required for InMemoryDexClassLoader used by the Slint Android backend + minSdk = 26 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + ndk { + // Add other ABIs as needed: "armeabi-v7a", "x86_64", "x86" + abiFilters += "arm64-v8a" + } + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + + externalNativeBuild { + cmake { + path = file("../CMakeLists.txt") + version = "3.22.1" + } + } +} diff --git a/demos/usecases/cpp/android/gradle.properties b/demos/usecases/cpp/android/gradle.properties new file mode 100644 index 00000000000..7b10b045673 --- /dev/null +++ b/demos/usecases/cpp/android/gradle.properties @@ -0,0 +1,4 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + +android.useAndroidX=true diff --git a/demos/usecases/cpp/android/gradle/wrapper/gradle-wrapper.properties b/demos/usecases/cpp/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..4894186e0de --- /dev/null +++ b/demos/usecases/cpp/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/demos/usecases/cpp/android/settings.gradle.kts b/demos/usecases/cpp/android/settings.gradle.kts new file mode 100644 index 00000000000..d990d30bf75 --- /dev/null +++ b/demos/usecases/cpp/android/settings.gradle.kts @@ -0,0 +1,19 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolution { + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "SlintUsecases" diff --git a/demos/usecases/cpp/android/src/main/AndroidManifest.xml b/demos/usecases/cpp/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..99e87f7d018 --- /dev/null +++ b/demos/usecases/cpp/android/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/demos/usecases/cpp/main.cpp b/demos/usecases/cpp/main.cpp index 805009df3d4..bd2be08bcd2 100644 --- a/demos/usecases/cpp/main.cpp +++ b/demos/usecases/cpp/main.cpp @@ -84,7 +84,11 @@ void run() app->run(); } +#ifdef __ANDROID__ +extern "C" void slint_main() +#else int main() +#endif { run(); } diff --git a/docs/astro/src/content/docs/guide/platforms/mobile/android.mdx b/docs/astro/src/content/docs/guide/platforms/mobile/android.mdx index 590d0c86366..f00cb085444 100644 --- a/docs/astro/src/content/docs/guide/platforms/mobile/android.mdx +++ b/docs/astro/src/content/docs/guide/platforms/mobile/android.mdx @@ -1,20 +1,21 @@ --- -// cSpell: ignore xbuild textfields +// cSpell: ignore xbuild textfields abifilters gradlew logcat armeabi androideabi title: Android description: Android Platform Guide --- import LangRefLink from '@slint/common-files/src/components/LangRefLink.astro'; import { Image } from 'astro:assets'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; -:::note[Note] -When developing Slint applications for Android, you can only use Rust as the programming language. -::: - -Also see the documentation of the android module in our Rust API documentation. +Slint supports building Android applications in both Rust and C++. +Also see the android module in the Rust API documentation. ## Project Setup + + + Slint uses the [android-activity crate](https://github.com/rust-mobile/android-activity) as the interface to the operating system, which is re-exported as `slint::android::android_activity`. To get started, follow these steps: @@ -53,6 +54,122 @@ listener by replacing the call to `slint::android::init` with `slint::android::i That's all of the necessary code changes. In the next section, we're going to set up the environment to build the project. + + + +A Slint C++ Android application is a standard CMake project (see the [getting started tutorial](/docs/tutorial/getting-started)) +with an Android-specific Gradle wrapper. The C++ code lives at the project root and works +for both desktop and Android builds. The only Android-specific difference in CMake is building +a shared library instead of an executable: + +```cmake +if(ANDROID) + add_library(app SHARED main.cpp) +else() + add_executable(app main.cpp) +endif() +``` + +When CMake is invoked with the Android NDK toolchain, Slint automatically: +- Detects the Android target and maps the ABI to the correct Rust target +- Enables the Android backend and Skia renderer +- Disables desktop-only backends (winit, Qt) +- Sets the Material style as default + +The `android/` subdirectory contains the Gradle project that wraps the CMake build: + +``` +my-app/ +├── CMakeLists.txt # Builds your app (desktop & Android) +├── main.cpp # Your application code +├── main.slint # Your Slint UI +├── android/ +│ ├── build.gradle.kts # Gradle config: SDK versions, CMake path +│ ├── settings.gradle.kts +│ ├── gradle.properties +│ └── src/main/ +│ └── AndroidManifest.xml +``` + +### android/build.gradle.kts + +```kotlin +plugins { + id("com.android.application") version "8.7.0" +} + +android { + namespace = "dev.slint.app" + compileSdk = 35 + + defaultConfig { + applicationId = "dev.slint.app" + minSdk = 26 + targetSdk = 35 + + ndk { + // Add other ABIs as needed: "armeabi-v7a", "x86_64", "x86" + abiFilters += "arm64-v8a" + } + } + + externalNativeBuild { + cmake { + path = file("../CMakeLists.txt") + version = "3.22.1" + } + } +} +``` + +The minimum SDK is 26 (Android 8.0), which is required by the Slint Android backend. + +### android/src/main/AndroidManifest.xml + +```xml + + + + + + + + + + + + +``` + +The `android:value="app"` must match the CMake library name in `add_library(app SHARED ...)`. + +### Rust Toolchain + +Slint's runtime is written in Rust. Install the Rust toolchain and add the Android target: + +```sh +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +rustup target add aarch64-linux-android +``` + +Add other targets if you need to support additional ABIs: + +```sh +rustup target add armv7-linux-androideabi # armeabi-v7a +rustup target add x86_64-linux-android # x86_64 (emulator) +rustup target add i686-linux-android # x86 (emulator) +``` + + + + ## Android Setup The Android development workflow centers around the `adb` command line tool. Use it to connect to @@ -71,6 +188,28 @@ import androidSdkManager from '/src/assets/android/android_sdk_manager.png'; Also note the SDK location on top, this path might have to be used for the `ANDROID_HOME` environment variable if the build tools can't detect it automatically. + + + +No additional SDK tools need to be installed beyond the default SDK platform. + + + + +In the SDK Manager, also install: +- The Android NDK (under the "SDK Tools" tab) +- CMake (under the "SDK Tools" tab) + +Set up these environment variables: + +```sh +export ANDROID_HOME=$HOME/Android/Sdk +export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/ +``` + + + + Add the `platform-tools` directory to your `PATH` so that the `adb` tool is available on the command line. @@ -139,7 +278,55 @@ import keyboardSettings from '/src/assets/android/android_keyboard_settings.png' Select "Write in textfields" in the list and then disable that feature. This enables the regular virtual keyboard. -### Running the Application +## Writing Your Application + + + + +Define your main function and UI in your `lib.rs`: + +```rs +#[cfg(target_os = "android")] +#[unsafe(no_mangle)] +fn android_main(app: slint::android::AndroidApp) { + slint::android::init(app).unwrap(); + let main_window = MainWindow::new().unwrap(); + main_window.run().unwrap(); +} +``` + +You can also add an Android event +([`android_activity::PollEvent`](https://docs.rs/android-activity/latest/android_activity/enum.PollEvent.html)) +listener by replacing the call to `slint::android::init` with `slint::android::init_with_event_listener`. + + + + +On Android, the entry point is `slint_main` instead of `main`. The Slint runtime provides +the actual `android_main` entry point internally and calls your `slint_main` once the +platform is initialized. + +To share a `main.cpp` between desktop and Android builds, use a preprocessor guard: + +```cpp +#ifdef __ANDROID__ +extern "C" void slint_main() +#else +int main() +#endif +{ + auto window = MainWindow::create(); + window->run(); +} +``` + + + + +## Building and Running + + + There are multiple ways to build, upload and run Android apps written in Rust. This page describes a way using [xbuild](https://github.com/rust-mobile/xbuild). This doesn't use Android Studio at all. @@ -177,12 +364,12 @@ automatically. To recompile and test changes, press ctrl-c and run the same command again. The running version on the device is replaced automatically. -#### Troubleshooting +### Troubleshooting If `x run` doesn't work, run `x doctor` to check if you have all required tools installed and that they're found by xbuild. -### Building +### Distribution There are many ways to distribute Android applications and xbuild supports all of them via `x build`. To get information about the configuration, use `x build --help`. @@ -194,3 +381,66 @@ x build --platform android --arch arm64 --format apk --release ``` The apk is then located in `target/x/release/android/.apk`. + + + + +Connect a device or start an emulator, then: + +```sh +./gradlew installDebug +``` + +This builds the native library (Rust + C++), packages it into an APK, and installs it on the +connected device. + +:::note[Note] +The first build takes longer because it compiles the Slint runtime and Skia renderer from source. +Subsequent builds are faster thanks to Cargo's incremental compilation. +::: + +### View Logs + +```sh +adb logcat -s "slint" +``` + +### Release Builds + +```sh +./gradlew assembleRelease +``` + +The APK is in `app/build/outputs/apk/release/`. + +### Multiple ABIs + +To build for multiple ABIs, add them to `abiFilters` in `build.gradle.kts`: + +```kotlin +ndk { + abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") +} +``` + +Each ABI requires the corresponding Rust target to be installed and triggers a separate Rust +compilation, which increases build time. + +### Troubleshooting + +**"Rust target not installed"**: Run `rustup target add aarch64-linux-android` (or the target +matching your ABI). + +**"ANDROID_NDK_ROOT not set"**: The NDK path must be available to the Rust build. Set it as an +environment variable or ensure the NDK is installed via Android Studio's SDK Manager. + +**Build fails with Skia errors**: The Skia renderer requires `clang` to be available on your +host system. Install it with your system package manager. + +**App crashes on startup**: Check `adb logcat` for errors. Common causes: +- Library name in `AndroidManifest.xml` doesn't match the CMake target name +- Missing `slint_main` function (must be `extern "C"`) +- API level below 26 + + + diff --git a/docs/astro/src/content/docs/guide/platforms/mobile/general.mdx b/docs/astro/src/content/docs/guide/platforms/mobile/general.mdx index 7d3a03c509a..c4c762372ee 100644 --- a/docs/astro/src/content/docs/guide/platforms/mobile/general.mdx +++ b/docs/astro/src/content/docs/guide/platforms/mobile/general.mdx @@ -8,7 +8,7 @@ import Link from '@slint/common-files/src/components/Link.astro'; import { Image } from 'astro:assets'; :::note[Note] -When developing Slint applications for Android or iOS, you can only use Rust as the programming language for now. +Slint supports Rust and C++ for Android development. For iOS, only Rust is supported for now. ::: While Slint is used in the same way for mobile and desktop applications, there are a few things that have to be kept in diff --git a/examples/todo/cpp/CMakeLists.txt b/examples/todo/cpp/CMakeLists.txt index ade30a69709..ac1357f8cfc 100644 --- a/examples/todo/cpp/CMakeLists.txt +++ b/examples/todo/cpp/CMakeLists.txt @@ -12,7 +12,11 @@ add_library(todo_lib STATIC app.cpp) slint_target_sources(todo_lib ../ui/todo.slint NAMESPACE todo_ui) target_link_libraries(todo_lib PUBLIC Slint::Slint) -add_executable(todo main.cpp) +if(ANDROID) + add_library(todo SHARED main.cpp) +else() + add_executable(todo main.cpp) +endif() target_link_libraries(todo PRIVATE todo_lib) if(SLINT_BUILD_TESTING AND SLINT_FEATURE_TESTING AND SLINT_FEATURE_EXPERIMENTAL) diff --git a/examples/todo/cpp/android/app/src/main/AndroidManifest.xml b/examples/todo/cpp/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..1e29c70ff55 --- /dev/null +++ b/examples/todo/cpp/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/examples/todo/cpp/main.cpp b/examples/todo/cpp/main.cpp index 9080b723434..114d3e8fffe 100644 --- a/examples/todo/cpp/main.cpp +++ b/examples/todo/cpp/main.cpp @@ -3,7 +3,11 @@ #include "app.h" +#ifdef __ANDROID__ +extern "C" void slint_main() +#else int main() +#endif { auto state = create_ui(); state.mainWindow->run(); diff --git a/internal/core/items.rs b/internal/core/items.rs index 6161eed59d8..1ce97af5bf4 100644 --- a/internal/core/items.rs +++ b/internal/core/items.rs @@ -1510,7 +1510,7 @@ pub struct ContextMenu { pub popup_id: Cell>, pub enabled: Property, #[cfg(target_os = "android")] - long_press_timer: Cell>, + long_press_timer: crate::timers::Timer, } impl Item for ContextMenu { @@ -1555,10 +1555,9 @@ impl Item for ContextMenu { } #[cfg(target_os = "android")] MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => { - let timer = crate::timers::Timer::default(); let self_weak = _self_rc.downgrade(); let position = *position; - timer.start( + self.long_press_timer.start( crate::timers::TimerMode::SingleShot, WindowInner::from_pub(_window_adapter.window()) .context() @@ -1570,14 +1569,11 @@ impl Item for ContextMenu { self_.show.call(&(LogicalPosition::from_euclid(position),)); }, ); - self.long_press_timer.set(Some(timer)); InputEventResult::GrabMouse } #[cfg(target_os = "android")] MouseEvent::Released { .. } | MouseEvent::Exit => { - if let Some(timer) = self.long_press_timer.take() { - timer.stop(); - } + self.long_press_timer.stop(); InputEventResult::EventIgnored } #[cfg(target_os = "android")] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8712ac520cc..6b8ece407d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3952,6 +3952,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@upsetjs/venn.js@2.0.0': resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} diff --git a/xtask/src/license_headers_check.rs b/xtask/src/license_headers_check.rs index 52ae09b55fc..81daa1c6a86 100644 --- a/xtask/src/license_headers_check.rs +++ b/xtask/src/license_headers_check.rs @@ -542,6 +542,7 @@ static LICENSE_LOCATION_FOR_FILE: LazyLock> ("\\.jpg$", LicenseLocation::NoLicense), ("\\.js$", LicenseLocation::Tag(LicenseTagStyle::cpp_style_comment_style())), ("\\.json$", LicenseLocation::NoLicense), + ("\\.kts$", LicenseLocation::Tag(LicenseTagStyle::cpp_style_comment_style())), ("\\.jsonc$", LicenseLocation::NoLicense), ("\\.license$", LicenseLocation::NoLicense), ("\\.md$", LicenseLocation::NoLicense), @@ -584,6 +585,7 @@ static LICENSE_LOCATION_FOR_FILE: LazyLock> ("\\.yml$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())), ("\\.py$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())), ("\\.pyi$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())), + ("\\.properties$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())), ("\\.proto$", LicenseLocation::Tag(LicenseTagStyle::cpp_style_comment_style())), ("\\.bazelrc$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())), ("MODULE.bazel$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())),