Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/release/1.0' into feature/fix_dr…
Browse files Browse the repository at this point in the history
…ag_undecorated2

# Conflicts:
#	compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt
  • Loading branch information
igordmn committed Nov 25, 2021
2 parents 58f8b2e + b22a324 commit 02261c9
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -610,6 +620,10 @@ class ComposerLambdaMemoization(
}
}

private fun hasTypeParameter(type: IrType): Boolean {
return type.anyTypeArgument { true }
}

private fun irGetComposableSingleton(
lambdaExpression: IrExpression,
lambdaType: IrType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,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, ::isUndecorated)
internal val layer get() = delegate.layer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,6 +29,10 @@ 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.
* 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
Expand All @@ -41,43 +46,58 @@ 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
@OptIn(ExperimentalComposeUiApi::class)
fun painterResource(
resourcePath: String
resourcePath: String,
loader: ResourceLoader = ResourceLoader.Default
): 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 {
@OptIn(ExperimentalComposeUiApi::class)
private fun rememberSvgResource(
resourcePath: String,
loader: ResourceLoader = ResourceLoader.Default
): Painter {
val density = LocalDensity.current
return remember(resourcePath, density) {
useResource(resourcePath) {
useResource(resourcePath, loader) {
loadSvgPainter(it, density)
}
}
}

@Composable
private fun rememberVectorXmlResource(resourcePath: String): 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) {
useResource(resourcePath, loader) {
loadXmlImageVector(InputSource(it), density)
}
}
return rememberVectorPainter(image)
}

@Composable
private fun rememberBitmapResource(resourcePath: String): Painter {
@OptIn(ExperimentalComposeUiApi::class)
private fun rememberBitmapResource(
resourcePath: String,
loader: ResourceLoader = ResourceLoader.Default
): Painter {
val image = remember(resourcePath) {
useResource(resourcePath, ::loadImageBitmap)
useResource(resourcePath, loader, ::loadImageBitmap)
}
return BitmapPainter(image)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,35 @@

package androidx.compose.ui.res

import androidx.compose.ui.ExperimentalComposeUiApi
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
*/
@ExperimentalComposeUiApi
internal inline fun <T> 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
Expand All @@ -35,15 +57,75 @@ inline fun <T> 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!!
@ExperimentalComposeUiApi
internal fun openResource(
resourcePath: String,
loader: ResourceLoader
): InputStream {
return loader.load(resourcePath)
}

return requireNotNull(classLoader.getResourceAsStream(resourcePath)) {
"Resource $resourcePath not found"
/**
* 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
@OptIn(ExperimentalComposeUiApi::class)
internal fun openResource(
resourcePath: String,
): InputStream {
return ResourceLoader.Default.load(resourcePath)
}

/**
* 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.
* Also the resource should be always available to load, and if you need to handle exceptions,
* it is better to use 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
// 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 a certain root location.
*/
@ExperimentalComposeUiApi
class FileResourceLoader(val root: File) : ResourceLoader {
override fun load(resourcePath: String): InputStream {
return FileInputStream(File(root, resourcePath))
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
}
}
}
Loading

0 comments on commit 02261c9

Please sign in to comment.