Skip to content

Commit

Permalink
Add tests for painter hint (#190)
Browse files Browse the repository at this point in the history
Add test for painter hints
  • Loading branch information
devkanro authored Oct 19, 2023
1 parent 7476c23 commit af53235
Show file tree
Hide file tree
Showing 29 changed files with 534 additions and 31 deletions.
5 changes: 5 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ private val composeVersion get() = ComposeBuildConfig.composeVersion

dependencies {
api("org.jetbrains.compose.foundation:foundation-desktop:$composeVersion")

testImplementation(compose.desktop.uiTestJUnit4)
testImplementation(compose.desktop.currentOs) {
exclude(group = "org.jetbrains.compose.material")
}
}
37 changes: 36 additions & 1 deletion core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,57 @@ import org.w3c.dom.Element
@Immutable
sealed interface PainterHint {

fun canApplyTo(path: String): Boolean = true

/**
* An empty [PainterHint], it will be ignored.
*/
companion object None : PainterHint {

override fun canApplyTo(path: String): Boolean = false

override fun toString(): String = "None"
}
}

/**
* Mark this [PainterHint] just available with SVG images.
*/
@Immutable
interface SvgPainterHint : PainterHint {

override fun canApplyTo(path: String): Boolean = path.substringAfterLast('.').lowercase() == "svg"
}

/**
* Mark this [PainterHint] just available with Bitmap images.
*/
@Immutable
interface BitmapPainterHint : PainterHint {

override fun canApplyTo(path: String): Boolean = when (path.substringAfterLast('.').lowercase()) {
"svg", "xml" -> false
else -> true
}
}

/**
* Mark this [PainterHint] just available with XML images.
*/
@Immutable
interface XmlPainterHint : PainterHint {

override fun canApplyTo(path: String): Boolean = path.substringAfterLast('.').lowercase() == "xml"
}

/**
* A [PainterHint] that modifies the path of the resource being loaded.
* Usage examples are applying the New UI icon mappings, or picking up
* dark theme variants of icons.
*/
@Immutable
interface PainterPathHint : PainterHint {

/**
* Replace the entire path with the given value.
*/
Expand All @@ -59,7 +94,7 @@ interface PainterResourcePathHint : PainterHint {
* to SVG resources; it doesn't affect other types of resources.
*/
@Immutable
interface PainterSvgPatchHint : PainterHint {
interface PainterSvgPatchHint : SvgPainterHint {

/**
* Patch the SVG content.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package org.jetbrains.jewel.painter
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalDensity
import org.jetbrains.jewel.IntelliJTheme
import org.jetbrains.jewel.painter.hints.Dark
import org.jetbrains.jewel.painter.hints.HiDpi

/**
* Provides [hints][PainterHint] to a [PainterProvider].
*
* @see DarkPainterHintsProvider
* @see CommonPainterHintsProvider
* @see org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider
* @see org.jetbrains.jewel.intui.standalone.StandalonePainterHintsProvider
*/
Expand All @@ -24,12 +26,15 @@ interface PainterHintsProvider {
* The default [PainterHintsProvider] to load dark theme icon variants.
* It will provide the [Dark] hint when [LocalIsDarkTheme][org.jetbrains.jewel.LocalIsDarkTheme] is true.
*/
object DarkPainterHintsProvider : PainterHintsProvider {
object CommonPainterHintsProvider : PainterHintsProvider {

@Composable
override fun hints(path: String): List<PainterHint> = listOf(Dark(IntelliJTheme.isDark))
override fun hints(path: String): List<PainterHint> = listOf(
HiDpi(LocalDensity.current),
Dark(IntelliJTheme.isDark),
)
}

val LocalPainterHintsProvider = staticCompositionLocalOf<PainterHintsProvider> {
DarkPainterHintsProvider
CommonPainterHintsProvider
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class ResourcePainterProvider(
@Composable
override fun getPainter(vararg hints: PainterHint): State<Painter> {
val resolvedHints = (hints.toList() + LocalPainterHintsProvider.current.hints(basePath))
.filter { it != PainterHint.None }
.filter { it.canApplyTo(basePath) }

val cacheKey = resolvedHints.hashCode()

Expand Down Expand Up @@ -190,24 +190,6 @@ class ResourcePainterProvider(
rememberAction = { remember(url, density) { it } },
)

private fun Document.writeToString(): String {
val tf = TransformerFactory.newInstance()
val transformer: Transformer

try {
transformer = tf.newTransformer()
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")

val writer = StringWriter()
transformer.transform(DOMSource(this), StreamResult(writer))
return writer.buffer.toString()
} catch (e: TransformerException) {
error("Unable to render XML document to string: ${e.message}")
} catch (e: IOException) {
error("Unable to render XML document to string: ${e.message}")
}
}

@Composable
private fun <T> tryLoadingResource(
url: URL,
Expand All @@ -231,6 +213,24 @@ class ResourcePainterProvider(
}
}

internal fun Document.writeToString(): String {
val tf = TransformerFactory.newInstance()
val transformer: Transformer

try {
transformer = tf.newTransformer()
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")

val writer = StringWriter()
transformer.transform(DOMSource(this), StreamResult(writer))
return writer.buffer.toString()
} catch (e: TransformerException) {
error("Unable to render XML document to string: ${e.message}")
} catch (e: IOException) {
error("Unable to render XML document to string: ${e.message}")
}
}

@Composable
fun rememberResourcePainterProvider(path: String, iconClass: Class<*>): PainterProvider =
remember(path, iconClass.classLoader) {
Expand Down
22 changes: 22 additions & 0 deletions core/src/main/kotlin/org/jetbrains/jewel/painter/hints/HiDpi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.jetbrains.jewel.painter.hints

import androidx.compose.runtime.Immutable
import org.jetbrains.jewel.painter.BitmapPainterHint
import org.jetbrains.jewel.painter.PainterHint
import org.jetbrains.jewel.painter.PainterSuffixHint

@Immutable
private object HiDpiImpl : PainterSuffixHint(), BitmapPainterHint {

override fun suffix(): String = "@2x"

override fun toString(): String = "HiDpi"
}

fun HiDpi(isHiDpi: Boolean): PainterHint = if (isHiDpi) {
HiDpiImpl
} else {
PainterHint.None
}

fun HiDpi(density: androidx.compose.ui.unit.Density): PainterHint = HiDpi(density.density > 1)
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ private class PaletteImpl(val map: Map<Color, Color>) : PainterSvgPatchHint {
val alpha = opacity.toFloatOrNull() ?: 1.0f
val originalColor = tryParseColor(color, alpha) ?: return
val newColor = pattern[originalColor] ?: return
setAttribute(attrName, newColor.copy(alpha = alpha).toRgbaHexString())
setAttribute(attrName, newColor.copy(alpha = 1.0f).toRgbaHexString())
if (newColor.alpha != alpha) {
setAttribute("$attrName-opacity", newColor.alpha.toString())
}
}
}

Expand Down
25 changes: 19 additions & 6 deletions core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package org.jetbrains.jewel.painter.hints
import androidx.compose.runtime.Immutable
import org.jetbrains.jewel.painter.PainterHint
import org.jetbrains.jewel.painter.PainterSuffixHint
import org.jetbrains.jewel.painter.SvgPainterHint

@Immutable
private class SizeImpl(private val size: String) : PainterSuffixHint() {
private class SizeImpl(private val size: String) : PainterSuffixHint(), SvgPainterHint {

override fun suffix(): String = buildString {
append("@")
Expand All @@ -26,10 +27,22 @@ private class SizeImpl(private val size: String) : PainterSuffixHint() {
}
}

fun Size(size: String?): PainterHint = if (size.isNullOrEmpty()) {
PainterHint.None
} else {
SizeImpl(size)
private val sizeHintValidateRegex = """\d+x\d+""".toRegex()

fun Size(size: String): PainterHint {
if (size.isBlank()) return PainterHint.None
val trimmed = size.trim()
require(sizeHintValidateRegex.matches(trimmed)) {
"Size must be in format of <width>x<height>"
}

return SizeImpl(trimmed)
}

fun Size(width: Int, height: Int): PainterHint = SizeImpl("${width}x$height")
fun Size(width: Int, height: Int = width): PainterHint {
require(width > 0 && height > 0) {
"Width and height must be positive"
}

return SizeImpl("${width}x$height")
}
28 changes: 28 additions & 0 deletions core/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jetbrains.jewel

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule

open class BasicJewelUiTest {

@get:Rule
val composeRule = createComposeRule()

@Suppress("ImplicitUnitReturnType")
protected fun runComposeTest(
composable: @Composable () -> Unit,
block: suspend ComposeContentTestRule.() -> Unit,
) = runBlocking {
composeRule.setContent(composable)
composeRule.block()
}

@Before
fun setUpProperties() {
System.setProperty("org.jetbrains.jewel.debug", "true")
}
}
Loading

0 comments on commit af53235

Please sign in to comment.