akurasov
3 years ago
586 changed files with 12995 additions and 8303 deletions
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
# __LATEST_COMPOSE_RELEASE_VERSION__ |
# __LATEST_COMPOSE_RELEASE_VERSION__ |
||||||
compose.version=0.4.0 |
compose.version=1.0.0-alpha1 |
||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
|
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
||||||
|
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
||||||
|
@ -1 +1 @@ |
|||||||
Subproject commit 94cefabe7303d41aef797722ee3ab331a21689aa |
Subproject commit aadb6bb9988bd5b232b2922fa5a248b823f0d5a5 |
@ -1 +1 @@ |
|||||||
Subproject commit cd6860e33655776f6533790a27cd37eb04b40e40 |
Subproject commit 1b20aa551446123340cb42b4eb21d2f2797e608a |
@ -1 +1 @@ |
|||||||
Subproject commit f37dc6b42fe7838e9e37fbe8a9eb063a1550acd8 |
Subproject commit 818a882ba70e8603d6a22b17d421c9049926da4c |
@ -0,0 +1,8 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
cd "$(dirname "$0")" |
||||||
|
. ./prepare |
||||||
|
|
||||||
|
pushd .. |
||||||
|
./gradlew buildNativeDemo $COMPOSE_DEFAULT_GRADLE_ARGS "$@" || exit 1 |
||||||
|
popd |
@ -0,0 +1,8 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
cd "$(dirname "$0")" |
||||||
|
. ./prepare |
||||||
|
|
||||||
|
pushd .. |
||||||
|
./gradlew testRuntimeNative $COMPOSE_DEFAULT_GRADLE_ARGS "$@" || exit 1 |
||||||
|
popd |
@ -1,7 +1,7 @@ |
|||||||
package org.jetbrains.codeviewer.platform |
package org.jetbrains.codeviewer.platform |
||||||
|
|
||||||
import androidx.compose.desktop.DesktopTheme |
import androidx.compose.desktop.DesktopMaterialTheme |
||||||
import androidx.compose.runtime.Composable |
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
@Composable |
@Composable |
||||||
actual fun PlatformTheme(content: @Composable () -> Unit) = DesktopTheme(content = content) |
actual fun PlatformTheme(content: @Composable () -> Unit) = DesktopMaterialTheme(content = content) |
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
@ -0,0 +1,15 @@ |
|||||||
|
package org.jetbrains.compose.demo.falling |
||||||
|
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import androidx.compose.ui.window.WindowSize |
||||||
|
import androidx.compose.ui.window.WindowState |
||||||
|
import androidx.compose.ui.window.singleWindowApplication |
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class) |
||||||
|
fun main() = singleWindowApplication( |
||||||
|
title = "Falling Balls", state = WindowState(size = WindowSize(800.dp, 800.dp)) |
||||||
|
) { |
||||||
|
FallingBallsGame() |
||||||
|
} |
||||||
|
|
@ -1,10 +0,0 @@ |
|||||||
package org.jetbrains.compose.demo.falling |
|
||||||
|
|
||||||
import androidx.compose.desktop.Window |
|
||||||
import androidx.compose.ui.unit.IntSize |
|
||||||
|
|
||||||
fun main() = |
|
||||||
Window(title = "Falling Balls", size = IntSize(800, 800)) { |
|
||||||
FallingBallsGame() |
|
||||||
} |
|
||||||
|
|
@ -0,0 +1,43 @@ |
|||||||
|
package example.imageviewer.view |
||||||
|
|
||||||
|
import androidx.compose.foundation.background |
||||||
|
import androidx.compose.foundation.layout.Box |
||||||
|
import androidx.compose.foundation.layout.fillMaxSize |
||||||
|
import androidx.compose.foundation.layout.padding |
||||||
|
import androidx.compose.foundation.layout.offset |
||||||
|
import androidx.compose.foundation.layout.size |
||||||
|
import androidx.compose.foundation.shape.CircleShape |
||||||
|
import androidx.compose.material.CircularProgressIndicator |
||||||
|
import androidx.compose.material.MaterialTheme |
||||||
|
import androidx.compose.material.Surface |
||||||
|
import androidx.compose.material.Text |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.ui.Alignment |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import example.imageviewer.style.DarkGray |
||||||
|
import example.imageviewer.style.DarkGreen |
||||||
|
import example.imageviewer.style.Foreground |
||||||
|
import example.imageviewer.style.TranslucentBlack |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun LoadingScreen(text: String = "") { |
||||||
|
Box( |
||||||
|
modifier = Modifier.fillMaxSize().background(color = TranslucentBlack) |
||||||
|
) { |
||||||
|
Box(modifier = Modifier.align(Alignment.Center)) { |
||||||
|
Surface(color = DarkGray, elevation = 4.dp, shape = CircleShape) { |
||||||
|
CircularProgressIndicator( |
||||||
|
modifier = Modifier.size(50.dp).padding(3.dp, 3.dp, 4.dp, 4.dp), |
||||||
|
color = DarkGreen |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
Text( |
||||||
|
text = text, |
||||||
|
modifier = Modifier.align(Alignment.Center).offset(0.dp, 70.dp), |
||||||
|
style = MaterialTheme.typography.body1, |
||||||
|
color = Foreground |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -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,314 +0,0 @@ |
|||||||
package example.imageviewer.view |
|
||||||
|
|
||||||
import androidx.compose.foundation.Image |
|
||||||
import androidx.compose.foundation.background |
|
||||||
import androidx.compose.foundation.horizontalScroll |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.foundation.layout.Column |
|
||||||
import androidx.compose.foundation.layout.Row |
|
||||||
import androidx.compose.foundation.layout.Spacer |
|
||||||
import androidx.compose.foundation.layout.fillMaxSize |
|
||||||
import androidx.compose.foundation.layout.padding |
|
||||||
import androidx.compose.foundation.layout.height |
|
||||||
import androidx.compose.foundation.layout.size |
|
||||||
import androidx.compose.foundation.layout.width |
|
||||||
import androidx.compose.foundation.rememberScrollState |
|
||||||
import androidx.compose.foundation.ScrollState |
|
||||||
import androidx.compose.foundation.shape.CircleShape |
|
||||||
import androidx.compose.material.CircularProgressIndicator |
|
||||||
import androidx.compose.material.MaterialTheme |
|
||||||
import androidx.compose.material.Surface |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.runtime.mutableStateOf |
|
||||||
import androidx.compose.runtime.remember |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.draw.clip |
|
||||||
import androidx.compose.ui.graphics.Color |
|
||||||
import androidx.compose.ui.graphics.ImageBitmap |
|
||||||
import androidx.compose.ui.graphics.asImageBitmap |
|
||||||
import androidx.compose.ui.input.key.Key |
|
||||||
import androidx.compose.ui.input.key.shortcuts |
|
||||||
import androidx.compose.ui.layout.ContentScale |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
import example.imageviewer.core.FilterType |
|
||||||
import example.imageviewer.model.AppState |
|
||||||
import example.imageviewer.model.ContentState |
|
||||||
import example.imageviewer.model.ScreenType |
|
||||||
import example.imageviewer.style.DarkGray |
|
||||||
import example.imageviewer.style.DarkGreen |
|
||||||
import example.imageviewer.style.Foreground |
|
||||||
import example.imageviewer.style.MiniatureColor |
|
||||||
import example.imageviewer.style.TranslucentBlack |
|
||||||
import example.imageviewer.style.Transparent |
|
||||||
import example.imageviewer.style.icBack |
|
||||||
import example.imageviewer.style.icFilterBlurOff |
|
||||||
import example.imageviewer.style.icFilterBlurOn |
|
||||||
import example.imageviewer.style.icFilterGrayscaleOff |
|
||||||
import example.imageviewer.style.icFilterGrayscaleOn |
|
||||||
import example.imageviewer.style.icFilterPixelOff |
|
||||||
import example.imageviewer.style.icFilterPixelOn |
|
||||||
import example.imageviewer.utils.cropImage |
|
||||||
import example.imageviewer.utils.displayWidth |
|
||||||
import example.imageviewer.utils.getDisplayBounds |
|
||||||
import example.imageviewer.utils.toByteArray |
|
||||||
import java.awt.Rectangle |
|
||||||
import java.awt.event.KeyEvent |
|
||||||
import java.awt.image.BufferedImage |
|
||||||
import kotlin.math.pow |
|
||||||
import kotlin.math.roundToInt |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun setImageFullScreen( |
|
||||||
content: ContentState |
|
||||||
) { |
|
||||||
if (content.isContentReady()) { |
|
||||||
Column { |
|
||||||
setToolBar(content.getSelectedImageName(), content) |
|
||||||
setImage(content) |
|
||||||
} |
|
||||||
} else { |
|
||||||
setLoadingScreen() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
private fun setLoadingScreen() { |
|
||||||
|
|
||||||
Box { |
|
||||||
Surface(color = MiniatureColor, modifier = Modifier.height(44.dp)) {} |
|
||||||
Box(modifier = Modifier.align(Alignment.Center)) { |
|
||||||
Surface(color = DarkGray, elevation = 4.dp, shape = CircleShape) { |
|
||||||
CircularProgressIndicator( |
|
||||||
modifier = Modifier.size(50.dp).padding(3.dp, 3.dp, 4.dp, 4.dp), |
|
||||||
color = DarkGreen |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun setToolBar( |
|
||||||
text: String, |
|
||||||
content: ContentState |
|
||||||
) { |
|
||||||
val backButtonHover = remember { mutableStateOf(false) } |
|
||||||
Surface( |
|
||||||
color = MiniatureColor, |
|
||||||
modifier = Modifier.height(44.dp) |
|
||||||
) { |
|
||||||
Row(modifier = Modifier.padding(end = 30.dp)) { |
|
||||||
Surface( |
|
||||||
color = Transparent, |
|
||||||
modifier = Modifier.padding(start = 20.dp).align(Alignment.CenterVertically), |
|
||||||
shape = CircleShape |
|
||||||
) { |
|
||||||
Clickable( |
|
||||||
modifier = Modifier.hover( |
|
||||||
onEnter = { |
|
||||||
backButtonHover.value = true |
|
||||||
false |
|
||||||
}, |
|
||||||
onExit = { |
|
||||||
backButtonHover.value = false |
|
||||||
false |
|
||||||
}) |
|
||||||
.background(color = if (backButtonHover.value) TranslucentBlack else Transparent), |
|
||||||
onClick = { |
|
||||||
if (content.isContentReady()) { |
|
||||||
content.restoreMainImage() |
|
||||||
AppState.screenState(ScreenType.Main) |
|
||||||
} |
|
||||||
}) { |
|
||||||
Image( |
|
||||||
icBack(), |
|
||||||
contentDescription = null, |
|
||||||
modifier = Modifier.size(38.dp) |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
Text( |
|
||||||
text, |
|
||||||
color = Foreground, |
|
||||||
maxLines = 1, |
|
||||||
modifier = Modifier.padding(start = 30.dp).weight(1f) |
|
||||||
.align(Alignment.CenterVertically), |
|
||||||
style = MaterialTheme.typography.body1 |
|
||||||
) |
|
||||||
|
|
||||||
Surface( |
|
||||||
color = Color(255, 255, 255, 40), |
|
||||||
modifier = Modifier.size(154.dp, 38.dp) |
|
||||||
.align(Alignment.CenterVertically), |
|
||||||
shape = CircleShape |
|
||||||
) { |
|
||||||
val state = rememberScrollState(0) |
|
||||||
Row(modifier = Modifier.horizontalScroll(state)) { |
|
||||||
Row { |
|
||||||
for (type in FilterType.values()) { |
|
||||||
FilterButton(content, type) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun FilterButton( |
|
||||||
content: ContentState, |
|
||||||
type: FilterType, |
|
||||||
modifier: Modifier = Modifier.size(38.dp) |
|
||||||
) { |
|
||||||
val filterButtonHover = remember { mutableStateOf(false) } |
|
||||||
Box( |
|
||||||
modifier = Modifier.background(color = Transparent).clip(CircleShape) |
|
||||||
) { |
|
||||||
Clickable( |
|
||||||
modifier = Modifier.hover( |
|
||||||
onEnter = { |
|
||||||
filterButtonHover.value = true |
|
||||||
false |
|
||||||
}, |
|
||||||
onExit = { |
|
||||||
filterButtonHover.value = false |
|
||||||
false |
|
||||||
}) |
|
||||||
.background(color = if (filterButtonHover.value) TranslucentBlack else Transparent), |
|
||||||
onClick = { content.toggleFilter(type)} |
|
||||||
) { |
|
||||||
Image( |
|
||||||
getFilterImage(type = type, content = content), |
|
||||||
contentDescription = null, |
|
||||||
modifier |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Spacer(Modifier.width(20.dp)) |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun getFilterImage(type: FilterType, content: ContentState): ImageBitmap { |
|
||||||
|
|
||||||
return when (type) { |
|
||||||
FilterType.GrayScale -> if (content.isFilterEnabled(type)) icFilterGrayscaleOn() else icFilterGrayscaleOff() |
|
||||||
FilterType.Pixel -> if (content.isFilterEnabled(type)) icFilterPixelOn() else icFilterPixelOff() |
|
||||||
FilterType.Blur -> if (content.isFilterEnabled(type)) icFilterBlurOn() else icFilterBlurOff() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun setImage(content: ContentState) { |
|
||||||
val drag = remember { DragHandler() } |
|
||||||
val scale = remember { ScaleHandler() } |
|
||||||
|
|
||||||
Surface( |
|
||||||
color = DarkGray, |
|
||||||
modifier = Modifier.fillMaxSize() |
|
||||||
) { |
|
||||||
Draggable(dragHandler = drag, modifier = Modifier.fillMaxSize()) { |
|
||||||
Zoomable( |
|
||||||
onScale = scale, |
|
||||||
modifier = Modifier.fillMaxSize() |
|
||||||
.shortcuts { |
|
||||||
on(Key(KeyEvent.VK_LEFT)) { |
|
||||||
content.swipePrevious() |
|
||||||
} |
|
||||||
on(Key(KeyEvent.VK_RIGHT)) { |
|
||||||
content.swipeNext() |
|
||||||
} |
|
||||||
} |
|
||||||
) { |
|
||||||
val bitmap = imageByGesture(content, scale, drag) |
|
||||||
Image( |
|
||||||
bitmap = bitmap, |
|
||||||
contentDescription = null, |
|
||||||
contentScale = ContentScale.Fit |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun imageByGesture( |
|
||||||
content: ContentState, |
|
||||||
scale: ScaleHandler, |
|
||||||
drag: DragHandler |
|
||||||
): ImageBitmap { |
|
||||||
val bitmap = cropBitmapByScale(content.getSelectedImage(), scale.factor.value, drag) |
|
||||||
return org.jetbrains.skija.Image.makeFromEncoded(toByteArray(bitmap)).asImageBitmap() |
|
||||||
} |
|
||||||
|
|
||||||
private fun cropBitmapByScale(bitmap: BufferedImage, scale: Float, drag: DragHandler): BufferedImage { |
|
||||||
|
|
||||||
val crop = cropBitmapByBounds( |
|
||||||
bitmap, |
|
||||||
getDisplayBounds(bitmap), |
|
||||||
scale, |
|
||||||
drag |
|
||||||
) |
|
||||||
return cropImage( |
|
||||||
bitmap, |
|
||||||
Rectangle(crop.x, crop.y, crop.width - crop.x, crop.height - crop.y) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
private fun cropBitmapByBounds( |
|
||||||
bitmap: BufferedImage, |
|
||||||
bounds: Rectangle, |
|
||||||
scaleFactor: Float, |
|
||||||
drag: DragHandler |
|
||||||
): Rectangle { |
|
||||||
|
|
||||||
if (scaleFactor <= 1f) { |
|
||||||
return Rectangle(0, 0, bitmap.width, bitmap.height) |
|
||||||
} |
|
||||||
|
|
||||||
var scale = scaleFactor.toDouble().pow(1.4) |
|
||||||
|
|
||||||
var boundW = (bounds.width / scale).roundToInt() |
|
||||||
var boundH = (bounds.height / scale).roundToInt() |
|
||||||
|
|
||||||
scale *= displayWidth() / bounds.width.toDouble() |
|
||||||
|
|
||||||
val offsetX = drag.getAmount().x / scale |
|
||||||
val offsetY = drag.getAmount().y / scale |
|
||||||
|
|
||||||
if (boundW > bitmap.width) { |
|
||||||
boundW = bitmap.width |
|
||||||
} |
|
||||||
if (boundH > bitmap.height) { |
|
||||||
boundH = bitmap.height |
|
||||||
} |
|
||||||
|
|
||||||
val invisibleW = bitmap.width - boundW |
|
||||||
var leftOffset = (invisibleW / 2.0 - offsetX).roundToInt() |
|
||||||
|
|
||||||
if (leftOffset > invisibleW) { |
|
||||||
leftOffset = invisibleW |
|
||||||
drag.getAmount().x = -((invisibleW / 2.0) * scale).roundToInt().toFloat() |
|
||||||
} |
|
||||||
if (leftOffset < 0) { |
|
||||||
drag.getAmount().x = ((invisibleW / 2.0) * scale).roundToInt().toFloat() |
|
||||||
leftOffset = 0 |
|
||||||
} |
|
||||||
|
|
||||||
val invisibleH = bitmap.height - boundH |
|
||||||
var topOffset = (invisibleH / 2 - offsetY).roundToInt() |
|
||||||
|
|
||||||
if (topOffset > invisibleH) { |
|
||||||
topOffset = invisibleH |
|
||||||
drag.getAmount().y = -((invisibleH / 2.0) * scale).roundToInt().toFloat() |
|
||||||
} |
|
||||||
if (topOffset < 0) { |
|
||||||
drag.getAmount().y = ((invisibleH / 2.0) * scale).roundToInt().toFloat() |
|
||||||
topOffset = 0 |
|
||||||
} |
|
||||||
|
|
||||||
return Rectangle(leftOffset, topOffset, leftOffset + boundW, topOffset + boundH) |
|
||||||
} |
|
@ -0,0 +1,225 @@ |
|||||||
|
package example.imageviewer.view |
||||||
|
|
||||||
|
import androidx.compose.foundation.Image |
||||||
|
import androidx.compose.foundation.background |
||||||
|
import androidx.compose.foundation.horizontalScroll |
||||||
|
import androidx.compose.foundation.layout.Box |
||||||
|
import androidx.compose.foundation.layout.Column |
||||||
|
import androidx.compose.foundation.layout.Row |
||||||
|
import androidx.compose.foundation.layout.Spacer |
||||||
|
import androidx.compose.foundation.layout.fillMaxSize |
||||||
|
import androidx.compose.foundation.layout.padding |
||||||
|
import androidx.compose.foundation.layout.height |
||||||
|
import androidx.compose.foundation.layout.size |
||||||
|
import androidx.compose.foundation.layout.width |
||||||
|
import androidx.compose.foundation.rememberScrollState |
||||||
|
import androidx.compose.foundation.ScrollState |
||||||
|
import androidx.compose.foundation.shape.CircleShape |
||||||
|
import androidx.compose.material.CircularProgressIndicator |
||||||
|
import androidx.compose.material.MaterialTheme |
||||||
|
import androidx.compose.material.Surface |
||||||
|
import androidx.compose.material.Text |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import androidx.compose.ui.Alignment |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.draw.clip |
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi |
||||||
|
import androidx.compose.ui.graphics.Color |
||||||
|
import androidx.compose.ui.graphics.ImageBitmap |
||||||
|
import androidx.compose.ui.graphics.painter.Painter |
||||||
|
import androidx.compose.ui.input.key.Key |
||||||
|
import androidx.compose.ui.input.key.key |
||||||
|
import androidx.compose.ui.input.key.type |
||||||
|
import androidx.compose.ui.input.key.KeyEventType |
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent |
||||||
|
import androidx.compose.ui.layout.ContentScale |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import androidx.compose.ui.window.WindowSize |
||||||
|
import example.imageviewer.core.FilterType |
||||||
|
import example.imageviewer.model.AppState |
||||||
|
import example.imageviewer.model.ContentState |
||||||
|
import example.imageviewer.model.ScreenType |
||||||
|
import example.imageviewer.ResString |
||||||
|
import example.imageviewer.style.DarkGray |
||||||
|
import example.imageviewer.style.DarkGreen |
||||||
|
import example.imageviewer.style.Foreground |
||||||
|
import example.imageviewer.style.MiniatureColor |
||||||
|
import example.imageviewer.style.TranslucentBlack |
||||||
|
import example.imageviewer.style.Transparent |
||||||
|
import example.imageviewer.style.icBack |
||||||
|
import example.imageviewer.style.icFilterBlurOff |
||||||
|
import example.imageviewer.style.icFilterBlurOn |
||||||
|
import example.imageviewer.style.icFilterGrayscaleOff |
||||||
|
import example.imageviewer.style.icFilterGrayscaleOn |
||||||
|
import example.imageviewer.style.icFilterPixelOff |
||||||
|
import example.imageviewer.style.icFilterPixelOn |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun FullscreenImage( |
||||||
|
content: ContentState |
||||||
|
) { |
||||||
|
Column { |
||||||
|
ToolBar(content.getSelectedImageName(), content) |
||||||
|
Image(content) |
||||||
|
} |
||||||
|
if (!content.isContentReady()) { |
||||||
|
LoadingScreen() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun ToolBar( |
||||||
|
text: String, |
||||||
|
content: ContentState |
||||||
|
) { |
||||||
|
val backButtonHover = remember { mutableStateOf(false) } |
||||||
|
Surface( |
||||||
|
color = MiniatureColor, |
||||||
|
modifier = Modifier.height(44.dp) |
||||||
|
) { |
||||||
|
Row(modifier = Modifier.padding(end = 30.dp)) { |
||||||
|
Surface( |
||||||
|
color = Transparent, |
||||||
|
modifier = Modifier.padding(start = 20.dp).align(Alignment.CenterVertically), |
||||||
|
shape = CircleShape |
||||||
|
) { |
||||||
|
Tooltip(ResString.back) { |
||||||
|
Clickable( |
||||||
|
modifier = Modifier.hover( |
||||||
|
onEnter = { |
||||||
|
backButtonHover.value = true |
||||||
|
false |
||||||
|
}, |
||||||
|
onExit = { |
||||||
|
backButtonHover.value = false |
||||||
|
false |
||||||
|
}) |
||||||
|
.background(color = if (backButtonHover.value) TranslucentBlack else Transparent), |
||||||
|
onClick = { |
||||||
|
if (content.isContentReady()) { |
||||||
|
content.restoreMainImage() |
||||||
|
AppState.screenState(ScreenType.MainScreen) |
||||||
|
} |
||||||
|
}) { |
||||||
|
Image( |
||||||
|
icBack(), |
||||||
|
contentDescription = null, |
||||||
|
modifier = Modifier.size(38.dp) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Text( |
||||||
|
text, |
||||||
|
color = Foreground, |
||||||
|
maxLines = 1, |
||||||
|
modifier = Modifier.padding(start = 30.dp).weight(1f) |
||||||
|
.align(Alignment.CenterVertically), |
||||||
|
style = MaterialTheme.typography.body1 |
||||||
|
) |
||||||
|
|
||||||
|
Surface( |
||||||
|
color = Color(255, 255, 255, 40), |
||||||
|
modifier = Modifier.size(154.dp, 38.dp) |
||||||
|
.align(Alignment.CenterVertically), |
||||||
|
shape = CircleShape |
||||||
|
) { |
||||||
|
val state = rememberScrollState(0) |
||||||
|
Row(modifier = Modifier.horizontalScroll(state)) { |
||||||
|
Row { |
||||||
|
for (type in FilterType.values()) { |
||||||
|
FilterButton(content, type) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun FilterButton( |
||||||
|
content: ContentState, |
||||||
|
type: FilterType, |
||||||
|
modifier: Modifier = Modifier.size(38.dp) |
||||||
|
) { |
||||||
|
val filterButtonHover = remember { mutableStateOf(false) } |
||||||
|
Box( |
||||||
|
modifier = Modifier.background(color = Transparent).clip(CircleShape) |
||||||
|
) { |
||||||
|
Tooltip("$type") { |
||||||
|
Clickable( |
||||||
|
modifier = Modifier.hover( |
||||||
|
onEnter = { |
||||||
|
filterButtonHover.value = true |
||||||
|
false |
||||||
|
}, |
||||||
|
onExit = { |
||||||
|
filterButtonHover.value = false |
||||||
|
false |
||||||
|
}) |
||||||
|
.background(color = if (filterButtonHover.value) TranslucentBlack else Transparent), |
||||||
|
onClick = { content.toggleFilter(type)} |
||||||
|
) { |
||||||
|
Image( |
||||||
|
getFilterImage(type = type, content = content), |
||||||
|
contentDescription = null, |
||||||
|
modifier |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Spacer(Modifier.width(20.dp)) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun getFilterImage(type: FilterType, content: ContentState): Painter { |
||||||
|
return when (type) { |
||||||
|
FilterType.GrayScale -> if (content.isFilterEnabled(type)) icFilterGrayscaleOn() else icFilterGrayscaleOff() |
||||||
|
FilterType.Pixel -> if (content.isFilterEnabled(type)) icFilterPixelOn() else icFilterPixelOff() |
||||||
|
FilterType.Blur -> if (content.isFilterEnabled(type)) icFilterBlurOn() else icFilterBlurOff() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class) |
||||||
|
@Composable |
||||||
|
fun Image(content: ContentState) { |
||||||
|
val onUpdate = remember { { content.updateMainImage() } } |
||||||
|
Surface( |
||||||
|
color = DarkGray, |
||||||
|
modifier = Modifier.fillMaxSize() |
||||||
|
) { |
||||||
|
Draggable( |
||||||
|
onUpdate = onUpdate, |
||||||
|
dragHandler = content.drag, |
||||||
|
modifier = Modifier.fillMaxSize() |
||||||
|
) { |
||||||
|
Zoomable( |
||||||
|
onUpdate = onUpdate, |
||||||
|
scaleHandler = content.scale, |
||||||
|
modifier = Modifier.fillMaxSize() |
||||||
|
.onPreviewKeyEvent { |
||||||
|
if (it.type == KeyEventType.KeyUp) { |
||||||
|
when (it.key) { |
||||||
|
Key.DirectionLeft -> { |
||||||
|
content.swipePrevious() |
||||||
|
} |
||||||
|
Key.DirectionRight -> { |
||||||
|
content.swipeNext() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
false |
||||||
|
} |
||||||
|
) { |
||||||
|
Image( |
||||||
|
bitmap = content.getSelectedImage(), |
||||||
|
contentDescription = null, |
||||||
|
contentScale = ContentScale.Fit |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
package example.imageviewer.view |
||||||
|
|
||||||
|
import androidx.compose.foundation.BoxWithTooltip |
||||||
|
import androidx.compose.foundation.layout.padding |
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape |
||||||
|
import androidx.compose.material.MaterialTheme |
||||||
|
import androidx.compose.material.Text |
||||||
|
import androidx.compose.material.Surface |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.ui.graphics.Color |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Tooltip( |
||||||
|
text: String = "Tooltip", |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { |
||||||
|
BoxWithTooltip( |
||||||
|
tooltip = { |
||||||
|
Surface( |
||||||
|
color = Color(210, 210, 210), |
||||||
|
shape = RoundedCornerShape(4.dp) |
||||||
|
) { |
||||||
|
Text( |
||||||
|
text = text, |
||||||
|
modifier = Modifier.padding(10.dp), |
||||||
|
style = MaterialTheme.typography.caption |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
@ -1,48 +1,59 @@ |
|||||||
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.AppUI |
||||||
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 { |
AppUI(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() |
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
} |
|
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
||||||
|
@ -1,45 +0,0 @@ |
|||||||
package com.jetbrains.compose |
|
||||||
|
|
||||||
import androidx.compose.desktop.ComposePanel |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.layout.Layout |
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned |
|
||||||
import androidx.compose.ui.unit.IntSize |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.runtime.remember |
|
||||||
import com.intellij.openapi.ui.DialogWrapper |
|
||||||
import java.awt.Dimension |
|
||||||
import java.awt.Window |
|
||||||
import javax.swing.JComponent |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun ComposeSizeAdjustmentWrapper( |
|
||||||
window: DialogWrapper, |
|
||||||
panel: ComposePanel, |
|
||||||
preferredSize: IntSize, |
|
||||||
content: @Composable () -> Unit |
|
||||||
) { |
|
||||||
var packed = false |
|
||||||
Box { |
|
||||||
content() |
|
||||||
Layout( |
|
||||||
content = {}, |
|
||||||
modifier = Modifier.onGloballyPositioned { childCoordinates -> |
|
||||||
// adjust size of the dialog |
|
||||||
if (!packed) { |
|
||||||
val contentSize = childCoordinates.parentCoordinates!!.size |
|
||||||
panel.preferredSize = Dimension( |
|
||||||
if (contentSize.width < preferredSize.width) preferredSize.width else contentSize.width, |
|
||||||
if (contentSize.height < preferredSize.height) preferredSize.height else contentSize.height, |
|
||||||
) |
|
||||||
window.pack() |
|
||||||
packed = true |
|
||||||
} |
|
||||||
}, |
|
||||||
measurePolicy = { _, _ -> |
|
||||||
layout(0, 0) {} |
|
||||||
} |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -1,5 +1,5 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
distributionBase=GRADLE_USER_HOME |
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
zipStoreBase=GRADLE_USER_HOME |
||||||
zipStorePath=wrapper/dists |
zipStorePath=wrapper/dists |
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 206 KiB |
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue