compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

Compose for Desktop fails to render if rootPanes' client properties are changed

Open rno opened this issue 4 years ago • 7 comments

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: ComposeDesktopRenderFail

Here's a link to a repo that contains code that reproduce the issue: https://github.com/rno/test-compose-desktop

rno avatar Dec 28 '20 19:12 rno

Why do you believe that this behavior is incorrect? What do you think is the correct behavior?

olonho avatar Dec 29 '20 08:12 olonho

@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: WithSwing

rno avatar Dec 29 '20 18:12 rno

Thanks, we'll take a look.

olonho avatar Jan 13 '21 10:01 olonho

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
}

Rsedaikin avatar Jan 13 '21 12:01 Rsedaikin

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 avatar Feb 01 '21 02:02 rno

@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.

ovitrif avatar Jun 19 '22 20:06 ovitrif

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

atomofiron avatar Jun 23 '24 12:06 atomofiron

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

okushnikov avatar Jul 14 '24 14:07 okushnikov