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 3 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. 190
      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 package example.imageviewer.model
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import example.imageviewer.ResString import example.imageviewer.ResString
import example.imageviewer.core.FilterType import example.imageviewer.core.FilterType
@ -16,7 +17,7 @@ import java.util.concurrent.Executors
import javax.swing.SwingUtilities.invokeLater import javax.swing.SwingUtilities.invokeLater
object ContentState { object ContentState : RememberObserver {
private lateinit var repository: ImageRepository private lateinit var repository: ImageRepository
private lateinit var uriRepository: String 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 { private object MainImageWrapper {

190
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.AppManager
import androidx.compose.desktop.AppWindow import androidx.compose.desktop.AppWindow
import androidx.compose.desktop.WindowEvents import androidx.compose.desktop.WindowEvents
import androidx.compose.runtime.Applier import androidx.compose.runtime.*
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.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.MenuBar
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import javax.swing.SwingUtilities
import kotlin.system.exitProcess
fun Application( fun Application(
content: @Composable ApplicationScope.() -> Unit 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() GlobalScope.launch(Dispatchers.Swing + ImmediateFrameClock()) {
private val context = Dispatchers.Main + frameClock AppManager.setEvents(onWindowsEmpty = null)
private val scope = CoroutineScope(context)
private val recomposer = Recomposer(context) withRunningRecomposer { recomposer ->
private val composition = Composition(EmptyApplier(), recomposer) val latch = CompletableDeferred<Unit>()
val applier = ApplicationApplier { latch.complete(Unit) }
private val windows = mutableSetOf<AppWindow>() val composition = Composition(applier, recomposer)
private var windowsVersion by mutableStateOf(Any()) try {
val scope = ApplicationScope(recomposer)
fun start() { composition.setContent { scope.content() }
scope.launch(start = CoroutineStart.UNDISPATCHED) {
recomposer.runRecomposeAndApplyChanges()
}
composition.setContent {
content()
WindowsMonitor()
}
}
@Composable latch.join()
private fun WindowsMonitor() { } finally {
LaunchedEffect(windowsVersion) { composition.dispose()
if (windows.isEmpty()) {
dispose()
exitProcess(0)
} }
} }
} }
}
private fun dispose() { class ApplicationScope internal constructor(private val recomposer: Recomposer) {
composition.dispose()
scope.cancel()
}
// TODO make parameters observable (now if any parameter is changed we don't change the window)
@Composable @Composable
fun ComposableWindow( fun ComposableWindow(
title: String = "JetpackDesktopWindow", title: String = "JetpackDesktopWindow",
@ -91,20 +51,10 @@ class ApplicationScope(
content: @Composable () -> Unit = {} content: @Composable () -> Unit = {}
) { ) {
var isOpened by remember { mutableStateOf(true) } var isOpened by remember { mutableStateOf(true) }
if (isOpened) { if (!isOpened) return
DisposableEffect(Unit) { ComposeNode<AppWindow, ApplicationApplier>(
lateinit var window: AppWindow factory = {
val window = AppWindow(
fun onClose() {
if (isOpened) {
windows.remove(window)
onDismissRequest?.invoke()
windowsVersion = Any()
isOpened = false
}
}
window = AppWindow(
title = title, title = title,
size = size, size = size,
location = location, location = location,
@ -115,21 +65,24 @@ class ApplicationScope(
resizable = resizable, resizable = resizable,
events = events, events = events,
onDismissRequest = { onDismissRequest = {
onClose() onDismissRequest?.invoke()
isOpened = false
} }
) )
windows.add(window)
window.show(recomposer, content) window.show(recomposer, content)
window
onDispose { },
if (!window.isClosed) { update = {
window.close() set(title) { setTitle(it) }
} set(size) { setSize(it.width, it.height) }
onClose() // 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) }
} }
} )
} }
} }
@ -139,14 +92,67 @@ private class ImmediateFrameClock : MonotonicFrameClock {
) = onFrame(System.nanoTime()) ) = onFrame(System.nanoTime())
} }
@OptIn(ExperimentalComposeApi::class) private class ApplicationApplier(
private class EmptyApplier : Applier<Unit> { private val onWindowsEmpty: () -> Unit
override val current: Unit = Unit ) : Applier<AppWindow?> {
override fun down(node: Unit) = Unit private val windows = mutableListOf<AppWindow>()
override fun up() = Unit
override fun insertTopDown(index: Int, instance: Unit) = Unit override var current: AppWindow? = null
override fun insertBottomUp(index: Int, instance: Unit) = Unit
override fun remove(index: Int, count: Int) = Unit override fun insertBottomUp(index: Int, instance: AppWindow?) {
override fun move(from: Int, to: Int, count: Int) = Unit requireNotNull(instance)
override fun clear() = Unit 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()
}
}
}
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)
}
}
}
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
}
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