Compose for Desktop fails to render if rootPanes' client properties are changed
Hi,
I was playing around on Mac OS to make the window title bar overlay the window content and found the following 2 client properties to apply on the rootPane:
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
Unfortunately, when applying those 2 client properties, Compose for Desktop does not render except if the window is resized.
Video:

Here's a link to a repo that contains code that reproduce the issue: https://github.com/rno/test-compose-desktop
Why do you believe that this behavior is incorrect? What do you think is the correct behavior?
@olonho, I would expect the UI to render as normal.
I did the following with just Swing and it renders as normal:
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.SwingUtilities
fun main() {
SwingUtilities.invokeLater {
SwingMainWindow()
}
}
fun SwingMainWindow() {
val mainWindow = JFrame()
val isMacOSX = isMacOSX()
if (isMacOSX) {
val rootPane = mainWindow.rootPane
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
}
mainWindow.contentPane.add(JButton("Hello, World!"))
mainWindow.setSize(800, 600)
mainWindow.setLocationRelativeTo(null)
mainWindow.isVisible = true
}
private fun isMacOSX(): Boolean {
val osName = System.getProperty("os.name")
return "OS X" == osName || "Mac OS X" == osName
}
Video:

Thanks, we'll take a look.
I found a solution. it looks like these properties (in compose for desktop) should be set after the window is displayed. Try this:
import androidx.compose.desktop.AppWindow
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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
import androidx.compose.ui.unit.dp
import javax.swing.SwingUtilities
fun main() {
SwingUtilities.invokeLater {
MainWindow()
}
}
fun MainWindow() {
val isMacOSX = isMacOSX()
val topPadding = if (isMacOSX) 16.dp else 0.dp
val mainWindow = AppWindow(
title = ""
)
// onOpen - event that is invoked after the window appears.
mainWindow.events.onOpen = {
if (isMacOSX) {
val rootPane = mainWindow.window.rootPane
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
}
}
mainWindow.show {
var text by remember { mutableStateOf("Hello, World!") }
MaterialTheme {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
.padding(16.dp)
.padding(top = topPadding)
) {
Button(onClick = {
text = "Hello, Desktop!"
}) {
Text(text)
}
}
}
}
}
private fun isMacOSX(): Boolean {
val osName = System.getProperty("os.name")
return "OS X" == osName || "Mac OS X" == osName
}
I apologize, I didn't see any notifications about the update to the issue.
Thank you @Rsedaikin for the workaround, it does work.
That being said, because the client property change is happening once the window is displayed, there's a flickering happening.
@rno It works for me, using this code:
Window(
) {
window.ootPane.putClientProperty("apple.awt.fullWindowContent", true)
//...
}
Which basically means I'm making use of recomposition.
this works well enough for me:
main.kt
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import java.awt.Color as JColor
fun main() = application {
val windowState = rememberWindowState()
var maxInsets = remember { Insets() }
Window(
state = windowState,
onCloseRequest = ::exitApplication,
title = "Sample",
) {
window.background = JColor.BLACK
if (isMacOS()) {
val rootPane = window.rootPane
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
rootPane.putClientProperty("apple.awt.windowTitleVisible", false)
}
// windowState.placement is not suitable because window.insets has old value when placement become Floating.
// it is the reason why needed collect full insets and toggle it when placement change
maxInsets = maxInsets union when (windowState.size) {
DpSize.Zero -> Insets.Empty
else -> Insets(top = window.insets.top.dp)
}
val actualInsets = when (windowState.placement) {
WindowPlacement.Floating,
WindowPlacement.Maximized -> maxInsets
WindowPlacement.Fullscreen -> Insets.Empty
}
App(actualInsets)
}
}
private fun isMacOS(): Boolean {
val name = System.getProperty("os.name")
return name == "Mac OS X" || name == "OS X"
}
// commonMain/kotlin/Insets.kt:
@Immutable
data class Insets(
val left: Dp = 0.dp,
val top: Dp = 0.dp,
val right: Dp = 0.dp,
val bottom: Dp = 0.dp,
) {
companion object {
val Empty = Insets()
operator fun invoke() = Empty
}
}
infix fun Insets.union(other: Insets) = Insets(
left = max(left, other.left),
top = max(top, other.top),
right = max(right, other.right),
bottom = max(bottom, other.bottom),
)
https://github.com/JetBrains/compose-multiplatform/assets/14147217/308f0cdd-abf9-4cc3-9821-858017c44c20
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.