Browse Source

Imageviewer. Refactor Application for observable properties (#447)

* Refactor Application for observable properties

* Style

* Add RememberObserver

* Comment out problematic update
pull/478/head 0.4.0-build173
Dominic Fischer 4 years ago committed by GitHub
parent
commit
7e5cfc53a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/DesktopContentState.kt
  2. 184
      examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Application.kt

9
examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/model/DesktopContentState.kt

@ -1,6 +1,7 @@
package example.imageviewer.model
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.mutableStateOf
import example.imageviewer.ResString
import example.imageviewer.core.FilterType
@ -16,7 +17,7 @@ import java.util.concurrent.Executors
import javax.swing.SwingUtilities.invokeLater
object ContentState {
object ContentState : RememberObserver {
private lateinit var repository: ImageRepository
private lateinit var uriRepository: String
@ -280,6 +281,12 @@ object ContentState {
}
}
}
override fun onRemembered() { }
override fun onAbandoned() { }
override fun onForgotten() {
executor.shutdown()
}
}
private object MainImageWrapper {

184
examples/imageviewer/common/src/desktopMain/kotlin/example/imageviewer/utils/Application.kt

@ -3,79 +3,39 @@ package example.imageviewer.utils
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.AppWindow
import androidx.compose.desktop.WindowEvents
import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.window.MenuBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import java.awt.image.BufferedImage
import javax.swing.SwingUtilities
import kotlin.system.exitProcess
fun Application(
content: @Composable ApplicationScope.() -> Unit
) = SwingUtilities.invokeLater {
AppManager.setEvents(onWindowsEmpty = null)
val scope = ApplicationScope(content)
scope.start()
}
@OptIn(ExperimentalComposeApi::class, ExperimentalCoroutinesApi::class)
class ApplicationScope(
private val content: @Composable ApplicationScope.() -> Unit
) {
private val frameClock = ImmediateFrameClock()
private val context = Dispatchers.Main + frameClock
private val scope = CoroutineScope(context)
GlobalScope.launch(Dispatchers.Swing + ImmediateFrameClock()) {
AppManager.setEvents(onWindowsEmpty = null)
private val recomposer = Recomposer(context)
private val composition = Composition(EmptyApplier(), recomposer)
withRunningRecomposer { recomposer ->
val latch = CompletableDeferred<Unit>()
val applier = ApplicationApplier { latch.complete(Unit) }
private val windows = mutableSetOf<AppWindow>()
private var windowsVersion by mutableStateOf(Any())
val composition = Composition(applier, recomposer)
try {
val scope = ApplicationScope(recomposer)
fun start() {
scope.launch(start = CoroutineStart.UNDISPATCHED) {
recomposer.runRecomposeAndApplyChanges()
}
composition.setContent {
content()
WindowsMonitor()
}
}
composition.setContent { scope.content() }
@Composable
private fun WindowsMonitor() {
LaunchedEffect(windowsVersion) {
if (windows.isEmpty()) {
dispose()
exitProcess(0)
latch.join()
} finally {
composition.dispose()
}
}
}
private fun dispose() {
composition.dispose()
scope.cancel()
}
// TODO make parameters observable (now if any parameter is changed we don't change the window)
class ApplicationScope internal constructor(private val recomposer: Recomposer) {
@Composable
fun ComposableWindow(
title: String = "JetpackDesktopWindow",
@ -91,20 +51,10 @@ class ApplicationScope(
content: @Composable () -> Unit = {}
) {
var isOpened by remember { mutableStateOf(true) }
if (isOpened) {
DisposableEffect(Unit) {
lateinit var window: AppWindow
fun onClose() {
if (isOpened) {
windows.remove(window)
onDismissRequest?.invoke()
windowsVersion = Any()
isOpened = false
}
}
window = AppWindow(
if (!isOpened) return
ComposeNode<AppWindow, ApplicationApplier>(
factory = {
val window = AppWindow(
title = title,
size = size,
location = location,
@ -115,38 +65,94 @@ class ApplicationScope(
resizable = resizable,
events = events,
onDismissRequest = {
onClose()
onDismissRequest?.invoke()
isOpened = false
}
)
windows.add(window)
window.show(recomposer, content)
window
},
update = {
set(title) { setTitle(it) }
set(size) { setSize(it.width, it.height) }
// set(location) { setLocation(it.x, it.y) }
set(icon) { setIcon(it) }
// set(menuBar) { if (it != null) setMenuBar(it) else removeMenuBar() }
// set(resizable) { setResizable(it) }
// set(events) { setEvents(it) }
// set(onDismissRequest) { setDismiss(it) }
}
)
}
}
onDispose {
private class ImmediateFrameClock : MonotonicFrameClock {
override suspend fun <R> withFrameNanos(
onFrame: (frameTimeNanos: Long) -> R
) = onFrame(System.nanoTime())
}
private class ApplicationApplier(
private val onWindowsEmpty: () -> Unit
) : Applier<AppWindow?> {
private val windows = mutableListOf<AppWindow>()
override var current: AppWindow? = null
override fun insertBottomUp(index: Int, instance: AppWindow?) {
requireNotNull(instance)
check(current == null) { "Windows cannot be nested!" }
windows.add(index, instance)
}
override fun remove(index: Int, count: Int) {
repeat(count) {
val window = windows.removeAt(index)
if (!window.isClosed) {
window.close()
}
onClose()
}
}
override fun move(from: Int, to: Int, count: Int) {
if (from > to) {
var current = to
repeat(count) {
val node = windows.removeAt(from)
windows.add(current, node)
current++
}
} else {
repeat(count) {
val node = windows.removeAt(from)
windows.add(to - 1, node)
}
}
}
private class ImmediateFrameClock : MonotonicFrameClock {
override suspend fun <R> withFrameNanos(
onFrame: (frameTimeNanos: Long) -> R
) = onFrame(System.nanoTime())
override fun clear() {
windows.forEach { if (!it.isClosed) it.close() }
windows.clear()
}
override fun onEndChanges() {
if (windows.isEmpty()) {
onWindowsEmpty()
}
}
override fun down(node: AppWindow?) {
requireNotNull(node)
check(current == null) { "Windows cannot be nested!" }
current = node
}
@OptIn(ExperimentalComposeApi::class)
private class EmptyApplier : Applier<Unit> {
override val current: Unit = Unit
override fun down(node: Unit) = Unit
override fun up() = Unit
override fun insertTopDown(index: Int, instance: Unit) = Unit
override fun insertBottomUp(index: Int, instance: Unit) = Unit
override fun remove(index: Int, count: Int) = Unit
override fun move(from: Int, to: Int, count: Int) = Unit
override fun clear() = Unit
override fun up() {
check(current != null) { "Windows cannot be nested!" }
current = null
}
override fun insertTopDown(index: Int, instance: AppWindow?) {
// ignored. Building tree bottom-up
}
}

Loading…
Cancel
Save