From a50b4575c3878ecdff2b967248b34adc0b6f691a Mon Sep 17 00:00:00 2001 From: XYZboom Date: Sat, 6 Apr 2024 21:30:47 +0800 Subject: [PATCH 1/2] fix IllegalArgumentException when setUndecorated at runtime. call ComposeWindow.super.dispose before calling setUndecorated --- .../compose/ui/awt/ComposeDialog.desktop.kt | 18 ++++++++ .../compose/ui/awt/ComposeWindow.desktop.kt | 35 +++++++++++++++ .../compose/ui/util/Windows.desktop.kt | 23 ---------- .../compose/ui/window/Dialog.desktop.kt | 1 - .../compose/ui/window/Window.desktop.kt | 2 - .../compose/ui/awt/ComposeDialogTest.kt | 44 +++++++++++++++++++ .../compose/ui/awt/ComposeWindowTest.kt | 44 +++++++++++++++++++ 7 files changed, 141 insertions(+), 26 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt index c06ba516e6f60..60ebedff58c86 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt @@ -225,6 +225,24 @@ class ComposeDialog : JDialog { super.dispose() } + /** + * We cannot call [ComposeDialog.setUndecorated] if window is showing - AWT will throw an exception. + * But we can call [ComposeDialog.super.dispose], [ComposeDialog.setUndecorated] and re-show it. + */ + internal fun setUndecoratedSafely(value: Boolean) { + if (isDisplayable) { + val visible = isVisible + /** + * we only need set decorate state here, so it's no need to dispose [composePanel] + */ + super.dispose() + isUndecorated = value + isVisible = visible + } else { + isUndecorated = value + } + } + override fun setUndecorated(value: Boolean) { super.setUndecorated(value) undecoratedWindowResizer.enabled = isUndecorated && isResizable diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt index d40a24ff19316..db4cfb2e8961f 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt @@ -160,11 +160,46 @@ class ComposeWindow @ExperimentalComposeUiApi constructor( } } + /** + * For users familiar with Java Swing: + * + * If you want to [setUndecorated] at runtime, do not call this function! + * Use code below: + * + * ```kotlin + * fun main() = application { + * var undecorated by remember { mutableStateOf(false) } + * Window(onCloseRequest = {}, undecorated = undecorated) { + * Button(onClick = { undecorated = !undecorated }) { + * Text("Toggle Decorated") + * } + * } + * } + * ``` + */ override fun dispose() { composePanel.dispose() super.dispose() } + /** + * We cannot call [ComposeWindow.setUndecorated] if window is showing - AWT will throw an exception. + * But we can call [ComposeWindow.super.dispose], [ComposeWindow.setUndecorated] and re-show it. + */ + internal fun setUndecoratedSafely(value: Boolean) { + if (isDisplayable) { + val visible = isVisible + /** + * we only need set decorate state here, so it's no need to dispose [composePanel] + */ + super.dispose() + isUndecorated = value + isVisible = visible + } else { + isUndecorated = value + } + } + override fun setUndecorated(value: Boolean) { super.setUndecorated(value) undecoratedWindowResizer.enabled = isUndecorated && isResizable diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/util/Windows.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/util/Windows.desktop.kt index be3b3dee62fd6..20489d9d4a524 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/util/Windows.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/util/Windows.desktop.kt @@ -27,12 +27,9 @@ import androidx.compose.ui.unit.isSpecified import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.density -import androidx.compose.ui.window.layoutDirection import androidx.compose.ui.window.layoutDirectionFor import java.awt.Component -import java.awt.Dialog import java.awt.Dimension -import java.awt.Frame import java.awt.Point import java.awt.Toolkit import java.awt.Window @@ -151,26 +148,6 @@ internal fun Window.align(alignment: Alignment) { ) } -/** - * We cannot call [Frame.setUndecorated] if window is showing - AWT will throw an exception. - * But we can call [Frame.setUndecoratedSafely] if isUndecorated isn't changed. - */ -internal fun Frame.setUndecoratedSafely(value: Boolean) { - if (this.isUndecorated != value) { - this.isUndecorated = value - } -} - -/** - * We cannot change call [Dialog.setUndecorated] if window is showing - AWT will throw an exception. - * But we can call [Dialog.setUndecoratedSafely] if isUndecorated isn't changed. - */ -internal fun Dialog.setUndecoratedSafely(value: Boolean) { - if (this.isUndecorated != value) { - this.isUndecorated = value - } -} - // We specify this to support Painter's with unspecified intrinsicSize private val iconSize = Size(192f, 192f) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt index bdff0d6f05057..0ed9a4e8ef002 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.util.componentListenerRef import androidx.compose.ui.util.setIcon import androidx.compose.ui.util.setPositionSafely import androidx.compose.ui.util.setSizeSafely -import androidx.compose.ui.util.setUndecoratedSafely import androidx.compose.ui.util.windowListenerRef import java.awt.Dialog.ModalityType import java.awt.Window diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt index db11c99169977..6a999a5b3553a 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.awt.ComposeWindow import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.scene.BaseComposeScene import androidx.compose.ui.scene.LocalComposeScene import androidx.compose.ui.scene.platformContext import androidx.compose.ui.unit.DpSize @@ -39,7 +38,6 @@ import androidx.compose.ui.util.componentListenerRef import androidx.compose.ui.util.setIcon import androidx.compose.ui.util.setPositionSafely import androidx.compose.ui.util.setSizeSafely -import androidx.compose.ui.util.setUndecoratedSafely import androidx.compose.ui.util.windowListenerRef import androidx.compose.ui.util.windowStateListenerRef import java.awt.Window diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeDialogTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeDialogTest.kt index 8b2f93945d638..257139aba6471 100644 --- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeDialogTest.kt +++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeDialogTest.kt @@ -21,6 +21,10 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.requiredSize +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -33,6 +37,7 @@ import androidx.compose.ui.sendMouseRelease import androidx.compose.ui.window.WindowExceptionHandler import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogWindow import androidx.compose.ui.window.density import androidx.compose.ui.window.runApplicationTest import com.google.common.truth.Truth.assertThat @@ -271,5 +276,44 @@ class ComposeDialogTest { } } + // bug https://github.com/JetBrains/compose-multiplatform/issues/4579 + @Test + fun `change dialog decorate state should success`() = runApplicationTest { + var window: ComposeDialog? = null + var isClickHappened = false + launchTestApplication { + var undecorated by remember { mutableStateOf(true) } + DialogWindow(onCloseRequest = ::exitApplication, undecorated = undecorated) { + window = this.window + Box(modifier = Modifier.fillMaxSize().background(Color.Blue).clickable { + undecorated = !undecorated + isClickHappened = true + }) + } + } + awaitIdle() + window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50) + awaitIdle() + window!!.sendMouseEvent(MOUSE_MOVED, 100, 50) + awaitIdle() + window!!.sendMousePress(BUTTON1, 100, 50) + awaitIdle() + window!!.sendMouseRelease(BUTTON1, 100, 50) + awaitIdle() + assertThat(isClickHappened).isTrue() + assertThat(window!!.isUndecorated).isFalse() + + awaitIdle() + window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50) + awaitIdle() + window!!.sendMouseEvent(MOUSE_MOVED, 100, 50) + awaitIdle() + window!!.sendMousePress(BUTTON1, 100, 50) + awaitIdle() + window!!.sendMouseRelease(BUTTON1, 100, 50) + awaitIdle() + assertThat(window!!.isUndecorated).isTrue() + } + private class TestException : Exception() } \ No newline at end of file diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt index c81d6bf12a081..75e6c64479d12 100644 --- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt +++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt @@ -21,6 +21,10 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.requiredSize +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -37,6 +41,7 @@ import androidx.compose.ui.window.WindowExceptionHandler import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window import androidx.compose.ui.window.density import androidx.compose.ui.window.runApplicationTest import com.google.common.truth.Truth.assertThat @@ -291,6 +296,45 @@ class ComposeWindowTest { } } + // bug https://github.com/JetBrains/compose-multiplatform/issues/4579 + @Test + fun `change window decorate state should success`() = runApplicationTest(timeoutMillis = Long.MAX_VALUE) { + var window: ComposeWindow? = null + var isClickHappened = false + launchTestApplication { + var undecorated by remember { mutableStateOf(true) } + Window(onCloseRequest = ::exitApplication, undecorated = undecorated) { + window = this.window + Box(modifier = Modifier.fillMaxSize().background(Color.Blue).clickable { + undecorated = !undecorated + isClickHappened = true + }) + } + } + awaitIdle() + window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50) + awaitIdle() + window!!.sendMouseEvent(MOUSE_MOVED, 100, 50) + awaitIdle() + window!!.sendMousePress(BUTTON1, 100, 50) + awaitIdle() + window!!.sendMouseRelease(BUTTON1, 100, 50) + awaitIdle() + assertThat(isClickHappened).isTrue() + assertThat(window!!.isUndecorated).isFalse() + + awaitIdle() + window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50) + awaitIdle() + window!!.sendMouseEvent(MOUSE_MOVED, 100, 50) + awaitIdle() + window!!.sendMousePress(BUTTON1, 100, 50) + awaitIdle() + window!!.sendMouseRelease(BUTTON1, 100, 50) + awaitIdle() + assertThat(window!!.isUndecorated).isTrue() + } + private class TestException : Exception() } From 791f1bd2726c765ecbf7ce1bdb51bc389ce6dc93 Mon Sep 17 00:00:00 2001 From: XYZboom Date: Sun, 7 Apr 2024 08:10:15 +0800 Subject: [PATCH 2/2] add missing document for ComposeDialog.desktop --- .../compose/ui/awt/ComposeDialog.desktop.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt index 60ebedff58c86..6dc42921fd87c 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt @@ -220,6 +220,23 @@ class ComposeDialog : JDialog { } } + /** + * For users familiar with Java Swing: + * + * If you want to [setUndecorated] at runtime, do not call this function! + * Use code below: + * + * ```kotlin + * fun main() = application { + * var undecorated by remember { mutableStateOf(false) } + * Window(onCloseRequest = {}, undecorated = undecorated) { + * Button(onClick = { undecorated = !undecorated }) { + * Text("Toggle Decorated") + * } + * } + * } + * ``` + */ override fun dispose() { composePanel.dispose() super.dispose()