[JEWEL-1050] Create palette color accessors with default fallback

closes https://github.com/JetBrains/intellij-community/pull/3592

(cherry picked from commit 7a29e6682b6058144b20fb4e66ce9c904eaaffc2)


(cherry picked from commit 8e28ddc686bb188b0863643fb88491bb0a7ddbc6)

IJ-MR-220560

GitOrigin-RevId: 5c1e31f4f74311899eb555a275dac787a5ef8285
This commit is contained in:
Daniel Bertoldi
2026-09-07 22:29:10 +00:00
committed by intellij-monorepo-bot
parent 53328934b1
commit c4a14ca6e2
21 changed files with 2103 additions and 506 deletions
@@ -0,0 +1,52 @@
import com.ncorti.ktfmt.gradle.tasks.KtfmtBaseTask
import dev.detekt.gradle.Detekt
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.dokka.gradle.tasks.DokkaGenerateModuleTask
import org.jetbrains.dokka.gradle.tasks.DokkaGeneratePublicationTask
import org.jetbrains.jewel.buildlogic.metalava.GenerateMetalavaApiTask
import org.jetbrains.jewel.buildlogic.theme.DefaultColorPaletteGeneratorTask
import org.jetbrains.jewel.buildlogic.theme.PaletteSource
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile
class ColorPaletteGenerator(val name: String, project: Project) {
val themeFilePath = project.objects.property<String>()
val themeFile: Provider<RegularFile> = project.layout.file(themeFilePath.map { project.rootProject.file(it) })
val propertyName = project.objects.property<String>()
val isIslands = project.objects.property<Boolean>()
}
val extension = container<ColorPaletteGenerator> { ColorPaletteGenerator(it, project) }
extensions.add("intelliJColorPaletteGenerator", extension)
val targetDir: Provider<Directory> = project.provider { project.layout.projectDirectory.dir("generated/theme") }
val generateDefaultColorPalette =
tasks.register<DefaultColorPaletteGeneratorTask>("generateDefaultColorPalette") {
sources.set(
extension.map { entry ->
project.objects.newInstance<PaletteSource>().apply {
themeFile.set(entry.themeFile)
propertyName.set(entry.propertyName)
islands.set(entry.isIslands)
}
}
)
outputFile = targetDir.map { it.file("org/jetbrains/jewel/ui/theme/DefaultColorPalette.kt") }
}
tasks {
withType<BaseKotlinCompile> { dependsOn(generateDefaultColorPalette) }
withType<Detekt> { dependsOn(generateDefaultColorPalette) }
withType<DokkaTask> { dependsOn(generateDefaultColorPalette) }
withType<DokkaGenerateModuleTask> { dependsOn(generateDefaultColorPalette) }
withType<DokkaGeneratePublicationTask> { dependsOn(generateDefaultColorPalette) }
withType<Jar> { dependsOn(generateDefaultColorPalette) }
withType<GenerateMetalavaApiTask> { dependsOn(generateDefaultColorPalette) }
withType<KtfmtBaseTask> { dependsOn(generateDefaultColorPalette) }
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
the<KotlinJvmProjectExtension>().sourceSets["main"].kotlin.srcDir(targetDir)
}
@@ -1,5 +1,6 @@
package org.jetbrains.jewel.buildlogic.theme
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
@@ -11,9 +12,6 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
internal object IntUiThemeDescriptorReader {
private val colorGroups = setOf("Gray", "Blue", "Green", "Red", "Yellow", "Orange", "Purple", "Teal")
private val colorClassName = ClassName("androidx.compose.ui.graphics", "Color")
fun readThemeFrom(
themeDescriptor: IntellijThemeDescriptor,
className: ClassName,
@@ -27,8 +25,6 @@ internal object IntUiThemeDescriptorReader {
addFileComment("*** DO NOT EDIT MANUALLY! Use generateThemes task to update. ***\n")
addFileComment("Source: $sourcePath")
addImport(colorClassName.packageName, colorClassName.simpleName)
addType(
TypeSpec.objectBuilder(className)
.apply {
@@ -48,7 +44,7 @@ internal object IntUiThemeDescriptorReader {
.build()
)
readColors(themeDescriptor.colors)
addColorsProperty(themeDescriptor.dark)
readIcons(themeDescriptor)
}
.build()
@@ -56,39 +52,20 @@ internal object IntUiThemeDescriptorReader {
}
.build()
private val defaultColorPaletteClassName = ClassName("org.jetbrains.jewel.ui.theme", "DefaultColorPalette")
private val colorPaletteClassName = ClassName.bestGuess("org.jetbrains.jewel.foundation.theme.ThemeColorPalette")
private val iconDataClassName = ClassName.bestGuess("org.jetbrains.jewel.foundation.theme.ThemeIconData")
private val internalJewelApiClassName = ClassName.bestGuess("org.jetbrains.jewel.foundation.InternalJewelApi")
private fun TypeSpec.Builder.readColors(colors: Map<String, String>) {
val colorGroups =
colors.entries
.groupBy { it.key.replace("""\d+""".toRegex(), "") }
.filterKeys { colorGroups.contains(it) }
.map { (groupName, colors) ->
// We assume color lists are in the same order as in colorGroups
colors
.map { (_, value) ->
val colorHexString = value.replace("#", "0xFF")
CodeBlock.of("Color(%L)", colorHexString)
}
.joinToCode(
prefix = "\n${groupName.lowercase()} = listOf(\n",
separator = ",\n",
suffix = "\n)",
)
}
val rawMap =
colors
.map { (key, value) ->
val colorHexString = value.replace("#", "0xFF")
CodeBlock.of("%S to Color(%L)", key, colorHexString)
}
.joinToCode(prefix = "\nrawMap = mapOf(\n", separator = ",\n", suffix = "\n)")
private fun TypeSpec.Builder.addColorsProperty(isDark: Boolean) {
addProperty(
PropertySpec.builder("colors", colorPaletteClassName, KModifier.OVERRIDE)
.initializer("ThemeColorPalette(%L,\n%L,\nisIslands = false\n)", colorGroups.joinToCode(","), rawMap)
.addAnnotation(
AnnotationSpec.builder(ClassName("kotlin", "OptIn"))
.addMember("%T::class", internalJewelApiClassName)
.build()
)
.initializer("%T.%L", defaultColorPaletteClassName, if (isDark) "Dark" else "Light")
.build()
)
}
@@ -0,0 +1,157 @@
package org.jetbrains.jewel.buildlogic.theme
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.joinToCode
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
abstract class PaletteSource {
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val themeFile: RegularFileProperty
@get:Input
abstract val propertyName: Property<String>
@get:Input
abstract val islands: Property<Boolean>
}
abstract class DefaultColorPaletteGeneratorTask : DefaultTask() {
@get:Nested
abstract val sources: ListProperty<PaletteSource>
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun generate() {
val file = DefaultColorPaletteReader.readPaletteFrom(sources.get(), readIjpMajor())
outputFile.get().asFile.bufferedWriter().use { file.writeTo(it) }
}
private fun readIjpMajor(): String {
val buildNumberFile = project.rootDir.resolve("../../build.txt")
check(buildNumberFile.isFile) { "The build.txt file must exist in the community root" }
val buildNumber = buildNumberFile.readText().trim()
check(validateBuildNumber(buildNumber)) { "The build number in build.txt does not seem valid: '$buildNumber'" }
return buildNumber.substringBefore('.')
}
private fun validateBuildNumber(buildNumber: String): Boolean {
// Examples:
// * 253.1234.567
// * 241.SNAPSHOT
// * 253.28294.SNAPSHOT
if (buildNumber.isBlank()) return false
if (buildNumber.length < 5) return false
if (buildNumber.take(3).toIntOrNull()?.takeIf { it > 240 } == null) return false
if (buildNumber[3] != '.') return false
val afterDot = buildNumber.drop(4)
return afterDot == "SNAPSHOT" ||
afterDot.all { it.isDigit() || it == '.' } ||
(afterDot.endsWith(".SNAPSHOT") && afterDot.removeSuffix(".SNAPSHOT").all { it.isDigit() || it == '.' })
}
}
internal object DefaultColorPaletteReader {
private val json = Json { ignoreUnknownKeys = true }
private val colorClassName = ClassName("androidx.compose.ui.graphics", "Color")
private val colorPaletteClassName = ClassName.bestGuess("org.jetbrains.jewel.foundation.theme.ThemeColorPalette")
private val defaultColorPaletteClassName = ClassName("org.jetbrains.jewel.ui.theme", "DefaultColorPalette")
private val internalJewelApiClassName = ClassName.bestGuess("org.jetbrains.jewel.foundation.InternalJewelApi")
private val apiStatusInternalClassName = ClassName("org.jetbrains.annotations", "ApiStatus", "Internal")
private val colorGroups = setOf("Gray", "Blue", "Green", "Red", "Yellow", "Orange", "Purple", "Teal")
fun readPaletteFrom(sources: List<PaletteSource>, ijpMajor: String): FileSpec =
FileSpec.builder(defaultColorPaletteClassName)
.apply {
addFileComment("Generated by the Jewel Int UI Color Palette Generator for IJP $ijpMajor\n")
addFileComment("*** DO NOT EDIT MANUALLY! Use intelliJColorPaletteGenerator task to update. ***\n")
addImport(colorClassName.packageName, colorClassName.simpleName)
addType(
TypeSpec.objectBuilder(defaultColorPaletteClassName)
.addAnnotation(
AnnotationSpec.builder(Suppress::class).addMember("%S", "LargeClass").build()
)
.addAnnotation(apiStatusInternalClassName)
.addAnnotation(internalJewelApiClassName)
.apply { sources.forEach { source -> addPaletteProperty(source) } }
.build()
)
}
.build()
private fun TypeSpec.Builder.addPaletteProperty(source: PaletteSource) {
val colors =
source.themeFile.get().asFile.inputStream().use { json.decodeFromStream<IntelliJColors>(it) }.colors
addProperty(
PropertySpec.builder(source.propertyName.get(), colorPaletteClassName)
.initializer(
"ThemeColorPalette(%L,\n%L,\nisIslands = %L\n)",
buildColorGroupBlocks(colors).joinToCode(","),
buildRawMapBlock(colors),
source.islands.get(),
)
.build()
)
}
/** Groups colors by family (e.g. "Gray1"/"gray-160" both group under "Gray"), sorted ascending by index. */
private fun buildColorGroupBlocks(colors: Map<String, String>): List<CodeBlock> =
colors.entries
.groupBy { it.key.replace("""-?\d+$""".toRegex(), "").replaceFirstChar { c -> c.uppercase() } }
.filterKeys { colorGroups.contains(it) }
.mapValues { (_, entries) -> entries.sortedBy { it.key.takeLastWhile(Char::isDigit).toInt() } }
.map { (groupName, entries) ->
entries
.map { (_, value) -> CodeBlock.of("Color(%L)", resolveHex(value, colors).replace("#", "0xFF")) }
.joinToCode(prefix = "\n${groupName.lowercase()} = listOf(\n", separator = ",\n", suffix = "\n)")
}
private fun buildRawMapBlock(colors: Map<String, String>): CodeBlock =
colors.entries
.map { (key, value) -> CodeBlock.of("%S to Color(%L)", key, resolveHex(value, colors).replace("#", "0xFF")) }
.joinToCode(prefix = "\nrawMap = mapOf(\n", separator = ",\n", suffix = "\n)")
/**
* Some theme JSONs (notably the Islands ones) define colors as aliases pointing at another key instead of a
* literal hex value (e.g. "toggle-off-bg": "gray-160"), sometimes several levels deep. Follows the chain down to
* an actual hex value.
*/
private fun resolveHex(value: String, colors: Map<String, String>, depth: Int = 0): String {
if (value.startsWith("#")) return value
check(depth < 10) { "Color alias chain too deep (possible cycle) resolving '$value'" }
val aliased = colors[value] ?: error("Unresolved color alias '$value'")
return resolveHex(aliased, colors, depth + 1)
}
}
@Serializable
data class IntelliJColors(
val colors: Map<String, String> = emptyMap(),
)
+1
View File
@@ -788,6 +788,7 @@ f:org.jetbrains.jewel.foundation.theme.ThemeColorPalette
- f:green-vNxB06k(I):J
- f:greenOrNull-ijrfgN4(I):androidx.compose.ui.graphics.Color
- hashCode():I
- f:isIslands():Z
- 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
@@ -987,6 +987,7 @@ package org.jetbrains.jewel.foundation.theme {
method public androidx.compose.ui.graphics.Color? grayOrNull(int index);
method @Deprecated public long green(int index);
method public androidx.compose.ui.graphics.Color? greenOrNull(int index);
method public boolean isIslands();
method public androidx.compose.ui.graphics.Color? lookup(String colorKey);
method @Deprecated public long orange(int index);
method public androidx.compose.ui.graphics.Color? orangeOrNull(int index);
@@ -1001,6 +1002,7 @@ package org.jetbrains.jewel.foundation.theme {
property public java.util.List<androidx.compose.ui.graphics.Color> blue;
property public java.util.List<androidx.compose.ui.graphics.Color> gray;
property public java.util.List<androidx.compose.ui.graphics.Color> green;
property public boolean isIslands;
property public java.util.List<androidx.compose.ui.graphics.Color> orange;
property public java.util.List<androidx.compose.ui.graphics.Color> purple;
property public java.util.Map<java.lang.String,androidx.compose.ui.graphics.Color> rawMap;
@@ -955,6 +955,7 @@ package org.jetbrains.jewel.foundation.theme {
method public androidx.compose.ui.graphics.Color? grayOrNull(int index);
method @Deprecated public long green(int index);
method public androidx.compose.ui.graphics.Color? greenOrNull(int index);
method public boolean isIslands();
method public androidx.compose.ui.graphics.Color? lookup(String colorKey);
method @Deprecated public long orange(int index);
method public androidx.compose.ui.graphics.Color? orangeOrNull(int index);
@@ -969,6 +970,7 @@ package org.jetbrains.jewel.foundation.theme {
property public java.util.List<androidx.compose.ui.graphics.Color> blue;
property public java.util.List<androidx.compose.ui.graphics.Color> gray;
property public java.util.List<androidx.compose.ui.graphics.Color> green;
property public boolean isIslands;
property public java.util.List<androidx.compose.ui.graphics.Color> orange;
property public java.util.List<androidx.compose.ui.graphics.Color> purple;
property public java.util.Map<java.lang.String,androidx.compose.ui.graphics.Color> rawMap;
@@ -2,6 +2,7 @@ package org.jetbrains.jewel.foundation.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import org.jetbrains.jewel.foundation.GenerateDataFunctions
private val nonIslandsColorKeyRegex: Regex
@@ -28,6 +29,7 @@ private val islandsColorKeyRegex: Regex
* @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 isIslands Whether the palette uses the Islands theme color key format (e.g., `gray-10` instead of `gray1`).
*/
@Suppress("MemberVisibilityCanBePrivate", "KDocUnresolvedReference")
@Immutable
@@ -42,7 +44,7 @@ public class ThemeColorPalette(
public val purple: List<Color>,
public val teal: List<Color>,
public val rawMap: Map<String, Color>,
private val isIslands: Boolean,
public val isIslands: Boolean,
) {
@Suppress("DEPRECATION")
@Deprecated("Use the constructor with isIslands parameter", level = DeprecationLevel.HIDDEN)
@@ -325,12 +327,16 @@ public class ThemeColorPalette(
*/
public fun tealOrNull(index: Int): Color? = getByIndexOrNull(teal, index)
private fun getByIndexOrNull(list: List<Color>, index: Int): Color? =
if (isIslands) {
if (index % 10 != 0) null else list.getOrNull(index / 10 - 1)
} else {
list.getOrNull(index - 1)
}
// Treat Color.Unspecified as null in index gaps in a sparse palette
private fun getByIndexOrNull(list: List<Color>, index: Int): Color? {
val resolved =
if (isIslands) {
if (index % 10 != 0) return null else list.getOrNull(index / 10 - 1)
} else {
list.getOrNull(index - 1)
}
return resolved?.takeIf { it.isSpecified }
}
/**
* Looks up a color in the palette by its key. The key can be in the format "colorNameN" (e.g., "gray1", "blue12"),
@@ -0,0 +1,58 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.foundation.theme
import androidx.compose.ui.graphics.Color
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
public class ThemeColorPaletteTest {
@Test
public fun `grayOrNull returns the color at the given index`() {
val palette = palette(gray = listOf(Color.Red, Color.Green, Color.Blue))
assertEquals(Color.Red, palette.grayOrNull(1)!!)
assertEquals(Color.Blue, palette.grayOrNull(3)!!)
}
@Test
public fun `grayOrNull returns null when the index is out of bounds`() {
val palette = palette(gray = listOf(Color.Red))
assertNull(palette.grayOrNull(2))
}
@Test
public fun `grayOrNull treats an index gap as absent, not the next real color`() {
// Simulates a partial 3rd-party LaF that declares Gray1 and Gray3 but not Gray2 — the gap must be padded
// with Color.Unspecified (see BridgeThemeColorPalette.readFromLaF), not simply omitted from the list.
val palette = palette(gray = listOf(Color.Red, Color.Unspecified, Color.Blue))
assertEquals(Color.Red, palette.grayOrNull(1)!!)
assertNull(palette.grayOrNull(2))
assertEquals(Color.Blue, palette.grayOrNull(3)!!)
}
@Test
public fun `grayOrNull treats an index gap as absent for Islands indexing too`() {
val palette = palette(gray = listOf(Color.Red, Color.Unspecified, Color.Blue), isIslands = true)
assertEquals(Color.Red, palette.grayOrNull(10)!!)
assertNull(palette.grayOrNull(20))
assertEquals(Color.Blue, palette.grayOrNull(30)!!)
}
private fun palette(gray: List<Color> = emptyList(), isIslands: Boolean = false): ThemeColorPalette =
ThemeColorPalette(
gray = gray,
blue = emptyList(),
green = emptyList(),
red = emptyList(),
yellow = emptyList(),
orange = emptyList(),
purple = emptyList(),
teal = emptyList(),
rawMap = emptyMap(),
isIslands = isIslands,
)
}
@@ -4,7 +4,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import com.intellij.openapi.diagnostic.Logger
import com.intellij.ui.IslandsState
import java.util.TreeMap
import javax.swing.UIDefaults
import org.jetbrains.jewel.bridge.toComposeColor
import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
@@ -27,32 +26,38 @@ public fun ThemeColorPalette.Companion.readFromLaF(): ThemeColorPalette {
val windowsPopupBorder = readPaletteColor("windowsPopupBorder")
val rawMap = buildMap {
putAll(gray)
putAll(blue)
putAll(green)
putAll(red)
putAll(yellow)
putAll(orange)
putAll(purple)
putAll(teal)
putAll(gray.presentEntries)
putAll(blue.presentEntries)
putAll(green.presentEntries)
putAll(red.presentEntries)
putAll(yellow.presentEntries)
putAll(orange.presentEntries)
putAll(purple.presentEntries)
putAll(teal.presentEntries)
if (windowsPopupBorder.isSpecified) put("windowsPopupBorder", windowsPopupBorder)
}
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 = gray.list,
blue = blue.list,
green = green.list,
red = red.list,
yellow = yellow.list,
orange = orange.list,
purple = purple.list,
teal = teal.list,
rawMap = rawMap,
isIslands = isIslands,
)
}
private fun readPaletteColors(colorName: String, isIslands: Boolean): Map<String, Color> {
/**
* [list] is dense (gaps padded with [Color.Unspecified]) while [presentEntries] holds only keys the LaF actually
* declared.
*/
private class FamilyColors(val list: List<Color>, val presentEntries: Map<String, Color>)
private fun readPaletteColors(colorName: String, isIslands: Boolean): FamilyColors {
val defaults: UIDefaults = uiDefaults
val allKeys: Set<Any> = defaults.keys
val colorNameKeyPrefix = if (isIslands) "ColorPalette.${colorName.lowercase()}-" else "ColorPalette.$colorName"
@@ -66,8 +71,7 @@ private fun readPaletteColors(colorName: String, isIslands: Boolean): Map<String
val afterName = it.substring(colorNameKeyPrefixLength)
afterName.toIntOrNull()
}
.maxOrNull()
if (lastColorIndex == null) return TreeMap()
.maxOrNull() ?: return FamilyColors(emptyList(), emptyMap())
val indices =
if (isIslands) {
@@ -76,18 +80,20 @@ private fun readPaletteColors(colorName: String, isIslands: Boolean): Map<String
(1..lastColorIndex)
}
return buildMap {
for (i in indices) {
val presentEntries = mutableMapOf<String, Color>()
val list =
indices.map { i ->
val key = "$colorNameKeyPrefix$i"
val value = defaults[key] as? java.awt.Color
if (value == null) {
logger.error("Unable to find color value for palette key '$colorNameKeyPrefix$i'")
continue
Color.Unspecified
} else {
value.toComposeColor().also { presentEntries[key] = it }
}
put(key, value.toComposeColor())
}
}
return FamilyColors(list, presentEntries)
}
private fun readPaletteColor(colorName: String): Color {
@@ -3,243 +3,21 @@
// Source: ../../platform/platform-resources/src/themes/expUI/expUI_dark.theme.json
package org.jetbrains.jewel.intui.core.theme
import androidx.compose.ui.graphics.Color
import kotlin.Boolean
import kotlin.OptIn
import kotlin.String
import org.jetbrains.jewel.foundation.InternalJewelApi
import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
import org.jetbrains.jewel.foundation.theme.ThemeDescriptor
import org.jetbrains.jewel.foundation.theme.ThemeIconData
import org.jetbrains.jewel.ui.theme.DefaultColorPalette
public object IntUiDarkTheme : ThemeDescriptor {
override val isDark: Boolean = true
override val name: String = "Dark (Int UI)"
override val colors: ThemeColorPalette =
ThemeColorPalette(
gray =
listOf(
Color(0xFF1E1F22),
Color(0xFF2B2D30),
Color(0xFF393B40),
Color(0xFF43454A),
Color(0xFF4E5157),
Color(0xFF5A5D63),
Color(0xFF6F737A),
Color(0xFF868A91),
Color(0xFF9DA0A8),
Color(0xFFB4B8BF),
Color(0xFFCED0D6),
Color(0xFFDFE1E5),
Color(0xFFF0F1F2),
Color(0xFFFFFFFF),
),
blue =
listOf(
Color(0xFF25324D),
Color(0xFF2E436E),
Color(0xFF35538F),
Color(0xFF375FAD),
Color(0xFF366ACE),
Color(0xFF3574F0),
Color(0xFF467FF2),
Color(0xFF548AF7),
Color(0xFF6B9BFA),
Color(0xFF83ACFC),
Color(0xFF99BBFF),
Color(0xFFB5CEFF),
Color(0xFFD1E0FF),
),
green =
listOf(
Color(0xFF253627),
Color(0xFF273828),
Color(0xFF375239),
Color(0xFF436946),
Color(0xFF4E8052),
Color(0xFF57965C),
Color(0xFF5FAD65),
Color(0xFF73BD79),
Color(0xFF89CC8E),
Color(0xFFA0DBA5),
Color(0xFFB9EBBD),
Color(0xFFD4FAD7),
),
yellow =
listOf(
Color(0xFF3D3223),
Color(0xFF5E4D33),
Color(0xFF826A41),
Color(0xFF9E814A),
Color(0xFFBA9752),
Color(0xFFD6AE58),
Color(0xFFF2C55C),
Color(0xFFF5D273),
Color(0xFFF7DE8B),
Color(0xFFFCEBA4),
Color(0xFFFFF6BD),
),
red =
listOf(
Color(0xFF402929),
Color(0xFF472B2B),
Color(0xFF5E3838),
Color(0xFF7A4343),
Color(0xFF9C4E4E),
Color(0xFFBD5757),
Color(0xFFDB5C5C),
Color(0xFFE37774),
Color(0xFFEB938D),
Color(0xFFF2B1AA),
Color(0xFFF7CCC6),
Color(0xFFFAE3DE),
),
orange =
listOf(
Color(0xFF45322B),
Color(0xFF614438),
Color(0xFF825845),
Color(0xFFA36B4E),
Color(0xFFC27A53),
Color(0xFFE08855),
Color(0xFFE5986C),
Color(0xFFF0AC81),
Color(0xFFF5BD98),
Color(0xFFFACEAF),
Color(0xFFFFDFC7),
),
purple =
listOf(
Color(0xFF2F2936),
Color(0xFF3B3147),
Color(0xFF433358),
Color(0xFF583D7A),
Color(0xFF6C469C),
Color(0xFF8150BE),
Color(0xFF955AE0),
Color(0xFFA571E6),
Color(0xFFB589EC),
Color(0xFFC4A0F3),
Color(0xFFD4B8F9),
Color(0xFFE4CEFF),
),
teal =
listOf(
Color(0xFF1D3838),
Color(0xFF1D3D3B),
Color(0xFF1E4D4A),
Color(0xFF20635D),
Color(0xFF21786F),
Color(0xFF238E82),
Color(0xFF24A394),
Color(0xFF42B1A4),
Color(0xFF60C0B5),
Color(0xFF7DCEC5),
Color(0xFF9BDDD6),
Color(0xFFB9EBE6),
),
rawMap =
mapOf(
"Gray1" to Color(0xFF1E1F22),
"Gray2" to Color(0xFF2B2D30),
"Gray3" to Color(0xFF393B40),
"Gray4" to Color(0xFF43454A),
"Gray5" to Color(0xFF4E5157),
"Gray6" to Color(0xFF5A5D63),
"Gray7" to Color(0xFF6F737A),
"Gray8" to Color(0xFF868A91),
"Gray9" to Color(0xFF9DA0A8),
"Gray10" to Color(0xFFB4B8BF),
"Gray11" to Color(0xFFCED0D6),
"Gray12" to Color(0xFFDFE1E5),
"Gray13" to Color(0xFFF0F1F2),
"Gray14" to Color(0xFFFFFFFF),
"Blue1" to Color(0xFF25324D),
"Blue2" to Color(0xFF2E436E),
"Blue3" to Color(0xFF35538F),
"Blue4" to Color(0xFF375FAD),
"Blue5" to Color(0xFF366ACE),
"Blue6" to Color(0xFF3574F0),
"Blue7" to Color(0xFF467FF2),
"Blue8" to Color(0xFF548AF7),
"Blue9" to Color(0xFF6B9BFA),
"Blue10" to Color(0xFF83ACFC),
"Blue11" to Color(0xFF99BBFF),
"Blue12" to Color(0xFFB5CEFF),
"Blue13" to Color(0xFFD1E0FF),
"Green1" to Color(0xFF253627),
"Green2" to Color(0xFF273828),
"Green3" to Color(0xFF375239),
"Green4" to Color(0xFF436946),
"Green5" to Color(0xFF4E8052),
"Green6" to Color(0xFF57965C),
"Green7" to Color(0xFF5FAD65),
"Green8" to Color(0xFF73BD79),
"Green9" to Color(0xFF89CC8E),
"Green10" to Color(0xFFA0DBA5),
"Green11" to Color(0xFFB9EBBD),
"Green12" to Color(0xFFD4FAD7),
"Yellow1" to Color(0xFF3D3223),
"Yellow2" to Color(0xFF5E4D33),
"Yellow3" to Color(0xFF826A41),
"Yellow4" to Color(0xFF9E814A),
"Yellow5" to Color(0xFFBA9752),
"Yellow6" to Color(0xFFD6AE58),
"Yellow7" to Color(0xFFF2C55C),
"Yellow8" to Color(0xFFF5D273),
"Yellow9" to Color(0xFFF7DE8B),
"Yellow10" to Color(0xFFFCEBA4),
"Yellow11" to Color(0xFFFFF6BD),
"Red1" to Color(0xFF402929),
"Red2" to Color(0xFF472B2B),
"Red3" to Color(0xFF5E3838),
"Red4" to Color(0xFF7A4343),
"Red5" to Color(0xFF9C4E4E),
"Red6" to Color(0xFFBD5757),
"Red7" to Color(0xFFDB5C5C),
"Red8" to Color(0xFFE37774),
"Red9" to Color(0xFFEB938D),
"Red10" to Color(0xFFF2B1AA),
"Red11" to Color(0xFFF7CCC6),
"Red12" to Color(0xFFFAE3DE),
"Orange1" to Color(0xFF45322B),
"Orange2" to Color(0xFF614438),
"Orange3" to Color(0xFF825845),
"Orange4" to Color(0xFFA36B4E),
"Orange5" to Color(0xFFC27A53),
"Orange6" to Color(0xFFE08855),
"Orange7" to Color(0xFFE5986C),
"Orange8" to Color(0xFFF0AC81),
"Orange9" to Color(0xFFF5BD98),
"Orange10" to Color(0xFFFACEAF),
"Orange11" to Color(0xFFFFDFC7),
"Purple1" to Color(0xFF2F2936),
"Purple2" to Color(0xFF3B3147),
"Purple3" to Color(0xFF433358),
"Purple4" to Color(0xFF583D7A),
"Purple5" to Color(0xFF6C469C),
"Purple6" to Color(0xFF8150BE),
"Purple7" to Color(0xFF955AE0),
"Purple8" to Color(0xFFA571E6),
"Purple9" to Color(0xFFB589EC),
"Purple10" to Color(0xFFC4A0F3),
"Purple11" to Color(0xFFD4B8F9),
"Purple12" to Color(0xFFE4CEFF),
"Teal1" to Color(0xFF1D3838),
"Teal2" to Color(0xFF1D3D3B),
"Teal3" to Color(0xFF1E4D4A),
"Teal4" to Color(0xFF20635D),
"Teal5" to Color(0xFF21786F),
"Teal6" to Color(0xFF238E82),
"Teal7" to Color(0xFF24A394),
"Teal8" to Color(0xFF42B1A4),
"Teal9" to Color(0xFF60C0B5),
"Teal10" to Color(0xFF7DCEC5),
"Teal11" to Color(0xFF9BDDD6),
"Teal12" to Color(0xFFB9EBE6),
),
isIslands = false,
)
@OptIn(InternalJewelApi::class) override val colors: ThemeColorPalette = DefaultColorPalette.Dark
override val iconData: ThemeIconData =
ThemeIconData(
@@ -3,226 +3,21 @@
// Source: ../../platform/platform-resources/src/themes/expUI/expUI_light.theme.json
package org.jetbrains.jewel.intui.core.theme
import androidx.compose.ui.graphics.Color
import kotlin.Boolean
import kotlin.OptIn
import kotlin.String
import org.jetbrains.jewel.foundation.InternalJewelApi
import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
import org.jetbrains.jewel.foundation.theme.ThemeDescriptor
import org.jetbrains.jewel.foundation.theme.ThemeIconData
import org.jetbrains.jewel.ui.theme.DefaultColorPalette
public object IntUiLightTheme : ThemeDescriptor {
override val isDark: Boolean = false
override val name: String = "Light (Int UI)"
override val colors: ThemeColorPalette =
ThemeColorPalette(
gray =
listOf(
Color(0xFF000000),
Color(0xFF27282E),
Color(0xFF383A42),
Color(0xFF494B57),
Color(0xFF5A5D6B),
Color(0xFF6C707E),
Color(0xFF818594),
Color(0xFFA8ADBD),
Color(0xFFC9CCD6),
Color(0xFFD3D5DB),
Color(0xFFDFE1E5),
Color(0xFFEBECF0),
Color(0xFFF7F8FA),
Color(0xFFFFFFFF),
),
blue =
listOf(
Color(0xFF2E55A3),
Color(0xFF315FBD),
Color(0xFF3369D6),
Color(0xFF3574F0),
Color(0xFF4682FA),
Color(0xFF588CF3),
Color(0xFF709CF5),
Color(0xFF88ADF7),
Color(0xFFA0BDF8),
Color(0xFFC2D6FC),
Color(0xFFD4E2FF),
Color(0xFFEDF3FF),
Color(0xFFF5F8FE),
),
green =
listOf(
Color(0xFF1E6B33),
Color(0xFF1F7536),
Color(0xFF1F8039),
Color(0xFF208A3C),
Color(0xFF369650),
Color(0xFF55A76A),
Color(0xFF89C398),
Color(0xFFAFDBB8),
Color(0xFFC5E5CC),
Color(0xFFE3F7E7),
Color(0xFFF2FCF3),
),
yellow =
listOf(
Color(0xFFA46704),
Color(0xFFC27D04),
Color(0xFFDF9303),
Color(0xFFFFAF0F),
Color(0xFFFDBD3D),
Color(0xFFFED277),
Color(0xFFFEE6B1),
Color(0xFFFFF1D1),
Color(0xFFFFF5DB),
Color(0xFFFFFAEB),
),
red =
listOf(
Color(0xFFAD2B38),
Color(0xFFBC303E),
Color(0xFFCC3645),
Color(0xFFDB3B4B),
Color(0xFFE55765),
Color(0xFFE46A76),
Color(0xFFED99A1),
Color(0xFFF2B6BB),
Color(0xFFFAD4D8),
Color(0xFFFFEBEC),
Color(0xFFFFF2F3),
Color(0xFFFFF7F7),
),
orange =
listOf(
Color(0xFFA14916),
Color(0xFFB85516),
Color(0xFFCE6117),
Color(0xFFE56D17),
Color(0xFFEC8F4C),
Color(0xFFF2B181),
Color(0xFFF9D2B6),
Color(0xFFFFEFE3),
Color(0xFFFFF4EB),
),
teal =
listOf(
Color(0xFF096A6E),
Color(0xFF077A7F),
Color(0xFF058B90),
Color(0xFF039BA1),
Color(0xFF3FB3B8),
Color(0xFF7BCCCF),
Color(0xFFB6E4E5),
Color(0xFFDAF4F5),
Color(0xFFF2FCFC),
),
purple =
listOf(
Color(0xFF55339C),
Color(0xFF643CB8),
Color(0xFF7444D4),
Color(0xFF834DF0),
Color(0xFFA177F4),
Color(0xFFBFA1F8),
Color(0xFFDCCBFB),
Color(0xFFEFE5FF),
Color(0xFFF5EDFF),
Color(0xFFFAF5FF),
),
rawMap =
mapOf(
"Gray1" to Color(0xFF000000),
"Gray2" to Color(0xFF27282E),
"Gray3" to Color(0xFF383A42),
"Gray4" to Color(0xFF494B57),
"Gray5" to Color(0xFF5A5D6B),
"Gray6" to Color(0xFF6C707E),
"Gray7" to Color(0xFF818594),
"Gray8" to Color(0xFFA8ADBD),
"Gray9" to Color(0xFFC9CCD6),
"Gray10" to Color(0xFFD3D5DB),
"Gray11" to Color(0xFFDFE1E5),
"Gray12" to Color(0xFFEBECF0),
"Gray13" to Color(0xFFF7F8FA),
"Gray14" to Color(0xFFFFFFFF),
"windowsPopupBorder" to Color(0xFFB9BDC9),
"Blue1" to Color(0xFF2E55A3),
"Blue2" to Color(0xFF315FBD),
"Blue3" to Color(0xFF3369D6),
"Blue4" to Color(0xFF3574F0),
"Blue5" to Color(0xFF4682FA),
"Blue6" to Color(0xFF588CF3),
"Blue7" to Color(0xFF709CF5),
"Blue8" to Color(0xFF88ADF7),
"Blue9" to Color(0xFFA0BDF8),
"Blue10" to Color(0xFFC2D6FC),
"Blue11" to Color(0xFFD4E2FF),
"Blue12" to Color(0xFFEDF3FF),
"Blue13" to Color(0xFFF5F8FE),
"Green1" to Color(0xFF1E6B33),
"Green2" to Color(0xFF1F7536),
"Green3" to Color(0xFF1F8039),
"Green4" to Color(0xFF208A3C),
"Green5" to Color(0xFF369650),
"Green6" to Color(0xFF55A76A),
"Green7" to Color(0xFF89C398),
"Green8" to Color(0xFFAFDBB8),
"Green9" to Color(0xFFC5E5CC),
"Green10" to Color(0xFFE3F7E7),
"Green11" to Color(0xFFF2FCF3),
"Yellow1" to Color(0xFFA46704),
"Yellow2" to Color(0xFFC27D04),
"Yellow3" to Color(0xFFDF9303),
"Yellow4" to Color(0xFFFFAF0F),
"Yellow5" to Color(0xFFFDBD3D),
"Yellow6" to Color(0xFFFED277),
"Yellow7" to Color(0xFFFEE6B1),
"Yellow8" to Color(0xFFFFF1D1),
"Yellow9" to Color(0xFFFFF5DB),
"Yellow10" to Color(0xFFFFFAEB),
"Red1" to Color(0xFFAD2B38),
"Red2" to Color(0xFFBC303E),
"Red3" to Color(0xFFCC3645),
"Red4" to Color(0xFFDB3B4B),
"Red5" to Color(0xFFE55765),
"Red6" to Color(0xFFE46A76),
"Red7" to Color(0xFFED99A1),
"Red8" to Color(0xFFF2B6BB),
"Red9" to Color(0xFFFAD4D8),
"Red10" to Color(0xFFFFEBEC),
"Red11" to Color(0xFFFFF2F3),
"Red12" to Color(0xFFFFF7F7),
"Orange1" to Color(0xFFA14916),
"Orange2" to Color(0xFFB85516),
"Orange3" to Color(0xFFCE6117),
"Orange4" to Color(0xFFE56D17),
"Orange5" to Color(0xFFEC8F4C),
"Orange6" to Color(0xFFF2B181),
"Orange7" to Color(0xFFF9D2B6),
"Orange8" to Color(0xFFFFEFE3),
"Orange9" to Color(0xFFFFF4EB),
"Teal1" to Color(0xFF096A6E),
"Teal2" to Color(0xFF077A7F),
"Teal3" to Color(0xFF058B90),
"Teal4" to Color(0xFF039BA1),
"Teal5" to Color(0xFF3FB3B8),
"Teal6" to Color(0xFF7BCCCF),
"Teal7" to Color(0xFFB6E4E5),
"Teal8" to Color(0xFFDAF4F5),
"Teal9" to Color(0xFFF2FCFC),
"Purple1" to Color(0xFF55339C),
"Purple2" to Color(0xFF643CB8),
"Purple3" to Color(0xFF7444D4),
"Purple4" to Color(0xFF834DF0),
"Purple5" to Color(0xFFA177F4),
"Purple6" to Color(0xFFBFA1F8),
"Purple7" to Color(0xFFDCCBFB),
"Purple8" to Color(0xFFEFE5FF),
"Purple9" to Color(0xFFF5EDFF),
"Purple10" to Color(0xFFFAF5FF),
),
isIslands = false,
)
@OptIn(InternalJewelApi::class) override val colors: ThemeColorPalette = DefaultColorPalette.Light
override val iconData: ThemeIconData =
ThemeIconData(
@@ -0,0 +1,91 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.ui.theme
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.v2.createComposeRule
import org.jetbrains.jewel.foundation.InternalJewelApi
import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
@OptIn(InternalJewelApi::class)
class ThemeColorPaletteOrDefaultTest {
@get:Rule val rule = createComposeRule()
@Test
fun `grayOrDefault returns the palette's own value when present`() {
val ownValue = Color(0xFF123456)
val palette = palette(gray = listOf(ownValue))
var result: Color? = null
rule.setContent { IntUiTheme { result = palette.grayOrDefault(1) } }
assertEquals(ownValue, result!!)
}
@Test
fun `grayOrDefault falls back to the light default table when missing`() {
val palette = palette(gray = emptyList())
var result: Color? = null
rule.setContent { IntUiTheme(isDark = false) { result = palette.grayOrDefault(1) } }
assertEquals(DefaultColorPalette.Light.grayOrNull(1), result)
}
@Test
fun `grayOrDefault falls back to the dark default table when missing`() {
val palette = palette(gray = emptyList())
var result: Color? = null
rule.setContent { IntUiTheme(isDark = true) { result = palette.grayOrDefault(1) } }
assertEquals(DefaultColorPalette.Dark.grayOrNull(1), result)
}
@Test
fun `grayOrDefault falls back to the Islands light default table when missing`() {
val palette = palette(gray = emptyList(), isIslands = true)
var result: Color? = null
rule.setContent { IntUiTheme(isDark = false) { result = palette.grayOrDefault(10) } }
assertEquals(DefaultColorPalette.IslandsLight.grayOrNull(10), result)
}
@Test
fun `grayOrDefault falls back to the Islands dark default table when missing`() {
val palette = palette(gray = emptyList(), isIslands = true)
var result: Color? = null
rule.setContent { IntUiTheme(isDark = true) { result = palette.grayOrDefault(10) } }
assertEquals(DefaultColorPalette.IslandsDark.grayOrNull(10), result)
}
@Test
fun `grayOrDefault returns Unspecified when the index is out of bounds everywhere`() {
val palette = palette(gray = emptyList())
var result: Color? = null
rule.setContent { IntUiTheme { result = palette.grayOrDefault(9999) } }
assertEquals(Color.Unspecified, result!!)
}
private fun palette(gray: List<Color>, isIslands: Boolean = false): ThemeColorPalette =
ThemeColorPalette(
gray = gray,
blue = emptyList(),
green = emptyList(),
red = emptyList(),
yellow = emptyList(),
orange = emptyList(),
purple = emptyList(),
teal = emptyList(),
rawMap = emptyMap(),
isIslands = isIslands,
)
}
+4 -2
View File
@@ -23,8 +23,10 @@ jvm_library(
"src/main/kotlin/**/*.kt",
"src/main/kotlin/**/*.java",
"src/main/kotlin/**/*.form",
"generated/**/*.kt",
"generated/**/*.java",
"generated/icons/**/*.kt",
"generated/icons/**/*.java",
"generated/theme/**/*.kt",
"generated/theme/**/*.java",
],
allow_empty = True,
),
+9
View File
@@ -4206,6 +4206,15 @@ f:org.jetbrains.jewel.ui.theme.JewelThemeKt
- sf:getTextFieldStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.TextFieldStyle
- sf:getTooltipStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.TooltipStyle
- sf:getTreeStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.LazyTreeStyle
f:org.jetbrains.jewel.ui.theme.ThemeColorPaletteKt
- sf:blueOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
- sf:grayOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
- sf:greenOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
- sf:orangeOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
- sf:purpleOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
- sf:redOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
- sf:tealOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
- sf:yellowOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette,I,androidx.compose.runtime.Composer,I):J
f:org.jetbrains.jewel.ui.util.ColorExtensionsKt
- sf:fromArgbHexStringOrNull(androidx.compose.ui.graphics.Color$Companion,java.lang.String):androidx.compose.ui.graphics.Color
- sf:fromRgbaHexStringOrNull(androidx.compose.ui.graphics.Color$Companion,java.lang.String):androidx.compose.ui.graphics.Color
+44 -1
View File
@@ -1,11 +1,13 @@
import org.jetbrains.jewel.buildlogic.icons.IconKeysGeneratorTask
import org.jetbrains.jewel.buildlogic.metalava.GenerateMetalavaApiTask
import org.jetbrains.jewel.buildlogic.theme.DefaultColorPaletteGeneratorTask
import org.jmailen.gradle.kotlinter.tasks.FormatTask
import org.jmailen.gradle.kotlinter.tasks.LintTask
plugins {
jewel
`jewel-check-public-api`
`intellij-color-palette-generator`
alias(libs.plugins.composeDesktop)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlinx.serialization)
@@ -21,7 +23,7 @@ dependencies {
testImplementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") }
}
sourceSets.main { java.srcDir(project.file("generated")) }
sourceSets.main { java.srcDir(project.file("generated/icons")) }
tasks {
withType<LintTask> {
@@ -34,4 +36,45 @@ tasks {
val iconGeneratorTasks = withType<IconKeysGeneratorTask>()
withType<GenerateMetalavaApiTask> { dependsOn(iconGeneratorTasks) }
// Same pattern as int-ui-standalone/build.gradle.kts for its own generated theme files.
val paletteGeneratorTasks = withType<DefaultColorPaletteGeneratorTask>()
paletteGeneratorTasks.configureEach { finalizedBy(ktfmtFormatMain) }
ktfmtFormatMain {
// Ensure the ktfmtFormatMain task is not considered UP-TO-DATE when we've regenerated
// DefaultColorPalette.kt (it doesn't always pick it up for some reason)
outputs.upToDateWhen { paletteGeneratorTasks.none { it.state.executed } }
}
ktfmtCheckMain {
mustRunAfter(paletteGeneratorTasks)
mustRunAfter(ktfmtFormatMain)
}
}
intelliJColorPaletteGenerator {
register("Light") {
themeFilePath = "../../platform/platform-resources/src/themes/expUI/expUI_light.theme.json"
propertyName = "Light"
isIslands = false
}
register("Dark") {
themeFilePath = "../../platform/platform-resources/src/themes/expUI/expUI_dark.theme.json"
propertyName = "Dark"
isIslands = false
}
register("IslandsLight") {
themeFilePath = "../../platform/platform-resources/src/themes/islands/ManyIslandsLight.theme.json"
propertyName = "IslandsLight"
isIslands = true
}
register("IslandsDark") {
themeFilePath = "../../platform/platform-resources/src/themes/islands/ManyIslandsDark.theme.json"
propertyName = "IslandsDark"
isIslands = true
}
}
@@ -28,7 +28,8 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/generated" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/generated/icons" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/generated/theme" isTestSource="false" generated="true" />
<excludeFolder url="file://$MODULE_DIR$/bin" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
@@ -5114,6 +5114,17 @@ package org.jetbrains.jewel.ui.theme {
property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static org.jetbrains.jewel.ui.component.styling.LazyTreeStyle org.jetbrains.jewel.foundation.theme.JewelTheme.Companion.treeStyle;
}
public final class ThemeColorPaletteKt {
method @androidx.compose.runtime.Composable public static long blueOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long grayOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long greenOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long orangeOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long purpleOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long redOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long tealOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long yellowOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
}
}
package org.jetbrains.jewel.ui.util {
@@ -4973,6 +4973,17 @@ package org.jetbrains.jewel.ui.theme {
property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static org.jetbrains.jewel.ui.component.styling.LazyTreeStyle org.jetbrains.jewel.foundation.theme.JewelTheme.Companion.treeStyle;
}
public final class ThemeColorPaletteKt {
method @androidx.compose.runtime.Composable public static long blueOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long grayOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long greenOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long orangeOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long purpleOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long redOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long tealOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
method @androidx.compose.runtime.Composable public static long yellowOrDefault(org.jetbrains.jewel.foundation.theme.ThemeColorPalette, int index);
}
}
package org.jetbrains.jewel.ui.util {
@@ -0,0 +1,166 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jewel.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import org.jetbrains.jewel.foundation.InternalJewelApi
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
@OptIn(InternalJewelApi::class)
@Composable
private fun ThemeColorPalette.defaultTable(): ThemeColorPalette =
when {
isIslands && JewelTheme.isDark -> DefaultColorPalette.IslandsDark
isIslands && !JewelTheme.isDark -> DefaultColorPalette.IslandsLight
!isIslands && JewelTheme.isDark -> DefaultColorPalette.Dark
else -> DefaultColorPalette.Light
}
@Composable
private fun ThemeColorPalette.orDefault(index: Int, accessor: ThemeColorPalette.(Int) -> Color?): Color =
accessor(index) ?: defaultTable().accessor(index) ?: Color.Unspecified
/**
* Retrieves a gray color from the palette by its index, falling back to Jewel's built-in default palette if the current
* Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.grayOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::grayOrNull)
/**
* Retrieves a blue color from the palette by its index, falling back to Jewel's built-in default palette if the current
* Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.blueOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::blueOrNull)
/**
* Retrieves a green color from the palette by its index, falling back to Jewel's built-in default palette if the
* current Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.greenOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::greenOrNull)
/**
* Retrieves a red color from the palette by its index, falling back to Jewel's built-in default palette if the current
* Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.redOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::redOrNull)
/**
* Retrieves a yellow color from the palette by its index, falling back to Jewel's built-in default palette if the
* current Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.yellowOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::yellowOrNull)
/**
* Retrieves an orange color from the palette by its index, falling back to Jewel's built-in default palette if the
* current Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.orangeOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::orangeOrNull)
/**
* Retrieves a purple color from the palette by its index, falling back to Jewel's built-in default palette if the
* current Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.purpleOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::purpleOrNull)
/**
* Retrieves a teal color from the palette by its index, falling back to Jewel's built-in default palette if the current
* Look and Feel does not provide a color for the requested index.
*
* This spares callers from having to keep their own copy of the default palette values around just to have a sensible
* fallback for incomplete or missing LaF palettes (e.g. most 3rd-party themes).
*
* Palette indices start at 1 (or 10 for Islands themes); 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 (or 10-based for Islands themes) index of the color to retrieve. Only values of 1 and above
* are valid.
* @return The [Color] at the specified index from this palette if available, otherwise the corresponding color from
* Jewel's default palette, or [Color.Unspecified] if the index is out of bounds even there.
*/
@Composable
public fun ThemeColorPalette.tealOrDefault(index: Int): Color = orDefault(index, ThemeColorPalette::tealOrNull)