Roman Sedaikin
3 years ago
9 changed files with 104 additions and 267 deletions
@ -1,58 +1,42 @@
|
||||
package example.imageviewer.style |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.res.imageResource |
||||
import androidx.compose.ui.res.painterResource |
||||
import java.awt.image.BufferedImage |
||||
import javax.imageio.ImageIO |
||||
|
||||
@Composable |
||||
fun icEmpty() = imageResource("images/empty.png") |
||||
fun icEmpty() = painterResource("images/empty.png") |
||||
|
||||
@Composable |
||||
fun icBack() = imageResource("images/back.png") |
||||
fun icBack() = painterResource("images/back.png") |
||||
|
||||
@Composable |
||||
fun icRefresh() = imageResource("images/refresh.png") |
||||
fun icRefresh() = painterResource("images/refresh.png") |
||||
|
||||
@Composable |
||||
fun icDots() = imageResource("images/dots.png") |
||||
fun icDots() = painterResource("images/dots.png") |
||||
|
||||
@Composable |
||||
fun icFilterGrayscaleOn() = imageResource("images/grayscale_on.png") |
||||
fun icFilterGrayscaleOn() = painterResource("images/grayscale_on.png") |
||||
|
||||
@Composable |
||||
fun icFilterGrayscaleOff() = imageResource("images/grayscale_off.png") |
||||
fun icFilterGrayscaleOff() = painterResource("images/grayscale_off.png") |
||||
|
||||
@Composable |
||||
fun icFilterPixelOn() = imageResource("images/pixel_on.png") |
||||
fun icFilterPixelOn() = painterResource("images/pixel_on.png") |
||||
|
||||
@Composable |
||||
fun icFilterPixelOff() = imageResource("images/pixel_off.png") |
||||
fun icFilterPixelOff() = painterResource("images/pixel_off.png") |
||||
|
||||
@Composable |
||||
fun icFilterBlurOn() = imageResource("images/blur_on.png") |
||||
fun icFilterBlurOn() = painterResource("images/blur_on.png") |
||||
|
||||
@Composable |
||||
fun icFilterBlurOff() = imageResource("images/blur_off.png") |
||||
fun icFilterBlurOff() = painterResource("images/blur_off.png") |
||||
|
||||
@Composable |
||||
fun icFilterUnknown() = imageResource("images/filter_unknown.png") |
||||
fun icFilterUnknown() = painterResource("images/filter_unknown.png") |
||||
|
||||
private var icon: BufferedImage? = null |
||||
fun icAppRounded(): BufferedImage { |
||||
if (icon != null) { |
||||
return icon!! |
||||
} |
||||
try { |
||||
val imageRes = "images/ic_imageviewer_round.png" |
||||
val img = Thread.currentThread().contextClassLoader.getResource(imageRes) |
||||
val bitmap: BufferedImage? = ImageIO.read(img) |
||||
if (bitmap != null) { |
||||
icon = bitmap |
||||
return bitmap |
||||
} |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} |
||||
return BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB) |
||||
} |
||||
@Composable |
||||
fun icAppRounded() = painterResource("images/ic_imageviewer_round.png") |
||||
|
@ -1,158 +0,0 @@
|
||||
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.v1.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 |
||||
} |
||||
} |
@ -1,47 +1,58 @@
|
||||
package example.imageviewer |
||||
|
||||
import androidx.compose.desktop.DesktopTheme |
||||
import androidx.compose.material.MaterialTheme |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.window.Window |
||||
import androidx.compose.ui.window.WindowState |
||||
import androidx.compose.ui.window.WindowPosition |
||||
import androidx.compose.ui.window.application |
||||
import androidx.compose.ui.window.rememberWindowState |
||||
import example.imageviewer.model.ContentState |
||||
import example.imageviewer.style.icAppRounded |
||||
import example.imageviewer.utils.Application |
||||
import example.imageviewer.utils.getPreferredWindowSize |
||||
import example.imageviewer.view.BuildAppUI |
||||
import example.imageviewer.view.SplashUI |
||||
|
||||
fun main() = Application { |
||||
fun main() = application { |
||||
val state = rememberWindowState() |
||||
val content = remember { |
||||
ContentState.applyContent( |
||||
state, |
||||
"https://raw.githubusercontent.com/JetBrains/compose-jb/master/artwork/imageviewerrepo/fetching.list" |
||||
) |
||||
} |
||||
|
||||
val icon = remember(::icAppRounded) |
||||
val icon = icAppRounded() |
||||
|
||||
if (content.isAppReady()) { |
||||
ComposableWindow( |
||||
Window( |
||||
onCloseRequest = ::exitApplication, |
||||
title = "Image Viewer", |
||||
size = getPreferredWindowSize(800, 1000), |
||||
state = WindowState( |
||||
position = WindowPosition.Aligned(Alignment.Center), |
||||
size = getPreferredWindowSize(800, 1000) |
||||
), |
||||
icon = icon |
||||
) { |
||||
MaterialTheme { |
||||
DesktopTheme { |
||||
BuildAppUI(content) |
||||
} |
||||
BuildAppUI(content) |
||||
} |
||||
} |
||||
} else { |
||||
ComposableWindow( |
||||
Window( |
||||
onCloseRequest = ::exitApplication, |
||||
title = "Image Viewer", |
||||
size = getPreferredWindowSize(800, 300), |
||||
state = WindowState( |
||||
position = WindowPosition.Aligned(Alignment.Center), |
||||
size = getPreferredWindowSize(800, 300) |
||||
), |
||||
undecorated = true, |
||||
icon = icon, |
||||
) { |
||||
MaterialTheme { |
||||
DesktopTheme { |
||||
SplashUI() |
||||
} |
||||
SplashUI() |
||||
} |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue