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

Fix render order of interop views #1145

Merged
merged 12 commits into from
Feb 29, 2024
14 changes: 7 additions & 7 deletions compose/mpp/demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ kotlin {
implementation(project(":compose:ui:ui"))
implementation(project(":compose:ui:ui-graphics"))
implementation(project(":compose:ui:ui-text"))
implementation(libs.kotlinStdlib)
implementation(libs.kotlinCoroutinesCore)
api(libs.kotlinSerializationCore)
}
Expand All @@ -181,19 +182,18 @@ kotlin {
}
}

val jsMain by getting {
val webMain by creating {
dependsOn(skikoMain)
resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir })
}

val jsMain by getting {
dependsOn(webMain)
}

val wasmJsMain by getting {
dependsOn(skikoMain)
resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir })
dependencies {
implementation(libs.kotlinStdlib)
}
dependsOn(webMain)
}

val nativeMain by creating { dependsOn(skikoMain) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ val MainScreen = Screen.Selection(
Screen.Example("GraphicsLayerSettings") { GraphicsLayerSettings() },
Screen.Example("Blending") { Blending() },
Screen.Example("FontRasterization") { FontRasterization() },
Screen.Example("InteropOrder") { InteropOrder() },
igordmn marked this conversation as resolved.
Show resolved Hide resolved
AndroidTextFieldSamples,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.mpp.demo

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun InteropOrder() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I tried this strange example with useInteropBlending = true on Windows:

        Box {
            if (red) {
                Box(Modifier.size(150.dp).offset(0.dp, 0.dp).background(Color.Red))
                TestInteropView(Modifier.size(150.dp).offset(0.dp, 0.dp), Color.Red)
            }
            if (green) {
                TestInteropView(Modifier.size(150.dp).offset(75.dp, 75.dp), Color.Green)
            }
            if (blue) {
                Box(Modifier.size(150.dp).offset(150.dp, 150.dp).background(Color.Blue))
            }
        }

And sometimes it has a wrong ordering:
image

Sometimes correct:
image

Is it expected or is it a bug?

Copy link
Member Author

Choose a reason for hiding this comment

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

A bug. Fixed by adding validate/repaint after adding of interop (this PR) + Fix from #1147

var red by remember { mutableStateOf(true) }
var green by remember { mutableStateOf(true) }
var blue by remember { mutableStateOf(true) }
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Button(onClick = { red = !red }) {
Text("Red")
}
Button(onClick = { green = !green }) {
Text("Green")
}
Button(onClick = { blue = !blue }) {
Text("Blue")
}

Box {
if (red) {
TestInteropView(Modifier.size(150.dp).offset(0.dp, 0.dp), Color.Red)
}
if (green) {
TestInteropView(Modifier.size(150.dp).offset(75.dp, 75.dp), Color.Green)
}
if (blue) {
TestInteropView(Modifier.size(150.dp).offset(150.dp, 150.dp), Color.Blue)
}
}
}
}

@Composable
internal expect fun TestInteropView(modifier: Modifier, color: Color)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.mpp.demo

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.graphics.Color
import javax.swing.JPanel

@Composable
internal actual fun TestInteropView(modifier: Modifier, color: Color) {
SwingPanel(
background = color,
factory = { JPanel().apply { background = color.toAwtColor() } },
modifier = modifier
)
}

private fun Color.toAwtColor() = java.awt.Color(red, green, blue, alpha)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 The Android Open Source Project
* 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.
Expand All @@ -14,11 +14,15 @@
* limitations under the License.
*/

package androidx.compose.ui.awt
package androidx.compose.mpp.demo

import androidx.compose.runtime.staticCompositionLocalOf
import java.awt.Container
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color

internal val LocalLayerContainer = staticCompositionLocalOf<Container> {
error("CompositionLocal LayerContainer not provided")
@Composable
internal actual fun TestInteropView(modifier: Modifier, color: Color) {
Box(modifier.background(color)) // TODO
}
52 changes: 0 additions & 52 deletions compose/mpp/demo/src/uikitMain/kotlin/UIKitViewOrder.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.mpp.demo

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.interop.UIKitView
import platform.UIKit.UIColor
import platform.UIKit.UIView

@Composable
internal actual fun TestInteropView(modifier: Modifier, color: Color) {
UIKitView(
factory = { UIView().apply { backgroundColor = color.toUIColor() } },
modifier = modifier,
)
}

private fun Color.toUIColor() = UIColor(
red = red.toDouble(),
green = green.toDouble(),
blue = blue.toDouble(),
alpha = alpha.toDouble(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,18 @@ package androidx.compose.mpp.demo

import NativeModalWithNaviationExample
import SwiftUIInteropExample
import UIKitViewOrder
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.ExperimentalComposeRuntimeApi
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.main.defaultUIKitMain
import androidx.compose.ui.platform.AccessibilityDebugLogger
import androidx.compose.ui.platform.AccessibilitySyncOptions
import androidx.compose.ui.window.ComposeUIViewController
import bugs.IosBugs
import bugs.ProperContainmentDisposal
import bugs.ComposeAndNativeScroll
import bugs.StartRecompositionCheck
import platform.UIKit.UIViewController


@OptIn(ExperimentalComposeApi::class, ExperimentalComposeUiApi::class)
fun main(vararg args: String) {
androidx.compose.ui.util.enableTraceOSLog()
Expand Down Expand Up @@ -47,9 +42,6 @@ fun IosDemo(arg: String, makeHostingController: ((Int) -> UIViewController)? = n
extraScreens = listOf(
IosBugs,
NativeModalWithNaviationExample,
UIKitViewOrder,
ProperContainmentDisposal,
ComposeAndNativeScroll
) + listOf(makeHostingController).mapNotNull {
it?.let {
SwiftUIInteropExample(it)
Expand Down
2 changes: 2 additions & 0 deletions compose/mpp/demo/src/uikitMain/kotlin/bugs/IosBugs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ val IosBugs = Screen.Selection(
BackspaceIssue,
DropdownMenuIssue,
KeyboardIMEActionPopup,
ProperContainmentDisposal,
ComposeAndNativeScroll
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 The Android Open Source Project
* 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.
Expand All @@ -14,11 +14,15 @@
* limitations under the License.
*/

package androidx.compose.ui.interop
package androidx.compose.mpp.demo

import androidx.compose.runtime.staticCompositionLocalOf
import platform.UIKit.UIView
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color

internal val LocalInteropContainer = staticCompositionLocalOf<UIView> {
error("CompositionLocal LayerContainer not provided")
@Composable
internal actual fun TestInteropView(modifier: Modifier, color: Color) {
Box(modifier.background(color)) // TODO
}
7 changes: 0 additions & 7 deletions compose/ui/ui/api/desktop/ui.api
Original file line number Diff line number Diff line change
Expand Up @@ -414,13 +414,6 @@ public final class androidx/compose/ui/awt/AwtEvents_desktopKt {
public static final fun getAwtEventOrNull-ZmokQxo (Ljava/lang/Object;)Ljava/awt/event/KeyEvent;
}

public final class androidx/compose/ui/awt/ComposableSingletons$SwingPanel_desktopKt {
public static final field INSTANCE Landroidx/compose/ui/awt/ComposableSingletons$SwingPanel_desktopKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public fun <init> ()V
public final fun getLambda-1$ui ()Lkotlin/jvm/functions/Function2;
}

public final class androidx/compose/ui/awt/ComposeDialog : javax/swing/JDialog {
public static final field $stable I
public fun <init> ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ internal object ComposeFeatureFlags {
* Indicates whether interop blending is enabled.
* It allows drawing compose elements above interop and apply clip/shape modifiers to it.
*
* Note that it currently works only with Metal, DirectX and offscreen rendering.
* Known limitations:
* - Works only with Metal, DirectX and offscreen rendering
* - On DirectX, it cannot overlay another DirectX component (due to OS blending limitation)
* - On macOS, render and event dispatching order differs. It means that interop view might
* catch the mouse event even if visually it renders below Compose content
*/
val useInteropBlending: Boolean
get() = System.getProperty("compose.interop.blending").toBoolean()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
}

override fun add(component: Component): Component {
_composeContainer?.addToComponentLayer(component)
return component
return super.add(component)
}

override fun remove(component: Component) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,6 @@ internal class ComposeWindowPanel(
composeContainer.setBounds(0, 0, width, height)
}

override fun add(component: Component): Component {
composeContainer.addToComponentLayer(component)
return component
}

override fun getPreferredSize(): Dimension? = if (isPreferredSizeSet) {
super.getPreferredSize()
} else {
Expand Down
Loading
Loading