JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.24k stars 1.18k forks source link

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

Closed rno closed 1 month ago

rno commented 3 years ago

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

olonho commented 3 years ago

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

rno commented 3 years ago

@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

olonho commented 3 years ago

Thanks, we'll take a look.

Rsedaikin commented 3 years ago

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
}
rno commented 3 years ago

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.

ovitrif commented 2 years ago

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

atomofiron commented 4 months ago

this works well enough for me:

main.kt ```kotlin 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

okushnikov commented 4 months ago

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