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
15.9k stars 1.16k forks source link

Support for undecorated windows with custom title pane decoration #178

Open kirill-grouchnikov opened 3 years ago

kirill-grouchnikov commented 3 years ago

What is the proposed approach to creating undecorated windows in Compose for Desktop that still need to display some sort of a draggable title pane area with close/minimize/maximize buttons?

The dragging / resizing in Swing is done as part of the JRootPane UI delegate. For example https://github.com/openjdk/jdk/blob/master/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalRootPaneUI.java#L698 for dragging and resizing under the Metal look-and-feel. The listener is then set on the whole window in https://github.com/openjdk/jdk/blob/master/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalRootPaneUI.java#L246 and processes all relevant mouse events.

Is this the same approach that we should adopt for Windows created with undecorated=true?

Rsedaikin commented 3 years ago

You could try this template:

import androidx.compose.desktop.AppManager
import androidx.compose.desktop.Window
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.material.Surface
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.window.WindowDraggableArea
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import javax.swing.JFrame

fun main() {
    Window(
        undecorated = true
    ) {
        Row(
            modifier = Modifier.background(color = Color(75, 75, 75))
                .fillMaxWidth()
                .height(30.dp)
                .padding(start = 20.dp, end = 10.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            WindowDraggableArea(
                modifier = Modifier.weight(1f)
            ) {
                Text(text = "Undecorated window", color = Color.White)
            }
            Row {
                Button(
                    onClick = {
                        val current = AppManager.focusedWindow
                        if (current != null) {
                            current.window.setExtendedState(JFrame.ICONIFIED)
                        }
                    }
                )
                Spacer(modifier = Modifier.width(5.dp))
                Button(
                    onClick = {
                        val current = AppManager.focusedWindow
                        if (current != null) {
                            if (current.window.extendedState == JFrame.MAXIMIZED_BOTH) {
                                current.window.setExtendedState(JFrame.NORMAL)
                            } else {
                                current.window.setExtendedState(JFrame.MAXIMIZED_BOTH)
                            }
                        }
                    }
                )
                Spacer(modifier = Modifier.width(5.dp))
                Button(
                    onClick = {
                        AppManager.focusedWindow?.close()
                    }
                )
            }
        }
    }
}

@Composable
fun Button(
    text: String = "",
    onClick: () -> Unit = {},
    color: Color = Color(210, 210, 210),
    size: Int = 16
) {
    val buttonHover = remember { mutableStateOf(false) }
    Surface(
        color = if (buttonHover.value)
                    Color(color.red / 1.3f, color.green / 1.3f, color.blue / 1.3f)
                else 
                    color,
        shape = RoundedCornerShape((size / 2).dp)
    ) {
        Box(
            modifier = Modifier
                .clickable(onClick = onClick)
                .size(size.dp, size.dp)
                .pointerMoveFilter(
                    onEnter = {
                        buttonHover.value = true
                        false
                    },
                    onExit = {
                        buttonHover.value = false
                        false
                    },
                    onMove = { false }
                )
        ) {
            Text(text = text)
        }
    }
}

A more convenient API for managing window state (minimize, maximize, restore, fullscreen) will be ready soon. But for now, if you want to resize the undecorated window using the mouse, you must add listeners directly to the AppWindow.window ( JFrame). Also transparency is not ready for Compose for Desktop so you cannot create rounded undecorated windows.

kirill-grouchnikov commented 3 years ago

Thanks Roman. We'd also need something for resizing undecorated windows by grabbing the window edge / corner and dragging those.

TheMrCodes commented 3 years ago

@Rsedaikin Is there any reference for the features this new API will support? I would be interested if it will support native window features like maximising without going into fullscreen or snap to side (aero snap) under windows

viktorisacenko commented 2 years ago

1318

devkanro commented 1 year ago

There are some resolution, but it require the JetBrains/JetBrainsRuntime

Some code for it: https://github.com/ButterCam/compose-jetbrains-theme/blob/main/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/util/CustomWindowDecorationAccessing.kt

See the readme for more information: https://github.com/ButterCam/compose-jetbrains-theme

taodongl commented 1 year ago

Thanks Roman. We'd also need something for resizing undecorated windows by grabbing the window edge / corner and dragging those.

There are some resolution, but it require the JetBrains/JetBrainsRuntime

So far I use the latest compose-multiplatform (version: 1.4.0) with any jdk11 or jdk17 distributions. Using "undecorated=true" attribute, it works well for "resizing undecorated windows by grabbing the window edge / corner and dragging those".

So it seem it isn't necessary to get awt method using reflect @devkanro

plugins {
    kotlin("jvm") version "1.8.20"
    id("org.jetbrains.compose") version "1.4.0"
}
Skaldebane commented 11 months ago

I think this is already fixed now with WindowDraggableArea?

kirill-grouchnikov commented 11 months ago

Not the resizing functionality

amir1376 commented 10 months ago

I created a tiny library for this problem it is based on library that @devkanro mentioned earlier

here is mine It also supports hit spots for close/minimize/maximize system buttons and draggable by adding Modifier to a composable

devkanro commented 10 months ago

@amir1376 there are some updates for JBR, you can check the latest impl for it:

https://github.com/JetBrains/jewel/tree/main/decorated-window

okushnikov commented 2 weeks ago

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