Browse Source

Merge pull request #15 from JetBrains/windowTutorial

Windows and tray tutorials
pull/24/head
Nikolay Igotti 4 years ago committed by GitHub
parent
commit
52e50a9b30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. BIN
      tutorials/Tray_Notifications_MenuBar/app_menubar.gif
  3. 304
      tutorials/Tray_Notifications_MenuBar/main.md
  4. BIN
      tutorials/Tray_Notifications_MenuBar/notifier.gif
  5. BIN
      tutorials/Tray_Notifications_MenuBar/tray.gif
  6. BIN
      tutorials/Tray_Notifications_MenuBar/window_menubar.gif
  7. BIN
      tutorials/Window_API/center_the_window.gif
  8. BIN
      tutorials/Window_API/current_window.gif
  9. BIN
      tutorials/Window_API/focus_the_window.gif
  10. 422
      tutorials/Window_API/main.md
  11. BIN
      tutorials/Window_API/scaling_factor.jpg
  12. BIN
      tutorials/Window_API/window_attr.gif

2
.gitignore vendored

@ -1,3 +1,5 @@
.DS_Store
# Compiled class file # Compiled class file
*.class *.class

BIN
tutorials/Tray_Notifications_MenuBar/app_menubar.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

304
tutorials/Tray_Notifications_MenuBar/main.md

@ -0,0 +1,304 @@
# Tray and menu notification
## What is covered
In this guide, we'll show you how to work with system tray, create application menu bar and create window specific menu bar, and send system notifications using Compose for Desktop.
## Tray
You can add an application icon into the system tray. Using Tray, you can also send notifications to the user. There are 3 types of notifications:
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.Text
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.onActive
import androidx.compose.runtime.onDispose
import androidx.compose.ui.Alignment
import androidx.compose.ui.window.MenuItem
import androidx.compose.ui.window.Tray
import androidx.compose.ui.Modifier
import java.awt.Color
import java.awt.Graphics2D
import java.awt.image.BufferedImage
fun main() {
val count = mutableStateOf(0)
Window(
icon = getMyAppIcon()
) {
onActive {
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(),
alignment = 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 notifications:
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.foundation.Text
import androidx.compose.material.Button
import androidx.compose.ui.window.Notifier
import java.awt.Color
import java.awt.Graphics2D
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 any particular window.
To create a common context menu for all application windows, you need to configure the AppManager.
```kotlin
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.Window
import androidx.compose.foundation.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.input.key.Key
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.KeyStroke
import androidx.compose.ui.window.MenuItem
import androidx.compose.ui.window.Menu
import androidx.compose.ui.window.MenuBar
fun main() {
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(),
alignment = Alignment.Center
) {
Text(text = action.value)
}
}
}
```
![Application MenuBar](app_menubar.gif)
You can to create a MenuBar for a specific window (while others will use the common MenuBar, if defined).
```kotlin
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.Window
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.input.key.Key
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.KeyStroke
import androidx.compose.ui.window.MenuItem
import androidx.compose.ui.window.Menu
import androidx.compose.ui.window.MenuBar
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
fun main() {
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(),
alignment = Alignment.Center
) {
Text(text = action.value)
}
}
}
```
![Window MenuBar](window_menubar.gif)

BIN
tutorials/Tray_Notifications_MenuBar/notifier.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

BIN
tutorials/Tray_Notifications_MenuBar/tray.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

BIN
tutorials/Tray_Notifications_MenuBar/window_menubar.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB

BIN
tutorials/Window_API/center_the_window.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

BIN
tutorials/Window_API/current_window.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

BIN
tutorials/Window_API/focus_the_window.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

422
tutorials/Window_API/main.md

@ -0,0 +1,422 @@
# OS windows management
## What is covered
In this guide, we'll 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
fun main() {
AppWindow().show {
// content
}
}
```
There are two types of windows - modal and regular. Below are 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 for both types of windows below.
```kotlin
import androidx.compose.desktop.Window
import androidx.compose.foundation.Text
import androidx.compose.material.Button
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.window.Dialog
fun main() {
Window {
val dialogState = remember { mutableStateOf(false) }
Button(onClick = { dialogState.value = true }) {
Text(text = "Open dialog")
}
if (dialogState.value) {
Dialog(
onDismissEvent = { dialogState.value = false }
) {
// dialog's content
}
}
}
}
```
## Window attributes
Each window has 9 parameters listed below, 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. events - window events
9. onDismissEvent - event when removing the window content from a composition
An example of using window parameters at the creation step:
```kotlin
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.Window
import androidx.compose.desktop.WindowEvents
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
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
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
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(),
alignment = 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.setColor(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. icon - window icon image
7. 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.AppWindowAmbient
import androidx.compose.desktop.Window
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
fun main() {
val windowPos = mutableStateOf(IntOffset.Zero)
Window {
val current = AppWindowAmbient.current
// 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")
}
}
}
}
}
```
2. Using AppManager:
```kotlin
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.Window
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
fun main() {
val windowPos = mutableStateOf(IntOffset.Zero)
Window {
// 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")
}
}
}
}
}
```
![Window properties](current_window.gif)
Using the following methods, one can change the properties of 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
```kotlin
import androidx.compose.desktop.AppWindowAmbient
import androidx.compose.desktop.Window
import androidx.compose.foundation.Text
import androidx.compose.material.Button
fun main() {
Window {
val current = AppWindowAmbient.current
// content
Button(
onClick = {
if (current != null) {
current.setWindowCentered()
}
}
) {
Text(text = "Center the window")
}
}
}
```
![Window properties](center_the_window.gif)
## Window events
Events could be defined using the events parameter at 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.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
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(),
alignment = 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 on the level of top level windows. For more detailed customization, you can access the JFrame (Swing window representation):
```kotlin
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.Window
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
fun main() {
val scaleFactor = mutableStateOf(0.0)
Window {
// 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")
}
Text(text = "Scaling factor: ${scaleFactor.value}}")
}
}
}
}
```
![Access to Swing components](scaling_factor.jpg)

BIN
tutorials/Window_API/scaling_factor.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

BIN
tutorials/Window_API/window_attr.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Loading…
Cancel
Save