Skip to content

Commit a50b457

Browse files
committed
fix IllegalArgumentException when setUndecorated at runtime.
call ComposeWindow.super.dispose before calling setUndecorated
1 parent 3c5febe commit a50b457

File tree

7 files changed

+141
-26
lines changed

7 files changed

+141
-26
lines changed

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,24 @@ class ComposeDialog : JDialog {
225225
super.dispose()
226226
}
227227

228+
/**
229+
* We cannot call [ComposeDialog.setUndecorated] if window is showing - AWT will throw an exception.
230+
* But we can call [ComposeDialog.super.dispose], [ComposeDialog.setUndecorated] and re-show it.
231+
*/
232+
internal fun setUndecoratedSafely(value: Boolean) {
233+
if (isDisplayable) {
234+
val visible = isVisible
235+
/**
236+
* we only need set decorate state here, so it's no need to dispose [composePanel]
237+
*/
238+
super.dispose()
239+
isUndecorated = value
240+
isVisible = visible
241+
} else {
242+
isUndecorated = value
243+
}
244+
}
245+
228246
override fun setUndecorated(value: Boolean) {
229247
super.setUndecorated(value)
230248
undecoratedWindowResizer.enabled = isUndecorated && isResizable

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,46 @@ class ComposeWindow @ExperimentalComposeUiApi constructor(
160160
}
161161
}
162162

163+
/**
164+
* For users familiar with Java Swing:
165+
*
166+
* If you want to [setUndecorated] at runtime, do not call this function!
167+
* Use code below:
168+
*
169+
* ```kotlin
170+
* fun main() = application {
171+
* var undecorated by remember { mutableStateOf(false) }
172+
* Window(onCloseRequest = {}, undecorated = undecorated) {
173+
* Button(onClick = { undecorated = !undecorated }) {
174+
* Text("Toggle Decorated")
175+
* }
176+
* }
177+
* }
178+
* ```
179+
*/
163180
override fun dispose() {
164181
composePanel.dispose()
165182
super.dispose()
166183
}
167184

185+
/**
186+
* We cannot call [ComposeWindow.setUndecorated] if window is showing - AWT will throw an exception.
187+
* But we can call [ComposeWindow.super.dispose], [ComposeWindow.setUndecorated] and re-show it.
188+
*/
189+
internal fun setUndecoratedSafely(value: Boolean) {
190+
if (isDisplayable) {
191+
val visible = isVisible
192+
/**
193+
* we only need set decorate state here, so it's no need to dispose [composePanel]
194+
*/
195+
super.dispose()
196+
isUndecorated = value
197+
isVisible = visible
198+
} else {
199+
isUndecorated = value
200+
}
201+
}
202+
168203
override fun setUndecorated(value: Boolean) {
169204
super.setUndecorated(value)
170205
undecoratedWindowResizer.enabled = isUndecorated && isResizable

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/util/Windows.desktop.kt

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,9 @@ import androidx.compose.ui.unit.isSpecified
2727
import androidx.compose.ui.window.WindowPlacement
2828
import androidx.compose.ui.window.WindowPosition
2929
import androidx.compose.ui.window.density
30-
import androidx.compose.ui.window.layoutDirection
3130
import androidx.compose.ui.window.layoutDirectionFor
3231
import java.awt.Component
33-
import java.awt.Dialog
3432
import java.awt.Dimension
35-
import java.awt.Frame
3633
import java.awt.Point
3734
import java.awt.Toolkit
3835
import java.awt.Window
@@ -151,26 +148,6 @@ internal fun Window.align(alignment: Alignment) {
151148
)
152149
}
153150

154-
/**
155-
* We cannot call [Frame.setUndecorated] if window is showing - AWT will throw an exception.
156-
* But we can call [Frame.setUndecoratedSafely] if isUndecorated isn't changed.
157-
*/
158-
internal fun Frame.setUndecoratedSafely(value: Boolean) {
159-
if (this.isUndecorated != value) {
160-
this.isUndecorated = value
161-
}
162-
}
163-
164-
/**
165-
* We cannot change call [Dialog.setUndecorated] if window is showing - AWT will throw an exception.
166-
* But we can call [Dialog.setUndecoratedSafely] if isUndecorated isn't changed.
167-
*/
168-
internal fun Dialog.setUndecoratedSafely(value: Boolean) {
169-
if (this.isUndecorated != value) {
170-
this.isUndecorated = value
171-
}
172-
}
173-
174151
// We specify this to support Painter's with unspecified intrinsicSize
175152
private val iconSize = Size(192f, 192f)
176153

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import androidx.compose.ui.util.componentListenerRef
3636
import androidx.compose.ui.util.setIcon
3737
import androidx.compose.ui.util.setPositionSafely
3838
import androidx.compose.ui.util.setSizeSafely
39-
import androidx.compose.ui.util.setUndecoratedSafely
4039
import androidx.compose.ui.util.windowListenerRef
4140
import java.awt.Dialog.ModalityType
4241
import java.awt.Window

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import androidx.compose.ui.awt.ComposeWindow
2929
import androidx.compose.ui.graphics.painter.Painter
3030
import androidx.compose.ui.input.key.KeyEvent
3131
import androidx.compose.ui.platform.LocalLayoutDirection
32-
import androidx.compose.ui.scene.BaseComposeScene
3332
import androidx.compose.ui.scene.LocalComposeScene
3433
import androidx.compose.ui.scene.platformContext
3534
import androidx.compose.ui.unit.DpSize
@@ -39,7 +38,6 @@ import androidx.compose.ui.util.componentListenerRef
3938
import androidx.compose.ui.util.setIcon
4039
import androidx.compose.ui.util.setPositionSafely
4140
import androidx.compose.ui.util.setSizeSafely
42-
import androidx.compose.ui.util.setUndecoratedSafely
4341
import androidx.compose.ui.util.windowListenerRef
4442
import androidx.compose.ui.util.windowStateListenerRef
4543
import java.awt.Window

compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeDialogTest.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import androidx.compose.foundation.clickable
2121
import androidx.compose.foundation.layout.Box
2222
import androidx.compose.foundation.layout.fillMaxSize
2323
import androidx.compose.foundation.layout.requiredSize
24+
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.mutableStateOf
26+
import androidx.compose.runtime.remember
27+
import androidx.compose.runtime.setValue
2428
import androidx.compose.ui.ExperimentalComposeUiApi
2529
import androidx.compose.ui.Modifier
2630
import androidx.compose.ui.graphics.Color
@@ -33,6 +37,7 @@ import androidx.compose.ui.sendMouseRelease
3337
import androidx.compose.ui.window.WindowExceptionHandler
3438
import androidx.compose.ui.unit.Constraints
3539
import androidx.compose.ui.unit.dp
40+
import androidx.compose.ui.window.DialogWindow
3641
import androidx.compose.ui.window.density
3742
import androidx.compose.ui.window.runApplicationTest
3843
import com.google.common.truth.Truth.assertThat
@@ -271,5 +276,44 @@ class ComposeDialogTest {
271276
}
272277
}
273278

279+
// bug https://github.com/JetBrains/compose-multiplatform/issues/4579
280+
@Test
281+
fun `change dialog decorate state should success`() = runApplicationTest {
282+
var window: ComposeDialog? = null
283+
var isClickHappened = false
284+
launchTestApplication {
285+
var undecorated by remember { mutableStateOf(true) }
286+
DialogWindow(onCloseRequest = ::exitApplication, undecorated = undecorated) {
287+
window = this.window
288+
Box(modifier = Modifier.fillMaxSize().background(Color.Blue).clickable {
289+
undecorated = !undecorated
290+
isClickHappened = true
291+
})
292+
}
293+
}
294+
awaitIdle()
295+
window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50)
296+
awaitIdle()
297+
window!!.sendMouseEvent(MOUSE_MOVED, 100, 50)
298+
awaitIdle()
299+
window!!.sendMousePress(BUTTON1, 100, 50)
300+
awaitIdle()
301+
window!!.sendMouseRelease(BUTTON1, 100, 50)
302+
awaitIdle()
303+
assertThat(isClickHappened).isTrue()
304+
assertThat(window!!.isUndecorated).isFalse()
305+
306+
awaitIdle()
307+
window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50)
308+
awaitIdle()
309+
window!!.sendMouseEvent(MOUSE_MOVED, 100, 50)
310+
awaitIdle()
311+
window!!.sendMousePress(BUTTON1, 100, 50)
312+
awaitIdle()
313+
window!!.sendMouseRelease(BUTTON1, 100, 50)
314+
awaitIdle()
315+
assertThat(window!!.isUndecorated).isTrue()
316+
}
317+
274318
private class TestException : Exception()
275319
}

compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import androidx.compose.foundation.clickable
2121
import androidx.compose.foundation.layout.Box
2222
import androidx.compose.foundation.layout.fillMaxSize
2323
import androidx.compose.foundation.layout.requiredSize
24+
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.mutableStateOf
26+
import androidx.compose.runtime.remember
27+
import androidx.compose.runtime.setValue
2428
import androidx.compose.ui.ExperimentalComposeUiApi
2529
import androidx.compose.ui.Modifier
2630
import androidx.compose.ui.graphics.Color
@@ -37,6 +41,7 @@ import androidx.compose.ui.window.WindowExceptionHandler
3741
import androidx.compose.ui.unit.Constraints
3842
import androidx.compose.ui.unit.IntSize
3943
import androidx.compose.ui.unit.dp
44+
import androidx.compose.ui.window.Window
4045
import androidx.compose.ui.window.density
4146
import androidx.compose.ui.window.runApplicationTest
4247
import com.google.common.truth.Truth.assertThat
@@ -291,6 +296,45 @@ class ComposeWindowTest {
291296
}
292297
}
293298

299+
// bug https://github.com/JetBrains/compose-multiplatform/issues/4579
300+
@Test
301+
fun `change window decorate state should success`() = runApplicationTest(timeoutMillis = Long.MAX_VALUE) {
302+
var window: ComposeWindow? = null
303+
var isClickHappened = false
304+
launchTestApplication {
305+
var undecorated by remember { mutableStateOf(true) }
306+
Window(onCloseRequest = ::exitApplication, undecorated = undecorated) {
307+
window = this.window
308+
Box(modifier = Modifier.fillMaxSize().background(Color.Blue).clickable {
309+
undecorated = !undecorated
310+
isClickHappened = true
311+
})
312+
}
313+
}
314+
awaitIdle()
315+
window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50)
316+
awaitIdle()
317+
window!!.sendMouseEvent(MOUSE_MOVED, 100, 50)
318+
awaitIdle()
319+
window!!.sendMousePress(BUTTON1, 100, 50)
320+
awaitIdle()
321+
window!!.sendMouseRelease(BUTTON1, 100, 50)
322+
awaitIdle()
323+
assertThat(isClickHappened).isTrue()
324+
assertThat(window!!.isUndecorated).isFalse()
325+
326+
awaitIdle()
327+
window!!.sendMouseEvent(MOUSE_ENTERED, 100, 50)
328+
awaitIdle()
329+
window!!.sendMouseEvent(MOUSE_MOVED, 100, 50)
330+
awaitIdle()
331+
window!!.sendMousePress(BUTTON1, 100, 50)
332+
awaitIdle()
333+
window!!.sendMouseRelease(BUTTON1, 100, 50)
334+
awaitIdle()
335+
assertThat(window!!.isUndecorated).isTrue()
336+
}
337+
294338
private class TestException : Exception()
295339
}
296340

0 commit comments

Comments
 (0)