Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 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
21 changes: 21 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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' }}
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <info@slint.dev>"
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 <info@slint.dev>"
SPDX-License-Identifier = "MIT"
68 changes: 68 additions & 0 deletions api/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if these defaults could be set at the source instead of here with these kind of ugly overrides. We're in the right file, aren't we?:)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Although these are not default but mandatory features

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment to say "Android requires the skia renderer and cannot use desktop backends".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could integrate it into the define_cargo_dependent_feature calls by adding AND NOT ANDROID to the depends_condition for winit/qt/femtovg, and using if(ANDROID) to flip the renderer-skia default to ON. The issue is that these are mandatory for Android, not just defaults — with the current approach, FORCE prevents the user from accidentally enabling winit on Android. With the depends_condition approach, we get the same effect since cmake_dependent_option forces the value OFF when the condition is false.

Want me to try that approach? It would replace the FORCE overrides with conditions on the feature definitions themselves.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If mandatory perhaps that should become an option in the macro/function. But IMO it's sufficient if defaults are on/off correctly and it's all in one place.

My concern is that if we remove/rename the feature we need to look at multiple places.

(Not a super big issue but worth research if it can be done better)


# 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")
Expand Down Expand Up @@ -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++)
Expand Down
4 changes: 4 additions & 0 deletions api/cpp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 }
Expand Down
26 changes: 22 additions & 4 deletions api/cpp/include/private/slint_callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,28 @@

#pragma once
#include <tuple>
#include <utility>
#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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? is that some specific version of libc++? is there some issue for that?
Could there be another fix that doesn't need this workaround?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an issue with NDK r27's libc++ (the version shipped with the current LTS NDK). When std::apply is called with a const std::tuple&, it fails to compile because their implementation tries to use std::get on a non-forwarding reference in a way that triggers a substitution failure.

I don't have a specific upstream issue link for this — I hit it during testing with NDK r27 (27.2.12829862). The custom detail::apply is a straightforward reimplementation that avoids the problematic code path.

An alternative would be to avoid passing const tuple refs to std::apply in the call sites, but that would require changing the Callback implementation more invasively. This seemed like the least disruptive fix. Happy to explore other approaches if you'd prefer.

template<typename F, typename Tuple, std::size_t... I>
decltype(auto) apply_impl(F &&f, Tuple &&t, std::index_sequence<I...>)
{
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename Tuple>
decltype(auto) apply(F &&f, Tuple &&t)
{
return apply_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>> {});
}
Comment thread
ruminorix marked this conversation as resolved.
} // 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.
Expand All @@ -33,9 +51,9 @@ struct Callback<Ret(Arg...)>
cbindgen_private::slint_callback_set_handler(
&inner,
[](void *user_data, const void *arg, void *ret) {
Tuple args = *reinterpret_cast<const Tuple *>(arg);
*reinterpret_cast<Ret *>(ret) =
std::apply(*reinterpret_cast<F *>(user_data),
*reinterpret_cast<const Tuple *>(arg));
detail::apply(*reinterpret_cast<F *>(user_data), std::move(args));
},
new F(std::move(binding)),
[](void *user_data) { delete reinterpret_cast<F *>(user_data); });
Expand Down Expand Up @@ -77,8 +95,8 @@ struct Callback<void(Arg...)>
cbindgen_private::slint_callback_set_handler(
&inner,
[](void *user_data, const void *arg, void *) {
std::apply(*reinterpret_cast<F *>(user_data),
*reinterpret_cast<const Tuple *>(arg));
Tuple args = *reinterpret_cast<const Tuple *>(arg);
detail::apply(*reinterpret_cast<F *>(user_data), std::move(args));
},
new F(std::move(binding)),
[](void *user_data) { delete reinterpret_cast<F *>(user_data); });
Expand Down
2 changes: 1 addition & 1 deletion api/cpp/include/private/slint_timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ struct Timer
}

private:
uint64_t id = 0;
uintptr_t id = 0;
};

} // namespace slint
16 changes: 16 additions & 0 deletions api/cpp/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
1 change: 1 addition & 0 deletions demos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ endif()

if (TARGET Slint::slint-compiler)
add_subdirectory(printerdemo/cpp/)
add_subdirectory(usecases/cpp/)
Comment thread
ruminorix marked this conversation as resolved.
endif()
if (SLINT_FEATURE_INTERPRETER)
add_subdirectory(printerdemo/cpp_interpreted/)
Expand Down
16 changes: 13 additions & 3 deletions demos/usecases/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ cmake_minimum_required(VERSION 3.21)
project(slint_cpp_usecases LANGUAGES CXX)

if (NOT TARGET Slint::Slint)
find_package(Slint REQUIRED)
if(SLINT_SOURCE_DIR)
Comment thread
ruminorix marked this conversation as resolved.
Outdated
add_subdirectory(${SLINT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/slint_build)
else()
find_package(Slint REQUIRED)
endif()
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)
38 changes: 38 additions & 0 deletions demos/usecases/cpp/android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// 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"
}
}
}
4 changes: 4 additions & 0 deletions demos/usecases/cpp/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright © SixtyFPS GmbH <info@slint.dev>
# SPDX-License-Identifier: MIT

android.useAndroidX=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright © SixtyFPS GmbH <info@slint.dev>
# SPDX-License-Identifier: MIT

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a maintenance point of view, i wonder if this is good to have this kind of file repeated for all our example. And when do we need to update this and other stuff. Could you suggest easier method than having all these files?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main files that get duplicated are: gradle-wrapper.properties (pinned gradle version), gradle.properties (just android.useAndroidX=true), settings.gradle.kts (project name), and build.gradle.kts (AGP plugin version). These are fairly stable — gradle version bumps happen infrequently.

One option to reduce duplication: a shared top-level android/ directory with a single gradle project that builds all examples as subprojects, each referencing their root CMakeLists.txt. This would consolidate the gradle wrapper and AGP version in one place. Want me to try that approach?

zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
19 changes: 19 additions & 0 deletions demos/usecases/cpp/android/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT

pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}

dependencyResolution {
repositories {
google()
mavenCentral()
}
}

rootProject.name = "SlintUsecases"
22 changes: 22 additions & 0 deletions demos/usecases/cpp/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> -->
<!-- SPDX-License-Identifier: MIT -->

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="Slint Usecases"
android:hasCode="false">
<activity
android:name="android.app.NativeActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden">
<meta-data
android:name="android.app.lib_name"
android:value="usecases" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
4 changes: 4 additions & 0 deletions demos/usecases/cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ void run()
app->run();
}

#ifdef __ANDROID__
extern "C" void slint_main()
#else
int main()
#endif
{
run();
}
Loading
Loading