Skip to content

Commit

Permalink
Re-do icon tinting logic, better match Swing
Browse files Browse the repository at this point in the history
  • Loading branch information
rock3r committed Sep 15, 2023
1 parent 0258458 commit 322b53b
Show file tree
Hide file tree
Showing 20 changed files with 408 additions and 221 deletions.
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/jewel.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@file:Suppress("UnstableApiUsage")

import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.api.attributes.Usage
import org.jmailen.gradle.kotlinter.tasks.LintTask

plugins {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@ internal object IntUiThemeDescriptorReader {
}.forEach { (group, colors) ->
readColorGroup(className, group, colors)
}

val rawMapProperty = PropertySpec
.builder(
"rawMap",
Map::class.asClassName().parameterizedBy(String::class.asClassName(), colorClassName),
KModifier.OVERRIDE
)
.initializer(
colors
.map { (key, value) ->
val colorHexString = value.replace("#", "0xFF")
CodeBlock.of("%S to Color(%L)", key, colorHexString)
}
.joinToCode(prefix = "mapOf(", separator = ",\n", suffix = ")")
)
.build()
addProperty(rawMapProperty)
}.build())

addProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
Expand All @@ -27,30 +28,32 @@ import java.net.URL

abstract class IntelliJThemeGeneratorPlugin : Plugin<Project> {

final override fun apply(target: Project): Unit = with(target) {
val extension = ThemeGeneratorContainer(container<ThemeGeneration> { ThemeGeneration(it, project) })
extensions.add("intelliJThemeGenerator", extension)

extension.all {
val task = tasks.register<IntelliJThemeGeneratorTask>("generate${GUtil.toCamelCase(name)}Theme") {
outputFile.set(targetDir.file(this@all.themeClassName.map {
val className = ClassName.bestGuess(it)
className.packageName.replace(".", "/")
.plus("/${className.simpleName}.kt")
}))
themeClassName.set(this@all.themeClassName)
ideaVersion.set(this@all.ideaVersion)
themeFile.set(this@all.themeFile)
}
tasks.withType<BaseKotlinCompile> {
dependsOn(task)
}
tasks.withType<Detekt> {
dependsOn(task)
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
extensions.getByType<KotlinJvmProjectExtension>().apply {
sourceSets["main"].kotlin.srcDir(targetDir)
final override fun apply(target: Project) {
with(target) {
val extension = ThemeGeneratorContainer(container<ThemeGeneration> { ThemeGeneration(it, project) })
extensions.add("intelliJThemeGenerator", extension)

extension.all {
val task = tasks.register<IntelliJThemeGeneratorTask>("generate${GUtil.toCamelCase(name)}Theme") {
outputFile.set(targetDir.file(this@all.themeClassName.map {
val className = ClassName.bestGuess(it)
className.packageName.replace(".", "/")
.plus("/${className.simpleName}.kt")
}))
themeClassName.set(this@all.themeClassName)
ideaVersion.set(this@all.ideaVersion)
themeFile.set(this@all.themeFile)
}
tasks.withType<BaseKotlinCompile> {
dependsOn(task)
}
tasks.withType<Detekt> {
dependsOn(task)
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
extensions.getByType<KotlinJvmProjectExtension>().apply {
sourceSets["main"].kotlin.srcDir(targetDir)
}
}
}
}
Expand All @@ -72,7 +75,7 @@ class ThemeGeneration(val name: String, project: Project) {
open class IntelliJThemeGeneratorTask : DefaultTask() {

@get:OutputFile
val outputFile = project.objects.fileProperty()
val outputFile: RegularFileProperty = project.objects.fileProperty()

@get:Input
val ideaVersion = project.objects.property<String>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import androidx.compose.ui.graphics.Color
@Stable
interface IntelliJThemeColorPalette {

fun lookup(colorKey: String): Color?
fun lookup(colorKey: String): Color? = rawMap[colorKey]

val rawMap: Map<String, Color>
}

@Immutable
object EmptyThemeColorPalette : IntelliJThemeColorPalette {

override fun lookup(colorKey: String): Color? = null
override val rawMap: Map<String, Color> = emptyMap()
}
24 changes: 24 additions & 0 deletions core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeIconData.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
package org.jetbrains.jewel

import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color

@Immutable
interface IntelliJThemeIconData {

val iconOverrides: Map<String, String>
val colorPalette: Map<String, String>
val selectionColorPalette: Map<String, String>

fun selectionColorMapping() =
selectionColorPalette.mapNotNull { (key, value) ->
val keyColor = key.toColorOrNull() ?: return@mapNotNull null
val valueColor = value.toColorOrNull() ?: return@mapNotNull null
keyColor to valueColor
}.toMap()
}

internal fun String.toColorOrNull() =
lowercase()
.removePrefix("#")
.removePrefix("0x")
.let {
when (it.length) {
3 -> "ff${it[0]}${it[0]}${it[1]}${it[1]}${it[2]}${it[2]}"
4 -> "${it[0]}${it[0]}${it[1]}${it[1]}${it[2]}${it[2]}${it[3]}${it[3]}"
6 -> "ff$it"
8 -> it
else -> null
}
}
?.toLongOrNull(radix = 16)
?.let { Color(it) }

@Immutable
object EmptyThemeIconData : IntelliJThemeIconData {

Expand Down
6 changes: 3 additions & 3 deletions core/src/main/kotlin/org/jetbrains/jewel/JewelSvgLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ class JewelSvgLoader(private val svgPatcher: SvgPatcher) : SvgLoader {
val density = LocalDensity.current

val painter = useResource(resourcePath, loader) {
loadSvgPainter(it.patchColors(), density)
loadSvgPainter(it.patchColors(resourcePath), density)
}
return remember(resourcePath, density, loader) { painter }
}

private fun InputStream.patchColors(): InputStream =
svgPatcher.patchSvg(this).byteInputStream()
private fun InputStream.patchColors(resourcePath: String): InputStream =
svgPatcher.patchSvg(this, resourcePath).byteInputStream()

// Copied from androidx.compose.ui.res.Resources
private inline fun <T> useResource(
Expand Down
54 changes: 30 additions & 24 deletions core/src/main/kotlin/org/jetbrains/jewel/PaletteMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,48 @@ package org.jetbrains.jewel
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color

// Replicates com.intellij.ide.ui.UITheme.PaletteScopeManager's functionality
// (note that in Swing, there is also a RadioButtons scope, but while it gets
// written to, it never gets accessed by the actual color patching, so we ignore
// writing any Radio button-related entries and read the CheckBox values for them)
@Immutable
class PaletteMapper(
private val colorOverrides: Map<Color, Color>,
private val selectedColorOverrides: Map<Color, Color>,
private val ui: Scope,
private val checkBoxes: Scope,
private val trees: Scope,
) {

fun mapColorOrNull(originalColor: Color): Color? {
if (colorOverrides.isEmpty()) return null

return colorOverrides[originalColor]
fun getScopeForPath(path: String?): Scope? {
if (path == null) return ui
if (!path.contains("com/intellij/ide/ui/laf/icons/")) return ui

val file = path.substringAfterLast('/')
return when {
file == "treeCollapsed.svg" || file == "treeExpanded.svg" -> trees
// ⚠️ This next line is not a copy-paste error — the code in UITheme.PaletteScopeManager.getScopeByPath()
// says they share the same colors
file.startsWith("check") || file.startsWith("radio") -> checkBoxes
else -> null
}
}

fun mapSelectedColorOrNull(originalColor: Color): Color? {
if (selectedColorOverrides.isEmpty()) return null
companion object {

return selectedColorOverrides[originalColor]
val Empty = PaletteMapper(Scope.Empty, Scope.Empty, Scope.Empty)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@Immutable
@JvmInline
value class Scope(val colorOverrides: Map<Color, Color>) {

other as PaletteMapper
fun mapColorOrNull(originalColor: Color): Color? =
colorOverrides[originalColor]

if (colorOverrides != other.colorOverrides) return false
if (selectedColorOverrides != other.selectedColorOverrides) return false
override fun toString(): String = "PaletteMapper.Scope(colorOverrides=$colorOverrides)"

return true
}
companion object {

override fun hashCode(): Int {
var result = colorOverrides.hashCode()
result = 31 * result + selectedColorOverrides.hashCode()
return result
val Empty = Scope(emptyMap())
}
}

override fun toString(): String =
"PaletteMapper(colorOverrides=$colorOverrides, selectedColorOverrides=$selectedColorOverrides)"
}
2 changes: 1 addition & 1 deletion core/src/main/kotlin/org/jetbrains/jewel/SvgPatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import java.io.InputStream

interface SvgPatcher {

fun patchSvg(rawSvg: InputStream): String
fun patchSvg(rawSvg: InputStream, path: String?): String
}
Loading

0 comments on commit 322b53b

Please sign in to comment.