diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe2e9ce0d..e2d4a39fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] commonmark = "0.22.0" -composeDesktop = "1.7.0-beta01" +composeDesktop = "1.7.0-beta02" detekt = "1.23.6" dokka = "1.9.20" idea = "2024.2.1" diff --git a/samples/standalone/build.gradle.kts b/samples/standalone/build.gradle.kts index 1aa6754cd..9ef9a4abe 100644 --- a/samples/standalone/build.gradle.kts +++ b/samples/standalone/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(projects.markdown.extension.gfmAlerts) implementation(projects.markdown.extension.autolink) implementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } + implementation(compose.components.resources) implementation(libs.intellijPlatform.icons) } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt index 5c4a9a17e..d962daf07 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt @@ -7,12 +7,11 @@ import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.isAltPressed import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type -import androidx.compose.ui.res.ResourceLoader -import androidx.compose.ui.res.loadSvgPainter import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.Density import androidx.compose.ui.window.application -import java.io.InputStream +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.decodeToSvgPainter import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.util.JewelLogger import org.jetbrains.jewel.intui.standalone.Inter @@ -113,5 +112,13 @@ private fun processKeyShortcuts(keyEvent: KeyEvent, onNavigateTo: (String) -> Un } } -private fun svgResource(resourcePath: String, loader: ResourceLoader = ResourceLoader.Default): Painter = - loader.load(resourcePath).use { stream: InputStream -> loadSvgPainter(stream, Density(1f)) } +@Suppress("SameParameterValue") +@OptIn(ExperimentalResourceApi::class) +private fun svgResource(resourcePath: String): Painter = + checkNotNull(ResourceLoader.javaClass.classLoader.getResourceAsStream(resourcePath)) { + "Could not load resource $resourcePath: it does not exist or can't be read." + } + .readAllBytes() + .decodeToSvgPainter(Density(1f)) + +private object ResourceLoader diff --git a/ui/api/ui.api b/ui/api/ui.api index c9d9bbb7e..5f3b9d687 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -370,7 +370,7 @@ public final class org/jetbrains/jewel/ui/component/IconKt { public static final fun Icon-ww6aTOc (Landroidx/compose/ui/graphics/ImageBitmap;Ljava/lang/String;Landroidx/compose/ui/Modifier;JLandroidx/compose/runtime/Composer;II)V public static final fun Icon-ww6aTOc (Landroidx/compose/ui/graphics/painter/Painter;Ljava/lang/String;Landroidx/compose/ui/Modifier;JLandroidx/compose/runtime/Composer;II)V public static final fun Icon-ww6aTOc (Landroidx/compose/ui/graphics/vector/ImageVector;Ljava/lang/String;Landroidx/compose/ui/Modifier;JLandroidx/compose/runtime/Composer;II)V - public static final fun painterResource (Ljava/lang/String;Landroidx/compose/ui/res/ResourceLoader;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; + public static final fun painterResource (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter; } public final class org/jetbrains/jewel/ui/component/InputFieldState : org/jetbrains/jewel/foundation/state/FocusableComponentState { diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 944468ae2..21922d76b 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -16,6 +16,7 @@ private val composeVersion dependencies { api(projects.foundation) + implementation(compose.components.resources) iconGeneration(libs.intellijPlatform.util.ui) iconGeneration(libs.intellijPlatform.icons) testImplementation(compose.desktop.uiTestJUnit4) diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/CircularProgressIndicator.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/CircularProgressIndicator.kt index 473168bbf..305f83fc3 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/CircularProgressIndicator.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/CircularProgressIndicator.kt @@ -16,12 +16,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.loadSvgPainter import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.decodeToSvgPainter import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.styling.CircularProgressStyle import org.jetbrains.jewel.ui.theme.circularProgressStyle @@ -57,6 +58,7 @@ public fun CircularProgressIndicatorBig( ) } +@OptIn(ExperimentalResourceApi::class) @Composable private fun CircularProgressIndicatorImpl( modifier: Modifier = Modifier, @@ -73,7 +75,7 @@ private fun CircularProgressIndicatorImpl( value = withContext(dispatcher) { frameRetriever(style.color.takeOrElse { defaultColor }).map { - loadSvgPainter(it.byteInputStream(), density) + it.toByteArray().decodeToSvgPainter(density) } } } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt index a5afb3973..32edb9bcd 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt @@ -22,24 +22,22 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.ResourceLoader -import androidx.compose.ui.res.loadImageBitmap -import androidx.compose.ui.res.loadSvgPainter -import androidx.compose.ui.res.loadXmlImageVector import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp -import java.io.InputStream import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.decodeToImageBitmap +import org.jetbrains.compose.resources.decodeToImageVector +import org.jetbrains.compose.resources.decodeToSvgPainter import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.icon.IconKey import org.jetbrains.jewel.ui.icon.newUiChecker import org.jetbrains.jewel.ui.painter.PainterHint import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider import org.jetbrains.jewel.ui.util.thenIf -import org.xml.sax.InputSource @Deprecated( "Use the IconKey-based API instead", @@ -310,37 +308,40 @@ public fun Icon( } @Composable -public fun painterResource(resourcePath: String, loader: ResourceLoader): Painter = +public fun painterResource(resourcePath: String): Painter = when (resourcePath.substringAfterLast(".").lowercase()) { - "svg" -> rememberSvgResource(resourcePath, loader) - "xml" -> rememberVectorXmlResource(resourcePath, loader) - else -> rememberBitmapResource(resourcePath, loader) + "svg" -> rememberSvgResource(resourcePath) + "xml" -> rememberVectorXmlResource(resourcePath) + else -> rememberBitmapResource(resourcePath) } +@OptIn(ExperimentalResourceApi::class) @Composable -private fun rememberSvgResource(resourcePath: String, loader: ResourceLoader = ResourceLoader.Default): Painter { +private fun rememberSvgResource(path: String): Painter { val density = LocalDensity.current - return remember(resourcePath, density, loader) { useResource(resourcePath, loader) { loadSvgPainter(it, density) } } + return remember(density, path) { readResourceBytes(path).decodeToSvgPainter(density) } } +@OptIn(ExperimentalResourceApi::class) @Composable -private fun rememberVectorXmlResource(resourcePath: String, loader: ResourceLoader = ResourceLoader.Default): Painter { +private fun rememberVectorXmlResource(path: String): Painter { val density = LocalDensity.current - val image = - remember(resourcePath, density, loader) { - useResource(resourcePath, loader) { loadXmlImageVector(InputSource(it), density) } - } - return rememberVectorPainter(image) + val imageVector = remember(density, path) { readResourceBytes(path).decodeToImageVector(density) } + return rememberVectorPainter(imageVector) } +@OptIn(ExperimentalResourceApi::class) @Composable -private fun rememberBitmapResource(resourcePath: String, loader: ResourceLoader = ResourceLoader.Default): Painter { - val image = remember(resourcePath) { useResource(resourcePath, loader, ::loadImageBitmap) } - return BitmapPainter(image) -} +private fun rememberBitmapResource(path: String): Painter = + remember(path) { BitmapPainter(readResourceBytes(path).decodeToImageBitmap()) } -private inline fun useResource(resourcePath: String, loader: ResourceLoader, block: (InputStream) -> T): T = - loader.load(resourcePath).use(block) +private object ResourceLoader + +private fun readResourceBytes(resourcePath: String) = + checkNotNull(ResourceLoader.javaClass.classLoader.getResourceAsStream(resourcePath)) { + "Could not load resource $resourcePath: it does not exist or can't be read." + } + .readAllBytes() private fun Modifier.defaultSizeFor(painter: Painter) = thenIf(painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) { DefaultIconSizeModifier } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt index e72acaba0..fdcebe202 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt @@ -10,9 +10,6 @@ import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.loadImageBitmap -import androidx.compose.ui.res.loadSvgPainter -import androidx.compose.ui.res.loadXmlImageVector import androidx.compose.ui.unit.Density import java.io.IOException import java.io.InputStream @@ -27,11 +24,14 @@ import javax.xml.transform.TransformerException import javax.xml.transform.TransformerFactory import javax.xml.transform.dom.DOMSource import javax.xml.transform.stream.StreamResult +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.decodeToImageBitmap +import org.jetbrains.compose.resources.decodeToImageVector +import org.jetbrains.compose.resources.decodeToSvgPainter import org.jetbrains.jewel.foundation.util.myLogger import org.jetbrains.jewel.ui.icon.IconKey import org.jetbrains.jewel.ui.icon.LocalNewUiChecker import org.w3c.dom.Document -import org.xml.sax.InputSource private val errorPainter = ColorPainter(Color.Magenta) @@ -137,6 +137,7 @@ public class ResourcePainterProvider(private val basePath: String, vararg classL return null } + @OptIn(ExperimentalResourceApi::class) @Composable private fun createSvgPainter(scope: Scope, url: URL): Painter = tryLoadingResource( @@ -144,7 +145,7 @@ public class ResourcePainterProvider(private val basePath: String, vararg classL loadingAction = { resourceUrl -> patchSvg(scope, url.openStream(), scope.acceptedHints).use { inputStream -> logger.debug("Loading icon $basePath(${scope.acceptedHints.joinToString()}) from $resourceUrl") - loadSvgPainter(inputStream, scope) + inputStream.readAllBytes().decodeToSvgPainter(scope) } }, paintAction = { it }, @@ -171,22 +172,24 @@ public class ResourcePainterProvider(private val basePath: String, vararg classL } } + @OptIn(ExperimentalResourceApi::class) @Composable private fun createVectorDrawablePainter(scope: Scope, url: URL): Painter = tryLoadingResource( url = url, loadingAction = { resourceUrl -> - resourceUrl.openStream().use { loadXmlImageVector(InputSource(it), scope) } + resourceUrl.openStream().use { inputStream -> inputStream.readAllBytes().decodeToImageVector(scope) } }, paintAction = { rememberVectorPainter(it) }, ) + @OptIn(ExperimentalResourceApi::class) @Composable private fun createBitmapPainter(url: URL) = tryLoadingResource( url = url, loadingAction = { resourceUrl -> - val bitmap = resourceUrl.openStream().use { loadImageBitmap(it) } + val bitmap = resourceUrl.openStream().use { it.readAllBytes().decodeToImageBitmap() } BitmapPainter(bitmap) }, paintAction = { it },