Skip to content

Compose UI for native Linux #2027

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

Draft
wants to merge 4 commits into
base: jb-main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 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
4 changes: 1 addition & 3 deletions annotation/annotation-compatibility-stub/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ androidXComposeMultiplatform {
js()
wasm()
darwin()

linuxX64()
linuxArm64()
linux()
}

kotlin {
Expand Down
4 changes: 1 addition & 3 deletions annotation/annotation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ androidXComposeMultiplatform {
js()
wasm()
darwin()

linuxX64()
linuxArm64()
linux()
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,7 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
iosArm64("uikitArm64")
iosSimulatorArm64("uikitSimArm64")

val commonMain = sourceSets.getByName("commonMain")
val nativeMain = sourceSets.create("nativeMain")
val nativeMain = getOrCreateNativeMain()
val darwinMain = sourceSets.create("darwinMain")
val macosMain = sourceSets.create("macosMain")
val macosX64Main = sourceSets.getByName("macosX64Main")
Expand All @@ -200,7 +199,6 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
val uikitX64Main = sourceSets.getByName("uikitX64Main")
val uikitArm64Main = sourceSets.getByName("uikitArm64Main")
val uikitSimArm64Main = sourceSets.getByName("uikitSimArm64Main")
nativeMain.dependsOn(commonMain)
darwinMain.dependsOn(nativeMain)
macosMain.dependsOn(darwinMain)
macosX64Main.dependsOn(macosMain)
Expand All @@ -210,8 +208,7 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
uikitArm64Main.dependsOn(uikitMain)
uikitSimArm64Main.dependsOn(uikitMain)

val commonTest = sourceSets.getByName("commonTest")
val nativeTest = sourceSets.create("nativeTest")
val nativeTest = getOrCreateNativeTest()
val darwinTest = sourceSets.create("darwinTest")
val macosTest = sourceSets.create("macosTest")
val macosX64Test = sourceSets.getByName("macosX64Test")
Expand All @@ -220,7 +217,6 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
val uikitX64Test = sourceSets.getByName("uikitX64Test")
val uikitArm64Test = sourceSets.getByName("uikitArm64Test")
val uikitSimArm64Test = sourceSets.getByName("uikitSimArm64Test")
nativeTest.dependsOn(commonTest)
darwinTest.dependsOn(nativeTest)
macosTest.dependsOn(darwinTest)
macosX64Test.dependsOn(macosTest)
Expand All @@ -231,12 +227,25 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
uikitSimArm64Test.dependsOn(uikitTest)
}

override fun linuxX64(): Unit = multiplatformExtension.run {
override fun linux(): Unit = multiplatformExtension.run {
linuxX64()
}

override fun linuxArm64(): Unit = multiplatformExtension.run {
linuxArm64()

val nativeMain = getOrCreateNativeMain()
val linuxMain = sourceSets.create("linuxMain")
val linuxX64Main = sourceSets.getByName("linuxX64Main")
val linuxArm64Main = sourceSets.getByName("linuxArm64Main")
linuxMain.dependsOn(nativeMain)
linuxX64Main.dependsOn(linuxMain)
linuxArm64Main.dependsOn(linuxMain)

val nativeTest = getOrCreateNativeTest()
val linuxTest = sourceSets.create("linuxTest")
val linuxX64Test = sourceSets.getByName("linuxX64Test")
val linuxArm64Test = sourceSets.getByName("linuxArm64Test")
linuxTest.dependsOn(nativeTest)
linuxX64Test.dependsOn(linuxTest)
linuxArm64Test.dependsOn(linuxTest)
}

private fun getOrCreateJvmMain(): KotlinSourceSet =
Expand All @@ -245,6 +254,12 @@ open class AndroidXComposeMultiplatformExtensionImpl @Inject constructor(
private fun getOrCreateJvmTest(): KotlinSourceSet =
getOrCreateSourceSet("jvmTest", "commonTest")

private fun getOrCreateNativeMain(): KotlinSourceSet =
getOrCreateSourceSet("nativeMain", "commonMain")

private fun getOrCreateNativeTest(): KotlinSourceSet =
getOrCreateSourceSet("nativeTest", "commonTest")

private fun getOrCreateSourceSet(
name: String,
dependsOnSourceSetName: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,9 @@ abstract class AndroidXComposeMultiplatformExtension {

/**
* Provides the default target configuration and source set dependencies
* for all linuxX64 native targets.
* for all linux native targets.
*/
abstract fun linuxX64(): Unit

/**
* Provides the default target configuration and source set dependencies
* for all linuxArm64 native targets.
*/
abstract fun linuxArm64(): Unit
abstract fun linux(): Unit

/**
* Configures native compilation tasks with flags to link required frameworks
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/ComposePlatforms.kt
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ enum class ComposePlatforms(vararg val alternativeNames: String) {
val GENERATE_KLIB = WEB + LINUX_NATIVE + WINDOWS_NATIVE + DARWIN

val SKIKO_SUPPORT =
EnumSet.of(KotlinMultiplatform) + JVM_BASED + UI_KIT + MACOS_NATIVE + WEB
EnumSet.of(KotlinMultiplatform) + JVM_BASED + UI_KIT + MACOS_NATIVE + LINUX_NATIVE + WEB

val ALL = EnumSet.allOf(ComposePlatforms::class.java) - IOS
val ALL_AOSP = EnumSet.allOf(ComposePlatforms::class.java) - UI_KIT
Expand Down
4 changes: 1 addition & 3 deletions collection/collection-compatibility-stub/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ androidXComposeMultiplatform {
js()
wasm()
darwin()

linuxX64()
linuxArm64()
linux()
}

kotlin {
Expand Down
4 changes: 1 addition & 3 deletions collection/collection/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ androidXComposeMultiplatform {
js()
wasm()
darwin()

linuxX64()
linuxArm64()
linux()
}

kotlin {
Expand Down
1 change: 1 addition & 0 deletions compose/animation/animation-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down
1 change: 1 addition & 0 deletions compose/animation/animation-graphics/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down
1 change: 1 addition & 0 deletions compose/animation/animation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down
2 changes: 2 additions & 0 deletions compose/foundation/foundation-layout/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down Expand Up @@ -122,6 +123,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
desktopMain.dependsOn(notMobileMain)
macosMain.dependsOn(notMobileMain)
jsWasmMain.dependsOn(notMobileMain)
linuxMain.dependsOn(notMobileMain)

jsMain {
dependsOn(jsWasmMain)
Expand Down
1 change: 1 addition & 0 deletions compose/foundation/foundation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()

configureDarwinFlags()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation

// TODO: b/168524931 - should this depend on the input device?
internal actual val TapIndicationDelay: Long = 0L
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalAccessorScope

@Composable
internal actual fun rememberPlatformOverscrollEffect(): OverscrollEffect? =
null

internal actual fun CompositionLocalAccessorScope.defaultOverscrollFactory(): OverscrollFactory? =
null
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation

internal actual fun isRequestFocusOnClickEnabled(): Boolean = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.gestures

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFold
import kotlin.math.sqrt

internal actual fun CompositionLocalConsumerModifierNode.platformScrollConfig(): ScrollConfig =
LinuxScrollConfig

private object LinuxScrollConfig : ScrollConfig {
// See https://developer.apple.com/documentation/appkit/nsevent/1535387-scrollingdeltay
override fun Density.calculateMouseWheelScroll(event: PointerEvent, bounds: IntSize): Offset {
// 64 dp value is taken from ViewConfiguration.java, replace with better solution

val verticalScrollFactor = -64.dp.toPx()

val horizontalScrollFactor = -64.dp.toPx()

return event.changes
.fastFold(Offset.Zero) { acc, c -> acc + c.scrollDelta }
.let { Offset(it.x * horizontalScrollFactor, it.y * verticalScrollFactor) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.gestures

import androidx.compose.animation.SplineBasedFloatDecayAnimationSpec
import androidx.compose.animation.core.generateDecayAnimationSpec
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember

internal actual fun platformDefaultFlingBehavior(): ScrollableDefaultFlingBehavior =
DefaultFlingBehavior(
SplineBasedFloatDecayAnimationSpec(UnityDensity).generateDecayAnimationSpec()
)

@Composable
internal actual fun rememberPlatformDefaultFlingBehavior(): FlingBehavior {
val flingSpec = rememberSplineBasedDecay<Float>()
return remember(flingSpec) {
DefaultFlingBehavior(flingSpec)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:OptIn(ExperimentalComposeUiApi::class)

package androidx.compose.foundation.internal

import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.text.AnnotatedString


internal actual suspend fun ClipEntry.readText(): String? {
return null
Copy link
Author

Choose a reason for hiding this comment

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

Many features in this PR are not implemented (this is just one random example). I am afraid that using wayland code here could break it for non-wayland apps.

My use case is using Compose on an embedded system which is not using wayland but manufacturer specific APIs for most (windowing) features. I need to implement some of these functions with manufacturer-specific code, which of course can never be merged into compose.

As this is an expect/actual (like almost everywhere), I do not know of an easy way to pull out the wayland code into a separate module. Ideally a user like me can provide their own implementation for the windowing features. Or maybe multiple separate modules (one for wayland, etc). Any ideas what would work best?

}

internal actual suspend fun ClipEntry.readAnnotatedString(): AnnotatedString? {
return null
}

internal actual fun AnnotatedString?.toClipEntry(): ClipEntry? {
return null
}

internal actual fun ClipEntry?.hasText(): Boolean = false
Loading