From 27f1d6bbb687803bde07f97f2947d05233fe7c82 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Thu, 1 Feb 2024 20:19:39 +0100 Subject: [PATCH] Fix icon loading and errors in bridge for 241 --- .../bridge/BridgePainterHintsProvider.kt | 48 +++++++++- .../jewel/bridge/theme/IntUiBridge.kt | 33 ++++--- .../api/int-ui-standalone.api | 3 +- .../StandalonePainterHintsProvider.kt | 95 ++++++++++++++++++- .../styling/IntUiCheckboxStyling.kt | 16 ++-- .../styling/IntUiRadioButtonStyling.kt | 6 +- .../themes/expUI/icons/dark/checkBox.svg | 4 +- .../expUI/icons/dark/checkBoxDisabled.svg | 4 +- .../expUI/icons/dark/checkBoxFocused.svg | 5 +- .../expUI/icons/dark/checkBoxFocusedTemp.svg | 6 -- .../dark/checkBoxIndeterminateSelected.svg | 6 +- .../checkBoxIndeterminateSelectedDisabled.svg | 6 +- .../checkBoxIndeterminateSelectedFocused.svg | 8 +- .../expUI/icons/dark/checkBoxSelected.svg | 6 +- .../icons/dark/checkBoxSelectedDisabled.svg | 6 +- .../icons/dark/checkBoxSelectedFocused.svg | 8 +- .../themes/expUI/icons/dark/checkBoxTemp.svg | 5 - .../themes/expUI/icons/dark/radio.svg | 4 +- .../themes/expUI/icons/dark/radioDisabled.svg | 4 +- .../themes/expUI/icons/dark/radioFocused.svg | 4 +- .../expUI/icons/dark/radioFocusedTemp.svg | 6 -- .../themes/expUI/icons/dark/radioSelected.svg | 6 +- .../icons/dark/radioSelectedDisabled.svg | 6 +- .../expUI/icons/dark/radioSelectedFocused.svg | 6 +- .../themes/expUI/icons/dark/radioTemp.svg | 5 - ui/api/ui.api | 36 ++++--- .../jetbrains/jewel/ui/component/Checkbox.kt | 28 +++--- .../jewel/ui/component/RadioButton.kt | 27 ++++-- .../ui/component/styling/CheckboxStyling.kt | 7 +- .../component/styling/RadioButtonStyling.kt | 2 + .../ui/painter/BasePainterHintsProvider.kt | 81 ---------------- .../ui/painter/PalettePainterHintsProvider.kt | 55 +++++++++++ .../ui/painter/ResourcePainterProvider.kt | 6 +- ...tte.kt => ColorBasedPaletteReplacement.kt} | 11 ++- .../hints/KeyBasedPaletteReplacement.kt | 67 +++++++++++++ .../org/jetbrains/jewel/PainterHintTest.kt | 4 +- 36 files changed, 416 insertions(+), 214 deletions(-) delete mode 100644 int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocusedTemp.svg delete mode 100644 int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxTemp.svg delete mode 100644 int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocusedTemp.svg delete mode 100644 int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioTemp.svg delete mode 100644 ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/BasePainterHintsProvider.kt create mode 100644 ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/PalettePainterHintsProvider.kt rename ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/{Palette.kt => ColorBasedPaletteReplacement.kt} (87%) create mode 100644 ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/KeyBasedPaletteReplacement.kt diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt index 839a666573..82e64888ea 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt @@ -6,10 +6,12 @@ import com.intellij.ide.ui.UITheme import com.intellij.openapi.diagnostic.thisLogger import org.jetbrains.jewel.foundation.InternalJewelApi import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.ui.painter.BasePainterHintsProvider +import org.jetbrains.jewel.ui.painter.PalettePainterHintsProvider import org.jetbrains.jewel.ui.painter.PainterHint +import org.jetbrains.jewel.ui.painter.hints.ColorBasedPaletteReplacement import org.jetbrains.jewel.ui.painter.hints.Dark import org.jetbrains.jewel.ui.painter.hints.HiDpi +import org.jetbrains.jewel.ui.util.fromRGBAHexStringOrNull @InternalJewelApi public class BridgePainterHintsProvider private constructor( @@ -17,7 +19,47 @@ public class BridgePainterHintsProvider private constructor( intellijIconPalette: Map = emptyMap(), themeIconPalette: Map = emptyMap(), themeColorPalette: Map = emptyMap(), -) : BasePainterHintsProvider(isDark, intellijIconPalette, themeIconPalette, themeColorPalette) { +) : PalettePainterHintsProvider(isDark, intellijIconPalette, themeIconPalette, themeColorPalette) { + + override val checkBoxPaletteHint: PainterHint + override val treePaletteHint: PainterHint + override val uiPaletteHint: PainterHint + + init { + val ui = mutableMapOf() + val checkBoxes = mutableMapOf() + val trees = mutableMapOf() + + @Suppress("LoopWithTooManyJumpStatements") + for ((key, value) in themeIconPalette) { + if (value == null) continue + val map = selectMap(key, checkBoxes, trees, ui) ?: continue + + // If either the key or the resolved value aren't valid colors, ignore the entry + val keyAsColor = resolveKeyColor(key, intellijIconPalette, isDark) ?: continue + val resolvedColor = resolveColor(value) ?: continue + + // Save the new entry (oldColor -> newColor) in the map + map[keyAsColor] = resolvedColor + } + + checkBoxPaletteHint = ColorBasedPaletteReplacement(checkBoxes) + treePaletteHint = ColorBasedPaletteReplacement(trees) + uiPaletteHint = ColorBasedPaletteReplacement(ui) + } + + private fun selectMap( + key: String, + checkBoxes: MutableMap, + trees: MutableMap, + ui: MutableMap, + ) = + when { + key.startsWith("Checkbox.") -> checkBoxes + key.startsWith("Tree.iconColor.") -> trees + key.startsWith("Objects.") || key.startsWith("Actions.") || key.startsWith("#") -> ui + else -> null + } @Composable override fun hints(path: String): List = buildList { @@ -32,7 +74,7 @@ public class BridgePainterHintsProvider private constructor( private val logger = thisLogger() @Suppress("UnstableApiUsage") // We need to call @Internal APIs - public operator fun invoke(isDark: Boolean): BasePainterHintsProvider { + public operator fun invoke(isDark: Boolean): PalettePainterHintsProvider { val uiTheme = currentUiThemeOrNull() ?: return BridgePainterHintsProvider(isDark) logger.info("Parsing theme info from theme ${uiTheme.name} (id: ${uiTheme.id}, isDark: ${uiTheme.isDark})") diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt index 107e6b1f26..9a675d1959 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt @@ -222,12 +222,13 @@ private fun readDefaultButtonStyle(): ButtonStyle { borderHovered = normalBorder, ) + val minimumSize = JBUI.CurrentTheme.Button.minimumSize() return ButtonStyle( colors = colors, metrics = ButtonMetrics( cornerSize = retrieveArcAsCornerSizeWithFallbacks("Button.default.arc", "Button.arc"), padding = PaddingValues(horizontal = 14.dp), // see DarculaButtonUI.HORIZONTAL_PADDING - minSize = DpSize(DarculaUIUtil.MINIMUM_WIDTH.dp, DarculaUIUtil.MINIMUM_HEIGHT.dp), + minSize = DpSize(minimumSize.width.dp, minimumSize.height.dp), borderWidth = DarculaUIUtil.LW.dp, ), ) @@ -264,13 +265,14 @@ private fun readOutlinedButtonStyle(): ButtonStyle { borderHovered = normalBorder, ) + val minimumSize = JBUI.CurrentTheme.Button.minimumSize() return ButtonStyle( colors = colors, metrics = ButtonMetrics( cornerSize = CornerSize(DarculaUIUtil.BUTTON_ARC.dp / 2), padding = PaddingValues(horizontal = 14.dp), // see DarculaButtonUI.HORIZONTAL_PADDING - minSize = DpSize(DarculaUIUtil.MINIMUM_WIDTH.dp, DarculaUIUtil.MINIMUM_HEIGHT.dp), + minSize = DpSize(minimumSize.width.dp, minimumSize.height.dp), borderWidth = DarculaUIUtil.LW.dp, ), ) @@ -288,9 +290,10 @@ private fun readCheckboxStyle(): CheckboxStyle { colors = colors, metrics = CheckboxMetrics( checkboxSize = DarculaCheckBoxUI().defaultIcon.let { DpSize(it.iconWidth.dp, it.iconHeight.dp) }, - checkboxCornerSize = CornerSize(3.dp), // See DarculaCheckBoxUI#drawCheckIcon + outlineCornerSize = CornerSize(3.dp), // See DarculaCheckBoxUI#drawCheckIcon + outlineSelectedCornerSize = CornerSize(3.dp), outlineSize = DpSize(15.dp, 15.dp), // Extrapolated from SVG - outlineOffset = DpOffset(2.5.dp, 1.5.dp), // Extrapolated from SVG + outlineSelectedSize = DpSize(16.dp, 16.dp), // Extrapolated from SVG iconContentGap = 5.dp, // See DarculaCheckBoxUI#textIconGap ), icons = CheckboxIcons(checkbox = bridgePainterProvider("${iconsBasePath}checkBox.svg")), @@ -394,12 +397,13 @@ private fun readDefaultDropdownStyle( iconTintHovered = Color.Unspecified, ) - val arrowWidth = DarculaUIUtil.ARROW_BUTTON_WIDTH.dp + val minimumSize = JBUI.CurrentTheme.ComboBox.minimumSize() + val arrowWidth = JBUI.CurrentTheme.Component.ARROW_AREA_WIDTH.dp return DropdownStyle( colors = colors, metrics = DropdownMetrics( - arrowMinSize = DpSize(arrowWidth, DarculaUIUtil.MINIMUM_HEIGHT.dp), - minSize = DpSize(DarculaUIUtil.MINIMUM_WIDTH.dp + arrowWidth, DarculaUIUtil.MINIMUM_HEIGHT.dp), + arrowMinSize = DpSize(arrowWidth, minimumSize.height.dp), + minSize = DpSize(minimumSize.width.dp + arrowWidth, minimumSize.height.dp), cornerSize = CornerSize(DarculaUIUtil.COMPONENT_ARC.dp), contentPadding = retrieveInsetsAsPaddingValues("ComboBox.padding"), borderWidth = DarculaUIUtil.BW.dp, @@ -441,12 +445,14 @@ private fun readUndecoratedDropdownStyle( iconTintHovered = Color.Unspecified, ) - val arrowWidth = DarculaUIUtil.ARROW_BUTTON_WIDTH.dp + val arrowWidth = JBUI.CurrentTheme.Component.ARROW_AREA_WIDTH.dp + val minimumSize = JBUI.CurrentTheme.Button.minimumSize() + return DropdownStyle( colors = colors, metrics = DropdownMetrics( - arrowMinSize = DpSize(arrowWidth, DarculaUIUtil.MINIMUM_HEIGHT.dp), - minSize = DpSize(DarculaUIUtil.MINIMUM_WIDTH.dp + arrowWidth, DarculaUIUtil.MINIMUM_HEIGHT.dp), + arrowMinSize = DpSize(arrowWidth, minimumSize.height.dp), + minSize = DpSize(minimumSize.width.dp + arrowWidth, minimumSize.height.dp), cornerSize = CornerSize(JBUI.CurrentTheme.MainToolbar.Dropdown.hoverArc().dp), contentPadding = JBUI.CurrentTheme.MainToolbar.Dropdown.borderInsets().toPaddingValues(), borderWidth = 0.dp, @@ -612,7 +618,9 @@ private fun readRadioButtonStyle(): RadioButtonStyle { return RadioButtonStyle( colors = colors, metrics = RadioButtonMetrics( - radioButtonSize = DpSize(19.dp, 19.dp), + radioButtonSize = DpSize(24.dp, 24.dp), // Extrapolated from SVG + outlineSize = DpSize(17.dp, 17.dp), // Extrapolated from SVG + outlineSelectedSize = DpSize(22.dp, 22.dp), // Extrapolated from SVG iconContentGap = retrieveIntAsDpOrUnspecified("RadioButton.textIconGap") .takeOrElse { 4.dp }, ), @@ -723,12 +731,13 @@ private fun readTextFieldStyle(textFieldStyle: TextStyle): TextFieldStyle { placeholder = NamedColorUtil.getInactiveTextColor().toComposeColor(), ) + val minimumSize = JBUI.CurrentTheme.TextField.minimumSize() return TextFieldStyle( colors = colors, metrics = TextFieldMetrics( cornerSize = CornerSize(DarculaUIUtil.COMPONENT_ARC.dp), contentPadding = PaddingValues(horizontal = 9.dp, vertical = 2.dp), - minSize = DpSize(DarculaUIUtil.MINIMUM_WIDTH.dp, DarculaUIUtil.MINIMUM_HEIGHT.dp), + minSize = DpSize(minimumSize.width.dp, minimumSize.height.dp), borderWidth = DarculaUIUtil.LW.dp, ), textStyle = textFieldStyle, diff --git a/int-ui/int-ui-standalone/api/int-ui-standalone.api b/int-ui/int-ui-standalone/api/int-ui-standalone.api index 66183c5b5a..4f6e3e0aaa 100644 --- a/int-ui/int-ui-standalone/api/int-ui-standalone.api +++ b/int-ui/int-ui-standalone/api/int-ui-standalone.api @@ -24,7 +24,7 @@ public final class org/jetbrains/jewel/intui/standalone/PainterProviderKt { public static final fun standalonePainterProvider (Ljava/lang/String;)Lorg/jetbrains/jewel/ui/painter/ResourcePainterProvider; } -public final class org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider : org/jetbrains/jewel/ui/painter/BasePainterHintsProvider { +public final class org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider : org/jetbrains/jewel/ui/painter/PalettePainterHintsProvider { public static final field $stable I public static final field Companion Lorg/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider$Companion; public fun (Lorg/jetbrains/jewel/foundation/theme/ThemeDefinition;)V @@ -340,4 +340,3 @@ public final class org/jetbrains/jewel/intui/standalone/theme/IntUiThemeKt { public static final fun light (Lorg/jetbrains/jewel/ui/ComponentStyling;Lorg/jetbrains/jewel/ui/component/styling/CheckboxStyle;Lorg/jetbrains/jewel/ui/component/styling/ChipStyle;Lorg/jetbrains/jewel/ui/component/styling/CircularProgressStyle;Lorg/jetbrains/jewel/ui/component/styling/ButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/TabStyle;Lorg/jetbrains/jewel/ui/component/styling/DividerStyle;Lorg/jetbrains/jewel/ui/component/styling/DropdownStyle;Lorg/jetbrains/jewel/ui/component/styling/TabStyle;Lorg/jetbrains/jewel/ui/component/styling/GroupHeaderStyle;Lorg/jetbrains/jewel/ui/component/styling/HorizontalProgressBarStyle;Lorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/LazyTreeStyle;Lorg/jetbrains/jewel/ui/component/styling/LinkStyle;Lorg/jetbrains/jewel/ui/component/styling/MenuStyle;Lorg/jetbrains/jewel/ui/component/styling/ButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/RadioButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lorg/jetbrains/jewel/ui/component/styling/SliderStyle;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Lorg/jetbrains/jewel/ui/component/styling/TextFieldStyle;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Lorg/jetbrains/jewel/ui/component/styling/DropdownStyle;Landroidx/compose/runtime/Composer;IIII)Lorg/jetbrains/jewel/ui/ComponentStyling; public static final fun lightThemeDefinition-RFMEUTM (Lorg/jetbrains/jewel/foundation/theme/JewelTheme$Companion;Lorg/jetbrains/jewel/foundation/GlobalColors;Lorg/jetbrains/jewel/foundation/GlobalMetrics;Lorg/jetbrains/jewel/foundation/theme/ThemeColorPalette;Lorg/jetbrains/jewel/foundation/theme/ThemeIconData;Landroidx/compose/ui/text/TextStyle;JLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/theme/ThemeDefinition; } - 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 052c067222..212c5a1c5a 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,23 +1,31 @@ package org.jetbrains.jewel.intui.standalone import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.ThemeDefinition -import org.jetbrains.jewel.ui.painter.BasePainterHintsProvider import org.jetbrains.jewel.ui.painter.PainterHint +import org.jetbrains.jewel.ui.painter.PalettePainterHintsProvider +import org.jetbrains.jewel.ui.painter.hints.ColorBasedPaletteReplacement import org.jetbrains.jewel.ui.painter.hints.Dark import org.jetbrains.jewel.ui.painter.hints.HiDpi +import org.jetbrains.jewel.ui.painter.hints.KeyBasedPaletteReplacement import org.jetbrains.jewel.ui.painter.hints.Override +import org.jetbrains.jewel.ui.util.inDebugMode public class StandalonePainterHintsProvider( theme: ThemeDefinition, -) : BasePainterHintsProvider( +) : PalettePainterHintsProvider( theme.isDark, intellijColorPalette, theme.iconData.colorPalette, theme.colorPalette.rawMap, ) { + override val checkBoxPaletteHint: PainterHint + override val treePaletteHint: PainterHint + override val uiPaletteHint: PainterHint + private val overrideHint: PainterHint = Override( theme.iconData.iconOverrides.entries.associate { (k, v) -> @@ -25,6 +33,77 @@ public class StandalonePainterHintsProvider( }, ) + init { + val ui = mutableMapOf() + val checkBoxes = mutableMapOf() + val trees = mutableMapOf() + + @Suppress("LoopWithTooManyJumpStatements") + for ((key, value) in themeIconPalette) { + if (value == null) continue + + // Checkbox (and radio button) entries work differently: the ID field + // for each element that needs patching has a "[fillKey]_[strokeKey]" + // format, starting from IJP 241. + if (key.startsWith("Checkbox.")) { + // Note: in the 241 bridge, we also need to check if the theme is New UI or not + registerIdBasedReplacement(checkBoxes, key, value) + } else { + val map = selectMap(key, trees, ui) ?: continue + registerColorBasedReplacement(map, key, value) + } + } + + checkBoxPaletteHint = KeyBasedPaletteReplacement(checkBoxes) + treePaletteHint = ColorBasedPaletteReplacement(trees) + uiPaletteHint = ColorBasedPaletteReplacement(ui) + } + + private fun selectMap( + key: String, + trees: MutableMap, + ui: MutableMap, + ) = + when { + key.startsWith("Tree.iconColor.") -> trees + key.startsWith("Objects.") || key.startsWith("Actions.") || key.startsWith("#") -> ui + else -> null + } + + private fun registerColorBasedReplacement(map: MutableMap, key: String, value: String) { + // If either the key or the resolved value aren't valid colors, ignore the entry + val keyAsColor = resolveKeyColor(key, intellijIconPalette, isDark) ?: return + val resolvedColor = resolveColor(value) ?: return + + // Save the new entry (oldColor -> newColor) in the map + map[keyAsColor] = resolvedColor + } + + private fun registerIdBasedReplacement(map: MutableMap, key: String, value: String) { + val adjustedKey = if (isDark) key.removeSuffix(".Dark") else key + + if (adjustedKey !in supportedCheckboxKeys) { + if (inDebugMode) { + println("${if (isDark) "Dark" else "Light"} theme: color key $key is not supported, will be ignored") + } + return + } + + if (adjustedKey != key && inDebugMode) { + println("${if (isDark) "Dark" else "Light"} theme: color key $key is deprecated, use $adjustedKey instead") + } + + val parsedValue = resolveColor(value) + if (parsedValue == null) { + if (inDebugMode) { + println("${if (isDark) "Dark" else "Light"} theme: color key $key has invalid value: '$value'") + } + return + } + + map[adjustedKey] = parsedValue + } + @Composable override fun hints(path: String): List = buildList { add(getPaletteHint(path)) @@ -86,5 +165,17 @@ public class StandalonePainterHintsProvider( "Tree.iconColor" to "#808080", "Tree.iconColor.Dark" to "#AFB1B3", ) + + private val supportedCheckboxKeys: Set = setOf( + "Checkbox.Background.Default", + "Checkbox.Border.Default", + "Checkbox.Foreground.Selected", + "Checkbox.Background.Selected", + "Checkbox.Border.Selected", + "Checkbox.Focus.Wide", + "Checkbox.Foreground.Disabled", + "Checkbox.Background.Disabled", + "Checkbox.Border.Disabled" + ) } } diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt index f4729f20fc..ca684708d8 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt @@ -4,9 +4,9 @@ import androidx.compose.foundation.shape.CornerSize import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.foundation.Stroke import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme import org.jetbrains.jewel.intui.standalone.standalonePainterProvider @@ -49,17 +49,19 @@ public fun CheckboxColors.Companion.dark( CheckboxColors(content, contentDisabled, contentSelected) public fun CheckboxMetrics.Companion.defaults( - checkboxSize: DpSize = DpSize(19.dp, 19.dp), - checkboxCornerSize: CornerSize = CornerSize(3.dp), - outlineSize: DpSize = DpSize(15.dp, 15.dp), - outlineOffset: DpOffset = DpOffset(2.5.dp, 1.5.dp), + checkboxSize: DpSize = DpSize(24.dp, 24.dp), + outlineCornerSize: CornerSize = CornerSize(3.dp), + outlineSelectedCornerSize: CornerSize = CornerSize(4.5.dp), + outlineSize: DpSize = DpSize(16.dp, 16.dp), + outlineSelectedSize: DpSize = DpSize(20.dp, 20.dp), iconContentGap: Dp = 5.dp, ): CheckboxMetrics = CheckboxMetrics( checkboxSize, - checkboxCornerSize, + outlineCornerSize, + outlineSelectedCornerSize, outlineSize, - outlineOffset, + outlineSelectedSize, iconContentGap, ) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt index 0f683234ce..6725cf0b0d 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt @@ -67,10 +67,12 @@ public fun RadioButtonColors.Companion.dark( ) public fun RadioButtonMetrics.Companion.defaults( - radioButtonSize: DpSize = DpSize(19.dp, 19.dp), + radioButtonSize: DpSize = DpSize(24.dp, 24.dp), + outlineSize: DpSize = DpSize(17.dp, 17.dp), + outlineSelectedSize: DpSize = DpSize(22.dp, 22.dp), iconContentGap: Dp = 8.dp, ): RadioButtonMetrics = - RadioButtonMetrics(radioButtonSize, iconContentGap) + RadioButtonMetrics(radioButtonSize, outlineSize, outlineSelectedSize, iconContentGap) public fun RadioButtonIcons.Companion.light( radioButton: PainterProvider = diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBox.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBox.svg index f6a69e272d..4fd5cfef2c 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBox.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBox.svg @@ -1,3 +1,3 @@ - - + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxDisabled.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxDisabled.svg index 54369e3934..ee471fb486 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxDisabled.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxDisabled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocused.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocused.svg index cd844a2a4b..a4f97b8ef8 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocused.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocused.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocusedTemp.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocusedTemp.svg deleted file mode 100644 index 8615c4cc45..0000000000 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxFocusedTemp.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelected.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelected.svg index fa7ec87d18..fa33475d79 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelected.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelected.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedDisabled.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedDisabled.svg index b4390406d3..2f644360e8 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedDisabled.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedDisabled.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedFocused.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedFocused.svg index 52fff945f1..984c7718ed 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedFocused.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxIndeterminateSelectedFocused.svg @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelected.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelected.svg index e45634c828..0c4b4c054e 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelected.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelected.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedDisabled.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedDisabled.svg index f8bcd2ae59..9d9bc40681 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedDisabled.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedDisabled.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedFocused.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedFocused.svg index 0023014d26..effc5248d9 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedFocused.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxSelectedFocused.svg @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxTemp.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxTemp.svg deleted file mode 100644 index 0a87d5a3d0..0000000000 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/checkBoxTemp.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radio.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radio.svg index 736c4d3f2d..ed961c8d8f 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radio.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radio.svg @@ -1,3 +1,3 @@ - - + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioDisabled.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioDisabled.svg index da3a396a0a..f4614a6583 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioDisabled.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioDisabled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocused.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocused.svg index 2d23cdfe86..1cfe677f41 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocused.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocused.svg @@ -1,3 +1,3 @@ - - + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocusedTemp.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocusedTemp.svg deleted file mode 100644 index 2e303ca374..0000000000 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioFocusedTemp.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelected.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelected.svg index 6b9b836dfe..54b3f93b97 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelected.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelected.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedDisabled.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedDisabled.svg index df5695d44a..0d74216795 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedDisabled.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedDisabled.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedFocused.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedFocused.svg index fc446c25b5..8838954056 100644 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedFocused.svg +++ b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioSelectedFocused.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioTemp.svg b/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioTemp.svg deleted file mode 100644 index c59b724291..0000000000 --- a/int-ui/int-ui-standalone/src/main/resources/themes/expUI/icons/dark/radioTemp.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ui/api/ui.api b/ui/api/ui.api index 4cc34977a0..f351543ebc 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -2102,13 +2102,6 @@ public final class org/jetbrains/jewel/ui/painter/BadgePainter : org/jetbrains/j public synthetic fun (Landroidx/compose/ui/graphics/painter/Painter;JLorg/jetbrains/jewel/ui/painter/badge/BadgeShape;Lkotlin/jvm/internal/DefaultConstructorMarker;)V } -public abstract class org/jetbrains/jewel/ui/painter/BasePainterHintsProvider : org/jetbrains/jewel/ui/painter/PainterHintsProvider { - public static final field $stable I - public fun (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;)V - protected final fun getPaletteHint (Ljava/lang/String;)Lorg/jetbrains/jewel/ui/painter/PainterHint; - public fun priorityHints (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Ljava/util/List; -} - public abstract interface class org/jetbrains/jewel/ui/painter/BitmapPainterHint : org/jetbrains/jewel/ui/painter/PainterHint { public abstract fun canApply (Lorg/jetbrains/jewel/ui/painter/PainterProviderScope;)Z } @@ -2218,6 +2211,22 @@ public final class org/jetbrains/jewel/ui/painter/PainterWrapperHint$DefaultImpl public static fun canApply (Lorg/jetbrains/jewel/ui/painter/PainterWrapperHint;Lorg/jetbrains/jewel/ui/painter/PainterProviderScope;)Z } +public abstract class org/jetbrains/jewel/ui/painter/PalettePainterHintsProvider : org/jetbrains/jewel/ui/painter/PainterHintsProvider { + public static final field $stable I + public fun (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;)V + protected abstract fun getCheckBoxPaletteHint ()Lorg/jetbrains/jewel/ui/painter/PainterHint; + protected final fun getIntellijIconPalette ()Ljava/util/Map; + protected final fun getPaletteHint (Ljava/lang/String;)Lorg/jetbrains/jewel/ui/painter/PainterHint; + protected final fun getThemeColorPalette ()Ljava/util/Map; + protected final fun getThemeIconPalette ()Ljava/util/Map; + protected abstract fun getTreePaletteHint ()Lorg/jetbrains/jewel/ui/painter/PainterHint; + protected abstract fun getUiPaletteHint ()Lorg/jetbrains/jewel/ui/painter/PainterHint; + protected final fun isDark ()Z + public fun priorityHints (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Ljava/util/List; + protected final fun resolveColor-ijrfgN4 (Ljava/lang/String;)Landroidx/compose/ui/graphics/Color; + protected final fun resolveKeyColor-8tov2TA (Ljava/lang/String;Ljava/util/Map;Z)Landroidx/compose/ui/graphics/Color; +} + public final class org/jetbrains/jewel/ui/painter/ResizedPainter : org/jetbrains/jewel/ui/painter/DelegatePainter { public static final field $stable I public synthetic fun (Landroidx/compose/ui/graphics/painter/Painter;JLkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2283,6 +2292,10 @@ public final class org/jetbrains/jewel/ui/painter/hints/BadgeKt { public static final fun Badge-DxMtmZc (JLorg/jetbrains/jewel/ui/painter/badge/BadgeShape;)Lorg/jetbrains/jewel/ui/painter/PainterHint; } +public final class org/jetbrains/jewel/ui/painter/hints/ColorBasedPaletteReplacementKt { + public static final fun ColorBasedPaletteReplacement (Ljava/util/Map;)Lorg/jetbrains/jewel/ui/painter/PainterHint; +} + public final class org/jetbrains/jewel/ui/painter/hints/DarkOrStrokeKt { public static final fun Dark (Z)Lorg/jetbrains/jewel/ui/painter/PainterHint; public static synthetic fun Dark$default (ZILjava/lang/Object;)Lorg/jetbrains/jewel/ui/painter/PainterHint; @@ -2293,12 +2306,12 @@ public final class org/jetbrains/jewel/ui/painter/hints/HiDpiKt { public static final fun HiDpi ()Lorg/jetbrains/jewel/ui/painter/PainterHint; } -public final class org/jetbrains/jewel/ui/painter/hints/OverrideKt { - public static final fun Override (Ljava/util/Map;)Lorg/jetbrains/jewel/ui/painter/PainterHint; +public final class org/jetbrains/jewel/ui/painter/hints/KeyBasedPaletteReplacementKt { + public static final fun KeyBasedPaletteReplacement (Ljava/util/Map;)Lorg/jetbrains/jewel/ui/painter/PainterHint; } -public final class org/jetbrains/jewel/ui/painter/hints/PaletteKt { - public static final fun Palette (Ljava/util/Map;)Lorg/jetbrains/jewel/ui/painter/PainterHint; +public final class org/jetbrains/jewel/ui/painter/hints/OverrideKt { + public static final fun Override (Ljava/util/Map;)Lorg/jetbrains/jewel/ui/painter/PainterHint; } public final class org/jetbrains/jewel/ui/painter/hints/SelectedKt { @@ -2357,4 +2370,3 @@ public final class org/jetbrains/jewel/ui/util/DebugKt { public final class org/jetbrains/jewel/ui/util/ModifierExtensionsKt { public static final fun thenIf (Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier; } - diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt index 2b364f7e2b..bae98974f0 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.triStateToggleable import androidx.compose.foundation.shape.RoundedCornerShape @@ -261,6 +260,7 @@ private fun CheckboxImpl( is PressInteraction.Cancel, is PressInteraction.Release, -> checkboxState = checkboxState.copy(pressed = false) + is HoverInteraction.Enter -> checkboxState = checkboxState.copy(hovered = true) is HoverInteraction.Exit -> checkboxState = checkboxState.copy(hovered = false) is FocusInteraction.Focus -> checkboxState = checkboxState.copy(focused = true) @@ -284,14 +284,20 @@ private fun CheckboxImpl( ) val checkBoxImageModifier = Modifier.size(metrics.checkboxSize) - val outlineModifier = Modifier.size(metrics.outlineSize) - .offset(metrics.outlineOffset.x, metrics.outlineOffset.y) - .outline( - state = checkboxState, - outline = outline, - outlineShape = RoundedCornerShape(metrics.checkboxCornerSize), - alignment = Stroke.Alignment.Center, - ) + val outlineModifier = + Modifier.size(if (checkboxState.isSelected) metrics.outlineSelectedSize else metrics.outlineSize) + .outline( + state = checkboxState, + outline = outline, + outlineShape = RoundedCornerShape( + if (checkboxState.isSelected) { + metrics.outlineSelectedCornerSize + } else { + metrics.outlineCornerSize + } + ), + alignment = Stroke.Alignment.Center, + ) val checkboxPainter by icons.checkbox.getPainter( if (checkboxState.toggleableState == ToggleableState.Indeterminate) { @@ -304,7 +310,7 @@ private fun CheckboxImpl( ) if (content == null) { - Box(contentAlignment = Alignment.TopStart) { + Box(contentAlignment = Alignment.Center) { CheckBoxImage(wrapperModifier, checkboxPainter, checkBoxImageModifier) Box(outlineModifier) } @@ -314,7 +320,7 @@ private fun CheckboxImpl( horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap), verticalAlignment = Alignment.CenterVertically, ) { - Box(contentAlignment = Alignment.TopStart) { + Box(contentAlignment = Alignment.Center) { CheckBoxImage(Modifier, checkboxPainter, checkBoxImageModifier) Box(outlineModifier) } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/RadioButton.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/RadioButton.kt index 7c992493ad..d10cb00a87 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/RadioButton.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/RadioButton.kt @@ -172,27 +172,36 @@ private fun RadioButtonImpl( val colors = style.colors val metrics = style.metrics - val radioButtonModifier = Modifier.size(metrics.radioButtonSize) - .outline( - radioButtonState, - outline, - outlineShape = CircleShape, - alignment = Stroke.Alignment.Inside, - ) + val radioButtonImageModifier = Modifier.size(metrics.radioButtonSize) + val outlineModifier = + Modifier.size(if (radioButtonState.isSelected) metrics.outlineSelectedSize else metrics.outlineSize) + .outline( + state = radioButtonState, + outline = outline, + outlineShape = CircleShape, + alignment = Stroke.Alignment.Center, + ) + val radioButtonPainter by style.icons.radioButton.getPainter( Selected(radioButtonState), Stateful(radioButtonState), ) if (content == null) { - RadioButtonImage(wrapperModifier, radioButtonPainter, radioButtonModifier) + Box(contentAlignment = Alignment.Center) { + RadioButtonImage(wrapperModifier, radioButtonPainter, radioButtonImageModifier) + Box(outlineModifier) + } } else { Row( wrapperModifier, horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap), verticalAlignment = Alignment.CenterVertically, ) { - RadioButtonImage(Modifier, radioButtonPainter, radioButtonModifier) + Box(contentAlignment = Alignment.Center) { + RadioButtonImage(wrapperModifier, radioButtonPainter, radioButtonImageModifier) + Box(outlineModifier) + } val contentColor by colors.contentFor(radioButtonState) val resolvedContentColor = contentColor.takeOrElse { textStyle.color } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/CheckboxStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/CheckboxStyling.kt index cbe3822d34..8f8f5e0e0b 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/CheckboxStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/CheckboxStyling.kt @@ -10,9 +10,9 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpSize import org.jetbrains.jewel.foundation.GenerateDataFunctions +import org.jetbrains.jewel.foundation.Stroke import org.jetbrains.jewel.ui.component.CheckboxState import org.jetbrains.jewel.ui.painter.PainterProvider @@ -52,9 +52,10 @@ public class CheckboxColors( @GenerateDataFunctions public class CheckboxMetrics( public val checkboxSize: DpSize, - public val checkboxCornerSize: CornerSize, + public val outlineCornerSize: CornerSize, + public val outlineSelectedCornerSize: CornerSize, public val outlineSize: DpSize, - public val outlineOffset: DpOffset, + public val outlineSelectedSize: DpSize, public val iconContentGap: Dp, ) { diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/RadioButtonStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/RadioButtonStyling.kt index a3eb2ba41a..dd49a7e1d8 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/RadioButtonStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/RadioButtonStyling.kt @@ -55,6 +55,8 @@ public class RadioButtonColors( @GenerateDataFunctions public class RadioButtonMetrics( public val radioButtonSize: DpSize, + public val outlineSize: DpSize, + public val outlineSelectedSize: DpSize, public val iconContentGap: Dp, ) { diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/BasePainterHintsProvider.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/BasePainterHintsProvider.kt deleted file mode 100644 index 9639fdd3c7..0000000000 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/BasePainterHintsProvider.kt +++ /dev/null @@ -1,81 +0,0 @@ -package org.jetbrains.jewel.ui.painter - -import androidx.compose.ui.graphics.Color -import org.jetbrains.jewel.ui.painter.hints.Palette -import org.jetbrains.jewel.ui.util.fromRGBAHexStringOrNull - -public abstract class BasePainterHintsProvider( - isDark: Boolean, - intellijIconPalette: Map, - themeIconPalette: Map, - themeColorPalette: Map, -) : PainterHintsProvider { - - private val checkBoxPaletteHint: PainterHint - private val treePaletteHint: PainterHint - private val uiPaletteHint: PainterHint - - init { - val ui = mutableMapOf() - val checkBoxes = mutableMapOf() - val trees = mutableMapOf() - - @Suppress("LoopWithTooManyJumpStatements") - for ((key, value) in themeIconPalette) { - value ?: continue - val map = selectMap(key, checkBoxes, trees, ui) ?: continue - - // If the value is one of the named colors in the theme, use that named color's value - val namedColor = themeColorPalette[value] - - // If either the key or the resolved value aren't valid colors, ignore the entry - val keyAsColor = resolveKeyColor(key, intellijIconPalette, isDark) ?: continue - val resolvedColor = namedColor ?: Color.fromRGBAHexStringOrNull(value) ?: continue - - // Save the new entry (oldColor -> newColor) in the map - map[keyAsColor] = resolvedColor - } - - checkBoxPaletteHint = Palette(checkBoxes) - treePaletteHint = Palette(trees) - uiPaletteHint = Palette(ui) - } - - private fun selectMap( - key: String, - checkBoxes: MutableMap, - trees: MutableMap, - ui: MutableMap, - ) = - when { - key.startsWith("Checkbox.") -> checkBoxes - key.startsWith("Tree.iconColor.") -> trees - key.startsWith("Objects.") || key.startsWith("Actions.") || key.startsWith("#") -> ui - else -> null - } - - // See com.intellij.ide.ui.UITheme.toColorString - private fun resolveKeyColor( - key: String, - keyPalette: Map, - isDark: Boolean, - ): Color? { - val darkKey = "$key.Dark" - val resolvedKey = if (isDark && keyPalette.containsKey(darkKey)) darkKey else key - return Color.fromRGBAHexStringOrNull(keyPalette[resolvedKey] ?: return null) - } - - protected fun getPaletteHint(path: String): PainterHint { - if (!path.contains("com/intellij/ide/ui/laf/icons/")) return uiPaletteHint - - val file = path.substringAfterLast('/') - return when { - file == "treeCollapsed.svg" || file == "treeExpanded.svg" -> treePaletteHint - // ⚠️ 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") -> checkBoxPaletteHint - else -> PainterHint.None - } - } -} diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/PalettePainterHintsProvider.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/PalettePainterHintsProvider.kt new file mode 100644 index 0000000000..2b86d2288c --- /dev/null +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/PalettePainterHintsProvider.kt @@ -0,0 +1,55 @@ +package org.jetbrains.jewel.ui.painter + +import androidx.compose.ui.graphics.Color +import org.jetbrains.jewel.ui.util.fromRGBAHexStringOrNull + +public abstract class PalettePainterHintsProvider( + protected val isDark: Boolean, + protected val intellijIconPalette: Map, + protected val themeIconPalette: Map, + protected val themeColorPalette: Map, +) : PainterHintsProvider { + + protected abstract val checkBoxPaletteHint: PainterHint + protected abstract val treePaletteHint: PainterHint + protected abstract val uiPaletteHint: PainterHint + + protected fun resolveColor(value: String): Color? { + // If the value is one of the named colors in the theme, use that named color's value + val namedColor = themeColorPalette[value] + return namedColor ?: Color.fromRGBAHexStringOrNull(value) + } + + // See com.intellij.ide.ui.UITheme.toColorString + protected fun resolveKeyColor( + key: String, + keyPalette: Map, + isDark: Boolean, + ): Color? { + val darkKey = "$key.Dark" + val resolvedKey = if (isDark && keyPalette.containsKey(darkKey)) darkKey else key + return Color.fromRGBAHexStringOrNull(keyPalette[resolvedKey] ?: return null) + } + + /** + * Returns a [PainterHint] that can be used to patch colors for a resource + * with a given [path]. + * + * The implementations vary depending on the path, and + * when running on the IntelliJ Platform, also on the IDE version and the + * current theme (New UI vs Classic UI). + */ + protected fun getPaletteHint(path: String): PainterHint { + if (!path.contains("com/intellij/ide/ui/laf/icons/")) return uiPaletteHint + + val file = path.substringAfterLast('/') + return when { + file == "treeCollapsed.svg" || file == "treeExpanded.svg" -> treePaletteHint + // ⚠️ 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") -> checkBoxPaletteHint + else -> PainterHint.None + } + } +} diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt index dc5c68909f..ae7fe57a8e 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/ResourcePainterProvider.kt @@ -179,7 +179,11 @@ public class ResourcePainterProvider( with(hint) { scope.patch(document.documentElement) } } - return document.writeToString().byteInputStream() + return document.writeToString() + .also { + if (inDebugMode) println("Patched SVG:\n\n$it") + } + .byteInputStream() } } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/Palette.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/ColorBasedPaletteReplacement.kt similarity index 87% rename from ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/Palette.kt rename to ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/ColorBasedPaletteReplacement.kt index 4eb40b6990..b7de5c88d3 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/Palette.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/ColorBasedPaletteReplacement.kt @@ -12,7 +12,7 @@ import kotlin.math.roundToInt @Immutable @GenerateDataFunctions -private class PaletteImpl(val map: Map) : PainterSvgPatchHint { +private class ColorBasedReplacementPainterSvgPatchHint(val map: Map) : PainterSvgPatchHint { override fun PainterProviderScope.patch(element: Element) { element.patchPalette(map) @@ -94,5 +94,10 @@ private fun fromHexOrNull(rawColor: String, alpha: Float): Color? { } } -public fun Palette(map: Map): PainterHint = - if (map.isEmpty()) PainterHint.None else PaletteImpl(map) +/** + * Creates a PainterHint that replaces all colors in the [paletteMap] with their + * corresponding new value. It is used in IJ up to 23.3 to support patching the + * SVG colors for checkboxes and radio buttons. + */ +public fun ColorBasedPaletteReplacement(paletteMap: Map): PainterHint = + if (paletteMap.isEmpty()) PainterHint.None else ColorBasedReplacementPainterSvgPatchHint(paletteMap) diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/KeyBasedPaletteReplacement.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/KeyBasedPaletteReplacement.kt new file mode 100644 index 0000000000..f422c0d3d6 --- /dev/null +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/KeyBasedPaletteReplacement.kt @@ -0,0 +1,67 @@ +package org.jetbrains.jewel.ui.painter.hints + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color +import org.jetbrains.jewel.foundation.GenerateDataFunctions +import org.jetbrains.jewel.ui.painter.PainterHint +import org.jetbrains.jewel.ui.painter.PainterProviderScope +import org.jetbrains.jewel.ui.painter.PainterSvgPatchHint +import org.jetbrains.jewel.ui.util.toRgbaHexString +import org.w3c.dom.Element + +@Immutable +@GenerateDataFunctions +private class KeyBasedReplacementPainterSvgPatchHint(val map: Map) : PainterSvgPatchHint { + + override fun PainterProviderScope.patch(element: Element) { + element.patchPalette(map) + } +} + +private fun Element.patchPalette(replacementColors: Map) { + val id = getAttribute("id").ifEmpty { null } + if (id != null) { + val (fillKey, strokeKey) = parseKeysFromId(id) + patchColorAttribute("fill", replacementColors[fillKey]) + patchColorAttribute("stroke", replacementColors[strokeKey]) + } + + val nodes = childNodes + val length = nodes.length + for (i in 0 until length) { + val item = nodes.item(i) + if (item is Element) { + item.patchPalette(replacementColors) + } + } +} + +private fun parseKeysFromId(id: String): Pair { + val parts = id.split('_') + + return if (parts.size == 2) { + parts.first() to parts.last() + } else { + id to id + } +} + +private fun Element.patchColorAttribute(attrName: String, newColor: Color?) { + if (newColor == null) return + if (!hasAttribute(attrName)) return + + setAttribute(attrName, newColor.copy(alpha = 1.0f).toRgbaHexString()) + if (newColor.alpha != 1f) { + setAttribute("$attrName-opacity", newColor.alpha.toString()) + } else { + removeAttribute("$attrName-opacity") + } +} + +/** + * Creates a PainterHint that replaces colors with their corresponding new value, + * based on the IDs of each element. It is used in IJ 24.1 and later to support + * patching the SVG colors for checkboxes and radio buttons. + */ +public fun KeyBasedPaletteReplacement(paletteMap: Map): PainterHint = + if (paletteMap.isEmpty()) PainterHint.None else KeyBasedReplacementPainterSvgPatchHint(paletteMap) diff --git a/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt b/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt index 92be9d1219..053a95e6b8 100644 --- a/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt +++ b/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt @@ -13,7 +13,7 @@ import org.jetbrains.jewel.ui.painter.PainterSvgPatchHint import org.jetbrains.jewel.ui.painter.hints.Dark import org.jetbrains.jewel.ui.painter.hints.HiDpi import org.jetbrains.jewel.ui.painter.hints.Override -import org.jetbrains.jewel.ui.painter.hints.Palette +import org.jetbrains.jewel.ui.painter.hints.ColorBasedPaletteReplacement import org.jetbrains.jewel.ui.painter.hints.Selected import org.jetbrains.jewel.ui.painter.hints.Size import org.jetbrains.jewel.ui.painter.hints.Stateful @@ -270,7 +270,7 @@ class PainterHintTest : BasicJewelUiTest() { val patchedSvg = testScope("fake_icon.svg") .applyPaletteHints( baseSvg, - Palette( + ColorBasedPaletteReplacement( mapOf( Color(0x80000000) to Color(0xFF123456), Color.Black to Color.White,