Browse Source

Add painterResource utility method to resource library. (#2753)

+ respect density in remembering state for ImageVector
pull/2756/head
Nikita Lipsky 2 years ago committed by GitHub
parent
commit
614fe78d71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/UseResources.kt
  2. 15
      components/resources/demo/shared/src/commonMain/resources/dir/vector.xml
  3. 7
      components/resources/library/build.gradle.kts
  4. 14
      components/resources/library/src/commonButJSMain/kotlin/org/jetbrains/compose/resources/Resource.commonbutjs.kt
  5. 94
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ComposeResource.common.kt
  6. 5
      components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt

8
components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/UseResources.kt

@ -5,7 +5,9 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.* import org.jetbrains.compose.resources.*
@OptIn(ExperimentalResourceApi::class) @OptIn(ExperimentalResourceApi::class)
@ -23,6 +25,12 @@ internal fun UseResources() {
) )
Icon( Icon(
imageVector = resource("vector.xml").rememberImageVector(LocalDensity.current).orEmpty(), imageVector = resource("vector.xml").rememberImageVector(LocalDensity.current).orEmpty(),
modifier = Modifier.size(150.dp),
contentDescription = null
)
Icon(
painter = painterResource("dir/vector.xml"),
modifier = Modifier.size(150.dp),
contentDescription = null contentDescription = null
) )
} }

15
components/resources/demo/shared/src/commonMain/resources/dir/vector.xml

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF000000"
android:pathData="M352,0H160C71.648,0 0,71.648 0,160v192c0,88.352 71.648,160 160,160h192c88.352,0 160,-71.648 160,-160V160C512,71.648 440.352,0 352,0zM464,352c0,61.76 -50.24,112 -112,112H160c-61.76,0 -112,-50.24 -112,-112V160C48,98.24 98.24,48 160,48h192c61.76,0 112,50.24 112,112V352z" />
<path
android:fillColor="#FF000000"
android:pathData="M256,128c-70.688,0 -128,57.312 -128,128s57.312,128 128,128s128,-57.312 128,-128S326.688,128 256,128zM256,336c-44.096,0 -80,-35.904 -80,-80c0,-44.128 35.904,-80 80,-80s80,35.872 80,80C336,300.096 300.096,336 256,336z" />
<path
android:fillColor="#FF000000"
android:pathData="M393.6,118.4m-17.056,0a17.056,17.056 0,1 1,34.112 0a17.056,17.056 0,1 1,-34.112 0" />
</vector>

7
components/resources/library/build.gradle.kts

@ -35,6 +35,9 @@ kotlin {
implementation(kotlin("test")) implementation(kotlin("test"))
} }
} }
val commonButJSMain by creating {
dependsOn(commonMain)
}
val skikoMain by creating { val skikoMain by creating {
dependsOn(commonMain) dependsOn(commonMain)
} }
@ -44,6 +47,7 @@ kotlin {
val desktopMain by getting { val desktopMain by getting {
dependsOn(skikoMain) dependsOn(skikoMain)
dependsOn(jvmAndAndroidMain) dependsOn(jvmAndAndroidMain)
dependsOn(commonButJSMain)
} }
val desktopTest by getting { val desktopTest by getting {
dependencies { dependencies {
@ -54,6 +58,7 @@ kotlin {
} }
val androidMain by getting { val androidMain by getting {
dependsOn(jvmAndAndroidMain) dependsOn(jvmAndAndroidMain)
dependsOn(commonButJSMain)
} }
val androidTest by getting { val androidTest by getting {
dependencies { dependencies {
@ -62,6 +67,7 @@ kotlin {
} }
val iosMain by getting { val iosMain by getting {
dependsOn(skikoMain) dependsOn(skikoMain)
dependsOn(commonButJSMain)
} }
val iosTest by getting val iosTest by getting
val iosSimulatorArm64Main by getting val iosSimulatorArm64Main by getting
@ -73,6 +79,7 @@ kotlin {
} }
val macosMain by creating { val macosMain by creating {
dependsOn(skikoMain) dependsOn(skikoMain)
dependsOn(commonButJSMain)
} }
val macosX64Main by getting { val macosX64Main by getting {
dependsOn(macosMain) dependsOn(macosMain)

14
components/resources/library/src/commonButJSMain/kotlin/org/jetbrains/compose/resources/Resource.commonbutjs.kt

@ -0,0 +1,14 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.resources
import kotlinx.coroutines.runBlocking
internal actual fun isSyncResourceLoadingSupported() = true
@OptIn(ExperimentalResourceApi::class)
internal actual fun Resource.readBytesSync(): ByteArray = runBlocking { readBytes() }

94
components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ComposeResource.common.kt

@ -7,7 +7,11 @@ package org.jetbrains.compose.resources
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Density
import org.jetbrains.compose.resources.vector.xmldom.Element import org.jetbrains.compose.resources.vector.xmldom.Element
import org.jetbrains.compose.resources.vector.parseVectorRoot import org.jetbrains.compose.resources.vector.parseVectorRoot
@ -19,13 +23,16 @@ private val emptyImageVector: ImageVector by lazy {
ImageVector.Builder(defaultWidth = 1.dp, defaultHeight = 1.dp, viewportWidth = 1f, viewportHeight = 1f).build() ImageVector.Builder(defaultWidth = 1.dp, defaultHeight = 1.dp, viewportWidth = 1f, viewportHeight = 1f).build()
} }
@OptIn(ExperimentalResourceApi::class) /**
* Get and remember resource. While loading and if resource not exists result will be null.
*/
@ExperimentalResourceApi
@Composable @Composable
private fun <T> Resource.rememberLoadingResource(fromByteArrayConverter: ByteArray.()->T): LoadState<T> { fun Resource.rememberImageBitmap(): LoadState<ImageBitmap> {
val state: MutableState<LoadState<T>> = remember(this) { mutableStateOf(LoadState.Loading()) } val state: MutableState<LoadState<ImageBitmap>> = remember(this) { mutableStateOf(LoadState.Loading()) }
LaunchedEffect(this) { LaunchedEffect(this) {
state.value = try { state.value = try {
LoadState.Success(readBytes().fromByteArrayConverter()) LoadState.Success(readBytes().toImageBitmap())
} catch (e: Exception) { } catch (e: Exception) {
LoadState.Error(e) LoadState.Error(e)
} }
@ -38,16 +45,17 @@ private fun <T> Resource.rememberLoadingResource(fromByteArrayConverter: ByteArr
*/ */
@ExperimentalResourceApi @ExperimentalResourceApi
@Composable @Composable
fun Resource.rememberImageBitmap(): LoadState<ImageBitmap> = fun Resource.rememberImageVector(density: Density): LoadState<ImageVector> {
rememberLoadingResource { toImageBitmap() } val state: MutableState<LoadState<ImageVector>> = remember(this, density) { mutableStateOf(LoadState.Loading()) }
LaunchedEffect(this, density) {
/** state.value = try {
* Get and remember resource. While loading and if resource not exists result will be null. LoadState.Success(readBytes().toImageVector(density))
*/ } catch (e: Exception) {
@ExperimentalResourceApi LoadState.Error(e)
@Composable }
fun Resource.rememberImageVector(density: Density): LoadState<ImageVector> = }
rememberLoadingResource { toImageVector(density) } return state.value
}
private fun <T> LoadState<T>.orEmpty(emptyValue: T): T = when (this) { private fun <T> LoadState<T>.orEmpty(emptyValue: T): T = when (this) {
is LoadState.Loading -> emptyValue is LoadState.Loading -> emptyValue
@ -56,17 +64,71 @@ private fun <T> LoadState<T>.orEmpty(emptyValue: T): T = when (this) {
} }
/** /**
* return current ImageBitmap or return empty while loading * Return current ImageBitmap or return empty while loading.
*/ */
@ExperimentalResourceApi @ExperimentalResourceApi
fun LoadState<ImageBitmap>.orEmpty(): ImageBitmap = orEmpty(emptyImageBitmap) fun LoadState<ImageBitmap>.orEmpty(): ImageBitmap = orEmpty(emptyImageBitmap)
/** /**
* return current ImageVector or return empty while loading * Return current ImageVector or return empty while loading.
*/ */
@ExperimentalResourceApi @ExperimentalResourceApi
fun LoadState<ImageVector>.orEmpty(): ImageVector = orEmpty(emptyImageVector) fun LoadState<ImageVector>.orEmpty(): ImageVector = orEmpty(emptyImageVector)
@OptIn(ExperimentalResourceApi::class)
@Composable
private fun Resource.rememberImageBitmapSync(): ImageBitmap = remember(this) {
readBytesSync().toImageBitmap()
}
@OptIn(ExperimentalResourceApi::class)
@Composable
private fun Resource.rememberImageVectorSync(density: Density): ImageVector = remember(this, density) {
readBytesSync().toImageVector(density)
}
@OptIn(ExperimentalResourceApi::class)
@Composable
private fun painterResource(
res: String,
rememberImageBitmap: @Composable Resource.() -> ImageBitmap,
rememberImageVector: @Composable Resource.(Density) -> ImageVector
): Painter =
if (res.endsWith(".xml")) {
rememberVectorPainter(resource(res).rememberImageVector(LocalDensity.current))
} else {
BitmapPainter(resource(res).rememberImageBitmap())
}
/**
* Return a Painter from the given resource path.
* Can load either a BitmapPainter for rasterized images (.png, .jpg) or
* a VectorPainter for XML Vector Drawables (.xml).
*
* XML Vector Drawables have the same format as for Android
* (https://developer.android.com/reference/android/graphics/drawable/VectorDrawable)
* except that external references to Android resources are not supported.
*
* Note that XML Vector Drawables are not supported for Web and native MacOS targets currently.
*
*/
@ExperimentalResourceApi
@Composable
fun painterResource(res: String): Painter =
if (isSyncResourceLoadingSupported()) {
painterResource(res, {rememberImageBitmapSync()}, {density->rememberImageVectorSync(density)})
} else {
painterResource(res, {rememberImageBitmap().orEmpty()}, {density->rememberImageVector(density).orEmpty()})
}
internal expect fun isSyncResourceLoadingSupported(): Boolean
@OptIn(ExperimentalResourceApi::class)
internal expect fun Resource.readBytesSync(): ByteArray
internal expect fun ByteArray.toImageBitmap(): ImageBitmap internal expect fun ByteArray.toImageBitmap(): ImageBitmap
internal expect fun parseXML(byteArray: ByteArray): Element internal expect fun parseXML(byteArray: ByteArray): Element

5
components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt

@ -47,3 +47,8 @@ internal actual class MissingResourceException actual constructor(path: String)
internal actual fun parseXML(byteArray: ByteArray): Element { internal actual fun parseXML(byteArray: ByteArray): Element {
throw UnsupportedOperationException("XML Vector Drawables are not supported for Web target") throw UnsupportedOperationException("XML Vector Drawables are not supported for Web target")
} }
internal actual fun isSyncResourceLoadingSupported() = false
@OptIn(ExperimentalResourceApi::class)
internal actual fun Resource.readBytesSync(): ByteArray = throw UnsupportedOperationException()
Loading…
Cancel
Save