Roman Sedaikin
3 years ago
9 changed files with 104 additions and 267 deletions
@ -1,27 +1,23 @@ |
|||||||
buildscript { |
buildscript { |
||||||
|
val composeVersion = System.getenv("COMPOSE_TEMPLATE_COMPOSE_VERSION") ?: "0.5.0-build270" |
||||||
|
|
||||||
repositories { |
repositories { |
||||||
mavenLocal().mavenContent { |
|
||||||
includeModule("org.jetbrains.compose", "compose-gradle-plugin") |
|
||||||
} |
|
||||||
google() |
google() |
||||||
mavenCentral() |
mavenCentral() |
||||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
||||||
} |
} |
||||||
|
|
||||||
dependencies { |
dependencies { |
||||||
// __LATEST_COMPOSE_RELEASE_VERSION__ |
classpath("org.jetbrains.compose:compose-gradle-plugin:$composeVersion") |
||||||
classpath("org.jetbrains.compose:compose-gradle-plugin:0.4.0") |
|
||||||
classpath("com.android.tools.build:gradle:4.0.1") |
classpath("com.android.tools.build:gradle:4.0.1") |
||||||
// __KOTLIN_COMPOSE_VERSION__ |
classpath(kotlin("gradle-plugin", version = "1.5.21")) |
||||||
classpath(kotlin("gradle-plugin", version = "1.5.10")) |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
allprojects { |
allprojects { |
||||||
repositories { |
repositories { |
||||||
mavenLocal() |
|
||||||
google() |
google() |
||||||
mavenCentral() |
mavenCentral() |
||||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
||||||
} |
} |
||||||
} |
} |
@ -1,58 +1,42 @@ |
|||||||
package example.imageviewer.style |
package example.imageviewer.style |
||||||
|
|
||||||
import androidx.compose.runtime.Composable |
import androidx.compose.runtime.Composable |
||||||
import androidx.compose.ui.res.imageResource |
import androidx.compose.ui.res.painterResource |
||||||
import java.awt.image.BufferedImage |
import java.awt.image.BufferedImage |
||||||
import javax.imageio.ImageIO |
import javax.imageio.ImageIO |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icEmpty() = imageResource("images/empty.png") |
fun icEmpty() = painterResource("images/empty.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icBack() = imageResource("images/back.png") |
fun icBack() = painterResource("images/back.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icRefresh() = imageResource("images/refresh.png") |
fun icRefresh() = painterResource("images/refresh.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icDots() = imageResource("images/dots.png") |
fun icDots() = painterResource("images/dots.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icFilterGrayscaleOn() = imageResource("images/grayscale_on.png") |
fun icFilterGrayscaleOn() = painterResource("images/grayscale_on.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icFilterGrayscaleOff() = imageResource("images/grayscale_off.png") |
fun icFilterGrayscaleOff() = painterResource("images/grayscale_off.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icFilterPixelOn() = imageResource("images/pixel_on.png") |
fun icFilterPixelOn() = painterResource("images/pixel_on.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icFilterPixelOff() = imageResource("images/pixel_off.png") |
fun icFilterPixelOff() = painterResource("images/pixel_off.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icFilterBlurOn() = imageResource("images/blur_on.png") |
fun icFilterBlurOn() = painterResource("images/blur_on.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icFilterBlurOff() = imageResource("images/blur_off.png") |
fun icFilterBlurOff() = painterResource("images/blur_off.png") |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
fun icFilterUnknown() = imageResource("images/filter_unknown.png") |
fun icFilterUnknown() = painterResource("images/filter_unknown.png") |
||||||
|
|
||||||
private var icon: BufferedImage? = null |
@Composable |
||||||
fun icAppRounded(): BufferedImage { |
fun icAppRounded() = painterResource("images/ic_imageviewer_round.png") |
||||||
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) |
|
||||||
} |
|
||||||
|
@ -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 |
package example.imageviewer |
||||||
|
|
||||||
import androidx.compose.desktop.DesktopTheme |
|
||||||
import androidx.compose.material.MaterialTheme |
import androidx.compose.material.MaterialTheme |
||||||
import androidx.compose.runtime.remember |
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.model.ContentState |
||||||
import example.imageviewer.style.icAppRounded |
import example.imageviewer.style.icAppRounded |
||||||
import example.imageviewer.utils.Application |
|
||||||
import example.imageviewer.utils.getPreferredWindowSize |
import example.imageviewer.utils.getPreferredWindowSize |
||||||
import example.imageviewer.view.BuildAppUI |
import example.imageviewer.view.BuildAppUI |
||||||
import example.imageviewer.view.SplashUI |
import example.imageviewer.view.SplashUI |
||||||
|
|
||||||
fun main() = Application { |
fun main() = application { |
||||||
|
val state = rememberWindowState() |
||||||
val content = remember { |
val content = remember { |
||||||
ContentState.applyContent( |
ContentState.applyContent( |
||||||
|
state, |
||||||
"https://raw.githubusercontent.com/JetBrains/compose-jb/master/artwork/imageviewerrepo/fetching.list" |
"https://raw.githubusercontent.com/JetBrains/compose-jb/master/artwork/imageviewerrepo/fetching.list" |
||||||
) |
) |
||||||
} |
} |
||||||
|
|
||||||
val icon = remember(::icAppRounded) |
val icon = icAppRounded() |
||||||
|
|
||||||
if (content.isAppReady()) { |
if (content.isAppReady()) { |
||||||
ComposableWindow( |
Window( |
||||||
|
onCloseRequest = ::exitApplication, |
||||||
title = "Image Viewer", |
title = "Image Viewer", |
||||||
size = getPreferredWindowSize(800, 1000), |
state = WindowState( |
||||||
|
position = WindowPosition.Aligned(Alignment.Center), |
||||||
|
size = getPreferredWindowSize(800, 1000) |
||||||
|
), |
||||||
icon = icon |
icon = icon |
||||||
) { |
) { |
||||||
MaterialTheme { |
MaterialTheme { |
||||||
DesktopTheme { |
BuildAppUI(content) |
||||||
BuildAppUI(content) |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
} else { |
} else { |
||||||
ComposableWindow( |
Window( |
||||||
|
onCloseRequest = ::exitApplication, |
||||||
title = "Image Viewer", |
title = "Image Viewer", |
||||||
size = getPreferredWindowSize(800, 300), |
state = WindowState( |
||||||
|
position = WindowPosition.Aligned(Alignment.Center), |
||||||
|
size = getPreferredWindowSize(800, 300) |
||||||
|
), |
||||||
undecorated = true, |
undecorated = true, |
||||||
icon = icon, |
icon = icon, |
||||||
) { |
) { |
||||||
MaterialTheme { |
MaterialTheme { |
||||||
DesktopTheme { |
SplashUI() |
||||||
SplashUI() |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue