Skip to content

fix IllegalArgumentException when re-show a disposed ComposeWindow #1240

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

Open
wants to merge 2 commits into
base: jb-main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,46 @@ 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()
}

/**
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
}

Expand Down