@ -1 +1 @@ |
|||||||
Subproject commit 3de47bade63fae3a9ee0b7b5a5d9028ccaf0e3ef |
Subproject commit 883fe193e46c2ad23b3aa50d4122f97a679b56ea |
@ -1,5 +1,8 @@ |
|||||||
import androidx.compose.desktop.Window |
import androidx.compose.ui.window.Window |
||||||
|
import androidx.compose.ui.window.application |
||||||
|
|
||||||
fun main() = Window { |
fun main() = application { |
||||||
|
Window(onCloseRequest = ::exitApplication) { |
||||||
App() |
App() |
||||||
|
} |
||||||
} |
} |
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 52 KiB |
@ -1,311 +0,0 @@ |
|||||||
# Menu, tray, notifications |
|
||||||
|
|
||||||
## What is covered |
|
||||||
|
|
||||||
In this tutorial we'll show you how to work with the system tray, create an application menu bar and a window-specific menu bar, and send system notifications using Compose for Desktop. |
|
||||||
|
|
||||||
## Tray |
|
||||||
|
|
||||||
You can add an application icon to the system tray. You can also send notifications to the user using the system tray. There are 3 types of notification: |
|
||||||
|
|
||||||
1. notify - simple notification |
|
||||||
2. warn - warning notification |
|
||||||
3. error - error notification |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.AppManager |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.foundation.layout.fillMaxSize |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.runtime.DisposableEffect |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.window.v1.MenuItem |
|
||||||
import androidx.compose.ui.window.v1.Tray |
|
||||||
import java.awt.Color |
|
||||||
import java.awt.image.BufferedImage |
|
||||||
|
|
||||||
fun main() { |
|
||||||
val count = mutableStateOf(0) |
|
||||||
Window( |
|
||||||
icon = getMyAppIcon() |
|
||||||
) { |
|
||||||
DisposableEffect(Unit) { |
|
||||||
val tray = Tray().apply { |
|
||||||
icon(getTrayIcon()) |
|
||||||
menu( |
|
||||||
MenuItem( |
|
||||||
name = "Increment value", |
|
||||||
onClick = { |
|
||||||
count.value++ |
|
||||||
} |
|
||||||
), |
|
||||||
MenuItem( |
|
||||||
name = "Send notification", |
|
||||||
onClick = { |
|
||||||
notify("Notification", "Message from MyApp!") |
|
||||||
} |
|
||||||
), |
|
||||||
MenuItem( |
|
||||||
name = "Exit", |
|
||||||
onClick = { |
|
||||||
AppManager.exit() |
|
||||||
} |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
onDispose { |
|
||||||
tray.remove() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// content |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = Alignment.Center |
|
||||||
) { |
|
||||||
Text(text = "Value: ${count.value}") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun getMyAppIcon(): BufferedImage { |
|
||||||
val size = 256 |
|
||||||
val image = BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB) |
|
||||||
val graphics = image.createGraphics() |
|
||||||
graphics.setColor(Color.green) |
|
||||||
graphics.fillOval(size / 4, 0, size / 2, size) |
|
||||||
graphics.setColor(Color.blue) |
|
||||||
graphics.fillOval(0, size / 4, size, size / 2) |
|
||||||
graphics.setColor(Color.red) |
|
||||||
graphics.fillOval(size / 4, size / 4, size / 2, size / 2) |
|
||||||
graphics.dispose() |
|
||||||
return image |
|
||||||
} |
|
||||||
|
|
||||||
fun getTrayIcon(): 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 |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Tray](tray.gif) |
|
||||||
|
|
||||||
## Notifier |
|
||||||
You can send system notifications with Notifier without using the system tray. |
|
||||||
Notifier also has 3 types of notification: |
|
||||||
|
|
||||||
1. notify - simple notification |
|
||||||
2. warn - warning notification |
|
||||||
3. error - error notification |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.foundation.layout.Column |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.material.Button |
|
||||||
import androidx.compose.ui.window.Notifier |
|
||||||
import java.awt.Color |
|
||||||
import java.awt.image.BufferedImage |
|
||||||
|
|
||||||
fun main() { |
|
||||||
val message = "Some message!" |
|
||||||
val notifier = Notifier() |
|
||||||
Window( |
|
||||||
icon = getMyAppIcon() |
|
||||||
) { |
|
||||||
Column { |
|
||||||
Button(onClick = { notifier.notify("Notification.", message) }) { |
|
||||||
Text(text = "Notify") |
|
||||||
} |
|
||||||
Button(onClick = { notifier.warn("Warning.", message) }) { |
|
||||||
Text(text = "Warning") |
|
||||||
} |
|
||||||
Button(onClick = { notifier.error("Error.", message) }) { |
|
||||||
Text(text = "Error") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun getMyAppIcon() : BufferedImage { |
|
||||||
val size = 256 |
|
||||||
val image = BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB) |
|
||||||
val graphics = image.createGraphics() |
|
||||||
graphics.setColor(Color.green) |
|
||||||
graphics.fillOval(size / 4, 0, size / 2, size) |
|
||||||
graphics.setColor(Color.blue) |
|
||||||
graphics.fillOval(0, size / 4, size, size / 2) |
|
||||||
graphics.setColor(Color.red) |
|
||||||
graphics.fillOval(size / 4, size / 4, size / 2, size / 2) |
|
||||||
graphics.dispose() |
|
||||||
return image |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Notifier](notifier.gif) |
|
||||||
|
|
||||||
## MenuBar |
|
||||||
|
|
||||||
MenuBar is used to create and customize the common context menu of the application or a particular window. |
|
||||||
To create a common context menu for all the application windows, you need to configure the AppManager. |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.AppManager |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.foundation.layout.fillMaxSize |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi |
|
||||||
import androidx.compose.ui.input.key.Key |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.window.v1.KeyStroke |
|
||||||
import androidx.compose.ui.window.v1.MenuItem |
|
||||||
import androidx.compose.ui.window.v1.Menu |
|
||||||
import androidx.compose.ui.window.v1.MenuBar |
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class) |
|
||||||
fun main() { |
|
||||||
// To use Apple global menu. |
|
||||||
System.setProperty("apple.laf.useScreenMenuBar", "true") |
|
||||||
|
|
||||||
val action = mutableStateOf("Last action: None") |
|
||||||
|
|
||||||
AppManager.setMenu( |
|
||||||
MenuBar( |
|
||||||
Menu( |
|
||||||
name = "Actions", |
|
||||||
MenuItem( |
|
||||||
name = "About", |
|
||||||
onClick = { action.value = "Last action: About (Command + I)" }, |
|
||||||
shortcut = KeyStroke(Key.I) |
|
||||||
), |
|
||||||
MenuItem( |
|
||||||
name = "Exit", |
|
||||||
onClick = { AppManager.exit() }, |
|
||||||
shortcut = KeyStroke(Key.X) |
|
||||||
) |
|
||||||
), |
|
||||||
Menu( |
|
||||||
name = "File", |
|
||||||
MenuItem( |
|
||||||
name = "Copy", |
|
||||||
onClick = { action.value = "Last action: Copy (Command + C)" }, |
|
||||||
shortcut = KeyStroke(Key.C) |
|
||||||
), |
|
||||||
MenuItem( |
|
||||||
name = "Paste", |
|
||||||
onClick = { action.value = "Last action: Paste (Command + V)" }, |
|
||||||
shortcut = KeyStroke(Key.V) |
|
||||||
) |
|
||||||
) |
|
||||||
) |
|
||||||
) |
|
||||||
|
|
||||||
Window { |
|
||||||
// content |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = Alignment.Center |
|
||||||
) { |
|
||||||
Text(text = action.value) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Application MenuBar](app_menubar.gif) |
|
||||||
|
|
||||||
You can create a MenuBar for a specific window, and have the other windows use the defined MenuBar. |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.AppManager |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.material.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.ExperimentalComposeUiApi |
|
||||||
import androidx.compose.ui.input.key.Key |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.window.v1.KeyStroke |
|
||||||
import androidx.compose.ui.window.v1.MenuItem |
|
||||||
import androidx.compose.ui.window.v1.Menu |
|
||||||
import androidx.compose.ui.window.v1.MenuBar |
|
||||||
import androidx.compose.ui.unit.IntOffset |
|
||||||
import androidx.compose.ui.unit.IntSize |
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class) |
|
||||||
fun main() { |
|
||||||
// To use Apple global menu. |
|
||||||
System.setProperty("apple.laf.useScreenMenuBar", "true") |
|
||||||
|
|
||||||
val action = mutableStateOf("Last action: None") |
|
||||||
|
|
||||||
Window( |
|
||||||
menuBar = MenuBar( |
|
||||||
Menu( |
|
||||||
name = "Actions", |
|
||||||
MenuItem( |
|
||||||
name = "About", |
|
||||||
onClick = { action.value = "Last action: About (Command + I)" }, |
|
||||||
shortcut = KeyStroke(Key.I) |
|
||||||
), |
|
||||||
MenuItem( |
|
||||||
name = "Exit", |
|
||||||
onClick = { AppManager.exit() }, |
|
||||||
shortcut = KeyStroke(Key.X) |
|
||||||
) |
|
||||||
), |
|
||||||
Menu( |
|
||||||
name = "File", |
|
||||||
MenuItem( |
|
||||||
name = "Copy", |
|
||||||
onClick = { action.value = "Last action: Copy (Command + C)" }, |
|
||||||
shortcut = KeyStroke(Key.C) |
|
||||||
), |
|
||||||
MenuItem( |
|
||||||
name = "Paste", |
|
||||||
onClick = { action.value = "Last action: Paste (Command + V)" }, |
|
||||||
shortcut = KeyStroke(Key.V) |
|
||||||
) |
|
||||||
) |
|
||||||
) |
|
||||||
) { |
|
||||||
// content |
|
||||||
Button( |
|
||||||
onClick = { |
|
||||||
Window( |
|
||||||
title = "Another window", |
|
||||||
size = IntSize(350, 200), |
|
||||||
location = IntOffset(100, 100), |
|
||||||
centered = false |
|
||||||
) { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
) { |
|
||||||
Text(text = "New window") |
|
||||||
} |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = Alignment.Center |
|
||||||
) { |
|
||||||
Text(text = action.value) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Window MenuBar](window_menubar.gif) |
|
Before Width: | Height: | Size: 4.9 MiB |
Before Width: | Height: | Size: 4.3 MiB |
Before Width: | Height: | Size: 6.5 MiB |
Before Width: | Height: | Size: 6.9 MiB |
@ -1,502 +0,0 @@ |
|||||||
# Top level windows management |
|
||||||
|
|
||||||
## What is covered |
|
||||||
|
|
||||||
In this tutorial we will show you how to work with windows using Compose for Desktop. |
|
||||||
|
|
||||||
## Windows creation |
|
||||||
|
|
||||||
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: |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.AppWindow |
|
||||||
import javax.swing.SwingUtilities.invokeLater |
|
||||||
|
|
||||||
fun main() = invokeLater { |
|
||||||
AppWindow().show { |
|
||||||
// Content |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Note that AppWindow should be created in AWT Event Thread. Instead of calling `invokeLater()` explicitly you can use `Window` DSL: |
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
|
|
||||||
fun main() { |
|
||||||
Window { |
|
||||||
// Content |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
There are two types of window – modal and regular. Below are the functions for creating each type of window: |
|
||||||
|
|
||||||
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. |
|
||||||
|
|
||||||
You can see an example of both types of window below. |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.material.Button |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.runtime.remember |
|
||||||
import androidx.compose.ui.window.v1.Dialog |
|
||||||
|
|
||||||
fun main() { |
|
||||||
Window { |
|
||||||
val dialogState = remember { mutableStateOf(false) } |
|
||||||
|
|
||||||
Button(onClick = { dialogState.value = true }) { |
|
||||||
Text(text = "Open dialog") |
|
||||||
} |
|
||||||
|
|
||||||
if (dialogState.value) { |
|
||||||
Dialog( |
|
||||||
onDismissRequest = { dialogState.value = false } |
|
||||||
) { |
|
||||||
// Dialog's content |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Window attributes |
|
||||||
|
|
||||||
Each window has following parameters, all of them could be omitted and have default values: |
|
||||||
|
|
||||||
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. resizable – makes the window resizable or unresizable |
|
||||||
9. events – window events |
|
||||||
10. onDismissEvent – event when removing the window content from a composition |
|
||||||
|
|
||||||
An example of using window parameters in the creation step: |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.AppManager |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.desktop.WindowEvents |
|
||||||
import androidx.compose.material.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.ExperimentalComposeUiApi |
|
||||||
import androidx.compose.ui.input.key.Key |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.IntOffset |
|
||||||
import androidx.compose.ui.unit.IntSize |
|
||||||
import androidx.compose.ui.window.v1.MenuItem |
|
||||||
import androidx.compose.ui.window.v1.KeyStroke |
|
||||||
import androidx.compose.ui.window.v1.Menu |
|
||||||
import androidx.compose.ui.window.v1.MenuBar |
|
||||||
import java.awt.Color |
|
||||||
import java.awt.image.BufferedImage |
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class) |
|
||||||
fun main() { |
|
||||||
val count = mutableStateOf(0) |
|
||||||
val windowPos = mutableStateOf(IntOffset.Zero) |
|
||||||
|
|
||||||
Window( |
|
||||||
title = "MyApp", |
|
||||||
size = IntSize(400, 250), |
|
||||||
location = IntOffset(100, 100), |
|
||||||
centered = false, // true - by default |
|
||||||
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) |
|
||||||
) |
|
||||||
) |
|
||||||
), |
|
||||||
undecorated = true, // false - by default |
|
||||||
events = WindowEvents( |
|
||||||
onRelocate = { location -> |
|
||||||
windowPos.value = location |
|
||||||
} |
|
||||||
) |
|
||||||
) { |
|
||||||
// content |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = Alignment.Center |
|
||||||
) { |
|
||||||
Column { |
|
||||||
Text(text = "Location: ${windowPos.value} Value: ${count.value}") |
|
||||||
Button( |
|
||||||
onClick = { |
|
||||||
AppManager.exit() |
|
||||||
} |
|
||||||
) { |
|
||||||
Text(text = "Close app") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun getMyAppIcon() : BufferedImage { |
|
||||||
val size = 256 |
|
||||||
val image = BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB) |
|
||||||
val graphics = image.createGraphics() |
|
||||||
graphics.color = Color.orange |
|
||||||
graphics.fillOval(0, 0, size, size) |
|
||||||
graphics.dispose() |
|
||||||
return image |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Window attributes](window_attr.gif) |
|
||||||
|
|
||||||
## Window properties |
|
||||||
|
|
||||||
AppWindow parameters correspond to the following properties: |
|
||||||
|
|
||||||
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. resizable - returns `true` if the window resizable, `false` otherwise |
|
||||||
7. icon – window icon image |
|
||||||
8. events – window events |
|
||||||
|
|
||||||
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.LocalAppWindow |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
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.material.Text |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.IntOffset |
|
||||||
|
|
||||||
fun main() { |
|
||||||
val windowPos = mutableStateOf(IntOffset.Zero) |
|
||||||
|
|
||||||
Window { |
|
||||||
val current = LocalAppWindow.current |
|
||||||
|
|
||||||
// Content |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = Alignment.Center |
|
||||||
) { |
|
||||||
Column { |
|
||||||
Text(text = "Location: ${windowPos.value}") |
|
||||||
Button( |
|
||||||
onClick = { |
|
||||||
windowPos.value = IntOffset(current.x, current.y) |
|
||||||
} |
|
||||||
) { |
|
||||||
Text(text = "Print window location") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
2. Using AppManager: |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.AppManager |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.material.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 |
|
||||||
|
|
||||||
fun main() { |
|
||||||
val windowPos = mutableStateOf(IntOffset.Zero) |
|
||||||
|
|
||||||
Window { |
|
||||||
// Content |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = 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") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Window properties](current_window.gif) |
|
||||||
|
|
||||||
Using the following methods, you can change the properties of the AppWindow: |
|
||||||
|
|
||||||
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 |
|
||||||
6. setMenuBar(menuBar: MenuBar) - window menu bar |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.LocalAppWindow |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.material.Button |
|
||||||
|
|
||||||
fun main() { |
|
||||||
Window { |
|
||||||
val window = LocalAppWindow.current |
|
||||||
// Content |
|
||||||
Button( |
|
||||||
onClick = { |
|
||||||
window.setWindowCentered() |
|
||||||
} |
|
||||||
) { |
|
||||||
Text(text = "Center the window") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Window properties](center_the_window.gif) |
|
||||||
|
|
||||||
## Methods |
|
||||||
|
|
||||||
Using the following methods, you can change the state of the AppWindow: |
|
||||||
|
|
||||||
1. show(parentComposition: CompositionReference? = null, content: @Composable () -> Unit) – shows a window with the given Compose content, |
|
||||||
`parentComposition` is the parent of this window's composition. |
|
||||||
2. close() - closes the window. |
|
||||||
3. minimize() - minimizes the window to the taskbar. If the window is in fullscreen mode this method is ignored. |
|
||||||
4. maximize() - maximizes the window to fill all available screen space. If the window is in fullscreen mode this method is ignored. |
|
||||||
5. makeFullscreen() - switches the window to fullscreen mode if the window is resizable. If the window is in fullscreen mode `minimize()` and `maximize()` methods are ignored. |
|
||||||
6. restore() - restores the normal state and size of the window after maximizing/minimizing/fullscreen mode. |
|
||||||
|
|
||||||
You can know about window state via properties below: |
|
||||||
|
|
||||||
1. isMinimized - returns true if the window is minimized, false otherwise. |
|
||||||
2. isMaximized - returns true if the window is maximized, false otherwise. |
|
||||||
3. isFullscreen - returns true if the window is in fullscreen state, false otherwise. |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.foundation.layout.Column |
|
||||||
import androidx.compose.foundation.layout.fillMaxSize |
|
||||||
import androidx.compose.foundation.layout.height |
|
||||||
import androidx.compose.foundation.layout.padding |
|
||||||
import androidx.compose.foundation.layout.size |
|
||||||
import androidx.compose.foundation.layout.Spacer |
|
||||||
import androidx.compose.desktop.AppManager |
|
||||||
import androidx.compose.desktop.AppWindow |
|
||||||
import androidx.compose.material.Button |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
import javax.swing.SwingUtilities.invokeLater |
|
||||||
|
|
||||||
fun main() = invokeLater { |
|
||||||
AppWindow().show { |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = Alignment.Center |
|
||||||
) { |
|
||||||
Column( |
|
||||||
modifier = Modifier.padding(top = 20.dp, bottom = 20.dp) |
|
||||||
) { |
|
||||||
Button("Minimize", { AppManager.focusedWindow?.minimize() }) |
|
||||||
Button("Maximize", { AppManager.focusedWindow?.maximize() }) |
|
||||||
Button("Fullscreen", { AppManager.focusedWindow?.makeFullscreen() }) |
|
||||||
Button("Restore", { AppManager.focusedWindow?.restore() }) |
|
||||||
Spacer(modifier = Modifier.height(20.dp)) |
|
||||||
Button("Close", { AppManager.focusedWindow?.close() }) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun Button(text: String = "", action: (() -> Unit)? = null) { |
|
||||||
Button( |
|
||||||
modifier = Modifier.size(150.dp, 30.dp), |
|
||||||
onClick = { action?.invoke() } |
|
||||||
) { |
|
||||||
Text(text) |
|
||||||
} |
|
||||||
Spacer(modifier = Modifier.height(10.dp)) |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Window state](window_state.gif) |
|
||||||
|
|
||||||
## Window events |
|
||||||
|
|
||||||
Events can be defined using the events parameter in the window creation step or redefine using the events property at runtime. |
|
||||||
Actions can be assigned to the following window events: |
|
||||||
|
|
||||||
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) |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.desktop.WindowEvents |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.foundation.layout.fillMaxSize |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.IntSize |
|
||||||
|
|
||||||
fun main() { |
|
||||||
val windowSize = mutableStateOf(IntSize.Zero) |
|
||||||
val focused = mutableStateOf(false) |
|
||||||
|
|
||||||
Window( |
|
||||||
events = WindowEvents( |
|
||||||
onFocusGet = { focused.value = true }, |
|
||||||
onFocusLost = { focused.value = false }, |
|
||||||
onResize = { size -> |
|
||||||
windowSize.value = size |
|
||||||
} |
|
||||||
) |
|
||||||
) { |
|
||||||
// Content |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = Alignment.Center |
|
||||||
) { |
|
||||||
Text(text = "Size: ${windowSize.value} Focused: ${focused.value}") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Window events](focus_the_window.gif) |
|
||||||
|
|
||||||
## AppManager |
|
||||||
|
|
||||||
The AppManager singleton is used to customize the behavior of the entire application. Its main features: |
|
||||||
|
|
||||||
1. Description of common application events |
|
||||||
``` kotlin |
|
||||||
AppManager.setEvents( |
|
||||||
onAppStart = { println("onAppStart") }, // Invoked before the first window is created |
|
||||||
onAppExit = { println("onAppExit") } // Invoked after all windows are closed |
|
||||||
) |
|
||||||
``` |
|
||||||
2. Customization of common application context menu |
|
||||||
``` kotlin |
|
||||||
AppManager.setMenu( |
|
||||||
getCommonAppMenuBar() // Custom function that returns MenuBar |
|
||||||
) |
|
||||||
``` |
|
||||||
3. Access to the application windows list |
|
||||||
``` kotlin |
|
||||||
val windows = AppManager.windows |
|
||||||
``` |
|
||||||
4. Getting the current focused window |
|
||||||
``` kotlin |
|
||||||
val current = AppManager.focusedWindow |
|
||||||
``` |
|
||||||
5. Application exit |
|
||||||
``` kotlin |
|
||||||
AppManager.exit() // Closes all windows |
|
||||||
``` |
|
||||||
|
|
||||||
## Access to Swing components |
|
||||||
|
|
||||||
Compose for Desktop is tightly integrated with Swing at the top-level windows layer. For more detailed customization, you can access the JFrame class: |
|
||||||
|
|
||||||
```kotlin |
|
||||||
import androidx.compose.desktop.AppManager |
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.material.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 |
|
||||||
|
|
||||||
fun main() { |
|
||||||
val scaleFactor = mutableStateOf(0.0) |
|
||||||
Window { |
|
||||||
// Content |
|
||||||
Box( |
|
||||||
modifier = Modifier.fillMaxSize(), |
|
||||||
contentAlignment = 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") |
|
||||||
} |
|
||||||
Text(text = "Scaling factor: ${scaleFactor.value}") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
![Access to Swing components](scaling_factor.jpg) |
|
Before Width: | Height: | Size: 6.5 MiB |
Before Width: | Height: | Size: 3.9 MiB |
Before Width: | Height: | Size: 5.4 MiB |
Before Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 3.8 MiB |
Before Width: | Height: | Size: 5.6 MiB |