You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
4.8 KiB
158 lines
4.8 KiB
package example.imageviewer.utils |
|
|
|
import androidx.compose.desktop.AppManager |
|
import androidx.compose.desktop.AppWindow |
|
import androidx.compose.desktop.WindowEvents |
|
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.* |
|
import kotlinx.coroutines.swing.Swing |
|
import java.awt.image.BufferedImage |
|
|
|
fun Application( |
|
content: @Composable ApplicationScope.() -> Unit |
|
) { |
|
GlobalScope.launch(Dispatchers.Swing + ImmediateFrameClock()) { |
|
AppManager.setEvents(onWindowsEmpty = null) |
|
|
|
withRunningRecomposer { recomposer -> |
|
val latch = CompletableDeferred<Unit>() |
|
val applier = ApplicationApplier { latch.complete(Unit) } |
|
|
|
val composition = Composition(applier, recomposer) |
|
try { |
|
val scope = ApplicationScope(recomposer) |
|
|
|
composition.setContent { scope.content() } |
|
|
|
latch.join() |
|
} finally { |
|
composition.dispose() |
|
} |
|
} |
|
} |
|
} |
|
|
|
class ApplicationScope internal constructor(private val recomposer: Recomposer) { |
|
@Composable |
|
fun ComposableWindow( |
|
title: String = "JetpackDesktopWindow", |
|
size: IntSize = IntSize(800, 600), |
|
location: IntOffset = IntOffset.Zero, |
|
centered: Boolean = true, |
|
icon: BufferedImage? = null, |
|
menuBar: MenuBar? = null, |
|
undecorated: Boolean = false, |
|
resizable: Boolean = true, |
|
events: WindowEvents = WindowEvents(), |
|
onDismissRequest: (() -> Unit)? = null, |
|
content: @Composable () -> Unit = {} |
|
) { |
|
var isOpened by remember { mutableStateOf(true) } |
|
if (!isOpened) return |
|
ComposeNode<AppWindow, ApplicationApplier>( |
|
factory = { |
|
val window = AppWindow( |
|
title = title, |
|
size = size, |
|
location = location, |
|
centered = centered, |
|
icon = icon, |
|
menuBar = menuBar, |
|
undecorated = undecorated, |
|
resizable = resizable, |
|
events = events, |
|
onDismissRequest = { |
|
onDismissRequest?.invoke() |
|
isOpened = false |
|
} |
|
) |
|
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) } |
|
} |
|
) |
|
} |
|
} |
|
|
|
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() |
|
} |
|
} |
|
} |
|
|
|
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 |
|
} |
|
}
|
|
|