diff --git a/core/build.gradle.kts b/core/build.gradle.kts index f0a74b272..9e7ce6d3f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -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") + } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt index 342b657c0..60f210ea5 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt @@ -19,15 +19,49 @@ 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 @@ -35,6 +69,7 @@ sealed interface PainterHint { */ @Immutable interface PainterPathHint : PainterHint { + /** * Replace the entire path with the given value. */ @@ -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. diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHintsProvider.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHintsProvider.kt index 4f1d42a40..a97fff26c 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHintsProvider.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHintsProvider.kt @@ -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 */ @@ -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 = listOf(Dark(IntelliJTheme.isDark)) + override fun hints(path: String): List = listOf( + HiDpi(LocalDensity.current), + Dark(IntelliJTheme.isDark), + ) } val LocalPainterHintsProvider = staticCompositionLocalOf { - DarkPainterHintsProvider + CommonPainterHintsProvider } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/ResourcePainterProvider.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/ResourcePainterProvider.kt index dea2af98c..aeee86d2c 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/painter/ResourcePainterProvider.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/ResourcePainterProvider.kt @@ -61,7 +61,7 @@ class ResourcePainterProvider( @Composable override fun getPainter(vararg hints: PainterHint): State { val resolvedHints = (hints.toList() + LocalPainterHintsProvider.current.hints(basePath)) - .filter { it != PainterHint.None } + .filter { it.canApplyTo(basePath) } val cacheKey = resolvedHints.hashCode() @@ -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 tryLoadingResource( url: URL, @@ -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) { diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/HiDpi.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/HiDpi.kt new file mode 100644 index 000000000..3588796a4 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/HiDpi.kt @@ -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) diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Palette.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Palette.kt index c5dd528d5..10794eb9a 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Palette.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Palette.kt @@ -33,7 +33,10 @@ private class PaletteImpl(val map: Map) : 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()) + } } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt index 756304827..426745ab3 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt @@ -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("@") @@ -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 x" + } + + 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") +} diff --git a/core/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt b/core/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt new file mode 100644 index 000000000..e8bde5213 --- /dev/null +++ b/core/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt @@ -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") + } +} diff --git a/core/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt b/core/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt new file mode 100644 index 000000000..7dd81e165 --- /dev/null +++ b/core/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt @@ -0,0 +1,293 @@ +package org.jetbrains.jewel + +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.state.ToggleableState +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterPathHint +import org.jetbrains.jewel.painter.PainterResourcePathHint +import org.jetbrains.jewel.painter.PainterSvgPatchHint +import org.jetbrains.jewel.painter.hints.Dark +import org.jetbrains.jewel.painter.hints.HiDpi +import org.jetbrains.jewel.painter.hints.Override +import org.jetbrains.jewel.painter.hints.Palette +import org.jetbrains.jewel.painter.hints.Selected +import org.jetbrains.jewel.painter.hints.Size +import org.jetbrains.jewel.painter.hints.Stateful +import org.jetbrains.jewel.painter.hints.Stroke +import org.jetbrains.jewel.painter.rememberResourcePainterProvider +import org.jetbrains.jewel.painter.writeToString +import org.junit.Assert +import org.junit.Test +import javax.xml.XMLConstants +import javax.xml.parsers.DocumentBuilderFactory + +@Suppress("ImplicitUnitReturnType") +class PainterHintTest : BasicJewelUiTest() { + + @Test + fun `empty hint should be ignored`() = runComposeTest({ + CompositionLocalProvider(LocalIsDarkTheme provides false) { + val provider = rememberResourcePainterProvider("icons/github.svg", PainterHintTest::class.java) + + val painter1 by provider.getPainter() + // must be ignored the None and hit cache + val painter2 by provider.getPainter(PainterHint.None) + // must be ignored the None and hit cache too + val painter3 by provider.getPainter(PainterHint.None, PainterHint.None) + + Assert.assertEquals(painter1, painter2) + Assert.assertEquals(painter3, painter2) + } + }) { + awaitIdle() + } + + private fun String.applyPathHints(vararg hints: PainterHint): String { + var result = this + val format = this.substringAfterLast('.').lowercase() + hints.forEach { + if (!it.canApplyTo(format)) return@forEach + result = when (it) { + is PainterResourcePathHint -> it.patch(result, emptyList()) + is PainterPathHint -> it.patch(result) + else -> return@forEach + } + } + return result + } + + private val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance() + .apply { setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) } + + private fun String.applyPaletteHints(vararg hints: PainterHint): String { + val doc = documentBuilderFactory.newDocumentBuilder().parse(this.toByteArray().inputStream()) + + hints.filterIsInstance() + .onEach { it.patch(doc.documentElement) } + + return doc.writeToString() + } + + @Test + fun `dark painter hint should append suffix when isDark is true`() { + val basePath = "icons/github.svg" + val patchedPath = basePath.applyPathHints(Dark(true)) + Assert.assertEquals("icons/github_dark.svg", patchedPath) + } + + @Test + fun `dark painter hint should not append suffix when isDark is false`() { + val basePath = "icons/github.svg" + val patchedPath = basePath.applyPathHints(Dark(false)) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `override painter hint should replace path entirely`() { + val basePath = "icons/github.svg" + val patchedPath = basePath.applyPathHints(Override(mapOf("icons/github.svg" to "icons/search.svg"))) + Assert.assertEquals("icons/search.svg", patchedPath) + } + + @Test + fun `override painter hint should not replace path when not matched`() { + val basePath = "icons/github.svg" + val patchedPath = basePath.applyPathHints(Override(mapOf("icons/settings.svg" to "icons/search.svg"))) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `selected painter hint should append suffix when selected is true`() { + val basePath = "icons/checkbox.svg" + val patchedPath = basePath.applyPathHints(Selected(true)) + Assert.assertEquals("icons/checkboxSelected.svg", patchedPath) + } + + @Test + fun `selected painter hint should not append suffix when selected is false`() { + val basePath = "icons/checkbox.svg" + val patchedPath = basePath.applyPathHints(Selected(false)) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `size painter hint should append suffix when size is not empty`() { + val basePath = "icons/github.svg" + val patchedPath = basePath.applyPathHints(Size("20x20")) + Assert.assertEquals("icons/github@20x20.svg", patchedPath) + } + + @Test + fun `size painter hint should not append suffix when size is empty or null`() { + val basePath = "icons/github.svg" + val patchedPath = basePath.applyPathHints(Size("")) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `highDpi painter hint should not append suffix for svg`() { + val basePath = "icons/github.svg" + val patchedPath = basePath.applyPathHints(HiDpi(true)) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `highDpi painter hint should append suffix when isHiDpi is true`() { + val basePath = "icons/github.png" + val patchedPath = basePath.applyPathHints(HiDpi(true)) + Assert.assertEquals("icons/github@2x.png", patchedPath) + } + + @Test + fun `highDpi painter hint should not append suffix when isHiDpi is false`() { + val basePath = "icons/github.png" + val patchedPath = basePath.applyPathHints(HiDpi(false)) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `size painter hint should not append suffix for bitmap`() { + val basePath = "icons/github.png" + val patchedPath = basePath.applyPathHints(Size(20)) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `size painter hint should throw when size format incorrect`() { + val basePath = "icons/github.svg" + + Assert.assertThrows(IllegalArgumentException::class.java) { + basePath.applyPathHints(Size("wrongSizeString")) + } + } + + @Test + fun `size painter hint should throw with wrong width or height`() { + val basePath = "icons/github.svg" + + Assert.assertThrows(IllegalArgumentException::class.java) { + basePath.applyPathHints(Size(-1, 20)) + } + + Assert.assertThrows(IllegalArgumentException::class.java) { + basePath.applyPathHints(Size(20, 0)) + } + } + + @Test + fun `stateful painter hint should append Disabled suffix when enabled is false`() { + val basePath = "icons/checkbox.svg" + val state = CheckboxState.of(toggleableState = ToggleableState.Off) + val patchedPath = basePath.applyPathHints(Stateful(state.copy(enabled = false))) + Assert.assertEquals("icons/checkboxDisabled.svg", patchedPath) + + basePath.applyPathHints(Stateful(state.copy(enabled = false, pressed = true, hovered = true, focused = true))) + .let { + Assert.assertEquals("icons/checkboxDisabled.svg", it) + } + } + + @Test + fun `stateful painter hint disabled state takes higher priority over other states`() { + val basePath = "icons/checkbox.svg" + val state = CheckboxState.of(toggleableState = ToggleableState.Off) + val patchedPath = + basePath.applyPathHints( + Stateful( + state.copy( + enabled = false, + pressed = true, + hovered = true, + focused = true, + ), + ), + ) + Assert.assertEquals("icons/checkboxDisabled.svg", patchedPath) + } + + @Test + fun `stateful painter hint should append Focused suffix when focused is true`() { + val basePath = "icons/checkbox.svg" + val state = CheckboxState.of(toggleableState = ToggleableState.Off) + val patchedPath = basePath.applyPathHints(Stateful(state.copy(focused = true))) + Assert.assertEquals("icons/checkboxFocused.svg", patchedPath) + } + + @Test + fun `stateful painter hint focused state takes higher priority over pressed and hovered states`() { + val basePath = "icons/checkbox.svg" + val state = CheckboxState.of(toggleableState = ToggleableState.Off) + val patchedPath = basePath.applyPathHints(Stateful(state.copy(pressed = true, hovered = true, focused = true))) + Assert.assertEquals("icons/checkboxFocused.svg", patchedPath) + } + + @Test + fun `stateful painter hint should append Pressed suffix when pressed is true`() { + val basePath = "icons/checkbox.svg" + val state = CheckboxState.of(toggleableState = ToggleableState.Off) + val patchedPath = basePath.applyPathHints(Stateful(state.copy(pressed = true))) + Assert.assertEquals("icons/checkboxPressed.svg", patchedPath) + } + + @Test + fun `stateful painter hint pressed state takes higher priority over hovered state`() { + val basePath = "icons/checkbox.svg" + val state = CheckboxState.of(toggleableState = ToggleableState.Off) + val patchedPath = basePath.applyPathHints(Stateful(state.copy(pressed = true, hovered = true))) + Assert.assertEquals("icons/checkboxPressed.svg", patchedPath) + } + + @Test + fun `stateful painter hint should append Hovered suffix when hovered is true`() { + val basePath = "icons/checkbox.svg" + val state = CheckboxState.of(toggleableState = ToggleableState.Off) + val patchedPath = basePath.applyPathHints(Stateful(state.copy(hovered = true))) + Assert.assertEquals("icons/checkboxHovered.svg", patchedPath) + } + + @Test + fun `stroke painter hint should append suffix when stroked is true`() { + val basePath = "icons/rerun.svg" + val patchedPath = basePath.applyPathHints(Stroke(true)) + Assert.assertEquals("icons/rerun_stroke.svg", patchedPath) + } + + @Test + fun `stroke painter hint should not append suffix when stroked is false`() { + val basePath = "icons/rerun.svg" + val patchedPath = basePath.applyPathHints(Stroke(false)) + Assert.assertEquals(basePath, patchedPath) + } + + @Test + fun `palette painter hint should patch colors correctly in SVG`() { + val baseSvg = """ + + + + + + """.trimIndent() + val patchedSvg = baseSvg.applyPaletteHints( + Palette( + mapOf( + Color(0x80000000) to Color(0xFF123456), + Color.Black to Color.White, + Color.Green to Color.Red, + ), + ), + ) + Assert.assertEquals( + """ + + + + + + """.trimIndent(), + patchedSvg, + ) + } +} diff --git a/core/src/test/resources/icons/github.svg b/core/src/test/resources/icons/github.svg new file mode 100644 index 000000000..0c7982bf9 --- /dev/null +++ b/core/src/test/resources/icons/github.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/src/test/resources/icons/github@20x20.svg b/core/src/test/resources/icons/github@20x20.svg new file mode 100644 index 000000000..bf2e15399 --- /dev/null +++ b/core/src/test/resources/icons/github@20x20.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/src/test/resources/icons/github@20x20_dark.svg b/core/src/test/resources/icons/github@20x20_dark.svg new file mode 100644 index 000000000..f4a865d25 --- /dev/null +++ b/core/src/test/resources/icons/github@20x20_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/src/test/resources/icons/github_dark.svg b/core/src/test/resources/icons/github_dark.svg new file mode 100644 index 000000000..d86c44553 --- /dev/null +++ b/core/src/test/resources/icons/github_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/src/test/resources/icons/search.svg b/core/src/test/resources/icons/search.svg new file mode 100644 index 000000000..bdcd000f0 --- /dev/null +++ b/core/src/test/resources/icons/search.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/src/test/resources/icons/search@20x20.svg b/core/src/test/resources/icons/search@20x20.svg new file mode 100644 index 000000000..183a6100c --- /dev/null +++ b/core/src/test/resources/icons/search@20x20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/src/test/resources/icons/search@20x20_dark.svg b/core/src/test/resources/icons/search@20x20_dark.svg new file mode 100644 index 000000000..27f0332d1 --- /dev/null +++ b/core/src/test/resources/icons/search@20x20_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/src/test/resources/icons/search_dark.svg b/core/src/test/resources/icons/search_dark.svg new file mode 100644 index 000000000..533a8d80b --- /dev/null +++ b/core/src/test/resources/icons/search_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/core/src/test/resources/icons/settings.svg b/core/src/test/resources/icons/settings.svg new file mode 100644 index 000000000..c30de8209 --- /dev/null +++ b/core/src/test/resources/icons/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/src/test/resources/icons/settings@20x20.svg b/core/src/test/resources/icons/settings@20x20.svg new file mode 100644 index 000000000..fbe2af4df --- /dev/null +++ b/core/src/test/resources/icons/settings@20x20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/src/test/resources/icons/settings@20x20_dark.svg b/core/src/test/resources/icons/settings@20x20_dark.svg new file mode 100644 index 000000000..cc6378305 --- /dev/null +++ b/core/src/test/resources/icons/settings@20x20_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/src/test/resources/icons/settings_dark.svg b/core/src/test/resources/icons/settings_dark.svg new file mode 100644 index 000000000..62f328167 --- /dev/null +++ b/core/src/test/resources/icons/settings_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt b/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt index 861af5ce4..6bad8e231 100644 --- a/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt +++ b/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt @@ -2,11 +2,15 @@ package org.jetbrains.jewel.bridge import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import com.intellij.ide.ui.UITheme import com.intellij.openapi.diagnostic.thisLogger +import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.InternalJewelApi import org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.hints.Dark +import org.jetbrains.jewel.painter.hints.HiDpi import org.jetbrains.jewel.util.fromRGBAHexString @InternalJewelApi @@ -26,6 +30,8 @@ class BridgePainterHintsProvider private constructor( override fun hints(path: String): List = buildList { add(getPaletteHint(path)) add(BridgeOverride) + add(HiDpi(LocalDensity.current)) + add(Dark(IntelliJTheme.isDark)) } companion object { diff --git a/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt b/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt index eb7baee81..587920384 100644 --- a/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt +++ b/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt @@ -2,11 +2,15 @@ package org.jetbrains.jewel.bridge import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import com.intellij.ide.ui.UITheme import com.intellij.openapi.diagnostic.thisLogger +import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.InternalJewelApi import org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.hints.Dark +import org.jetbrains.jewel.painter.hints.HiDpi @InternalJewelApi class BridgePainterHintsProvider private constructor( @@ -25,6 +29,8 @@ class BridgePainterHintsProvider private constructor( override fun hints(path: String): List = buildList { add(getPaletteHint(path)) add(BridgeOverride) + add(HiDpi(LocalDensity.current)) + add(Dark(IntelliJTheme.isDark)) } companion object { diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider.kt index 53ca57477..0793126ce 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider.kt @@ -1,11 +1,13 @@ package org.jetbrains.jewel.intui.standalone import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalDensity import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider import org.jetbrains.jewel.intui.core.IntUiThemeDefinition import org.jetbrains.jewel.painter.PainterHint import org.jetbrains.jewel.painter.hints.Dark +import org.jetbrains.jewel.painter.hints.HiDpi import org.jetbrains.jewel.painter.hints.Override class StandalonePainterHintsProvider( @@ -31,6 +33,7 @@ class StandalonePainterHintsProvider( override fun hints(path: String): List = buildList { add(getPaletteHint(path)) add(overrideHint) + add(HiDpi(LocalDensity.current)) add(Dark(IntelliJTheme.isDark)) } diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt index c1df6e06e..77c04cfac 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.intellij.icons.AllIcons import com.intellij.ui.JBColor +import icons.JewelIcons import org.jetbrains.jewel.CheckboxRow import org.jetbrains.jewel.CircularProgressIndicator import org.jetbrains.jewel.CircularProgressIndicatorBig @@ -118,10 +119,19 @@ import org.jetbrains.jewel.intui.standalone.IntUiTheme modifier = Modifier.border(1.dp, Color.Magenta), contentDescription = "An icon", ) + Icon( + "icons/github.svg", + iconClass = JewelIcons::class.java, + modifier = Modifier.border(1.dp, Color.Magenta), + contentDescription = "An icon", + ) IconButton(onClick = { }) { Icon("actions/close.svg", contentDescription = "An icon", AllIcons::class.java) } + IconButton(onClick = { }) { + Icon("actions/addList.svg", contentDescription = "An icon", AllIcons::class.java) + } } Row( diff --git a/samples/ide-plugin/src/main/resources/icons/github.svg b/samples/ide-plugin/src/main/resources/icons/github.svg new file mode 100644 index 000000000..0c7982bf9 --- /dev/null +++ b/samples/ide-plugin/src/main/resources/icons/github.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/ide-plugin/src/main/resources/icons/github@20x20.svg b/samples/ide-plugin/src/main/resources/icons/github@20x20.svg new file mode 100644 index 000000000..bf2e15399 --- /dev/null +++ b/samples/ide-plugin/src/main/resources/icons/github@20x20.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/ide-plugin/src/main/resources/icons/github@20x20_dark.svg b/samples/ide-plugin/src/main/resources/icons/github@20x20_dark.svg new file mode 100644 index 000000000..f4a865d25 --- /dev/null +++ b/samples/ide-plugin/src/main/resources/icons/github@20x20_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/ide-plugin/src/main/resources/icons/github_dark.svg b/samples/ide-plugin/src/main/resources/icons/github_dark.svg new file mode 100644 index 000000000..d86c44553 --- /dev/null +++ b/samples/ide-plugin/src/main/resources/icons/github_dark.svg @@ -0,0 +1,5 @@ + + + + +