You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
423 lines
12 KiB
423 lines
12 KiB
4 years ago
|
# OS windows management
|
||
4 years ago
|
|
||
|
## What is covered
|
||
|
|
||
4 years ago
|
In this tutorial we will show you how to work with windows using Compose for Desktop.
|
||
4 years ago
|
|
||
|
## Windows creation
|
||
|
|
||
4 years ago
|
The main class for creating windows is AppWindow. The easiest way to create and launch a new window is to use an instance of the AppWindow class and call its method `show()`. You can see an example below:
|
||
4 years ago
|
|
||
|
```kotlin
|
||
|
import androidx.compose.desktop.AppWindow
|
||
|
|
||
|
fun main() {
|
||
4 years ago
|
AppWindow().show {
|
||
|
// content
|
||
|
}
|
||
4 years ago
|
}
|
||
|
```
|
||
|
|
||
4 years ago
|
There are two types of window – modal and regular. Below are the functions for creating each type of window:
|
||
4 years ago
|
|
||
4 years ago
|
1. Window – regular window type.
|
||
|
2. Dialog – modal window type. Such a window locks its parent window until the user completes working with it and closes the modal window.
|
||
4 years ago
|
|
||
4 years ago
|
You can see an example of both types of window below.
|
||
4 years ago
|
|
||
|
```kotlin
|
||
|
import androidx.compose.desktop.Window
|
||
4 years ago
|
import androidx.compose.foundation.Text
|
||
4 years ago
|
import androidx.compose.material.Button
|
||
|
import androidx.compose.runtime.mutableStateOf
|
||
|
import androidx.compose.runtime.remember
|
||
|
import androidx.compose.ui.window.Dialog
|
||
|
|
||
|
fun main() {
|
||
4 years ago
|
Window {
|
||
|
val dialogState = remember { mutableStateOf(false) }
|
||
|
|
||
4 years ago
|
Button(onClick = { dialogState.value = true }) {
|
||
|
Text(text = "Open dialog")
|
||
|
}
|
||
4 years ago
|
|
||
|
if (dialogState.value) {
|
||
|
Dialog(
|
||
|
onDismissEvent = { dialogState.value = false }
|
||
|
) {
|
||
|
// dialog's content
|
||
|
}
|
||
|
}
|
||
|
}
|
||
4 years ago
|
}
|
||
|
```
|
||
|
|
||
|
## Window attributes
|
||
|
|
||
4 years ago
|
Each window has 9 parameters, all of them could be omitted and have default values:
|
||
4 years ago
|
|
||
4 years ago
|
1. title – window title
|
||
|
2. size – initial window size
|
||
|
3. location – initial window position
|
||
|
4. centered – set the window to the center of the display
|
||
|
5. icon – window icon
|
||
|
6. menuBar – window context menu
|
||
|
7. undecorated – disable native border and title bar of the window
|
||
|
8. events – window events
|
||
|
9. onDismissEvent – event when removing the window content from a composition
|
||
4 years ago
|
|
||
4 years ago
|
An example of using window parameters in the creation step:
|
||
4 years ago
|
|
||
|
```kotlin
|
||
4 years ago
|
import androidx.compose.desktop.AppManager
|
||
4 years ago
|
import androidx.compose.desktop.Window
|
||
|
import androidx.compose.desktop.WindowEvents
|
||
4 years ago
|
import androidx.compose.foundation.Text
|
||
|
import androidx.compose.foundation.layout.Box
|
||
|
import androidx.compose.foundation.layout.Column
|
||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||
|
import androidx.compose.material.Button
|
||
|
import androidx.compose.runtime.mutableStateOf
|
||
|
import androidx.compose.ui.Alignment
|
||
|
import androidx.compose.ui.input.key.Key
|
||
|
import androidx.compose.ui.Modifier
|
||
4 years ago
|
import androidx.compose.ui.unit.IntOffset
|
||
|
import androidx.compose.ui.unit.IntSize
|
||
4 years ago
|
import androidx.compose.ui.window.MenuItem
|
||
|
import androidx.compose.ui.window.KeyStroke
|
||
|
import androidx.compose.ui.window.Menu
|
||
|
import androidx.compose.ui.window.MenuBar
|
||
|
import java.awt.Color
|
||
|
import java.awt.Graphics2D
|
||
|
import java.awt.image.BufferedImage
|
||
4 years ago
|
|
||
|
fun main() {
|
||
4 years ago
|
val count = mutableStateOf(0)
|
||
|
val windowPos = mutableStateOf(IntOffset.Zero)
|
||
|
|
||
4 years ago
|
Window(
|
||
|
title = "MyApp",
|
||
4 years ago
|
size = IntSize(400, 250),
|
||
|
location = IntOffset(100, 100),
|
||
4 years ago
|
centered = false, // true - by default
|
||
4 years ago
|
icon = getMyAppIcon(),
|
||
|
menuBar = MenuBar(
|
||
|
Menu(
|
||
|
name = "Actions",
|
||
|
MenuItem(
|
||
|
name = "Increment value",
|
||
|
onClick = {
|
||
|
count.value++
|
||
|
},
|
||
|
shortcut = KeyStroke(Key.I)
|
||
|
),
|
||
|
MenuItem(
|
||
|
name = "Exit",
|
||
|
onClick = { AppManager.exit() },
|
||
|
shortcut = KeyStroke(Key.X)
|
||
|
)
|
||
|
)
|
||
|
),
|
||
4 years ago
|
undecorated = true, // false - by default
|
||
|
events = WindowEvents(
|
||
4 years ago
|
onRelocate = { location ->
|
||
|
windowPos.value = location
|
||
4 years ago
|
}
|
||
|
)
|
||
|
) {
|
||
|
// content
|
||
4 years ago
|
Box(
|
||
|
modifier = Modifier.fillMaxSize(),
|
||
|
alignment = Alignment.Center
|
||
|
) {
|
||
|
Column {
|
||
|
Text(text = "Location: ${windowPos.value} Value: ${count.value}")
|
||
|
Button(
|
||
|
onClick = {
|
||
|
AppManager.exit()
|
||
|
}
|
||
|
) {
|
||
|
Text(text = "Close app")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
|
||
|
fun getMyAppIcon() : BufferedImage {
|
||
|
val size = 256
|
||
|
val image = BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB)
|
||
|
val graphics = image.createGraphics()
|
||
|
graphics.setColor(Color.orange)
|
||
|
graphics.fillOval(0, 0, size, size)
|
||
|
graphics.dispose()
|
||
|
return image
|
||
|
}
|
||
4 years ago
|
```
|
||
|
|
||
4 years ago
|
![Window attributes](window_attr.gif)
|
||
|
|
||
4 years ago
|
## Window properties
|
||
|
|
||
|
AppWindow parameters correspond to the following properties:
|
||
|
|
||
4 years ago
|
1. title – window title
|
||
|
2. width – window width
|
||
|
3. height – window height
|
||
|
4. x – position of the left top corner of the window along the X axis
|
||
|
5. y – position of the left top corner of the window along the Y axis
|
||
|
6. icon – window icon image
|
||
|
7. events – window events
|
||
4 years ago
|
|
||
|
To get the properties of a window, it is enough to have a link to the current or specific window. There are two ways to get the current focused window:
|
||
|
|
||
|
1. Using the global environment:
|
||
|
|
||
|
```kotlin
|
||
|
import androidx.compose.desktop.AppWindowAmbient
|
||
|
import androidx.compose.desktop.Window
|
||
4 years ago
|
import androidx.compose.foundation.Text
|
||
|
import androidx.compose.foundation.layout.Box
|
||
|
import androidx.compose.foundation.layout.Column
|
||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||
|
import androidx.compose.material.Button
|
||
|
import androidx.compose.runtime.mutableStateOf
|
||
|
import androidx.compose.ui.Alignment
|
||
|
import androidx.compose.ui.Modifier
|
||
|
import androidx.compose.ui.unit.IntOffset
|
||
4 years ago
|
|
||
|
fun main() {
|
||
4 years ago
|
val windowPos = mutableStateOf(IntOffset.Zero)
|
||
|
|
||
4 years ago
|
Window {
|
||
|
val current = AppWindowAmbient.current
|
||
4 years ago
|
|
||
|
// content
|
||
|
Box(
|
||
|
modifier = Modifier.fillMaxSize(),
|
||
|
alignment = Alignment.Center
|
||
|
) {
|
||
|
Column {
|
||
|
Text(text = "Location: ${windowPos.value}")
|
||
|
Button(
|
||
|
onClick = {
|
||
|
if (current != null) {
|
||
|
windowPos.value = IntOffset(current.x, current.y)
|
||
|
}
|
||
|
}
|
||
|
) {
|
||
|
Text(text = "Print window location")
|
||
4 years ago
|
}
|
||
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
|
```
|
||
|
|
||
|
2. Using AppManager:
|
||
|
|
||
|
```kotlin
|
||
|
import androidx.compose.desktop.AppManager
|
||
|
import androidx.compose.desktop.Window
|
||
4 years ago
|
import androidx.compose.foundation.Text
|
||
|
import androidx.compose.foundation.layout.Box
|
||
|
import androidx.compose.foundation.layout.Column
|
||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||
|
import androidx.compose.material.Button
|
||
|
import androidx.compose.runtime.mutableStateOf
|
||
|
import androidx.compose.ui.Alignment
|
||
|
import androidx.compose.ui.Modifier
|
||
|
import androidx.compose.ui.unit.IntOffset
|
||
4 years ago
|
|
||
|
fun main() {
|
||
4 years ago
|
val windowPos = mutableStateOf(IntOffset.Zero)
|
||
|
|
||
4 years ago
|
Window {
|
||
4 years ago
|
// content
|
||
|
Box(
|
||
|
modifier = Modifier.fillMaxSize(),
|
||
|
alignment = Alignment.Center
|
||
|
) {
|
||
|
Column {
|
||
|
Text(text = "Location: ${windowPos.value}")
|
||
|
Button(
|
||
|
onClick = {
|
||
|
val current = AppManager.focusedWindow
|
||
|
if (current != null) {
|
||
|
windowPos.value = IntOffset(current.x, current.y)
|
||
|
}
|
||
|
}
|
||
|
) {
|
||
|
Text(text = "Print window location")
|
||
4 years ago
|
}
|
||
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
|
```
|
||
|
|
||
4 years ago
|
![Window properties](current_window.gif)
|
||
|
|
||
4 years ago
|
Using the following methods, you can change the properties of the AppWindow:
|
||
4 years ago
|
|
||
4 years ago
|
1. setTitle(title: String) – window title
|
||
|
2. setSize(width: Int, height: Int) – window size
|
||
|
3. setLocation(x: Int, y: Int) – window position
|
||
|
4. setWindowCentered() – set the window to the center of the display
|
||
|
5. setIcon(image: BufferedImage?) – window icon
|
||
4 years ago
|
|
||
|
```kotlin
|
||
|
import androidx.compose.desktop.AppWindowAmbient
|
||
|
import androidx.compose.desktop.Window
|
||
4 years ago
|
import androidx.compose.foundation.Text
|
||
|
import androidx.compose.material.Button
|
||
4 years ago
|
|
||
|
fun main() {
|
||
4 years ago
|
Window {
|
||
|
val current = AppWindowAmbient.current
|
||
4 years ago
|
|
||
|
// content
|
||
4 years ago
|
Button(
|
||
|
onClick = {
|
||
|
if (current != null) {
|
||
|
current.setWindowCentered()
|
||
|
}
|
||
|
}
|
||
4 years ago
|
) {
|
||
|
Text(text = "Center the window")
|
||
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
|
```
|
||
|
|
||
4 years ago
|
![Window properties](center_the_window.gif)
|
||
|
|
||
4 years ago
|
## Window events
|
||
|
|
||
4 years ago
|
Events can be defined using the events parameter in the window creation step or redefine using the events property at runtime.
|
||
4 years ago
|
Actions can be assigned to the following window events:
|
||
|
|
||
4 years ago
|
1. onOpen – event during window opening
|
||
|
2. onClose – event during window closing
|
||
|
3. onMinimize – event during window minimizing
|
||
|
4. onMaximize – event during window maximizing
|
||
|
5. onRestore – event during restoring window size after window minimize/maximize
|
||
|
6. onFocusGet – event when window gets focus
|
||
|
7. onFocusLost – event when window loses focus
|
||
|
8. onResize – event on window resize (argument is window size as IntSize)
|
||
|
9. onRelocate – event of the window reposition on display (argument is window position as IntOffset)
|
||
4 years ago
|
|
||
|
```kotlin
|
||
|
import androidx.compose.desktop.Window
|
||
|
import androidx.compose.desktop.WindowEvents
|
||
4 years ago
|
import androidx.compose.foundation.Text
|
||
|
import androidx.compose.foundation.layout.Box
|
||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||
|
import androidx.compose.material.Button
|
||
|
import androidx.compose.runtime.mutableStateOf
|
||
|
import androidx.compose.ui.Alignment
|
||
|
import androidx.compose.ui.Modifier
|
||
|
import androidx.compose.ui.unit.IntSize
|
||
4 years ago
|
|
||
|
fun main() {
|
||
4 years ago
|
val windowSize = mutableStateOf(IntSize.Zero)
|
||
|
val focused = mutableStateOf(false)
|
||
|
|
||
4 years ago
|
Window(
|
||
|
events = WindowEvents(
|
||
4 years ago
|
onFocusGet = { focused.value = true },
|
||
|
onFocusLost = { focused.value = false },
|
||
4 years ago
|
onResize = { size ->
|
||
4 years ago
|
windowSize.value = size
|
||
4 years ago
|
}
|
||
|
)
|
||
|
) {
|
||
|
// content
|
||
4 years ago
|
Box(
|
||
|
modifier = Modifier.fillMaxSize(),
|
||
|
alignment = Alignment.Center
|
||
|
) {
|
||
|
Text(text = "Size: ${windowSize.value} Focused: ${focused.value}")
|
||
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
|
```
|
||
|
|
||
4 years ago
|
![Window events](focus_the_window.gif)
|
||
|
|
||
4 years ago
|
## AppManager
|
||
|
|
||
4 years ago
|
The AppManager singleton is used to customize the behavior of the entire application. Its main features:
|
||
4 years ago
|
|
||
|
1. Description of common application events
|
||
|
```kotlin
|
||
4 years ago
|
AppManager.setEvents(
|
||
|
onAppStart = { println("onAppStart") }, // invoked before the first window is created
|
||
|
onAppExit = { println("onAppExit") } // invoked after all windows are closed
|
||
4 years ago
|
)
|
||
|
```
|
||
|
2. Customization of common application context menu
|
||
|
```kotlin
|
||
4 years ago
|
AppManager.setMenu(
|
||
4 years ago
|
getCommonAppMenuBar() // custom function that returns MenuBar
|
||
4 years ago
|
)
|
||
|
```
|
||
|
3. Access to the application windows list
|
||
|
```kotlin
|
||
4 years ago
|
val windows = AppManager.windows
|
||
4 years ago
|
```
|
||
|
4. Getting the current focused window
|
||
|
```kotlin
|
||
4 years ago
|
val current = AppManager.focusedWindow
|
||
4 years ago
|
```
|
||
|
5. Application exit
|
||
|
```kotlin
|
||
|
AppManager.exit() // closes all windows
|
||
|
```
|
||
|
|
||
4 years ago
|
## Access to Swing components
|
||
4 years ago
|
|
||
4 years ago
|
Compose for Desktop is tightly integrated with Swing at the top-level windows layer. For more detailed customization, you can access the JFrame class:
|
||
4 years ago
|
|
||
|
```kotlin
|
||
|
import androidx.compose.desktop.AppManager
|
||
|
import androidx.compose.desktop.Window
|
||
4 years ago
|
import androidx.compose.foundation.Text
|
||
|
import androidx.compose.foundation.layout.Box
|
||
|
import androidx.compose.foundation.layout.Column
|
||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||
|
import androidx.compose.material.Button
|
||
|
import androidx.compose.runtime.mutableStateOf
|
||
|
import androidx.compose.ui.Alignment
|
||
|
import androidx.compose.ui.Modifier
|
||
4 years ago
|
|
||
|
fun main() {
|
||
4 years ago
|
val scaleFactor = mutableStateOf(0.0)
|
||
4 years ago
|
Window {
|
||
4 years ago
|
// content
|
||
|
Box(
|
||
|
modifier = Modifier.fillMaxSize(),
|
||
|
alignment = Alignment.Center
|
||
|
) {
|
||
|
Column {
|
||
|
Button(
|
||
|
onClick = {
|
||
|
val current = AppManager.focusedWindow
|
||
|
if (current != null) {
|
||
|
val jFrame = current.window
|
||
|
// do whatever you want with it
|
||
|
scaleFactor.value = jFrame.graphicsConfiguration.defaultTransform.scaleX
|
||
|
}
|
||
|
}
|
||
|
) {
|
||
|
Text(text = "Check display scaling factor")
|
||
4 years ago
|
}
|
||
4 years ago
|
Text(text = "Scaling factor: ${scaleFactor.value}}")
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
4 years ago
|
}
|
||
|
```
|
||
4 years ago
|
|
||
|
![Access to Swing components](scaling_factor.jpg)
|