mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-03-10 08:11:20 +00:00
Add painterResource utility method to resource library. (#2753)
+ respect density in remembering state for ImageVector
This commit is contained in:
@@ -5,7 +5,9 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.*
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
@@ -23,6 +25,12 @@ internal fun UseResources() {
|
||||
)
|
||||
Icon(
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -35,6 +35,9 @@ kotlin {
|
||||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
val commonButJSMain by creating {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
val skikoMain by creating {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
@@ -44,6 +47,7 @@ kotlin {
|
||||
val desktopMain by getting {
|
||||
dependsOn(skikoMain)
|
||||
dependsOn(jvmAndAndroidMain)
|
||||
dependsOn(commonButJSMain)
|
||||
}
|
||||
val desktopTest by getting {
|
||||
dependencies {
|
||||
@@ -54,6 +58,7 @@ kotlin {
|
||||
}
|
||||
val androidMain by getting {
|
||||
dependsOn(jvmAndAndroidMain)
|
||||
dependsOn(commonButJSMain)
|
||||
}
|
||||
val androidTest by getting {
|
||||
dependencies {
|
||||
@@ -62,6 +67,7 @@ kotlin {
|
||||
}
|
||||
val iosMain by getting {
|
||||
dependsOn(skikoMain)
|
||||
dependsOn(commonButJSMain)
|
||||
}
|
||||
val iosTest by getting
|
||||
val iosSimulatorArm64Main by getting
|
||||
@@ -73,6 +79,7 @@ kotlin {
|
||||
}
|
||||
val macosMain by creating {
|
||||
dependsOn(skikoMain)
|
||||
dependsOn(commonButJSMain)
|
||||
}
|
||||
val macosX64Main by getting {
|
||||
dependsOn(macosMain)
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -7,7 +7,11 @@ package org.jetbrains.compose.resources
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
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.rememberVectorPainter
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Density
|
||||
import org.jetbrains.compose.resources.vector.xmldom.Element
|
||||
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()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
/**
|
||||
* Get and remember resource. While loading and if resource not exists result will be null.
|
||||
*/
|
||||
@ExperimentalResourceApi
|
||||
@Composable
|
||||
private fun <T> Resource.rememberLoadingResource(fromByteArrayConverter: ByteArray.()->T): LoadState<T> {
|
||||
val state: MutableState<LoadState<T>> = remember(this) { mutableStateOf(LoadState.Loading()) }
|
||||
fun Resource.rememberImageBitmap(): LoadState<ImageBitmap> {
|
||||
val state: MutableState<LoadState<ImageBitmap>> = remember(this) { mutableStateOf(LoadState.Loading()) }
|
||||
LaunchedEffect(this) {
|
||||
state.value = try {
|
||||
LoadState.Success(readBytes().fromByteArrayConverter())
|
||||
LoadState.Success(readBytes().toImageBitmap())
|
||||
} catch (e: Exception) {
|
||||
LoadState.Error(e)
|
||||
}
|
||||
@@ -38,16 +45,17 @@ private fun <T> Resource.rememberLoadingResource(fromByteArrayConverter: ByteArr
|
||||
*/
|
||||
@ExperimentalResourceApi
|
||||
@Composable
|
||||
fun Resource.rememberImageBitmap(): LoadState<ImageBitmap> =
|
||||
rememberLoadingResource { toImageBitmap() }
|
||||
|
||||
/**
|
||||
* Get and remember resource. While loading and if resource not exists result will be null.
|
||||
*/
|
||||
@ExperimentalResourceApi
|
||||
@Composable
|
||||
fun Resource.rememberImageVector(density: Density): LoadState<ImageVector> =
|
||||
rememberLoadingResource { toImageVector(density) }
|
||||
fun Resource.rememberImageVector(density: Density): LoadState<ImageVector> {
|
||||
val state: MutableState<LoadState<ImageVector>> = remember(this, density) { mutableStateOf(LoadState.Loading()) }
|
||||
LaunchedEffect(this, density) {
|
||||
state.value = try {
|
||||
LoadState.Success(readBytes().toImageVector(density))
|
||||
} catch (e: Exception) {
|
||||
LoadState.Error(e)
|
||||
}
|
||||
}
|
||||
return state.value
|
||||
}
|
||||
|
||||
private fun <T> LoadState<T>.orEmpty(emptyValue: T): T = when (this) {
|
||||
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
|
||||
fun LoadState<ImageBitmap>.orEmpty(): ImageBitmap = orEmpty(emptyImageBitmap)
|
||||
|
||||
/**
|
||||
* return current ImageVector or return empty while loading
|
||||
* Return current ImageVector or return empty while loading.
|
||||
*/
|
||||
@ExperimentalResourceApi
|
||||
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 parseXML(byteArray: ByteArray): Element
|
||||
|
||||
@@ -46,4 +46,9 @@ internal actual class MissingResourceException actual constructor(path: String)
|
||||
|
||||
internal actual fun parseXML(byteArray: ByteArray): Element {
|
||||
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()
|
||||
Reference in New Issue
Block a user