From ab9df9136ef9027b54ae13608d16baa4ea37146e Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Tue, 12 Mar 2024 17:47:32 +0100 Subject: [PATCH] Apply hack for window transparency to `WINDOW` layer too (#1185) ## Proposed Changes - Apply hack for window transparency that we already had for main window to `WINDOW` layer too ## Testing Test: Try mouse input at `WINDOW` layers on Windows ## Issues Fixed Fixes https://github.com/JetBrains/compose-multiplatform-core/pull/1181#issuecomment-1991851134 --- .../ui/awt/ComposeWindowPanel.desktop.kt | 33 +-------------- .../androidx/compose/ui/awt/Utils.desktop.kt | 42 +++++++++++++++++++ .../scene/WindowComposeSceneLayer.desktop.kt | 9 +++- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowPanel.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowPanel.desktop.kt index 5e2cdd3ff6a5c..0dc1cc341aaea 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowPanel.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowPanel.desktop.kt @@ -89,40 +89,11 @@ internal class ComposeWindowPanel( } field = value composeContainer.onChangeWindowTransparency(value) - - /* - * Windows makes clicks on transparent pixels fall through, but it doesn't work - * with GPU accelerated rendering since this check requires having access to pixels from CPU. - * - * JVM doesn't allow override this behaviour with low-level windows methods, so hack this in this way. - * Based on tests, it doesn't affect resulting pixel color. - * - * Note: Do not set isOpaque = false for this container - */ - if (value && hostOs == OS.Windows) { - background = Color(0, 0, 0, 1) - isOpaque = true - } else { - background = null - isOpaque = false - } - - window.background = if (value && !skikoTransparentWindowHack) Color(0, 0, 0, 0) else null + setTransparent(value) + window.background = getTransparentWindowBackground(value, renderApi) } } - /** - * There is a hack inside skiko OpenGL and Software redrawers for Windows that makes current - * window transparent without setting `background` to JDK's window. It's done by getting native - * component parent and calling `DwmEnableBlurBehindWindow`. - * - * FIXME: Make OpenGL work inside transparent window (background == Color(0, 0, 0, 0)) without this hack. - * - * See `enableTransparentWindow` (skiko/src/awtMain/cpp/windows/window_util.cc) - */ - private val skikoTransparentWindowHack: Boolean - get() = hostOs == OS.Windows && renderApi != GraphicsApi.DIRECT3D - init { layout = null focusTraversalPolicy = object : FocusTraversalPolicy() { diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt index 8adc337684ec4..25e5b9bfcc436 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt @@ -18,6 +18,12 @@ package androidx.compose.ui.awt import androidx.compose.ui.graphics.Color import java.awt.Component +import java.awt.Transparency +import javax.swing.JComponent +import org.jetbrains.skiko.ClipRectangle +import org.jetbrains.skiko.GraphicsApi +import org.jetbrains.skiko.OS +import org.jetbrains.skiko.hostOs internal fun Component.isParentOf(component: Component?): Boolean { var parent = component?.parent @@ -31,3 +37,39 @@ internal fun Component.isParentOf(component: Component?): Boolean { } internal fun Color.toAwtColor() = java.awt.Color(red, green, blue, alpha) + +internal fun getTransparentWindowBackground( + isWindowTransparent: Boolean, + renderApi: GraphicsApi +): java.awt.Color? { + /** + * There is a hack inside skiko OpenGL and Software redrawers for Windows that makes current + * window transparent without setting `background` to JDK's window. It's done by getting native + * component parent and calling `DwmEnableBlurBehindWindow`. + * + * FIXME: Make OpenGL work inside transparent window (background == Color(0, 0, 0, 0)) without this hack. + * + * See `enableTransparentWindow` (skiko/src/awtMain/cpp/windows/window_util.cc) + */ + val skikoTransparentWindowHack = hostOs == OS.Windows && renderApi != GraphicsApi.DIRECT3D + return if (isWindowTransparent && !skikoTransparentWindowHack) java.awt.Color(0, 0, 0, 0) else null +} + +internal fun JComponent.setTransparent(transparent: Boolean) { + /* + * Windows makes clicks on transparent pixels fall through, but it doesn't work + * with GPU accelerated rendering since this check requires having access to pixels from CPU. + * + * JVM doesn't allow override this behaviour with low-level windows methods, so hack this in this way. + * Based on tests, it doesn't affect resulting pixel color. + * + * Note: Do not set isOpaque = false for this container + */ + if (transparent && hostOs == OS.Windows) { + background = java.awt.Color(0, 0, 0, 1) + isOpaque = true + } else { + background = null + isOpaque = false + } +} diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/WindowComposeSceneLayer.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/WindowComposeSceneLayer.desktop.kt index 6e7ec19ed999d..a50a0f03c7bae 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/WindowComposeSceneLayer.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/WindowComposeSceneLayer.desktop.kt @@ -19,6 +19,8 @@ package androidx.compose.ui.scene import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionContext import androidx.compose.runtime.CompositionLocalContext +import androidx.compose.ui.awt.getTransparentWindowBackground +import androidx.compose.ui.awt.setTransparent import androidx.compose.ui.awt.toAwtColor import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.toRect @@ -70,7 +72,10 @@ internal class WindowComposeSceneLayer( ).also { it.isAlwaysOnTop = true it.isUndecorated = true - it.background = Color.Transparent.toAwtColor() + it.background = getTransparentWindowBackground( + isWindowTransparent = true, + renderApi = composeContainer.renderApi + ) } private val container = object : JLayeredPane() { override fun addNotify() { @@ -79,7 +84,7 @@ internal class WindowComposeSceneLayer( } }.also { it.layout = null - it.isOpaque = false + it.setTransparent(true) dialog.contentPane = it }