diff --git a/platform/jewel/foundation/api-dump.txt b/platform/jewel/foundation/api-dump.txt index c2b75157cb941..12e04e1fbdc8a 100644 --- a/platform/jewel/foundation/api-dump.txt +++ b/platform/jewel/foundation/api-dump.txt @@ -700,9 +700,12 @@ f:org.jetbrains.jewel.foundation.theme.JewelThemeKt f:org.jetbrains.jewel.foundation.theme.ThemeColorPalette - sf:$stable:I - sf:Companion:org.jetbrains.jewel.foundation.theme.ThemeColorPalette$Companion +- sf:PALETTE_KEY_PREFIX:java.lang.String - (java.util.List,java.util.List,java.util.List,java.util.List,java.util.List,java.util.List,java.util.List,java.util.List,java.util.Map):V - f:blue-vNxB06k(I):J -- f:blueOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:blueOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:blueOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:blueOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color - equals(java.lang.Object):Z - f:getBlue():java.util.List - f:getGray():java.util.List @@ -714,21 +717,35 @@ f:org.jetbrains.jewel.foundation.theme.ThemeColorPalette - f:getTeal():java.util.List - f:getYellow():java.util.List - f:gray-vNxB06k(I):J -- f:grayOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:grayOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:grayOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:grayOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color - f:green-vNxB06k(I):J -- f:greenOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:greenOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:greenOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:greenOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color - hashCode():I - f:lookup-ijrfgN4(java.lang.String):androidx.compose.ui.graphics.Color - f:orange-vNxB06k(I):J -- f:orangeOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:orangeOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:orangeOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:orangeOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color - f:purple-vNxB06k(I):J -- f:purpleOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:purpleOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:purpleOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:purpleOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color - f:red-vNxB06k(I):J -- f:redOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:redOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:redOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:redOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color - f:teal-vNxB06k(I):J -- f:tealOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:tealOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:tealOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:tealOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color - f:yellow-vNxB06k(I):J -- f:yellowOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color +- f:yellowOrNull-6MYuD4A(I,I):androidx.compose.ui.graphics.Color +- bs:yellowOrNull-6MYuD4A$default(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,I,I,java.lang.Object):androidx.compose.ui.graphics.Color +- bf:yellowOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color f:org.jetbrains.jewel.foundation.theme.ThemeColorPalette$Companion - f:getEmpty():org.jetbrains.jewel.foundation.theme.ThemeColorPalette f:org.jetbrains.jewel.foundation.theme.ThemeDefinition diff --git a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/theme/ThemeColorPalette.kt b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/theme/ThemeColorPalette.kt index 09849afb3f65a..fc2e29c8ce7d3 100644 --- a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/theme/ThemeColorPalette.kt +++ b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/theme/ThemeColorPalette.kt @@ -5,7 +5,7 @@ import androidx.compose.ui.graphics.Color import org.jetbrains.jewel.foundation.GenerateDataFunctions private val colorKeyRegex: Regex - get() = "([a-z]+)(\\d+)".toRegex(RegexOption.IGNORE_CASE) + get() = "([a-z]+)(\\d+)(?:\\.(\\d+))?".toRegex(RegexOption.IGNORE_CASE) /** * A palette of colors provided by the theme. @@ -16,15 +16,15 @@ private val colorKeyRegex: Regex * number of colors in each list depends on the implementation in the LaF. It is therefore important to use the * `*OrNull` accessors to avoid [IndexOutOfBoundsException]s. * - * @property gray A list of gray colors, from lightest to darkest. - * @property blue A list of blue colors. - * @property green A list of green colors. - * @property red A list of red colors. - * @property yellow A list of yellow colors. - * @property orange A list of orange colors. - * @property purple A list of purple colors. - * @property teal A list of teal colors. - * @property rawMap A map of all colors in the palette, with their original keys. + * @property gray A list of gray colors with integer indices, from lightest to darkest. + * @property blue A list of blue colors with integer indices. + * @property green A list of green colors with integer indices. + * @property red A list of red colors with integer indices. + * @property yellow A list of yellow colors with integer indices. + * @property orange A list of orange colors with integer indices. + * @property purple A list of purple colors with integer indices. + * @property teal A list of teal colors with integer indices. + * @property rawMap A map of all colors in the palette, including fractional indices colors, with their original keys. */ @Suppress("MemberVisibilityCanBePrivate", "KDocUnresolvedReference") @Immutable @@ -68,8 +68,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun grayOrNull(index: Int): Color? = gray.getOrNull(index - 1) + /** + * Retrieves a gray color from the palette by its index and optional fractional part, or `null` if the color is not + * found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Gray1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun grayOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) gray.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Gray$index.$fractional"] + /** * Retrieves a blue color from the palette by its index. Note that this function is not safe to use and can throw an * [IndexOutOfBoundsException] at runtime if the Look and Feel does not provide a full palette. @@ -98,8 +113,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun blueOrNull(index: Int): Color? = blue.getOrNull(index - 1) + /** + * Retrieves a blue color from the palette by its index and optional fractional part, or `null` if the color is not + * found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Blue1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun blueOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) blue.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Blue$index.$fractional"] + /** * Retrieves a green color from the palette by its index. Note that this function is not safe to use and can throw * an [IndexOutOfBoundsException] at runtime if the Look and Feel does not provide a full palette. @@ -128,8 +158,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun greenOrNull(index: Int): Color? = green.getOrNull(index - 1) + /** + * Retrieves a green color from the palette by its index and optional fractional part, or `null` if the color is not + * found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Green1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun greenOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) green.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Green$index.$fractional"] + /** * Retrieves a red color from the palette by its index. Note that this function is not safe to use and can throw an * [IndexOutOfBoundsException] at runtime if the Look and Feel does not provide a full palette. @@ -158,8 +203,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun redOrNull(index: Int): Color? = red.getOrNull(index - 1) + /** + * Retrieves a red color from the palette by its index and optional fractional part, or `null` if the color is not + * found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Red1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun redOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) red.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Red$index.$fractional"] + /** * Retrieves a yellow color from the palette by its index. Note that this function is not safe to use and can throw * an [IndexOutOfBoundsException] at runtime if the Look and Feel does not provide a full palette. @@ -188,8 +248,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun yellowOrNull(index: Int): Color? = yellow.getOrNull(index - 1) + /** + * Retrieves a yellow color from the palette by its index and optional fractional part, or `null` if the color is + * not found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Yellow1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun yellowOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) yellow.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Yellow$index.$fractional"] + /** * Retrieves an orange color from the palette by its index. Note that this function is not safe to use and can throw * an [IndexOutOfBoundsException] at runtime if the Look and Feel does not provide a full palette. @@ -218,8 +293,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun orangeOrNull(index: Int): Color? = orange.getOrNull(index - 1) + /** + * Retrieves an orange color from the palette by its index and optional fractional part, or `null` if the color is + * not found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Orange1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun orangeOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) orange.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Orange$index.$fractional"] + /** * Retrieves a purple color from the palette by its index. Note that this function is not safe to use and can throw * an [IndexOutOfBoundsException] at runtime if the Look and Feel does not provide a full palette. @@ -248,8 +338,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun purpleOrNull(index: Int): Color? = purple.getOrNull(index - 1) + /** + * Retrieves a purple color from the palette by its index and optional fractional part, or `null` if the color is + * not found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Purple1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun purpleOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) purple.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Purple$index.$fractional"] + /** * Retrieves a teal color from the palette by its index. Note that this function is not safe to use and can throw an * [IndexOutOfBoundsException] at runtime if the Look and Feel does not provide a full palette. @@ -278,8 +383,23 @@ public class ThemeColorPalette( * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. * @return The [Color] at the specified index, or `null` if the index is out of bounds. */ + @Deprecated("Use variant with fractional parameter", level = DeprecationLevel.HIDDEN) public fun tealOrNull(index: Int): Color? = teal.getOrNull(index - 1) + /** + * Retrieves a teal color from the palette by its index and optional fractional part, or `null` if the color is not + * found. + * + * Palette indices start at 1; how many entries exist for a color depends on the Look and Feel. Some LaFs may only + * have a partial palette, or none at all. + * + * @param index The 1-based index of the color to retrieve. Only values of 1 and above are valid. + * @param fractional The optional fractional part of the index (e.g., 25 for Teal1.25). The default value is 0. + * @return The [Color] at the specified index, or `null` if the index is out of bounds or the color is not found. + */ + public fun tealOrNull(index: Int, fractional: Int = 0): Color? = + if (fractional == 0) teal.getOrNull(index - 1) else rawMap["$PALETTE_KEY_PREFIX.Teal$index.$fractional"] + /** * Looks up a color in the palette by its key. The key can be in the format "colorNameN" (e.g., "gray1", "blue12") * or a raw key from the Look and Feel theme. @@ -288,9 +408,10 @@ public class ThemeColorPalette( * @return The [Color] associated with the key, or `null` if the key is not found. */ public fun lookup(colorKey: String): Color? { - val result = colorKeyRegex.matchEntire(colorKey.trim()) + val result = colorKeyRegex.matchEntire(colorKey.trim().removePrefix("$PALETTE_KEY_PREFIX.")) val colorGroup = result?.groupValues?.getOrNull(1)?.lowercase() val colorIndex = result?.groupValues?.getOrNull(2)?.toIntOrNull() + val fractionalIndex = result?.groupValues?.getOrNull(3)?.toIntOrNull() ?: 0 if (colorGroup == null || colorIndex == null) { return rawMap[colorKey] @@ -298,14 +419,14 @@ public class ThemeColorPalette( return when (colorGroup) { "grey", - "gray" -> grayOrNull(colorIndex) - "blue" -> blueOrNull(colorIndex) - "green" -> greenOrNull(colorIndex) - "red" -> redOrNull(colorIndex) - "yellow" -> yellowOrNull(colorIndex) - "orange" -> orangeOrNull(colorIndex) - "purple" -> purpleOrNull(colorIndex) - "teal" -> tealOrNull(colorIndex) + "gray" -> grayOrNull(colorIndex, fractionalIndex) + "blue" -> blueOrNull(colorIndex, fractionalIndex) + "green" -> greenOrNull(colorIndex, fractionalIndex) + "red" -> redOrNull(colorIndex, fractionalIndex) + "yellow" -> yellowOrNull(colorIndex, fractionalIndex) + "orange" -> orangeOrNull(colorIndex, fractionalIndex) + "purple" -> purpleOrNull(colorIndex, fractionalIndex) + "teal" -> tealOrNull(colorIndex, fractionalIndex) else -> null } } @@ -357,6 +478,8 @@ public class ThemeColorPalette( } public companion object { + public const val PALETTE_KEY_PREFIX: String = "ColorPalette" + public val Empty: ThemeColorPalette = ThemeColorPalette( gray = emptyList(), diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/BridgeThemeColorPalette.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/BridgeThemeColorPalette.kt index 6f452fd6167c6..f63c1bb3c7ce5 100644 --- a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/BridgeThemeColorPalette.kt +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/BridgeThemeColorPalette.kt @@ -23,6 +23,15 @@ public fun ThemeColorPalette.Companion.readFromLaF(): ThemeColorPalette { val teal = readPaletteColors("Teal") val windowsPopupBorder = readPaletteColor("windowsPopupBorder") + val grayInt = gray.filterIntKeys() + val blueInt = blue.filterIntKeys() + val greenInt = green.filterIntKeys() + val redInt = red.filterIntKeys() + val yellowInt = yellow.filterIntKeys() + val orangeInt = orange.filterIntKeys() + val purpleInt = purple.filterIntKeys() + val tealInt = teal.filterIntKeys() + val rawMap = buildMap { putAll(gray) putAll(blue) @@ -36,41 +45,55 @@ public fun ThemeColorPalette.Companion.readFromLaF(): ThemeColorPalette { } return ThemeColorPalette( - gray = gray.values.toList(), - blue = blue.values.toList(), - green = green.values.toList(), - red = red.values.toList(), - yellow = yellow.values.toList(), - orange = orange.values.toList(), - purple = purple.values.toList(), - teal = teal.values.toList(), + gray = grayInt.values.toList(), + blue = blueInt.values.toList(), + green = greenInt.values.toList(), + red = redInt.values.toList(), + yellow = yellowInt.values.toList(), + orange = orangeInt.values.toList(), + purple = purpleInt.values.toList(), + teal = tealInt.values.toList(), rawMap = rawMap, ) } +private fun Map.filterIntKeys(): Map { + val intMap = TreeMap() + + for ((key, value) in this) { + val colorName = key.substringAfter("${ThemeColorPalette.PALETTE_KEY_PREFIX}.") + val colorNameWithoutIndex = colorName.takeWhile { !it.isDigit() } + val index = colorName.substring(colorNameWithoutIndex.length) + + if (index.toIntOrNull() != null) { + intMap[key] = value + } + } + return intMap +} + private fun readPaletteColors(colorName: String): Map { val defaults = uiDefaults val allKeys = defaults.keys - val colorNameKeyPrefix = "ColorPalette.$colorName" + val colorNameKeyPrefix = "${ThemeColorPalette.PALETTE_KEY_PREFIX}.$colorName" val colorNameKeyPrefixLength = colorNameKeyPrefix.length - val lastColorIndex = + val keys = allKeys .asSequence() .filterIsInstance(String::class.java) .filter { it.startsWith(colorNameKeyPrefix) } - .mapNotNull { + .filter { val afterName = it.substring(colorNameKeyPrefixLength) - afterName.toIntOrNull() + // Match integer or fractional color + afterName.matches("\\d+\\.\\d+".toRegex()) || afterName.toIntOrNull() != null } - .maxOrNull() ?: return TreeMap() return buildMap { - for (i in 1..lastColorIndex) { - val key = "$colorNameKeyPrefix$i" + for (key in keys) { val value = defaults[key] as? java.awt.Color if (value == null) { - logger.error("Unable to find color value for palette key '$colorNameKeyPrefix$i'") + logger.error("Unable to find color value for palette key '$key'") continue } @@ -81,6 +104,6 @@ private fun readPaletteColors(colorName: String): Map { private fun readPaletteColor(colorName: String): Color { val defaults = uiDefaults - val colorNameKey = "ColorPalette.$colorName" + val colorNameKey = "${ThemeColorPalette.PALETTE_KEY_PREFIX}.$colorName" return (defaults[colorNameKey] as? java.awt.Color)?.toComposeColor() ?: Color.Unspecified }