From a8c7d5bff670668f6ebd183169d0f9df0c26e684 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Tue, 23 Nov 2021 11:47:09 +0300 Subject: [PATCH 01/15] Improve resource loading API --- .../ui/res/PainterResources.desktop.kt | 27 +++--- .../compose/ui/res/Resources.desktop.kt | 89 +++++++++++++++++-- 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt index 4890aed00fae8..8e650a2eb6dff 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt @@ -27,7 +27,8 @@ import org.xml.sax.InputSource /** * Load a [Painter] from an resource stored in resources for the application and decode - * it based on the file extension. + * it based on the file extension. Resource loading is not directly related to JVM classloader + * resources, so resources from dependencies JARs may stop working. * * Supported formats: * - SVG @@ -41,33 +42,35 @@ import org.xml.sax.InputSource * [loadSvgPainter] * [loadXmlImageVector] * - * @param resourcePath path to the file in the resources folder + * @param resourcePath path to the resource + * @param loader resources loader * @return [Painter] used for drawing the loaded resource */ @Composable fun painterResource( - resourcePath: String + resourcePath: String, + loader: ResourceLoader = ClassLoaderResourceLoader() ): Painter = when (resourcePath.substringAfterLast(".")) { - "svg" -> rememberSvgResource(resourcePath) - "xml" -> rememberVectorXmlResource(resourcePath) - else -> rememberBitmapResource(resourcePath) + "svg" -> rememberSvgResource(resourcePath, loader) + "xml" -> rememberVectorXmlResource(resourcePath, loader) + else -> rememberBitmapResource(resourcePath, loader) } @Composable -private fun rememberSvgResource(resourcePath: String): Painter { +private fun rememberSvgResource(resourcePath: String, loader: ResourceLoader = ClassLoaderResourceLoader()): Painter { val density = LocalDensity.current return remember(resourcePath, density) { - useResource(resourcePath) { + useResource(resourcePath, loader) { loadSvgPainter(it, density) } } } @Composable -private fun rememberVectorXmlResource(resourcePath: String): Painter { +private fun rememberVectorXmlResource(resourcePath: String, loader: ResourceLoader = ClassLoaderResourceLoader()): Painter { val density = LocalDensity.current val image = remember(resourcePath, density) { - useResource(resourcePath) { + useResource(resourcePath, loader) { loadXmlImageVector(InputSource(it), density) } } @@ -75,9 +78,9 @@ private fun rememberVectorXmlResource(resourcePath: String): Painter { } @Composable -private fun rememberBitmapResource(resourcePath: String): Painter { +private fun rememberBitmapResource(resourcePath: String, loader: ResourceLoader = ClassLoaderResourceLoader()): Painter { val image = remember(resourcePath) { - useResource(resourcePath, ::loadImageBitmap) + useResource(resourcePath, loader, ::loadImageBitmap) } return BitmapPainter(image) } \ No newline at end of file diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt index 6ad285315a1d2..b1ab0c0700c3a 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt @@ -17,12 +17,32 @@ package androidx.compose.ui.res import java.io.InputStream +import java.io.File +import java.io.FileInputStream /** * Open [InputStream] from a resource stored in resources for the application, calls the [block] * callback giving it a InputStream and closes stream once the processing is * complete. * + * @param resourcePath path of resource in loader + * @param loader resource loader + * @return object that was returned by [block] + * + * @throws IllegalArgumentException if there is no [resourcePath] in resources + */ +inline fun useResource( + resourcePath: String, + loader: ResourceLoader, + block: (InputStream) -> T +): T = openResource(resourcePath, loader).use(block) + +/** + * Open [InputStream] from a resource stored in resources for the application, calls the [block] + * callback giving it a InputStream and closes stream once the processing is + * complete. + * + * @param resourcePath path of resource * @return object that was returned by [block] * * @throws IllegalArgumentException if there is no [resourcePath] in resources @@ -35,15 +55,72 @@ inline fun useResource( /** * Open [InputStream] from a resource stored in resources for the application. * + * @param resourcePath path of resource in loader + * @param loader resource loader + * * @throws IllegalArgumentException if there is no [resourcePath] in resources */ @PublishedApi -internal fun openResource(resourcePath: String): InputStream { - // TODO(https://github.com/JetBrains/compose-jb/issues/618): probably we shouldn't use - // contextClassLoader here, as it is not defined in threads created by non-JVM - val classLoader = Thread.currentThread().contextClassLoader!! +internal fun openResource( + resourcePath: String, + loader: ResourceLoader = defaultResourceLoader() +): InputStream { + return requireNotNull(loader.load(resourcePath)) { + "Resource $resourcePath not found" + } +} - return requireNotNull(classLoader.getResourceAsStream(resourcePath)) { +/** + * Open [InputStream] from a resource stored in resources for the application. + * + * @param resourcePath path of resource + * + * @throws IllegalArgumentException if there is no [resourcePath] in resources + */ +@PublishedApi +internal fun openResource( + resourcePath: String, +): InputStream { + return requireNotNull(defaultResourceLoader().load(resourcePath)) { "Resource $resourcePath not found" } -} \ No newline at end of file +} + +/** + * Abstraction for loading resources. + */ +interface ResourceLoader { + fun load(resourcePath: String): InputStream? +} + +/** + * Resource loader based on JVM current context class loader. + */ +class ClassLoaderResourceLoader : ResourceLoader { + override fun load(resourcePath: String): InputStream? { + return try { + // TODO(https://github.com/JetBrains/compose-jb/issues/618): probably we shouldn't use + // contextClassLoader here, as it is not defined in threads created by non-JVM + Thread.currentThread().contextClassLoader!!.getResourceAsStream(resourcePath) + } catch (e: Throwable) { + null + } + } +} + +/** + * Resource loader from the file system relative to certain root location. + */ +class FileResourceLoader(val root: File) : ResourceLoader { + override fun load(resourcePath: String): InputStream? { + return try { + FileInputStream(File(root, resourcePath)) + } catch (e: Throwable) { + // TODO: or actually throw it instead? + null + } + } +} + +private fun defaultResourceLoader(): ResourceLoader = ClassLoaderResourceLoader() + From 283df0ba2f99e7bb623252240ac1686ed7ffb375 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Tue, 23 Nov 2021 11:53:20 +0300 Subject: [PATCH 02/15] Tweak --- .../androidx/compose/ui/res/PainterResources.desktop.kt | 8 ++++---- .../kotlin/androidx/compose/ui/res/Resources.desktop.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt index 8e650a2eb6dff..2e41f091aab65 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt @@ -49,7 +49,7 @@ import org.xml.sax.InputSource @Composable fun painterResource( resourcePath: String, - loader: ResourceLoader = ClassLoaderResourceLoader() + loader: ResourceLoader = defaultResourceLoader() ): Painter = when (resourcePath.substringAfterLast(".")) { "svg" -> rememberSvgResource(resourcePath, loader) "xml" -> rememberVectorXmlResource(resourcePath, loader) @@ -57,7 +57,7 @@ fun painterResource( } @Composable -private fun rememberSvgResource(resourcePath: String, loader: ResourceLoader = ClassLoaderResourceLoader()): Painter { +private fun rememberSvgResource(resourcePath: String, loader: ResourceLoader = defaultResourceLoader()): Painter { val density = LocalDensity.current return remember(resourcePath, density) { useResource(resourcePath, loader) { @@ -67,7 +67,7 @@ private fun rememberSvgResource(resourcePath: String, loader: ResourceLoader = C } @Composable -private fun rememberVectorXmlResource(resourcePath: String, loader: ResourceLoader = ClassLoaderResourceLoader()): Painter { +private fun rememberVectorXmlResource(resourcePath: String, loader: ResourceLoader = defaultResourceLoader()): Painter { val density = LocalDensity.current val image = remember(resourcePath, density) { useResource(resourcePath, loader) { @@ -78,7 +78,7 @@ private fun rememberVectorXmlResource(resourcePath: String, loader: ResourceLoad } @Composable -private fun rememberBitmapResource(resourcePath: String, loader: ResourceLoader = ClassLoaderResourceLoader()): Painter { +private fun rememberBitmapResource(resourcePath: String, loader: ResourceLoader = defaultResourceLoader()): Painter { val image = remember(resourcePath) { useResource(resourcePath, loader, ::loadImageBitmap) } diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt index b1ab0c0700c3a..e5cb794e23711 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt @@ -122,5 +122,5 @@ class FileResourceLoader(val root: File) : ResourceLoader { } } -private fun defaultResourceLoader(): ResourceLoader = ClassLoaderResourceLoader() +internal fun defaultResourceLoader(): ResourceLoader = ClassLoaderResourceLoader() From 905139fe7744c4e2f1e1a7ada35fbf4791554e53 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Tue, 23 Nov 2021 13:14:42 +0300 Subject: [PATCH 03/15] Review feedback. --- .../res/DesktopXmlVectorResources.desktop.kt | 2 +- .../compose/ui/res/Resources.desktop.kt | 48 +++++++++---------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/DesktopXmlVectorResources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/DesktopXmlVectorResources.desktop.kt index 9768f21b32e54..7a5056b71ebb2 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/DesktopXmlVectorResources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/DesktopXmlVectorResources.desktop.kt @@ -25,7 +25,7 @@ import javax.xml.parsers.DocumentBuilderFactory /** * Synchronously load an xml vector image from some [inputSource]. * - * XML Vector Image is came from Android world. See: + * XML Vector Image came from Android world. See: * https://developer.android.com/guide/topics/graphics/vector-drawable-resources * * On desktop it is fully implemented except there is no resource linking diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt index e5cb794e23711..36411b1624fd3 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt @@ -63,11 +63,9 @@ inline fun useResource( @PublishedApi internal fun openResource( resourcePath: String, - loader: ResourceLoader = defaultResourceLoader() + loader: ResourceLoader ): InputStream { - return requireNotNull(loader.load(resourcePath)) { - "Resource $resourcePath not found" - } + return loader.load(resourcePath) } /** @@ -81,46 +79,44 @@ internal fun openResource( internal fun openResource( resourcePath: String, ): InputStream { - return requireNotNull(defaultResourceLoader().load(resourcePath)) { - "Resource $resourcePath not found" - } + return defaultResourceLoader().load(resourcePath) } /** - * Abstraction for loading resources. + * Abstraction for loading resources. This API is intended for use in synchronous cases, + * where resource is expected to be loaded quick during the first composition, and so potentially + * slow operations like network access is not recommended. For such scenarious use functions + * [loadSvgPainter] and [loadXmlImageVector] instead on IO dispatcher. */ interface ResourceLoader { - fun load(resourcePath: String): InputStream? + fun load(resourcePath: String): InputStream } /** * Resource loader based on JVM current context class loader. */ class ClassLoaderResourceLoader : ResourceLoader { - override fun load(resourcePath: String): InputStream? { - return try { - // TODO(https://github.com/JetBrains/compose-jb/issues/618): probably we shouldn't use - // contextClassLoader here, as it is not defined in threads created by non-JVM - Thread.currentThread().contextClassLoader!!.getResourceAsStream(resourcePath) - } catch (e: Throwable) { - null - } + override fun load(resourcePath: String): InputStream { + // TODO(https://github.com/JetBrains/compose-jb/issues/618): probably we shouldn't use + // contextClassLoader here, as it is not defined in threads created by non-JVM + return Thread.currentThread().contextClassLoader!!.getResourceAsStream(resourcePath) } } /** - * Resource loader from the file system relative to certain root location. + * Resource loader from the file system relative to a certain root location. */ class FileResourceLoader(val root: File) : ResourceLoader { - override fun load(resourcePath: String): InputStream? { - return try { - FileInputStream(File(root, resourcePath)) - } catch (e: Throwable) { - // TODO: or actually throw it instead? - null - } + override fun load(resourcePath: String): InputStream { + return FileInputStream(File(root, resourcePath)) } } -internal fun defaultResourceLoader(): ResourceLoader = ClassLoaderResourceLoader() +/** + * Resource loader which is capable to load resources from `resources` folder in an application's + * project. Ability to load from dependent modules resources is not guaranteed in the future. + * Use explicit `ClassLoaderResourceLoader` instance if such guarantee is needed. + */ +fun defaultResourceLoader(): ResourceLoader = _defaultResourceLoader +private val _defaultResourceLoader = ClassLoaderResourceLoader() From c2a861d65e98d80b4868efd98e7de14267406d3a Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Tue, 23 Nov 2021 17:24:35 +0100 Subject: [PATCH 04/15] workaround default composable lambdas with typed parameters (for k/js and k/native) Change-Id: I4e24af974c550036687f2cc69202b5a3bbb3d501 --- .../kotlin/lower/ComposerLambdaMemoization.kt | 14 ++++++++++++++ .../kotlin/lower/ComposerParamTransformer.kt | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt index acbf897285178..948cbf5df627d 100644 --- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt +++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder import org.jetbrains.kotlin.backend.common.peek import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.backend.common.push +import org.jetbrains.kotlin.backend.jvm.codegen.anyTypeArgument import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.ir.IrStatement @@ -601,6 +602,15 @@ class ComposerLambdaMemoization( ) if (!collector.hasCaptures) { + if (!context.platform.isJvm() && hasTypeParameter(expression.type)) { + // This is a workaround + // for TypeParameters having initial parents (old IrFunctions before deepCopy). + // Otherwise it doesn't compile on k/js and k/native (can't find symbols). + // Ideally we will find a solution to remap symbols of TypeParameters in + // ComposableSingletons properties after ComposerParamTransformer + // (deepCopy in ComposerParamTransformer didn't help). + return wrapped + } return irGetComposableSingleton( lambdaExpression = wrapped, lambdaType = expression.type @@ -610,6 +620,10 @@ class ComposerLambdaMemoization( } } + private fun hasTypeParameter(type: IrType): Boolean { + return type.anyTypeArgument { true } + } + private fun irGetComposableSingleton( lambdaExpression: IrExpression, lambdaType: IrType diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt index 1287a2d4cc815..16fb91feacf59 100644 --- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt +++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt @@ -428,7 +428,10 @@ class ComposerParamTransformer( fn, name = newName, type = newType, - isAssignable = param.defaultValue != null + isAssignable = param.defaultValue != null, + defaultValue = param.defaultValue?.copyWithNewTypeParams( + source = this, target = fn + ) ) } fn.annotations = annotations.toList() From 9ab17015da89931f4a72604a9cc3037a2ef9fd55 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Tue, 23 Nov 2021 22:47:31 +0300 Subject: [PATCH 05/15] Fix disposing window in event callback Fixes https://github.com/JetBrains/compose-jb/issues/1448 The sequence of calls: mouseReleased window.dispose scene.dispose cancelRippleEffect scene.invalidate layer.needRedraw --- .../compose/ui/awt/ComposeWindowTest.kt | 30 +++++++++++++ .../androidx/compose/ui/window/TestUtils.kt | 43 +++++++++++++++++-- .../androidx/compose/ui/ComposeScene.skiko.kt | 2 +- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt index f45b06427349f..539fc7ac51830 100644 --- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt +++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposeWindowTest.kt @@ -1,13 +1,18 @@ package androidx.compose.ui.awt +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.requiredSize import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp +import androidx.compose.ui.sendMouseEvent import androidx.compose.ui.window.density +import androidx.compose.ui.window.runApplicationTest import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -16,6 +21,7 @@ import org.junit.Assume import org.junit.Test import java.awt.GraphicsEnvironment import java.awt.Dimension +import java.awt.event.MouseEvent class ComposeWindowTest { @Test @@ -94,4 +100,28 @@ class ComposeWindowTest { } } } + + @Test + fun `dispose window in event handler`() = runApplicationTest { + val window = ComposeWindow() + try { + window.size = Dimension(300, 400) + window.setContent { + Box(modifier = Modifier.fillMaxSize().background(Color.Blue).clickable { + window.dispose() + }) + } + window.isVisible = true + window.sendMouseEvent(MouseEvent.MOUSE_ENTERED, x = 100, y = 50) + awaitIdle() + window.sendMouseEvent(MouseEvent.MOUSE_MOVED, x = 100, y = 50) + awaitIdle() + window.sendMouseEvent(MouseEvent.MOUSE_PRESSED, x = 100, y = 50, modifiers = MouseEvent.BUTTON1_DOWN_MASK) + awaitIdle() + window.sendMouseEvent(MouseEvent.MOUSE_RELEASED, x = 100, y = 50) + awaitIdle() + } finally { + window.dispose() + } + } } \ No newline at end of file diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt index 88e7d081332eb..7439c137003c3 100644 --- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt +++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt @@ -53,10 +53,42 @@ internal fun runApplicationTest( runBlocking(Dispatchers.Swing) { withTimeout(30000) { - val testScope = WindowTestScope(this, useDelay) - if (testScope.isOpen) { - testScope.body() + val exceptionHandler = TestExceptionHandler() + withExceptionHandler(exceptionHandler) { + val testScope = WindowTestScope(this, useDelay, exceptionHandler) + if (testScope.isOpen) { + testScope.body() + } } + exceptionHandler.throwIfCaught() + } + } +} + +private inline fun withExceptionHandler(handler: Thread.UncaughtExceptionHandler, body: () -> Unit) { + val old = Thread.currentThread().uncaughtExceptionHandler + Thread.currentThread().uncaughtExceptionHandler = handler + try { + body() + } finally { + Thread.currentThread().uncaughtExceptionHandler = old + } +} + +internal class TestExceptionHandler : Thread.UncaughtExceptionHandler { + private var exception: Throwable? = null + + fun throwIfCaught() { + exception?.also { + throw it + } + } + + override fun uncaughtException(thread: Thread, throwable: Throwable) { + if (exception != null) { + exception?.addSuppressed(throwable) + } else { + exception = throwable } } } @@ -104,7 +136,8 @@ fun main() { internal class WindowTestScope( private val scope: CoroutineScope, - private val useDelay: Boolean + private val useDelay: Boolean, + private val exceptionHandler: TestExceptionHandler ) : CoroutineScope by CoroutineScope(scope.coroutineContext + Job()) { var isOpen by mutableStateOf(true) private val initialRecomposers = Recomposer.runningRecomposers.value @@ -132,5 +165,7 @@ internal class WindowTestScope( for (recomposerInfo in Recomposer.runningRecomposers.value - initialRecomposers) { recomposerInfo.state.takeWhile { it > Recomposer.State.Idle }.collect() } + + exceptionHandler.throwIfCaught() } } \ No newline at end of file diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt index 7ee1fd7021f84..0182c5f020271 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt @@ -121,7 +121,7 @@ class ComposeScene internal constructor( private fun invalidateIfNeeded() { hasPendingDraws = frameClock.hasAwaiters || list.any(SkiaBasedOwner::needRender) - if (hasPendingDraws && !isInvalidationDisabled) { + if (hasPendingDraws && !isInvalidationDisabled && !isClosed) { invalidate() } } From f06084f447e1385e1eab4428a8722e33fe6aa6d5 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Wed, 24 Nov 2021 11:05:08 +0300 Subject: [PATCH 06/15] Update docs for SwingPanel --- .../kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt index 664605fd5d41d..135e739acd6ee 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt @@ -38,8 +38,8 @@ val NoOpUpdate: Component.() -> Unit = {} /** * Composes an AWT/Swing component obtained from [factory]. The [factory] - * block will be called to obtain the [Component] to be composed. The Swing component is - * placed on top of the Compose layer. + * block will be called to obtain the [Component] to be composed. + * The Swing component is placed on top of the Compose layer (that means that Compose content can't overlap or clip it) * The [update] block runs due to recomposition, this is the place to set [Component] properties * depending on state. When state changes, the block will be reexecuted to set the new properties. * From 0c01d0d34ce7b4fb3baacbf19af69021343b06bd Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Wed, 24 Nov 2021 13:51:03 +0300 Subject: [PATCH 07/15] Update SwingPanel.desktop.kt --- .../kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt index 135e739acd6ee..8a52b46745246 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt @@ -39,7 +39,7 @@ val NoOpUpdate: Component.() -> Unit = {} /** * Composes an AWT/Swing component obtained from [factory]. The [factory] * block will be called to obtain the [Component] to be composed. - * The Swing component is placed on top of the Compose layer (that means that Compose content can't overlap or clip it) + * The Swing component is placed on top of the Compose layer (that means that Compose content can't overlap or clip it). * The [update] block runs due to recomposition, this is the place to set [Component] properties * depending on state. When state changes, the block will be reexecuted to set the new properties. * From 60cacb3d3331bbe9e1420927e074a0b863cc8150 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Wed, 24 Nov 2021 11:05:08 +0300 Subject: [PATCH 08/15] Update docs for SwingPanel # Conflicts: # compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt --- .../kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt index 8a52b46745246..7fb930cc50751 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingPanel.desktop.kt @@ -38,8 +38,8 @@ val NoOpUpdate: Component.() -> Unit = {} /** * Composes an AWT/Swing component obtained from [factory]. The [factory] - * block will be called to obtain the [Component] to be composed. - * The Swing component is placed on top of the Compose layer (that means that Compose content can't overlap or clip it). + * block will be called to obtain the [Component] to be composed. The Swing component is placed on + * top of the Compose layer (that means that Compose content can't overlap or clip it). * The [update] block runs due to recomposition, this is the place to set [Component] properties * depending on state. When state changes, the block will be reexecuted to set the new properties. * From 3e1c4a3d1ff0eb10405143c3ca4be39b1b07673c Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Wed, 24 Nov 2021 12:34:03 +0100 Subject: [PATCH 09/15] compose plugin: fix compilation error (in k/js) - issues/1306 https: //github.com/JetBrains/compose-jb/issues/1306 Change-Id: I15903b2cd257c9962b4d83a5694cc18c048aa91b --- .../plugins/kotlin/lower/decoys/FixComposableLambdaCalls.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/FixComposableLambdaCalls.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/FixComposableLambdaCalls.kt index 82d7135129d3b..8c8ed0e5fa89e 100644 --- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/FixComposableLambdaCalls.kt +++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/FixComposableLambdaCalls.kt @@ -85,11 +85,11 @@ class FixComposableLambdaCalls( module.transformChildrenVoid(this) } - private fun IrType.hasComposer(): Boolean { + private fun IrType.hasComposerDirectly(): Boolean { if (this == composerType) return true return when (this) { - is IrSimpleType -> arguments.any { (it as? IrType)?.hasComposer() == true } + is IrSimpleType -> arguments.any { (it as? IrType) == composerType } else -> false } } @@ -136,7 +136,7 @@ class FixComposableLambdaCalls( val dispatchReceiver = original.dispatchReceiver ?: return original - if (!dispatchReceiver.type.isFunction() || !dispatchReceiver.type.hasComposer()) { + if (!dispatchReceiver.type.isFunction() || !dispatchReceiver.type.hasComposerDirectly()) { return original } From d0d51c23ac44577c8ed564e5265ba8b439c3a4c4 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Thu, 25 Nov 2021 12:25:15 +0300 Subject: [PATCH 10/15] Review feedback. --- .../ui/res/PainterResources.desktop.kt | 29 +++++++++++++---- .../compose/ui/res/Resources.desktop.kt | 31 ++++++++++++------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt index 2e41f091aab65..639719f6dd045 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/PainterResources.desktop.kt @@ -19,6 +19,7 @@ package androidx.compose.ui.res import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.rememberVectorPainter @@ -27,8 +28,11 @@ import org.xml.sax.InputSource /** * Load a [Painter] from an resource stored in resources for the application and decode - * it based on the file extension. Resource loading is not directly related to JVM classloader - * resources, so resources from dependencies JARs may stop working. + * it based on the file extension. + * Resources for the application are in `src/main/resources` in JVM Gradle projects. + * Resource loading is not directly related to JVM classloader resources, so resources + * loading from dependencies JARs may stop working. + * See [ResourceLoader] for more information. * * Supported formats: * - SVG @@ -47,9 +51,10 @@ import org.xml.sax.InputSource * @return [Painter] used for drawing the loaded resource */ @Composable +@OptIn(ExperimentalComposeUiApi::class) fun painterResource( resourcePath: String, - loader: ResourceLoader = defaultResourceLoader() + loader: ResourceLoader = ResourceLoader.Default ): Painter = when (resourcePath.substringAfterLast(".")) { "svg" -> rememberSvgResource(resourcePath, loader) "xml" -> rememberVectorXmlResource(resourcePath, loader) @@ -57,7 +62,11 @@ fun painterResource( } @Composable -private fun rememberSvgResource(resourcePath: String, loader: ResourceLoader = defaultResourceLoader()): Painter { +@OptIn(ExperimentalComposeUiApi::class) +private fun rememberSvgResource( + resourcePath: String, + loader: ResourceLoader = ResourceLoader.Default +): Painter { val density = LocalDensity.current return remember(resourcePath, density) { useResource(resourcePath, loader) { @@ -67,7 +76,11 @@ private fun rememberSvgResource(resourcePath: String, loader: ResourceLoader = d } @Composable -private fun rememberVectorXmlResource(resourcePath: String, loader: ResourceLoader = defaultResourceLoader()): Painter { +@OptIn(ExperimentalComposeUiApi::class) +private fun rememberVectorXmlResource( + resourcePath: String, + loader: ResourceLoader = ResourceLoader.Default +): Painter { val density = LocalDensity.current val image = remember(resourcePath, density) { useResource(resourcePath, loader) { @@ -78,7 +91,11 @@ private fun rememberVectorXmlResource(resourcePath: String, loader: ResourceLoad } @Composable -private fun rememberBitmapResource(resourcePath: String, loader: ResourceLoader = defaultResourceLoader()): Painter { +@OptIn(ExperimentalComposeUiApi::class) +private fun rememberBitmapResource( + resourcePath: String, + loader: ResourceLoader = ResourceLoader.Default +): Painter { val image = remember(resourcePath) { useResource(resourcePath, loader, ::loadImageBitmap) } diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt index 36411b1624fd3..0d4ccc3df96d9 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt @@ -16,6 +16,7 @@ package androidx.compose.ui.res +import androidx.compose.ui.ExperimentalComposeUiApi import java.io.InputStream import java.io.File import java.io.FileInputStream @@ -31,7 +32,8 @@ import java.io.FileInputStream * * @throws IllegalArgumentException if there is no [resourcePath] in resources */ -inline fun useResource( +@ExperimentalComposeUiApi +internal inline fun useResource( resourcePath: String, loader: ResourceLoader, block: (InputStream) -> T @@ -61,6 +63,7 @@ inline fun useResource( * @throws IllegalArgumentException if there is no [resourcePath] in resources */ @PublishedApi +@ExperimentalComposeUiApi internal fun openResource( resourcePath: String, loader: ResourceLoader @@ -76,10 +79,11 @@ internal fun openResource( * @throws IllegalArgumentException if there is no [resourcePath] in resources */ @PublishedApi +@OptIn(ExperimentalComposeUiApi::class) internal fun openResource( resourcePath: String, ): InputStream { - return defaultResourceLoader().load(resourcePath) + return ResourceLoader.Default.load(resourcePath) } /** @@ -87,14 +91,27 @@ internal fun openResource( * where resource is expected to be loaded quick during the first composition, and so potentially * slow operations like network access is not recommended. For such scenarious use functions * [loadSvgPainter] and [loadXmlImageVector] instead on IO dispatcher. + * Also the resource should be always available to load, and if you need to handle exceptions, + * better these functions as well. */ +@ExperimentalComposeUiApi interface ResourceLoader { + companion object { + /** + * Resource loader which is capable to load resources from `resources` folder in an application's + * project. Ability to load from dependent modules resources is not guaranteed in the future. + * Use explicit `ClassLoaderResourceLoader` instance if such guarantee is needed. + */ + @ExperimentalComposeUiApi + val Default = ClassLoaderResourceLoader() + } fun load(resourcePath: String): InputStream } /** * Resource loader based on JVM current context class loader. */ +@ExperimentalComposeUiApi class ClassLoaderResourceLoader : ResourceLoader { override fun load(resourcePath: String): InputStream { // TODO(https://github.com/JetBrains/compose-jb/issues/618): probably we shouldn't use @@ -106,17 +123,9 @@ class ClassLoaderResourceLoader : ResourceLoader { /** * Resource loader from the file system relative to a certain root location. */ +@ExperimentalComposeUiApi class FileResourceLoader(val root: File) : ResourceLoader { override fun load(resourcePath: String): InputStream { return FileInputStream(File(root, resourcePath)) } } - -/** - * Resource loader which is capable to load resources from `resources` folder in an application's - * project. Ability to load from dependent modules resources is not guaranteed in the future. - * Use explicit `ClassLoaderResourceLoader` instance if such guarantee is needed. - */ -fun defaultResourceLoader(): ResourceLoader = _defaultResourceLoader - -private val _defaultResourceLoader = ClassLoaderResourceLoader() From 4bc3efd0c1230cc04bae0179f395bd873313bb22 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Thu, 25 Nov 2021 12:35:41 +0300 Subject: [PATCH 11/15] Typo --- .../kotlin/androidx/compose/ui/res/Resources.desktop.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt index 0d4ccc3df96d9..571d6176560a6 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/Resources.desktop.kt @@ -92,7 +92,7 @@ internal fun openResource( * slow operations like network access is not recommended. For such scenarious use functions * [loadSvgPainter] and [loadXmlImageVector] instead on IO dispatcher. * Also the resource should be always available to load, and if you need to handle exceptions, - * better these functions as well. + * it is better to use these functions as well. */ @ExperimentalComposeUiApi interface ResourceLoader { From 747eddd46f6f3a16737f0a72020d2b104a868ed0 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Thu, 25 Nov 2021 12:50:08 +0300 Subject: [PATCH 12/15] Add ComposeWindow(GraphicsConfiguration) constructor to be able to open window on another display. See https://kotlinlang.slack.com/archives/C01D6HTPATV/p1627407411167600?thread_ts=1627406646.167200&cid=C01D6HTPATV --- .../kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt index faf1b0c44f41f..cc255e658b14c 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt @@ -28,6 +28,7 @@ import org.jetbrains.skiko.hostOs import org.jetbrains.skiko.OS import java.awt.Color import java.awt.Component +import java.awt.GraphicsConfiguration import java.awt.event.MouseListener import java.awt.event.MouseMotionListener import java.awt.event.MouseWheelListener @@ -37,7 +38,7 @@ import javax.swing.JFrame * ComposeWindow is a window for building UI using Compose for Desktop. * ComposeWindow inherits javax.swing.JFrame. */ -class ComposeWindow : JFrame() { +class ComposeWindow : JFrame { private val delegate = ComposeWindowDelegate(this) internal val layer get() = delegate.layer @@ -45,6 +46,9 @@ class ComposeWindow : JFrame() { contentPane.add(delegate.pane) } + constructor() : super() + constructor(gc: GraphicsConfiguration) : super(gc) + override fun add(component: Component) = delegate.add(component) override fun remove(component: Component) = delegate.remove(component) From c6996fe0c8fac448446d397ea2c100182a637ac2 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Thu, 25 Nov 2021 12:55:32 +0300 Subject: [PATCH 13/15] Refactor --- .../kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt index cc255e658b14c..4c142b449d304 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt @@ -47,7 +47,7 @@ class ComposeWindow : JFrame { } constructor() : super() - constructor(gc: GraphicsConfiguration) : super(gc) + constructor(graphicsConfiguration: GraphicsConfiguration) : super(graphicsConfiguration) override fun add(component: Component) = delegate.add(component) From 64ff7609e847c0897b04bad04cd99da236fca30f Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Thu, 25 Nov 2021 13:44:21 +0300 Subject: [PATCH 14/15] Refactor --- .../androidx/compose/ui/awt/ComposeWindow.desktop.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt index 4c142b449d304..46c2fe882f485 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt @@ -37,8 +37,13 @@ import javax.swing.JFrame /** * ComposeWindow is a window for building UI using Compose for Desktop. * ComposeWindow inherits javax.swing.JFrame. + * + * @param graphicsConfiguration the GraphicsConfiguration that is used to construct the new window + * if null, the system default GraphicsConfiguration is assumed. */ -class ComposeWindow : JFrame { +class ComposeWindow( + graphicsConfiguration: GraphicsConfiguration? = null +) : JFrame(graphicsConfiguration) { private val delegate = ComposeWindowDelegate(this) internal val layer get() = delegate.layer @@ -46,9 +51,6 @@ class ComposeWindow : JFrame { contentPane.add(delegate.pane) } - constructor() : super() - constructor(graphicsConfiguration: GraphicsConfiguration) : super(graphicsConfiguration) - override fun add(component: Component) = delegate.add(component) override fun remove(component: Component) = delegate.remove(component) From bece24c9ccd05ed4c7d982128860f79a36d6e0f6 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Thu, 25 Nov 2021 13:51:57 +0300 Subject: [PATCH 15/15] Update ComposeWindow.desktop.kt --- .../kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt index 46c2fe882f485..f16189ab28533 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt @@ -38,8 +38,8 @@ import javax.swing.JFrame * ComposeWindow is a window for building UI using Compose for Desktop. * ComposeWindow inherits javax.swing.JFrame. * - * @param graphicsConfiguration the GraphicsConfiguration that is used to construct the new window - * if null, the system default GraphicsConfiguration is assumed. + * @param graphicsConfiguration the GraphicsConfiguration that is used to construct the new window. + * If null, the system default GraphicsConfiguration is assumed. */ class ComposeWindow( graphicsConfiguration: GraphicsConfiguration? = null