Window resizing API update.
- added [resizable] argument to AppWindow, Window, DesktopDialog
- added [makeFullscreen], [maximize], [minimize], [restore] methods to AppFrame, AppWindow
- added [isFullscreen], [isMaximized], [isMinimized] properties to AppFrame, AppWindow
- fixed possible memory leak: when ComposeLayer is disposed, [composition.dispose()] is invoked
- added kdoc to new API
Test: manual.
Change-Id: I3ea11677288baeab2762665f859e8227a51e1bc7
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 1e2c5ac..d3040c6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -99,7 +99,7 @@
const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.2.9"
const val RX_JAVA3 = "io.reactivex.rxjava3:rxjava:3.0.0"
-val SKIKO_VERSION = System.getenv("SKIKO_VERSION") ?: "0.1.18"
+val SKIKO_VERSION = System.getenv("SKIKO_VERSION") ?: "0.1.21"
val SKIKO = "org.jetbrains.skiko:skiko-jvm:$SKIKO_VERSION"
val SKIKO_LINUX_X64 = "org.jetbrains.skiko:skiko-jvm-runtime-linux-x64:$SKIKO_VERSION"
val SKIKO_MACOS_X64 = "org.jetbrains.skiko:skiko-jvm-runtime-macos-x64:$SKIKO_VERSION"
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
index db9c668..734012b 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
@@ -96,9 +96,29 @@
TextBox(text = AppState.wndTitle.value)
}
Row {
- Button(color = Color(232, 182, 109), size = IntSize(16, 16))
+ Button(
+ color = Color(210, 210, 210),
+ size = IntSize(16, 16),
+ >
+ AppManager.focusedWindow?.makeFullscreen()
+ }
+ )
+ Spacer(modifier = Modifier.width(30.dp))
+ Button(
+ color = Color(232, 182, 109),
+ size = IntSize(16, 16),
+ >
+ AppManager.focusedWindow?.minimize()
+ }
+ )
Spacer(modifier = Modifier.width(3.dp))
- Button(color = Color(150, 232, 150), size = IntSize(16, 16))
+ Button(
+ color = Color(150, 232, 150),
+ size = IntSize(16, 16),
+ >
+ AppManager.focusedWindow?.maximize()
+ }
+ )
Spacer(modifier = Modifier.width(3.dp))
Button(
AppManager.exit() },
@@ -130,6 +150,9 @@
AppManager.focusedWindow?.close()
}
)
+ onDispose {
+ println("Dispose composition")
+ }
}
},
color = Color(26, 198, 188)
@@ -160,15 +183,14 @@
.fillMaxWidth()
) {
ContextMenu()
- Spacer(modifier = Modifier.height(30.dp))
- Spacer(modifier = Modifier.height(60.dp))
+ Spacer(modifier = Modifier.height(100.dp))
Row {
Checkbox(
checked = AppState.undecorated.value,
>
AppState.undecorated.value = !AppState.undecorated.value
},
- modifier = Modifier.height(30.dp).padding(start = 20.dp)
+ modifier = Modifier.height(35.dp).padding(start = 20.dp)
)
Spacer(modifier = Modifier.width(5.dp))
TextBox(text = "- undecorated")
@@ -320,7 +342,7 @@
text: String = "",
onClick: () -> Unit = {},
color: Color = Color(10, 162, 232),
- size: IntSize = IntSize(150, 30)
+ size: IntSize = IntSize(200, 35)
) {
val buttonHover = remember { mutableStateOf(false) }
Button(
@@ -371,7 +393,8 @@
Surface(
modifier = Modifier
- .padding(start = 4.dp, top = 2.dp),
+ .padding(start = 4.dp, top = 2.dp)
+ .clickable( showMenu.value = true }),
color = Color(255, 255, 255, 40),
shape = RoundedCornerShape(4.dp)
) {
@@ -380,9 +403,8 @@
TextBox(
text = "Selected: ${items[selectedIndex.value]}",
modifier = Modifier
- .height(26.dp)
+ .height(35.dp)
.padding(start = 4.dp, end = 4.dp)
- .clickable( showMenu.value = true })
)
},
expanded = showMenu.value,
@@ -405,7 +427,7 @@
@Composable
fun RadioButton(text: String, state: MutableState<Boolean>) {
Box(
- modifier = Modifier.height(30.dp),
+ modifier = Modifier.height(35.dp),
contentAlignment = Alignment.Center
) {
RadioButton(
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/Main.kt
index 4269d43..3fc4f935 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/Main.kt
@@ -71,6 +71,7 @@
),
Menu(
"About",
+ MenuItems.IsFullscreen,
MenuItems.About,
MenuItems.Update
)
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/MenuItems.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/MenuItems.kt
index 0d41ab0..e43c1d2 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/MenuItems.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/MenuItems.kt
@@ -60,4 +60,12 @@
},
shortcut = KeyStroke(Key.U)
)
+
+ val IsFullscreen = MenuItem(
+ name = "Is fullscreen mode",
+ >
+ println("Fullscreen mode: ${AppManager.focusedWindow?.isFullscreen}")
+ },
+ shortcut = KeyStroke(Key.F)
+ )
}
\ No newline at end of file
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt
index bfb6073..d3826cd 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt
@@ -36,6 +36,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.onDispose
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -70,6 +71,9 @@
// setting the content
composePanel.setContent {
ComposeContent()
+ onDispose {
+ println("Dispose composition")
+ }
}
val window = JFrame()
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
index c2f7983..3a7a05b 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
@@ -67,7 +67,7 @@
val rule = createComposeRule()
// don't inline, surface controls canvas life time
- private val surface = Surface.makeRasterN32Premul(100, 100)
+ private val surface = Surface.makeRasterN32Premul(100, 100)!!
private val canvas = surface.canvas
@Test
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt
index f66b098..c112bfd 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt
+++ b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt
@@ -159,7 +159,7 @@
}
private fun performSetContent(composable: @Composable() () -> Unit) {
- val surface = Surface.makeRasterN32Premul(displaySize.width, displaySize.height)
+ val surface = Surface.makeRasterN32Premul(displaySize.width, displaySize.height)!!
val canvas = surface.canvas
val owners = DesktopOwners(invalidate = {}).also {
owners = it
diff --git a/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/TestComposeWindow.kt b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
index 3c47455..1f05602 100644
--- a/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
+++ b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
@@ -32,7 +32,7 @@
val density: Density = Density(1f, 1f),
var desktopPlatform: DesktopPlatform = DesktopPlatform.Linux
) {
- val surface = Surface.makeRasterN32Premul(width, height)
+ val surface = Surface.makeRasterN32Premul(width, height)!!
val canvas = surface.canvas
val owners = DesktopOwners(invalidate = {})
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppFrame.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppFrame.kt
index cd34bb7..2f06ec2 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppFrame.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppFrame.kt
@@ -19,6 +19,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.window.MenuBar
import java.awt.image.BufferedImage
+import javax.swing.JFrame
/**
* AppFrame is an abstract class that represents a window.
@@ -121,6 +122,54 @@
abstract fun removeMenuBar()
/**
+ * Switches the window to fullscreen mode if the window is resizable. If the window is in
+ * fullscreen mode [minimize] and [maximize] methods are ignored.
+ */
+ abstract fun makeFullscreen()
+
+ /**
+ * Returns true if the window is in fullscreen state, false otherwise.
+ */
+ abstract val isFullscreen: Boolean
+ get
+
+ /**
+ * Minimizes the window to the taskbar.
+ */
+ abstract fun minimize()
+
+ /**
+ * Returns true if the window is minimized, false otherwise.
+ */
+ val isMinimized: Boolean
+ get() = window.extendedState == JFrame.ICONIFIED
+
+ /**
+ * Maximizes the window to fill all available screen space.
+ */
+ abstract fun maximize()
+
+ /**
+ * Returns true if the window is maximized, false otherwise.
+ */
+ val isMaximized: Boolean
+ get() = window.extendedState == JFrame.MAXIMIZED_BOTH
+
+ /**
+ * Restores the previous state and size of the window after
+ * maximizing/minimizing/fullscreen mode.
+ */
+ abstract fun restore()
+
+ /**
+ * Sets the ability to resize the window. True - the window can be resized,
+ * false - the window cannot be resized. If the window is in fullscreen mode
+ * setter of this property is ignored. If this property is true the [makeFullscreen()]
+ * method is ignored.
+ */
+ abstract var resizable: Boolean
+
+ /**
* Sets the new position of the window on the screen.
*
* @param x the new x-coordinate of the window.
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppWindow.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppWindow.kt
index a4a323b..36b80cd 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppWindow.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/AppWindow.kt
@@ -32,6 +32,7 @@
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.awt.image.BufferedImage
+import javax.swing.JFrame
import javax.swing.JMenuBar
import javax.swing.SwingUtilities
import javax.swing.WindowConstants
@@ -52,6 +53,8 @@
* @param menuBar Window menu bar. The menu bar can be displayed inside a window (Windows,
* Linux) or at the top of the screen (Mac OS).
* @param undecorated Removes the native window border if set to true. The default value is false.
+ * @param resizable Makes the window resizable if is set to true and unresizable if is set to
+ * false. The default value is true.
* @param events Allows to describe events of the window.
* Supported events: onOpen, onClose, onMinimize, onMaximize, onRestore, onFocusGet, onFocusLost,
* onResize, onRelocate.
@@ -65,6 +68,7 @@
icon: BufferedImage? = null,
menuBar: MenuBar? = null,
undecorated: Boolean = false,
+ resizable: Boolean = true,
events: WindowEvents = WindowEvents(),
onDismissRequest: (() -> Unit)? = null,
content: @Composable () -> Unit = emptyContent()
@@ -77,6 +81,7 @@
icon = icon,
menuBar = menuBar,
undecorated = undecorated,
+ resizable = resizable,
events = events,
>
).show {
@@ -121,7 +126,10 @@
})
addWindowFocusListener(object : WindowAdapter() {
override fun windowGainedFocus(event: WindowEvent) {
- window.setJMenuBar(parent.menuBar?.menuBar)
+ // Dialogs should not receive a common application menu bar
+ if (invoker == null) {
+ window.setJMenuBar(parent.menuBar?.menuBar)
+ }
events.invokeOnFocusGet()
}
override fun windowLostFocus(event: WindowEvent) {
@@ -158,6 +166,7 @@
icon: BufferedImage? = null,
menuBar: MenuBar? = null,
undecorated: Boolean = false,
+ resizable: Boolean = true,
events: WindowEvents = WindowEvents(),
onDismissRequest: (() -> Unit)? = null
) : this(
@@ -168,6 +177,7 @@
icon = icon,
menuBar = menuBar,
undecorated = undecorated,
+ resizable = resizable,
events = events,
>
) {
@@ -188,6 +198,8 @@
* @param menuBar Window menu bar. The menu bar can be displayed inside a window (Windows,
* Linux) or at the top of the screen (Mac OS).
* @param undecorated Removes the native window border if set to true. The default value is false.
+ * @param resizable Makes the window resizable if is set to true and unresizable if is set to
+ * false. The default value is true.
* @param events Allows to describe events of the window.
* Supported events: onOpen, onClose, onMinimize, onMaximize, onRestore, onFocusGet, onFocusLost,
* onResize, onRelocate.
@@ -201,6 +213,7 @@
icon: BufferedImage? = null,
menuBar: MenuBar? = null,
undecorated: Boolean = false,
+ resizable: Boolean = true,
events: WindowEvents = WindowEvents(),
onDismissRequest: (() -> Unit)? = null
) {
@@ -209,6 +222,7 @@
setTitle(title)
setIcon(icon)
setSize(size.width, size.height)
+ this.resizable = resizable
if (centered) {
setWindowCentered()
} else {
@@ -282,6 +296,72 @@
}
/**
+ * Returns true if the window is in fullscreen mode, false otherwise.
+ */
+ override val isFullscreen: Boolean
+ get() = window.layer.wrapped.fullscreen
+
+ /**
+ * Switches the window to fullscreen mode if the window is resizable. If the window is in
+ * fullscreen mode [minimize] and [maximize] methods are ignored.
+ */
+ override fun makeFullscreen() {
+ if (!isFullscreen && resizable) {
+ window.layer.wrapped.fullscreen = true
+ }
+ }
+
+ /**
+ * Minimizes the window to the taskbar. If the window is in fullscreen mode this method
+ * is ignored.
+ */
+ override fun minimize() {
+ if (!isFullscreen) {
+ window.setExtendedState(JFrame.ICONIFIED)
+ }
+ }
+
+ /**
+ * Maximizes the window to fill all available screen space. If the window is in fullscreen mode
+ * this method is ignored.
+ */
+ override fun maximize() {
+ if (!isFullscreen) {
+ window.setExtendedState(JFrame.MAXIMIZED_BOTH)
+ }
+ }
+
+ /**
+ * Restores the previous state and size of the window after
+ * maximizing/minimizing/fullscreen mode.
+ */
+ override fun restore() {
+ if (isFullscreen) {
+ window.layer.wrapped.fullscreen = false
+ }
+ window.setExtendedState(JFrame.NORMAL)
+ }
+
+ private var _resizable: Boolean = true
+
+ /**
+ * Sets the ability to resize the window. True - the window can be resized,
+ * false - the window cannot be resized. If the window is in fullscreen mode
+ * setter of this property is ignored. If this property is true the [makeFullscreen()]
+ * method is ignored.
+ */
+ override var resizable: Boolean
+ get() {
+ return window.isResizable()
+ }
+ set(value) {
+ if (!isFullscreen) {
+ _resizable = value
+ window.setResizable(value)
+ }
+ }
+
+ /**
* Sets the new size of the window.
*
* @param width the new width of the window.
@@ -384,11 +464,11 @@
window.apply {
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
setFocusableWindowState(true)
- setResizable(true)
setEnabled(true)
toFront()
requestFocus()
}
+ resizable = _resizable
disconnectPair()
}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.kt
index 66110a8..062c2a9 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.kt
@@ -50,6 +50,7 @@
internal class ComposeLayer {
+ private var composition: Composition? = null
private val events = AWTDebounceEventQueue()
var owners: DesktopOwners? = null
@@ -275,6 +276,7 @@
}
fun dispose() {
+ composition?.dispose()
events.cancel()
check(!isDisposed)
frameDispatcher.cancel()
@@ -298,7 +300,7 @@
invalidate: () -> Unit = this::needRedrawLayer,
parentComposition: CompositionReference? = null,
content: @Composable () -> Unit
- ): Composition {
+ ) {
check(owners == null) {
"Cannot setContent twice."
}
@@ -306,7 +308,7 @@
val desktopOwner = DesktopOwner(desktopOwners, density)
owners = desktopOwners
- val composition = desktopOwner.setContent(parent = parentComposition, content = content)
+ composition = desktopOwner.setContent(parent = parentComposition, content = content)
onDensityChanged(desktopOwner::density::set)
@@ -314,7 +316,5 @@
is AppFrame -> parent.>
is ComposePanel -> parent.>
}
-
- return composition
}
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.kt
index 6915ec41..57d1f10 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.kt
@@ -114,6 +114,8 @@
}
override fun paint(g: Graphics?) {
+ super.paint(g)
+ layer?.reinit()
needRedrawLayer()
}
}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
index 4b5c6b7..677caf8 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
@@ -16,7 +16,6 @@
package androidx.compose.desktop
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionReference
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
@@ -47,14 +46,12 @@
* scheduling of composition updates.
* If null then default root composition will be used.
* @param content Composable content of the ComposeWindow.
- *
- * @return Composition of the content.
*/
fun setContent(
parentComposition: CompositionReference? = null,
content: @Composable () -> Unit
- ): Composition {
- return layer.setContent(
+ ) {
+ layer.setContent(
parent = parent,
invalidate = this::needRedrawLayer,
parentComposition = parentComposition,
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/DesktopDialog.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/DesktopDialog.kt
index 9b5cc81..f9a249d 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/DesktopDialog.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/DesktopDialog.kt
@@ -43,6 +43,8 @@
* @param menuBar Window menu bar. The menu bar can be displayed inside a window (Windows,
* Linux) or at the top of the screen (Mac OS).
* @param undecorated Removes the native window border if set to true. The default value is false.
+ * @param resizable Makes the window resizable if is set to true and unresizable if is set to
+ * false. The default value is true.
* @param events Allows to describe events of the window.
* Supported events: onOpen, onClose, onMinimize, onMaximize, onRestore, onFocusGet, onFocusLost,
* onResize, onRelocate.
@@ -56,6 +58,7 @@
val icon: BufferedImage? = null,
val menuBar: MenuBar? = null,
val undecorated: Boolean = false,
+ val resizable: Boolean = true,
val events: WindowEvents = WindowEvents()
) : DialogProperties
@@ -86,6 +89,7 @@
icon = desktopProperties.icon,
menuBar = desktopProperties.menuBar,
undecorated = desktopProperties.undecorated,
+ resizable = desktopProperties.resizable,
events = desktopProperties.events,
>
)
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/RenderingTestScope.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/RenderingTestScope.kt
index 6c3c4b2..9f966fa 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/RenderingTestScope.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/RenderingTestScope.kt
@@ -55,7 +55,7 @@
nanoTime = { currentTimeMillis * 1_000_000 }
)
- val surface: Surface = Surface.makeRasterN32Premul(width, height)
+ val surface: Surface = Surface.makeRasterN32Premul(width, height)!!
val canvas: Canvas = surface.canvas
val owners = DesktopOwners(
invalidate = frameDispatcher::scheduleFrame