Fix icon loading and errors in bridge for 241 GitOrigin-RevId: 4df0132cdadf2d8297fd0fa57573f5630c481ddf
@@ -20,6 +20,7 @@ indent_size = 2
|
||||
ktlint_function_signature_body_expression_wrapping = multiline
|
||||
ktlint_ignore_back_ticked_identifier = true
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||
|
||||
[gradlew.bat]
|
||||
end_of_line = crlf
|
||||
|
||||
2
platform/jewel/.github/workflows/build.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Run :check task
|
||||
run: ./gradlew check --continue
|
||||
|
||||
- uses: github/codeql-action/upload-sarif@v2
|
||||
- uses: github/codeql-action/upload-sarif@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
sarif_file: ${{ github.workspace }}/build/reports/static-analysis.sarif
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import org.gradle.api.Project
|
||||
import java.util.Properties
|
||||
|
||||
internal fun Project.localProperty(propertyName: String): String? {
|
||||
val localPropertiesFile = rootProject.file("local.properties")
|
||||
if (!localPropertiesFile.exists()) {
|
||||
return null
|
||||
}
|
||||
val properties = Properties()
|
||||
localPropertiesFile.inputStream().use { properties.load(it) }
|
||||
return properties.getProperty(propertyName)
|
||||
}
|
||||
@@ -65,7 +65,7 @@ tasks {
|
||||
formatKotlinMain { exclude { it.file.absolutePath.contains("build/generated") } }
|
||||
|
||||
lintKotlinMain {
|
||||
exclude { it.file.absolutePath.contains("build/generated") }
|
||||
exclude { it.file.absolutePath.replace('\\', '/').contains("build/generated") }
|
||||
|
||||
reports = provider {
|
||||
mapOf(
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.kotlin.dsl.property
|
||||
import org.gradle.kotlin.dsl.setProperty
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.net.URI
|
||||
|
||||
open class StudioVersionsGenerationExtension(project: Project) {
|
||||
|
||||
@@ -69,7 +69,7 @@ open class AndroidStudioReleasesGeneratorTask : DefaultTask() {
|
||||
"Registered resources directories:\n" +
|
||||
lookupDirs.joinToString("\n") { " * ${it.absolutePath}" }
|
||||
)
|
||||
val releases = URL(url).openStream()
|
||||
val releases = URI.create(url).toURL().openStream()
|
||||
.use { json.decodeFromStream<ApiAndroidStudioReleases>(it) }
|
||||
|
||||
val className = ClassName.bestGuess(STUDIO_RELEASES_OUTPUT_CLASS_NAME)
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.net.URI
|
||||
|
||||
open class CheckIdeaVersionTask : DefaultTask() {
|
||||
|
||||
@@ -34,7 +34,7 @@ open class CheckIdeaVersionTask : DefaultTask() {
|
||||
logger.lifecycle("Fetching IntelliJ Platform releases from $releasesUrl...")
|
||||
val icReleases =
|
||||
try {
|
||||
URL(releasesUrl)
|
||||
URI.create(releasesUrl).toURL()
|
||||
.openStream()
|
||||
.use { json.decodeFromStream<List<ApiIdeaReleasesItem>>(it) }
|
||||
.first()
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.kotlin.dsl.property
|
||||
import java.net.URL
|
||||
import java.net.URI
|
||||
|
||||
class ThemeGeneratorContainer(container: NamedDomainObjectContainer<ThemeGeneration>) :
|
||||
NamedDomainObjectContainer<ThemeGeneration> by container
|
||||
@@ -59,7 +59,7 @@ open class IntelliJThemeGeneratorTask : DefaultTask() {
|
||||
}
|
||||
|
||||
logger.lifecycle("Fetching theme descriptor from $url...")
|
||||
val themeDescriptor = URL(url).openStream()
|
||||
val themeDescriptor = URI.create(url).toURL().openStream()
|
||||
.use { json.decodeFromStream<IntellijThemeDescriptor>(it) }
|
||||
|
||||
val className = ClassName.bestGuess(themeClassName.get())
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
composeDesktop = "1.6.0-dev1397"
|
||||
detekt = "1.23.4"
|
||||
dokka = "1.8.20"
|
||||
idea = "241.9959.31-EAP-SNAPSHOT"
|
||||
idea = "241.10840.26-EAP-SNAPSHOT"
|
||||
ideaGradlePlugin = "1.17.0"
|
||||
jna = "5.14.0"
|
||||
kotlin = "1.8.21"
|
||||
|
||||
@@ -2,7 +2,7 @@ public final class org/jetbrains/jewel/bridge/BridgeIconDataKt {
|
||||
public static final fun readFromLaF (Lorg/jetbrains/jewel/foundation/theme/ThemeIconData$Companion;)Lorg/jetbrains/jewel/foundation/theme/ThemeIconData;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/jewel/bridge/BridgePainterHintsProvider : org/jetbrains/jewel/ui/painter/BasePainterHintsProvider {
|
||||
public final class org/jetbrains/jewel/bridge/BridgePainterHintsProvider : org/jetbrains/jewel/ui/painter/PalettePainterHintsProvider {
|
||||
public static final field $stable I
|
||||
public static final field Companion Lorg/jetbrains/jewel/bridge/BridgePainterHintsProvider$Companion;
|
||||
public synthetic fun <init> (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
@@ -10,7 +10,7 @@ public final class org/jetbrains/jewel/bridge/BridgePainterHintsProvider : org/j
|
||||
}
|
||||
|
||||
public final class org/jetbrains/jewel/bridge/BridgePainterHintsProvider$Companion {
|
||||
public final fun invoke (Z)Lorg/jetbrains/jewel/ui/painter/BasePainterHintsProvider;
|
||||
public final fun invoke (Z)Lorg/jetbrains/jewel/ui/painter/PalettePainterHintsProvider;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/jewel/bridge/BridgeResourceResolverKt {
|
||||
|
||||
@@ -7,6 +7,11 @@ import org.jetbrains.jewel.ui.painter.PainterPathHint
|
||||
import org.jetbrains.jewel.ui.painter.PainterProviderScope
|
||||
import org.jetbrains.jewel.ui.painter.ResourcePainterProviderScope
|
||||
|
||||
/**
|
||||
* A [PainterPathHint] that implements the
|
||||
* [New UI Icon Mapping](https://plugins.jetbrains.com/docs/intellij/icons.html#mapping-entries)
|
||||
* by delegating to the IntelliJ Platform.
|
||||
*/
|
||||
internal object BridgeOverride : PainterPathHint {
|
||||
|
||||
private val dirProvider = DirProvider()
|
||||
@@ -28,7 +33,8 @@ internal object BridgeOverride : PainterPathHint {
|
||||
// 233 EAP 4 broke path patching horribly; now it can return a
|
||||
// "reflective path", which is a FQN to an ExpUIIcons entry.
|
||||
// As a (hopefully) temporary solution, we undo this transformation
|
||||
// back into the original path.
|
||||
// back into the original path. The initial transform is lossy, and
|
||||
// this attempt might fail.
|
||||
if (patchedPath?.startsWith("com.intellij.icons.ExpUiIcons") == true) {
|
||||
return inferActualPathFromReflectivePath(patchedPath)
|
||||
}
|
||||
|
||||
@@ -4,35 +4,121 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.intellij.ide.ui.UITheme
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.ui.NewUI
|
||||
import org.jetbrains.jewel.bridge.theme.isNewUiTheme
|
||||
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.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.util.inDebugMode
|
||||
import org.jetbrains.jewel.ui.util.toRgbaHexString
|
||||
|
||||
/**
|
||||
* Provide the default [PainterHint]s to use in the IDE.
|
||||
*
|
||||
* This is an internal Jewel API and should not be used directly.
|
||||
*/
|
||||
@InternalJewelApi
|
||||
public class BridgePainterHintsProvider private constructor(
|
||||
isDark: Boolean,
|
||||
intellijIconPalette: Map<String, String?> = emptyMap(),
|
||||
themeIconPalette: Map<String, String?> = emptyMap(),
|
||||
themeColorPalette: Map<String, Color?> = emptyMap(),
|
||||
) : BasePainterHintsProvider(isDark, intellijIconPalette, themeIconPalette, themeColorPalette) {
|
||||
) : PalettePainterHintsProvider(isDark, intellijIconPalette, themeIconPalette, themeColorPalette) {
|
||||
|
||||
override val checkBoxByColorPaletteHint: PainterHint
|
||||
override val checkBoxByKeyPaletteHint: PainterHint
|
||||
override val treePaletteHint: PainterHint
|
||||
override val uiPaletteHint: PainterHint
|
||||
|
||||
init {
|
||||
val ui = mutableMapOf<Color, Color>()
|
||||
val checkBoxesByColor = mutableMapOf<Color, Color>()
|
||||
val checkBoxesByKey = mutableMapOf<String, Color>()
|
||||
val trees = mutableMapOf<Color, Color>()
|
||||
|
||||
@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. This is only enabled for the New UI.
|
||||
if (key.startsWith("Checkbox.") && NewUI.isEnabled()) {
|
||||
registerIdBasedReplacement(checkBoxesByKey, key, value)
|
||||
}
|
||||
|
||||
val map = selectMap(key, checkBoxesByColor, trees, ui) ?: continue
|
||||
registerColorBasedReplacement(map, key, value)
|
||||
}
|
||||
|
||||
checkBoxByKeyPaletteHint = KeyBasedPaletteReplacement(checkBoxesByKey)
|
||||
checkBoxByColorPaletteHint = ColorBasedPaletteReplacement(checkBoxesByColor)
|
||||
treePaletteHint = ColorBasedPaletteReplacement(trees)
|
||||
uiPaletteHint = ColorBasedPaletteReplacement(ui)
|
||||
}
|
||||
|
||||
private fun registerColorBasedReplacement(
|
||||
map: MutableMap<Color, Color>,
|
||||
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<String, Color>,
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
val adjustedKey = if (isDark) key.removeSuffix(".Dark") else key
|
||||
|
||||
if (adjustedKey !in supportedCheckboxKeys) {
|
||||
if (inDebugMode) {
|
||||
logger.warn("${if (isDark) "Dark" else "Light"} theme: color key $key is not supported, will be ignored")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (adjustedKey != key && inDebugMode) {
|
||||
logger.warn("${if (isDark) "Dark" else "Light"} theme: color key $key is deprecated, use $adjustedKey instead")
|
||||
}
|
||||
|
||||
val parsedValue = resolveColor(value)
|
||||
if (parsedValue == null) {
|
||||
if (inDebugMode) {
|
||||
logger.warn("${if (isDark) "Dark" else "Light"} theme: color key $key has invalid value: '$value'")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
map[adjustedKey] = parsedValue
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun hints(path: String): List<PainterHint> = buildList {
|
||||
add(getPaletteHint(path))
|
||||
add(BridgeOverride)
|
||||
add(HiDpi())
|
||||
add(Dark(JewelTheme.isDark))
|
||||
}
|
||||
override fun hints(path: String): List<PainterHint> =
|
||||
buildList {
|
||||
add(BridgeOverride)
|
||||
add(getPaletteHint(path, isNewUi = isNewUiTheme()))
|
||||
add(HiDpi())
|
||||
add(Dark(JewelTheme.isDark))
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
||||
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})")
|
||||
|
||||
@@ -41,13 +127,32 @@ public class BridgePainterHintsProvider private constructor(
|
||||
(bean.colorPalette as Map<String, Any?>).mapValues {
|
||||
when (val value = it.value) {
|
||||
is String -> value
|
||||
is java.awt.Color -> value.toRgbaHexString()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
val keyPalette = UITheme.getColorPalette()
|
||||
val themeColors = bean.colors.mapValues { (_, v) -> Color(v) }
|
||||
|
||||
return BridgePainterHintsProvider(isDark, keyPalette, iconColorPalette, themeColors)
|
||||
return BridgePainterHintsProvider(
|
||||
isDark = isDark,
|
||||
intellijIconPalette = keyPalette,
|
||||
themeIconPalette = iconColorPalette,
|
||||
themeColorPalette = themeColors,
|
||||
)
|
||||
}
|
||||
|
||||
private val supportedCheckboxKeys: Set<String> =
|
||||
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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,18 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
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 androidx.compose.ui.unit.isSpecified
|
||||
import androidx.compose.ui.unit.takeOrElse
|
||||
import com.intellij.ide.ui.LafManager
|
||||
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil
|
||||
import com.intellij.ide.ui.laf.darcula.ui.DarculaCheckBoxUI
|
||||
import com.intellij.ide.ui.laf.intellij.IdeaPopupMenuUI
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.NewUI
|
||||
import com.intellij.util.ui.DirProvider
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.NamedColorUtil
|
||||
@@ -222,12 +225,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 +268,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,
|
||||
),
|
||||
)
|
||||
@@ -284,19 +289,86 @@ private fun readCheckboxStyle(): CheckboxStyle {
|
||||
contentSelected = textColor,
|
||||
)
|
||||
|
||||
val newUiTheme = isNewUiTheme()
|
||||
val metrics = if (newUiTheme) NewUiCheckboxMetrics else ClassicUiCheckboxMetrics
|
||||
|
||||
// This value is not normally defined in the themes, but Swing checks it anyway.
|
||||
// The default hardcoded in com.intellij.ide.ui.laf.darcula.ui.DarculaCheckBoxUI.getDefaultIcon()
|
||||
// is not correct though, the SVG is 19x19 and is missing 1px on the right
|
||||
val checkboxSize = retrieveIntAsDpOrUnspecified("CheckBox.iconSize")
|
||||
.let {
|
||||
when {
|
||||
it.isSpecified -> DpSize(it, it)
|
||||
else -> metrics.checkboxSize
|
||||
}
|
||||
}
|
||||
|
||||
return CheckboxStyle(
|
||||
colors = colors,
|
||||
metrics = CheckboxMetrics(
|
||||
checkboxSize = DarculaCheckBoxUI().defaultIcon.let { DpSize(it.iconWidth.dp, it.iconHeight.dp) },
|
||||
checkboxCornerSize = CornerSize(3.dp), // See DarculaCheckBoxUI#drawCheckIcon
|
||||
outlineSize = DpSize(15.dp, 15.dp), // Extrapolated from SVG
|
||||
outlineOffset = DpOffset(2.5.dp, 1.5.dp), // Extrapolated from SVG
|
||||
iconContentGap = 5.dp, // See DarculaCheckBoxUI#textIconGap
|
||||
checkboxSize = checkboxSize,
|
||||
outlineCornerSize = CornerSize(metrics.outlineCornerSize),
|
||||
outlineFocusedCornerSize = CornerSize(metrics.outlineFocusedCornerSize),
|
||||
outlineSelectedCornerSize = CornerSize(metrics.outlineSelectedCornerSize),
|
||||
outlineSelectedFocusedCornerSize = CornerSize(metrics.outlineSelectedFocusedCornerSize),
|
||||
outlineSize = metrics.outlineSize,
|
||||
outlineSelectedSize = metrics.outlineSelectedSize,
|
||||
outlineFocusedSize = metrics.outlineFocusedSize,
|
||||
outlineSelectedFocusedSize = metrics.outlineSelectedFocusedSize,
|
||||
iconContentGap = metrics.iconContentGap,
|
||||
),
|
||||
icons = CheckboxIcons(checkbox = bridgePainterProvider("${iconsBasePath}checkBox.svg")),
|
||||
)
|
||||
}
|
||||
|
||||
private interface BridgeCheckboxMetrics {
|
||||
|
||||
val outlineSize: DpSize
|
||||
val outlineFocusedSize: DpSize
|
||||
val outlineSelectedSize: DpSize
|
||||
val outlineSelectedFocusedSize: DpSize
|
||||
|
||||
val outlineCornerSize: Dp
|
||||
val outlineFocusedCornerSize: Dp
|
||||
val outlineSelectedCornerSize: Dp
|
||||
val outlineSelectedFocusedCornerSize: Dp
|
||||
|
||||
val checkboxSize: DpSize
|
||||
val iconContentGap: Dp
|
||||
}
|
||||
|
||||
private object ClassicUiCheckboxMetrics : BridgeCheckboxMetrics {
|
||||
|
||||
override val outlineSize = DpSize(14.dp, 14.dp)
|
||||
override val outlineFocusedSize = DpSize(15.dp, 15.dp)
|
||||
override val outlineSelectedSize = outlineSize
|
||||
override val outlineSelectedFocusedSize = outlineFocusedSize
|
||||
|
||||
override val outlineCornerSize = 2.dp
|
||||
override val outlineFocusedCornerSize = 3.dp
|
||||
override val outlineSelectedCornerSize = outlineCornerSize
|
||||
override val outlineSelectedFocusedCornerSize = outlineFocusedCornerSize
|
||||
|
||||
override val checkboxSize = DpSize(20.dp, 19.dp)
|
||||
override val iconContentGap = 4.dp
|
||||
}
|
||||
|
||||
private object NewUiCheckboxMetrics : BridgeCheckboxMetrics {
|
||||
|
||||
override val outlineSize = DpSize(16.dp, 16.dp)
|
||||
override val outlineFocusedSize = outlineSize
|
||||
override val outlineSelectedSize = DpSize(20.dp, 20.dp)
|
||||
override val outlineSelectedFocusedSize = outlineSelectedSize
|
||||
|
||||
override val outlineCornerSize = 3.dp
|
||||
override val outlineFocusedCornerSize = outlineCornerSize
|
||||
override val outlineSelectedCornerSize = 4.5.dp
|
||||
override val outlineSelectedFocusedCornerSize = outlineSelectedCornerSize
|
||||
|
||||
override val checkboxSize = DpSize(24.dp, 24.dp)
|
||||
override val iconContentGap = 5.dp
|
||||
}
|
||||
|
||||
// Note: there isn't a chip spec, nor a chip UI, so we're deriving this from the
|
||||
// styling defined in com.intellij.ide.ui.experimental.meetNewUi.MeetNewUiButton
|
||||
// To note:
|
||||
@@ -394,12 +466,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 +514,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,
|
||||
@@ -609,17 +684,66 @@ private fun readRadioButtonStyle(): RadioButtonStyle {
|
||||
contentSelectedDisabled = disabledContent,
|
||||
)
|
||||
|
||||
val newUiTheme = isNewUiTheme()
|
||||
val metrics = if (newUiTheme) NewUiRadioButtonMetrics else ClassicUiRadioButtonMetrics
|
||||
|
||||
// This value is not normally defined in the themes, but Swing checks it anyway
|
||||
// The default hardcoded in com.intellij.ide.ui.laf.darcula.ui.DarculaRadioButtonUI.getDefaultIcon()
|
||||
// is not correct though, the SVG is 19x19 and is missing 1px on the right
|
||||
val radioButtonSize = retrieveIntAsDpOrUnspecified("RadioButton.iconSize")
|
||||
.takeOrElse { metrics.radioButtonSize }
|
||||
.let { DpSize(it, it) }
|
||||
|
||||
// val outlineSize = if (isNewUiButNotDarcula() DpSize(17.dp, 17.dp) else
|
||||
|
||||
return RadioButtonStyle(
|
||||
colors = colors,
|
||||
metrics = RadioButtonMetrics(
|
||||
radioButtonSize = DpSize(19.dp, 19.dp),
|
||||
radioButtonSize = radioButtonSize,
|
||||
outlineSize = metrics.outlineSize,
|
||||
outlineFocusedSize = metrics.outlineFocusedSize,
|
||||
outlineSelectedSize = metrics.outlineSelectedSize,
|
||||
outlineSelectedFocusedSize = metrics.outlineSelectedFocusedSize,
|
||||
iconContentGap = retrieveIntAsDpOrUnspecified("RadioButton.textIconGap")
|
||||
.takeOrElse { 4.dp },
|
||||
.takeOrElse { metrics.iconContentGap },
|
||||
),
|
||||
icons = RadioButtonIcons(radioButton = bridgePainterProvider("${iconsBasePath}radio.svg")),
|
||||
)
|
||||
}
|
||||
|
||||
private interface BridgeRadioButtonMetrics {
|
||||
|
||||
val outlineSize: DpSize
|
||||
val outlineFocusedSize: DpSize
|
||||
val outlineSelectedSize: DpSize
|
||||
val outlineSelectedFocusedSize: DpSize
|
||||
|
||||
val radioButtonSize: Dp
|
||||
val iconContentGap: Dp
|
||||
}
|
||||
|
||||
private object ClassicUiRadioButtonMetrics : BridgeRadioButtonMetrics {
|
||||
|
||||
override val outlineSize = DpSize(17.dp, 17.dp)
|
||||
override val outlineFocusedSize = DpSize(19.dp, 19.dp)
|
||||
override val outlineSelectedSize = outlineSize
|
||||
override val outlineSelectedFocusedSize = outlineFocusedSize
|
||||
|
||||
override val radioButtonSize = 19.dp
|
||||
override val iconContentGap = 4.dp
|
||||
}
|
||||
|
||||
private object NewUiRadioButtonMetrics : BridgeRadioButtonMetrics {
|
||||
|
||||
override val outlineSize = DpSize(17.dp, 17.dp)
|
||||
override val outlineFocusedSize = outlineSize
|
||||
override val outlineSelectedSize = DpSize(22.dp, 22.dp)
|
||||
override val outlineSelectedFocusedSize = outlineSelectedSize
|
||||
|
||||
override val radioButtonSize = 24.dp
|
||||
override val iconContentGap = 4.dp
|
||||
}
|
||||
|
||||
private fun readScrollbarStyle(isDark: Boolean) =
|
||||
ScrollbarStyle(
|
||||
colors = ScrollbarColors(
|
||||
@@ -723,12 +847,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),
|
||||
cornerSize = CornerSize(DarculaUIUtil.COMPONENT_ARC.dp / 2),
|
||||
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,
|
||||
@@ -921,3 +1046,11 @@ private fun readIconButtonStyle(): IconButtonStyle =
|
||||
borderHovered = retrieveColorOrUnspecified("ActionButton.hoverBorderColor"),
|
||||
),
|
||||
)
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
internal fun isNewUiTheme(): Boolean {
|
||||
if (!NewUI.isEnabled()) return false
|
||||
|
||||
val lafInfo = LafManager.getInstance().currentUIThemeLookAndFeel
|
||||
return lafInfo.name == "Light" || lafInfo.name == "Dark" || lafInfo.name == "Light with Light Header"
|
||||
}
|
||||
|
||||
@@ -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 <init> (Lorg/jetbrains/jewel/foundation/theme/ThemeDefinition;)V
|
||||
@@ -49,8 +49,8 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxSty
|
||||
public static final fun dark (Lorg/jetbrains/jewel/ui/component/styling/CheckboxIcons$Companion;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/CheckboxIcons;
|
||||
public static final fun dark (Lorg/jetbrains/jewel/ui/component/styling/CheckboxStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/CheckboxColors;Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics;Lorg/jetbrains/jewel/ui/component/styling/CheckboxIcons;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/CheckboxStyle;
|
||||
public static final fun dark-GyCwops (Lorg/jetbrains/jewel/ui/component/styling/CheckboxColors$Companion;JJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/CheckboxColors;
|
||||
public static final fun defaults-RRvNTYw (Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics$Companion;JLandroidx/compose/foundation/shape/CornerSize;JJF)Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics;
|
||||
public static synthetic fun defaults-RRvNTYw$default (Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics$Companion;JLandroidx/compose/foundation/shape/CornerSize;JJFILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics;
|
||||
public static final fun defaults-xtx8w0A (Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics$Companion;JLandroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;JJJJF)Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics;
|
||||
public static synthetic fun defaults-xtx8w0A$default (Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics$Companion;JLandroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;JJJJFILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics;
|
||||
public static final fun light (Lorg/jetbrains/jewel/ui/component/styling/CheckboxIcons$Companion;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/CheckboxIcons;
|
||||
public static final fun light (Lorg/jetbrains/jewel/ui/component/styling/CheckboxStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/CheckboxColors;Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics;Lorg/jetbrains/jewel/ui/component/styling/CheckboxIcons;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/CheckboxStyle;
|
||||
public static final fun light-GyCwops (Lorg/jetbrains/jewel/ui/component/styling/CheckboxColors$Companion;JJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/CheckboxColors;
|
||||
@@ -237,8 +237,8 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButton
|
||||
public static final fun dark (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/RadioButtonColors;Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics;Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonStyle;
|
||||
public static synthetic fun dark$default (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons$Companion;Lorg/jetbrains/jewel/ui/painter/PainterProvider;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons;
|
||||
public static final fun dark-dPtIKUs (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonColors$Companion;JJJJJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonColors;
|
||||
public static final fun defaults-Q6CdCac (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics$Companion;JF)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics;
|
||||
public static synthetic fun defaults-Q6CdCac$default (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics$Companion;JFILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics;
|
||||
public static final fun defaults-Wf7Cy8o (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics$Companion;JJJJJF)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics;
|
||||
public static synthetic fun defaults-Wf7Cy8o$default (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics$Companion;JJJJJFILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics;
|
||||
public static final fun light (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons$Companion;Lorg/jetbrains/jewel/ui/painter/PainterProvider;)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons;
|
||||
public static final fun light (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/RadioButtonColors;Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics;Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonStyle;
|
||||
public static synthetic fun light$default (Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons$Companion;Lorg/jetbrains/jewel/ui/painter/PainterProvider;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/RadioButtonIcons;
|
||||
|
||||
@@ -27,6 +27,7 @@ tasks {
|
||||
dependsOn("generateIntUiDarkTheme")
|
||||
dependsOn("generateIntUiLightTheme")
|
||||
}
|
||||
|
||||
named<Jar>("sourcesJar") {
|
||||
dependsOn("generateIntUiDarkTheme")
|
||||
dependsOn("generateIntUiLightTheme")
|
||||
|
||||
@@ -1,38 +1,118 @@
|
||||
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.Override
|
||||
import org.jetbrains.jewel.ui.painter.hints.KeyBasedPaletteReplacement
|
||||
import org.jetbrains.jewel.ui.painter.hints.PathOverride
|
||||
import org.jetbrains.jewel.ui.util.inDebugMode
|
||||
|
||||
/** Provides the default [PainterHint]s to use to load images. */
|
||||
public class StandalonePainterHintsProvider(
|
||||
theme: ThemeDefinition,
|
||||
) : BasePainterHintsProvider(
|
||||
) : PalettePainterHintsProvider(
|
||||
theme.isDark,
|
||||
intellijColorPalette,
|
||||
theme.iconData.colorPalette,
|
||||
theme.colorPalette.rawMap,
|
||||
) {
|
||||
|
||||
override val checkBoxByColorPaletteHint: PainterHint
|
||||
override val checkBoxByKeyPaletteHint: PainterHint
|
||||
override val treePaletteHint: PainterHint
|
||||
override val uiPaletteHint: PainterHint
|
||||
|
||||
private val overrideHint: PainterHint =
|
||||
Override(
|
||||
PathOverride(
|
||||
theme.iconData.iconOverrides.entries.associate { (k, v) ->
|
||||
k.removePrefix("/") to v.removePrefix("/")
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun hints(path: String): List<PainterHint> = buildList {
|
||||
add(getPaletteHint(path))
|
||||
add(overrideHint)
|
||||
add(HiDpi())
|
||||
add(Dark(JewelTheme.isDark))
|
||||
init {
|
||||
val ui = mutableMapOf<Color, Color>()
|
||||
val checkBoxesByColor = mutableMapOf<Color, Color>()
|
||||
val checkBoxesByKey = mutableMapOf<String, Color>()
|
||||
val trees = mutableMapOf<Color, Color>()
|
||||
|
||||
@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.")) {
|
||||
registerIdBasedReplacement(checkBoxesByKey, key, value)
|
||||
}
|
||||
|
||||
val map = selectMap(key, checkBoxesByColor, trees, ui) ?: continue
|
||||
registerColorBasedReplacement(map, key, value)
|
||||
}
|
||||
|
||||
checkBoxByKeyPaletteHint = KeyBasedPaletteReplacement(checkBoxesByKey)
|
||||
checkBoxByColorPaletteHint = ColorBasedPaletteReplacement(checkBoxesByColor)
|
||||
treePaletteHint = ColorBasedPaletteReplacement(trees)
|
||||
uiPaletteHint = ColorBasedPaletteReplacement(ui)
|
||||
}
|
||||
|
||||
private fun registerColorBasedReplacement(
|
||||
map: MutableMap<Color, Color>,
|
||||
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<String, Color>,
|
||||
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<PainterHint> =
|
||||
buildList {
|
||||
add(overrideHint)
|
||||
add(getPaletteHint(path, isNewUi = true))
|
||||
add(HiDpi())
|
||||
add(Dark(JewelTheme.isDark))
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
||||
// Extracted from com.intellij.ide.ui.UITheme#colorPalette
|
||||
@@ -86,5 +166,18 @@ public class StandalonePainterHintsProvider(
|
||||
"Tree.iconColor" to "#808080",
|
||||
"Tree.iconColor.Dark" to "#AFB1B3",
|
||||
)
|
||||
|
||||
private val supportedCheckboxKeys: Set<String> =
|
||||
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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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.intui.core.theme.IntUiDarkTheme
|
||||
@@ -49,18 +48,28 @@ 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),
|
||||
outlineFocusedCornerSize: CornerSize = outlineCornerSize,
|
||||
outlineSelectedCornerSize: CornerSize = CornerSize(4.5.dp),
|
||||
outlineSelectedFocusedCornerSize: CornerSize = outlineSelectedCornerSize,
|
||||
outlineSize: DpSize = DpSize(16.dp, 16.dp),
|
||||
outlineFocusedSize: DpSize = outlineSize,
|
||||
outlineSelectedSize: DpSize = DpSize(20.dp, 20.dp),
|
||||
outlineSelectedFocusedSize: DpSize = outlineSelectedSize,
|
||||
iconContentGap: Dp = 5.dp,
|
||||
): CheckboxMetrics =
|
||||
CheckboxMetrics(
|
||||
checkboxSize,
|
||||
checkboxCornerSize,
|
||||
outlineSize,
|
||||
outlineOffset,
|
||||
iconContentGap,
|
||||
checkboxSize = checkboxSize,
|
||||
outlineCornerSize = outlineCornerSize,
|
||||
outlineFocusedCornerSize = outlineFocusedCornerSize,
|
||||
outlineSelectedCornerSize = outlineSelectedCornerSize,
|
||||
outlineSelectedFocusedCornerSize = outlineSelectedFocusedCornerSize,
|
||||
outlineSize = outlineSize,
|
||||
outlineFocusedSize = outlineFocusedSize,
|
||||
outlineSelectedSize = outlineSelectedSize,
|
||||
outlineSelectedFocusedSize = outlineSelectedFocusedSize,
|
||||
iconContentGap = iconContentGap,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -67,10 +67,21 @@ 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),
|
||||
outlineFocusedSize: DpSize = outlineSize,
|
||||
outlineSelectedSize: DpSize = DpSize(22.dp, 22.dp),
|
||||
outlineSelectedFocusedSize: DpSize = outlineSelectedSize,
|
||||
iconContentGap: Dp = 8.dp,
|
||||
): RadioButtonMetrics =
|
||||
RadioButtonMetrics(radioButtonSize, iconContentGap)
|
||||
RadioButtonMetrics(
|
||||
radioButtonSize = radioButtonSize,
|
||||
outlineSize = outlineSize,
|
||||
outlineFocusedSize = outlineFocusedSize,
|
||||
outlineSelectedSize = outlineSelectedSize,
|
||||
outlineSelectedFocusedSize = outlineSelectedFocusedSize,
|
||||
iconContentGap = iconContentGap,
|
||||
)
|
||||
|
||||
public fun RadioButtonIcons.Companion.light(
|
||||
radioButton: PainterProvider =
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="15" height="15" rx="2.5" fill="#2B2D30" stroke="#5A5D63"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Default_Checkbox.Border.Default" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#2B2D30" stroke="#5A5D63"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 248 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="15" height="15" rx="2.5" fill="#393B40" stroke="#5A5D63"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Disabled_Checkbox.Border.Disabled" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#393B40" stroke="#5A5D63"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 250 B |
@@ -1,4 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="16" height="16" transform="translate(2 2)" fill="#2B2D30"/>
|
||||
<rect x="2" y="2" width="16" height="16" rx="3" stroke="#3574F0" stroke-width="2"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Default_Checkbox.Border.Selected" x="4" y="4" width="16" height="16" rx="3" fill="#2B2D30" stroke="#3574F0" stroke-width="2"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 260 B |
@@ -1,6 +0,0 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="2" width="14" height="14" fill="#2B2D30"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 0C2.79086 0 1 1.79086 1 4V14C1 16.2091 2.79086 18 5 18H15C17.2091 18 19 16.2091 19 14V4C19 1.79086 17.2091 0 15 0H5ZM5 3C4.44772 3 4 3.44772 4 4V14C4 14.5523 4.44772 15 5 15H15C15.5523 15 16 14.5523 16 14V4C16 3.44772 15.5523 3 15 3H5Z" fill="#3574F0"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 2C3.89543 2 3 2.89543 3 4V14C3 15.1046 3.89543 16 5 16H15C16.1046 16 17 15.1046 17 14V4C17 2.89543 16.1046 2 15 2H5ZM5 3C4.44772 3 4 3.44772 4 4V14C4 14.5523 4.44772 15 5 15H15C15.5523 15 16 14.5523 16 14V4C16 3.44772 15.5523 3 15 3H5Z" fill="#2B2D30"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 900 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2" y="2" width="16" height="16" rx="3" fill="#3574F0"/>
|
||||
<rect x="4.5" y="9" width="11" height="2" rx="1" fill="white"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Selected_Checkbox.Border.Selected" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#3574F0" stroke="#3574F0"/>
|
||||
<rect id="Checkbox.Foreground.Selected" x="6.5" y="11" width="11" height="2" rx="1" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 349 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="15" height="15" rx="2.5" fill="#393B40" stroke="#393B41"/>
|
||||
<rect x="4.5" y="9" width="11" height="2" rx="1" fill="#868A91"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Disabled_Checkbox.Border.Disabled" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#393B40" stroke="#5A5D63"/>
|
||||
<rect id="Checkbox.Foreground.Disabled" x="6.5" y="11" width="11" height="2" rx="1" fill="#868A91"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 351 B |
@@ -1,5 +1,5 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="3" width="16" height="16" rx="3" fill="#3574F0"/>
|
||||
<rect x="5.5" y="10" width="11" height="2" rx="1" fill="white"/>
|
||||
<rect x="1" y="1" width="20" height="20" rx="5" stroke="#3574F0" stroke-width="2"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Selected_Checkbox.Border.Selected" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#3574F0" stroke="#3574F0"/>
|
||||
<rect id="Checkbox.Focus.Wide" x="2" y="2" width="20" height="20" rx="4.5" stroke="#3574F0" stroke-width="2"/>
|
||||
<rect id="Checkbox.Foreground.Selected" x="6.5" y="11" width="11" height="2" rx="1" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 317 B After Width: | Height: | Size: 460 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2" y="2" width="16" height="16" rx="3" fill="#3574F0"/>
|
||||
<path d="M6 10.5L9 13.5L14.5 7" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Selected_Checkbox.Border.Selected" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#3574F0" stroke="#3574F0"/>
|
||||
<path id="Checkbox.Foreground.Selected" d="M8 12.5L11 15.5L16.5 9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 398 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="15" height="15" rx="2.5" fill="#393B40" stroke="#393B41"/>
|
||||
<path d="M6 10.5L9 13.5L14.5 7" stroke="#868A91" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Disabled_Checkbox.Border.Disabled" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#393B40" stroke="#5A5D63"/>
|
||||
<path id="Checkbox.Foreground.Disabled" d="M8 12.5L11 15.5L16.5 9" stroke="#6F737A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 400 B |
@@ -1,5 +1,5 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="3" width="16" height="16" rx="3" fill="#3574F0"/>
|
||||
<path d="M7 11.5L10 14.5L15.5 8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="1" y="1" width="20" height="20" rx="5" stroke="#3574F0" stroke-width="2"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Checkbox.Background.Selected_Checkbox.Border.Selected" x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="#3574F0" stroke="#3574F0"/>
|
||||
<rect id="Checkbox.Focus.Wide" x="2" y="2" width="20" height="20" rx="4.5" stroke="#3574F0" stroke-width="2"/>
|
||||
<path id="Checkbox.Foreground.Selected" d="M8 12.5L11 15.5L16.5 9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 509 B |
@@ -1,5 +0,0 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="2" width="14" height="14" rx="2" fill="#2B2D30"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 2C3.89543 2 3 2.89543 3 4V14C3 15.1046 3.89543 16 5 16H15C16.1046 16 17 15.1046 17 14V4C17 2.89543 16.1046 2 15 2H5ZM5 3C4.44772 3 4 3.44772 4 4V14C4 14.5523 4.44772 15 5 15H15C15.5523 15 16 14.5523 16 14V4C16 3.44772 15.5523 3 15 3H5Z" fill="#6F737A"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 601 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="8" stroke="#5A5D63"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="Checkbox.Background.Default_Checkbox.Border.Default" cx="12" cy="12" r="8" fill="#2B2D30" stroke="#5A5D63"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 150 B After Width: | Height: | Size: 224 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="8" fill="#393B40" stroke="#5A5D63"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="Checkbox.Background.Disabled_Checkbox.Border.Disabled" cx="12" cy="12" r="8" fill="#393B40" stroke="#5A5D63"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 165 B After Width: | Height: | Size: 226 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="8.5" stroke="#3574F0" stroke-width="2"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="Checkbox.Background.Default_Checkbox.Border.Selected" cx="12" cy="12" r="8.5" fill="#2B2D30" stroke="#3574F0" stroke-width="2"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 244 B |
@@ -1,6 +0,0 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5 17C13.6421 17 17 13.6421 17 9.5C17 5.35786 13.6421 2 9.5 2C5.35786 2 2 5.35786 2 9.5C2 13.6421 5.35786 17 9.5 17Z" fill="#2B2D30"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19 9.5C19 14.7467 14.7467 19 9.5 19C4.25329 19 0 14.7467 0 9.5C0 4.25329 4.25329 0 9.5 0C14.7467 0 19 4.25329 19 9.5ZM16 9.5C16 13.0899 13.0899 16 9.5 16C5.91015 16 3 13.0899 3 9.5C3 5.91015 5.91015 3 9.5 3C13.0899 3 16 5.91015 16 9.5Z" fill="#3574F0"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 9.5C17 13.6421 13.6421 17 9.5 17C5.35786 17 2 13.6421 2 9.5C2 5.35786 5.35786 2 9.5 2C13.6421 2 17 5.35786 17 9.5ZM16 9.5C16 13.0899 13.0899 16 9.5 16C5.91015 16 3 13.0899 3 9.5C3 5.91015 5.91015 3 9.5 3C13.0899 3 16 5.91015 16 9.5Z" fill="#2B2D30"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="8.5" fill="#3574F0"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12C10.6569 12 12 10.6569 12 9C12 7.34315 10.6569 6 9 6C7.34315 6 6 7.34315 6 9C6 10.6569 7.34315 12 9 12Z" fill="white"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="Checkbox.Background.Selected_Checkbox.Border.Selected" cx="12" cy="12" r="8" fill="#3574F0" stroke="#3574F0"/>
|
||||
<path id="Checkbox.Foreground.Selected" fill-rule="evenodd" clip-rule="evenodd" d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 439 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="8" fill="#393B40" stroke="#5A5D63"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12C10.6569 12 12 10.6569 12 9C12 7.34315 10.6569 6 9 6C7.34315 6 6 7.34315 6 9C6 10.6569 7.34315 12 9 12Z" fill="#6F737A"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="Checkbox.Background.Disabled_Checkbox.Border.Disabled" cx="12" cy="12" r="8" fill="#393B40" stroke="#5A5D63"/>
|
||||
<path id="Checkbox.Foreground.Disabled" fill-rule="evenodd" clip-rule="evenodd" d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="#6F737A"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 441 B |
@@ -1,5 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="8.5" fill="#3574F0"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="#F0F1F2"/>
|
||||
<circle cx="12" cy="12" r="11" stroke="#3574F0" stroke-width="2"/>
|
||||
<circle id="Checkbox.Background.Selected_Checkbox.Border.Selected" cx="12" cy="12" r="8" fill="#3574F0" stroke="#3574F0"/>
|
||||
<circle id="Checkbox.Focus.Wide" cx="12" cy="12" r="11" stroke="#3574F0" stroke-width="2"/>
|
||||
<path id="Checkbox.Foreground.Selected" fill-rule="evenodd" clip-rule="evenodd" d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 531 B |
@@ -1,5 +0,0 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5 17C13.6421 17 17 13.6421 17 9.5C17 5.35786 13.6421 2 9.5 2C5.35786 2 2 5.35786 2 9.5C2 13.6421 5.35786 17 9.5 17Z" fill="#2B2D30"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 9.5C17 13.6421 13.6421 17 9.5 17C5.35786 17 2 13.6421 2 9.5C2 5.35786 5.35786 2 9.5 2C13.6421 2 17 5.35786 17 9.5ZM16 9.5C16 13.0899 13.0899 16 9.5 16C5.91015 16 3 13.0899 3 9.5C3 5.91015 5.91015 3 9.5 3C13.0899 3 16 5.91015 16 9.5Z" fill="#6F737A"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 720 B |
@@ -33,6 +33,7 @@ import org.jetbrains.jewel.foundation.modifier.onActivated
|
||||
import org.jetbrains.jewel.foundation.modifier.trackActivation
|
||||
import org.jetbrains.jewel.foundation.modifier.trackComponentActivation
|
||||
import org.jetbrains.jewel.foundation.theme.JewelTheme
|
||||
import org.jetbrains.jewel.ui.Outline
|
||||
import org.jetbrains.jewel.ui.component.CheckboxRow
|
||||
import org.jetbrains.jewel.ui.component.CircularProgressIndicator
|
||||
import org.jetbrains.jewel.ui.component.CircularProgressIndicatorBig
|
||||
@@ -49,7 +50,7 @@ import org.jetbrains.jewel.ui.component.Tooltip
|
||||
|
||||
@Composable
|
||||
internal fun ComponentShowcaseTab() {
|
||||
val bgColor by remember(JewelTheme.isDark) { mutableStateOf(JBColor.PanelBackground.toComposeColor()) }
|
||||
val bgColor by remember(JBColor.PanelBackground.rgb) { mutableStateOf(JBColor.PanelBackground.toComposeColor()) }
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
Row(
|
||||
@@ -109,19 +110,37 @@ private fun RowScope.ColumnOne() {
|
||||
)
|
||||
|
||||
var checked by remember { mutableStateOf(false) }
|
||||
CheckboxRow(
|
||||
checked = checked,
|
||||
onCheckedChange = { checked = it },
|
||||
) {
|
||||
Text("Hello, I am a themed checkbox")
|
||||
var validated by remember { mutableStateOf(false) }
|
||||
val outline = if (validated) Outline.Error else Outline.None
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
CheckboxRow(
|
||||
checked = checked,
|
||||
onCheckedChange = { checked = it },
|
||||
outline = outline,
|
||||
) {
|
||||
Text("Hello, I am a themed checkbox")
|
||||
}
|
||||
|
||||
CheckboxRow(checked = validated, onCheckedChange = { validated = it }) {
|
||||
Text("Show validation")
|
||||
}
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
var index by remember { mutableStateOf(0) }
|
||||
RadioButtonRow(selected = index == 0, onClick = { index = 0 }) {
|
||||
RadioButtonRow(
|
||||
selected = index == 0,
|
||||
onClick = { index = 0 },
|
||||
outline = outline,
|
||||
) {
|
||||
Text("I am number one")
|
||||
}
|
||||
RadioButtonRow(selected = index == 1, onClick = { index = 1 }) {
|
||||
RadioButtonRow(
|
||||
selected = index == 1,
|
||||
onClick = { index = 1 },
|
||||
outline = outline,
|
||||
) {
|
||||
Text("Sad second")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.jewel.bridge.addComposeTab
|
||||
import org.jetbrains.jewel.samples.ideplugin.releasessample.ReleasesSampleCompose
|
||||
import org.jetbrains.jewel.samples.ideplugin.releasessample.ReleasesSamplePanel
|
||||
|
||||
@Suppress("unused")
|
||||
@ExperimentalCoroutinesApi
|
||||
internal class JewelDemoToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||
|
||||
|
||||
@@ -855,14 +855,21 @@ public final class org/jetbrains/jewel/ui/component/styling/CheckboxIcons$Compan
|
||||
public final class org/jetbrains/jewel/ui/component/styling/CheckboxMetrics {
|
||||
public static final field $stable I
|
||||
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/CheckboxMetrics$Companion;
|
||||
public synthetic fun <init> (JLandroidx/compose/foundation/shape/CornerSize;JJFLkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public synthetic fun <init> (JLandroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;JJJJFLkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getCheckboxCornerSize ()Landroidx/compose/foundation/shape/CornerSize;
|
||||
public final fun getCheckboxSize-MYxV2XQ ()J
|
||||
public final fun getIconContentGap-D9Ej5fM ()F
|
||||
public final fun getOutlineOffset-RKDOV3M ()J
|
||||
public final fun getOutlineCornerSize ()Landroidx/compose/foundation/shape/CornerSize;
|
||||
public final fun getOutlineFocusedCornerSize ()Landroidx/compose/foundation/shape/CornerSize;
|
||||
public final fun getOutlineFocusedSize-MYxV2XQ ()J
|
||||
public final fun getOutlineSelectedCornerSize ()Landroidx/compose/foundation/shape/CornerSize;
|
||||
public final fun getOutlineSelectedFocusedCornerSize ()Landroidx/compose/foundation/shape/CornerSize;
|
||||
public final fun getOutlineSelectedFocusedSize-MYxV2XQ ()J
|
||||
public final fun getOutlineSelectedSize-MYxV2XQ ()J
|
||||
public final fun getOutlineSize-MYxV2XQ ()J
|
||||
public fun hashCode ()I
|
||||
public final fun outlineCornerSizeFor-f7CD9uA (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State;
|
||||
public final fun outlineSizeFor-f7CD9uA (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State;
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
@@ -1631,11 +1638,16 @@ public final class org/jetbrains/jewel/ui/component/styling/RadioButtonIcons$Com
|
||||
public final class org/jetbrains/jewel/ui/component/styling/RadioButtonMetrics {
|
||||
public static final field $stable I
|
||||
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/RadioButtonMetrics$Companion;
|
||||
public synthetic fun <init> (JFLkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public synthetic fun <init> (JJJJJFLkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getIconContentGap-D9Ej5fM ()F
|
||||
public final fun getOutlineFocusedSize-MYxV2XQ ()J
|
||||
public final fun getOutlineSelectedFocusedSize-MYxV2XQ ()J
|
||||
public final fun getOutlineSelectedSize-MYxV2XQ ()J
|
||||
public final fun getOutlineSize-MYxV2XQ ()J
|
||||
public final fun getRadioButtonSize-MYxV2XQ ()J
|
||||
public fun hashCode ()I
|
||||
public final fun outlineSizeFor-ehnS_G0 (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State;
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
@@ -2102,13 +2114,6 @@ public final class org/jetbrains/jewel/ui/painter/BadgePainter : org/jetbrains/j
|
||||
public synthetic fun <init> (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 <init> (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 +2223,24 @@ 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 <init> (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
|
||||
protected abstract fun getCheckBoxByColorPaletteHint ()Lorg/jetbrains/jewel/ui/painter/PainterHint;
|
||||
protected abstract fun getCheckBoxByKeyPaletteHint ()Lorg/jetbrains/jewel/ui/painter/PainterHint;
|
||||
protected final fun getIntellijIconPalette ()Ljava/util/Map;
|
||||
protected final fun getPaletteHint (Ljava/lang/String;Z)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;
|
||||
protected final fun selectMap (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/jewel/ui/painter/ResizedPainter : org/jetbrains/jewel/ui/painter/DelegatePainter {
|
||||
public static final field $stable I
|
||||
public synthetic fun <init> (Landroidx/compose/ui/graphics/painter/Painter;JLkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
@@ -2283,7 +2306,11 @@ 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/DarkOrStrokeKt {
|
||||
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/DarkAndStrokeKt {
|
||||
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;
|
||||
public static final fun Stroke-8_81llA (J)Lorg/jetbrains/jewel/ui/painter/PainterHint;
|
||||
@@ -2293,12 +2320,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/PathOverrideKt {
|
||||
public static final fun PathOverride (Ljava/util/Map;)Lorg/jetbrains/jewel/ui/painter/PainterHint;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/jewel/ui/painter/hints/SelectedKt {
|
||||
@@ -2308,8 +2335,8 @@ public final class org/jetbrains/jewel/ui/painter/hints/SelectedKt {
|
||||
}
|
||||
|
||||
public final class org/jetbrains/jewel/ui/painter/hints/SizeKt {
|
||||
public static final fun Size (I)Lorg/jetbrains/jewel/ui/painter/PainterHint;
|
||||
public static final fun Size (II)Lorg/jetbrains/jewel/ui/painter/PainterHint;
|
||||
public static synthetic fun Size$default (IIILjava/lang/Object;)Lorg/jetbrains/jewel/ui/painter/PainterHint;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/jewel/ui/painter/hints/StatefulKt {
|
||||
@@ -2347,6 +2374,7 @@ public final class org/jetbrains/jewel/ui/theme/JewelThemeKt {
|
||||
public final class org/jetbrains/jewel/ui/util/ColorExtensionsKt {
|
||||
public static final fun fromRGBAHexStringOrNull (Landroidx/compose/ui/graphics/Color$Companion;Ljava/lang/String;)Landroidx/compose/ui/graphics/Color;
|
||||
public static final fun isDark-8_81llA (J)Z
|
||||
public static final fun toRgbaHexString (Ljava/awt/Color;)Ljava/lang/String;
|
||||
public static final fun toRgbaHexString-8_81llA (J)Ljava/lang/String;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.jetbrains.jewel.ui.component
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.interaction.FocusInteraction
|
||||
import androidx.compose.foundation.interaction.HoverInteraction
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -9,7 +8,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
|
||||
@@ -23,6 +21,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.paint
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.semantics.Role
|
||||
@@ -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)
|
||||
@@ -283,15 +283,14 @@ private fun CheckboxImpl(
|
||||
indication = null,
|
||||
)
|
||||
|
||||
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(metrics.outlineSizeFor(checkboxState).value)
|
||||
.outline(
|
||||
state = checkboxState,
|
||||
outline = outline,
|
||||
outlineShape = RoundedCornerShape(metrics.outlineCornerSizeFor(checkboxState).value),
|
||||
alignment = Stroke.Alignment.Center,
|
||||
)
|
||||
|
||||
val checkboxPainter by icons.checkbox.getPainter(
|
||||
if (checkboxState.toggleableState == ToggleableState.Indeterminate) {
|
||||
@@ -303,10 +302,12 @@ private fun CheckboxImpl(
|
||||
Stateful(checkboxState),
|
||||
)
|
||||
|
||||
val checkboxBoxModifier = Modifier.size(metrics.checkboxSize)
|
||||
|
||||
if (content == null) {
|
||||
Box(contentAlignment = Alignment.TopStart) {
|
||||
CheckBoxImage(wrapperModifier, checkboxPainter, checkBoxImageModifier)
|
||||
Box(outlineModifier)
|
||||
Box(checkboxBoxModifier, contentAlignment = Alignment.TopStart) {
|
||||
CheckBoxImage(checkboxPainter)
|
||||
Box(outlineModifier.align(Alignment.Center))
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
@@ -314,9 +315,9 @@ private fun CheckboxImpl(
|
||||
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.TopStart) {
|
||||
CheckBoxImage(Modifier, checkboxPainter, checkBoxImageModifier)
|
||||
Box(outlineModifier)
|
||||
Box(checkboxBoxModifier, contentAlignment = Alignment.TopStart) {
|
||||
CheckBoxImage(checkboxPainter)
|
||||
Box(outlineModifier.align(Alignment.Center))
|
||||
}
|
||||
|
||||
val contentColor by colors.contentFor(checkboxState)
|
||||
@@ -331,25 +332,20 @@ private fun CheckboxImpl(
|
||||
}
|
||||
|
||||
private object CheckBoxIndeterminate : PainterSuffixHint() {
|
||||
|
||||
override fun PainterProviderScope.suffix(): String = "Indeterminate"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CheckBoxImage(
|
||||
modifier: Modifier,
|
||||
checkboxPainter: Painter,
|
||||
checkBoxModifier: Modifier,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier, contentAlignment = Alignment.Center) {
|
||||
Image(checkboxPainter, contentDescription = null, modifier = checkBoxModifier)
|
||||
}
|
||||
Box(modifier.paint(checkboxPainter, alignment = Alignment.TopStart))
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@JvmInline
|
||||
public value class CheckboxState(private val state: ULong) : ToggleableComponentState, FocusableComponentState {
|
||||
|
||||
override val toggleableState: ToggleableState
|
||||
get() = state.readToggleableState()
|
||||
|
||||
@@ -393,7 +389,6 @@ public value class CheckboxState(private val state: ULong) : ToggleableComponent
|
||||
"isHovered=$isHovered, isPressed=$isPressed, isSelected=$isSelected, isActive=$isActive)"
|
||||
|
||||
public companion object {
|
||||
|
||||
public fun of(
|
||||
toggleableState: ToggleableState,
|
||||
enabled: Boolean = true,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.jetbrains.jewel.ui.component
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.interaction.FocusInteraction
|
||||
import androidx.compose.foundation.interaction.HoverInteraction
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -22,6 +21,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.paint
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.semantics.Role
|
||||
@@ -172,27 +172,37 @@ 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 outlineModifier =
|
||||
Modifier.size(metrics.outlineSizeFor(radioButtonState).value)
|
||||
.outline(
|
||||
state = radioButtonState,
|
||||
outline = outline,
|
||||
outlineShape = CircleShape,
|
||||
alignment = Stroke.Alignment.Center,
|
||||
)
|
||||
|
||||
val radioButtonPainter by style.icons.radioButton.getPainter(
|
||||
Selected(radioButtonState),
|
||||
Stateful(radioButtonState),
|
||||
)
|
||||
|
||||
val radioButtonBoxModifier = Modifier.size(metrics.radioButtonSize)
|
||||
|
||||
if (content == null) {
|
||||
RadioButtonImage(wrapperModifier, radioButtonPainter, radioButtonModifier)
|
||||
Box(radioButtonBoxModifier, contentAlignment = Alignment.Center) {
|
||||
RadioButtonImage(radioButtonPainter)
|
||||
Box(outlineModifier)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
wrapperModifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(metrics.iconContentGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButtonImage(Modifier, radioButtonPainter, radioButtonModifier)
|
||||
Box(radioButtonBoxModifier, contentAlignment = Alignment.Center) {
|
||||
RadioButtonImage(radioButtonPainter)
|
||||
Box(outlineModifier)
|
||||
}
|
||||
|
||||
val contentColor by colors.contentFor(radioButtonState)
|
||||
val resolvedContentColor = contentColor.takeOrElse { textStyle.color }
|
||||
@@ -209,14 +219,8 @@ private fun RadioButtonImpl(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RadioButtonImage(
|
||||
outerModifier: Modifier,
|
||||
radioButtonPainter: Painter,
|
||||
radioButtonModifier: Modifier,
|
||||
) {
|
||||
Box(outerModifier) {
|
||||
Image(radioButtonPainter, contentDescription = null, modifier = radioButtonModifier)
|
||||
}
|
||||
private fun RadioButtonImage(radioButtonPainter: Painter, modifier: Modifier = Modifier) {
|
||||
Box(modifier.paint(radioButtonPainter, alignment = Alignment.TopStart))
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
||||
@@ -10,7 +10,6 @@ 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.ui.component.CheckboxState
|
||||
@@ -52,12 +51,37 @@ public class CheckboxColors(
|
||||
@GenerateDataFunctions
|
||||
public class CheckboxMetrics(
|
||||
public val checkboxSize: DpSize,
|
||||
public val checkboxCornerSize: CornerSize,
|
||||
public val outlineCornerSize: CornerSize,
|
||||
public val outlineFocusedCornerSize: CornerSize,
|
||||
public val outlineSelectedCornerSize: CornerSize,
|
||||
public val outlineSelectedFocusedCornerSize: CornerSize,
|
||||
public val outlineSize: DpSize,
|
||||
public val outlineOffset: DpOffset,
|
||||
public val outlineFocusedSize: DpSize,
|
||||
public val outlineSelectedSize: DpSize,
|
||||
public val outlineSelectedFocusedSize: DpSize,
|
||||
public val iconContentGap: Dp,
|
||||
) {
|
||||
|
||||
@Composable
|
||||
public fun outlineCornerSizeFor(state: CheckboxState): State<CornerSize> = rememberUpdatedState(
|
||||
when {
|
||||
state.isFocused && state.isSelected -> outlineSelectedFocusedCornerSize
|
||||
!state.isFocused && state.isSelected -> outlineSelectedCornerSize
|
||||
state.isFocused && !state.isSelected -> outlineFocusedCornerSize
|
||||
else -> outlineCornerSize
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
public fun outlineSizeFor(state: CheckboxState): State<DpSize> = rememberUpdatedState(
|
||||
when {
|
||||
state.isFocused && state.isSelected -> outlineSelectedFocusedSize
|
||||
!state.isFocused && state.isSelected -> outlineSelectedSize
|
||||
state.isFocused && !state.isSelected -> outlineFocusedSize
|
||||
else -> outlineSize
|
||||
},
|
||||
)
|
||||
|
||||
public companion object
|
||||
}
|
||||
|
||||
|
||||
@@ -55,9 +55,23 @@ public class RadioButtonColors(
|
||||
@GenerateDataFunctions
|
||||
public class RadioButtonMetrics(
|
||||
public val radioButtonSize: DpSize,
|
||||
public val outlineSize: DpSize,
|
||||
public val outlineFocusedSize: DpSize,
|
||||
public val outlineSelectedSize: DpSize,
|
||||
public val outlineSelectedFocusedSize: DpSize,
|
||||
public val iconContentGap: Dp,
|
||||
) {
|
||||
|
||||
@Composable
|
||||
public fun outlineSizeFor(state: RadioButtonState): State<DpSize> = rememberUpdatedState(
|
||||
when {
|
||||
state.isFocused && state.isSelected -> outlineSelectedFocusedSize
|
||||
!state.isFocused && state.isSelected -> outlineSelectedSize
|
||||
state.isFocused && !state.isSelected -> outlineFocusedSize
|
||||
else -> outlineSize
|
||||
},
|
||||
)
|
||||
|
||||
public companion object
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,15 @@ import androidx.compose.ui.graphics.withSaveLayer
|
||||
import androidx.compose.ui.unit.Density
|
||||
import org.jetbrains.jewel.ui.painter.badge.BadgeShape
|
||||
|
||||
/**
|
||||
* Paints a badge over the [source].
|
||||
*
|
||||
* An area corresponding to the result of [BadgeShape.createHoleOutline]
|
||||
* is cleared out first, to allow for visual separation with the badge,and
|
||||
* then the [BadgeShape.createOutline] is filled with the [color].
|
||||
*/
|
||||
public class BadgePainter(
|
||||
source: Painter,
|
||||
private val source: Painter,
|
||||
private val color: Color,
|
||||
private val shape: BadgeShape,
|
||||
) : DelegatePainter(source) {
|
||||
|
||||
@@ -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<String, String?>,
|
||||
themeIconPalette: Map<String, String?>,
|
||||
themeColorPalette: Map<String, Color?>,
|
||||
) : PainterHintsProvider {
|
||||
|
||||
private val checkBoxPaletteHint: PainterHint
|
||||
private val treePaletteHint: PainterHint
|
||||
private val uiPaletteHint: PainterHint
|
||||
|
||||
init {
|
||||
val ui = mutableMapOf<Color, Color>()
|
||||
val checkBoxes = mutableMapOf<Color, Color>()
|
||||
val trees = mutableMapOf<Color, Color>()
|
||||
|
||||
@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<Color, Color>,
|
||||
trees: MutableMap<Color, Color>,
|
||||
ui: MutableMap<Color, Color>,
|
||||
) =
|
||||
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<String, String?>,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
|
||||
/**
|
||||
* A painter that delegates drawing to another [Painter], but can apply
|
||||
* custom alphas, filters and layoutDirection to it.
|
||||
*/
|
||||
public open class DelegatePainter(private val delegate: Painter) : Painter() {
|
||||
|
||||
override val intrinsicSize: Size
|
||||
|
||||
@@ -75,9 +75,7 @@ public interface XmlPainterHint : PainterHint {
|
||||
@Immutable
|
||||
public interface PainterPathHint : PainterHint {
|
||||
|
||||
/**
|
||||
* Replace the entire path with the given value.
|
||||
*/
|
||||
/** Patch the path, if needed. */
|
||||
public fun PainterProviderScope.patch(): String
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.jetbrains.jewel.ui.painter
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.jetbrains.jewel.ui.util.fromRGBAHexStringOrNull
|
||||
|
||||
/** Provides the default [PainterHint]s to use to load images. */
|
||||
public abstract class PalettePainterHintsProvider(
|
||||
protected val isDark: Boolean,
|
||||
protected val intellijIconPalette: Map<String, String?>,
|
||||
protected val themeIconPalette: Map<String, String?>,
|
||||
protected val themeColorPalette: Map<String, Color?>,
|
||||
) : PainterHintsProvider {
|
||||
|
||||
protected abstract val checkBoxByKeyPaletteHint: PainterHint
|
||||
protected abstract val checkBoxByColorPaletteHint: 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<String, String?>,
|
||||
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 selectMap(
|
||||
key: String,
|
||||
checkboxes: MutableMap<Color, Color>,
|
||||
trees: MutableMap<Color, Color>,
|
||||
ui: MutableMap<Color, Color>,
|
||||
): MutableMap<Color, Color>? =
|
||||
when {
|
||||
key.startsWith("Checkbox.") -> checkboxes
|
||||
key.startsWith("Tree.iconColor.") -> trees
|
||||
key.startsWith("Objects.") || key.startsWith("Actions.") || key.startsWith("#") -> ui
|
||||
else -> 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, isNewUi: Boolean): PainterHint {
|
||||
if (!path.contains("com/intellij/ide/ui/laf/icons/") && !path.contains("themes/expUI/icons/dark/")) return uiPaletteHint
|
||||
|
||||
val file = path.substringAfterLast('/')
|
||||
// ⚠️ This next line is not a copy-paste error — the code in
|
||||
// UITheme.PaletteScopeManager.getScopeByPath()
|
||||
// says they share the same colors
|
||||
val isCheckboxScope = file.startsWith("check") || file.startsWith("radio")
|
||||
|
||||
return when {
|
||||
file == "treeCollapsed.svg" || file == "treeExpanded.svg" -> treePaletteHint
|
||||
isNewUi && isCheckboxScope -> checkBoxByKeyPaletteHint
|
||||
!isNewUi && isCheckboxScope -> checkBoxByColorPaletteHint
|
||||
else -> PainterHint.None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
|
||||
/** A delegate painter that overrides the intrinsic size of the [delegate]. */
|
||||
public class ResizedPainter(delegate: Painter, private val size: Size) : DelegatePainter(delegate) {
|
||||
|
||||
override val intrinsicSize: Size
|
||||
|
||||
@@ -87,13 +87,13 @@ public class ResourcePainterProvider(
|
||||
val cacheKey = scope.acceptedHints.hashCode() * 31 + LocalDensity.current.hashCode()
|
||||
|
||||
if (inDebugMode && cache[cacheKey] != null) {
|
||||
println("Cache hit for $basePath(${scope.acceptedHints.joinToString()})")
|
||||
println("Cache hit for $basePath (accepted hints: ${scope.acceptedHints.joinToString()})")
|
||||
}
|
||||
|
||||
val painter =
|
||||
cache.getOrPut(cacheKey) {
|
||||
if (inDebugMode) {
|
||||
println("Cache miss for $basePath(${scope.acceptedHints.joinToString()})")
|
||||
println("Cache miss for $basePath (accepted hints: ${scope.acceptedHints.joinToString()})")
|
||||
}
|
||||
loadPainter(scope)
|
||||
}
|
||||
@@ -110,14 +110,15 @@ public class ResourcePainterProvider(
|
||||
scopes = scopes.flatMap { listOfNotNull(it.apply(hint), it) }
|
||||
}
|
||||
|
||||
val (chosenScope, url) = scopes.firstNotNullOfOrNull { resolveResource(it) }
|
||||
?: run {
|
||||
if (inDebugMode) {
|
||||
error("Resource '$basePath(${scope.acceptedHints.joinToString()})' not found")
|
||||
} else {
|
||||
return errorPainter
|
||||
val (chosenScope, url) =
|
||||
scopes.firstNotNullOfOrNull { resolveResource(it) }
|
||||
?: run {
|
||||
if (inDebugMode) {
|
||||
error("Resource '$basePath(${scope.acceptedHints.joinToString()})' not found")
|
||||
} else {
|
||||
return errorPainter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val extension = basePath.substringAfterLast(".").lowercase()
|
||||
|
||||
@@ -151,7 +152,10 @@ public class ResourcePainterProvider(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun createSvgPainter(scope: Scope, url: URL): Painter =
|
||||
private fun createSvgPainter(
|
||||
scope: Scope,
|
||||
url: URL,
|
||||
): Painter =
|
||||
tryLoadingResource(
|
||||
url = url,
|
||||
loadingAction = { resourceUrl ->
|
||||
@@ -165,7 +169,11 @@ public class ResourcePainterProvider(
|
||||
rememberAction = { remember(url, scope.density) { it } },
|
||||
)
|
||||
|
||||
private fun patchSvg(scope: Scope, inputStream: InputStream, hints: List<PainterHint>): InputStream {
|
||||
private fun patchSvg(
|
||||
scope: Scope,
|
||||
inputStream: InputStream,
|
||||
hints: List<PainterHint>,
|
||||
): InputStream {
|
||||
if (hints.all { it !is PainterSvgPatchHint }) {
|
||||
return inputStream
|
||||
}
|
||||
@@ -179,12 +187,19 @@ public class ResourcePainterProvider(
|
||||
with(hint) { scope.patch(document.documentElement) }
|
||||
}
|
||||
|
||||
return document.writeToString().byteInputStream()
|
||||
return document.writeToString()
|
||||
.also { patchedSvg ->
|
||||
if (inDebugMode) println("Patched SVG:\n\n$patchedSvg")
|
||||
}
|
||||
.byteInputStream()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun createVectorDrawablePainter(scope: Scope, url: URL): Painter =
|
||||
private fun createVectorDrawablePainter(
|
||||
scope: Scope,
|
||||
url: URL,
|
||||
): Painter =
|
||||
tryLoadingResource(
|
||||
url = url,
|
||||
loadingAction = { resourceUrl ->
|
||||
@@ -194,15 +209,17 @@ public class ResourcePainterProvider(
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun createBitmapPainter(scope: Scope, url: URL) =
|
||||
tryLoadingResource(
|
||||
url = url,
|
||||
loadingAction = { resourceUrl ->
|
||||
val bitmap = resourceUrl.openStream().use { loadImageBitmap(it) }
|
||||
BitmapPainter(bitmap)
|
||||
},
|
||||
rememberAction = { remember(url, scope.density) { it } },
|
||||
)
|
||||
private fun createBitmapPainter(
|
||||
scope: Scope,
|
||||
url: URL,
|
||||
) = tryLoadingResource(
|
||||
url = url,
|
||||
loadingAction = { resourceUrl ->
|
||||
val bitmap = resourceUrl.openStream().use { loadImageBitmap(it) }
|
||||
BitmapPainter(bitmap)
|
||||
},
|
||||
rememberAction = { remember(url, scope.density) { it } },
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun <T> tryLoadingResource(
|
||||
@@ -272,5 +289,7 @@ internal fun Document.writeToString(): String {
|
||||
}
|
||||
|
||||
@Composable
|
||||
public fun rememberResourcePainterProvider(path: String, iconClass: Class<*>): PainterProvider =
|
||||
remember(path, iconClass.classLoader) { ResourcePainterProvider(path, iconClass.classLoader) }
|
||||
public fun rememberResourcePainterProvider(
|
||||
path: String,
|
||||
iconClass: Class<*>,
|
||||
): PainterProvider = remember(path, iconClass.classLoader) { ResourcePainterProvider(path, iconClass.classLoader) }
|
||||
|
||||
@@ -8,9 +8,20 @@ import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
|
||||
/**
|
||||
* A shape used to draw badges. Badge shapes have a clear area surrounding
|
||||
* them, whose outline is determined by [createHoleOutline].
|
||||
*
|
||||
* @see org.jetbrains.jewel.ui.painter.hints.Badge
|
||||
* @see org.jetbrains.jewel.ui.painter.BadgePainter
|
||||
*/
|
||||
@Immutable
|
||||
public interface BadgeShape : Shape {
|
||||
|
||||
/**
|
||||
* Create the outline of the clear area (or "hole") surrounding this badge
|
||||
* shape.
|
||||
*/
|
||||
public fun createHoleOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline
|
||||
}
|
||||
|
||||
|
||||
@@ -19,5 +19,7 @@ private class BadgeImpl(
|
||||
override fun PainterProviderScope.wrap(painter: Painter): Painter = BadgePainter(painter, color, shape)
|
||||
}
|
||||
|
||||
/** Adds a colored badge to the image being loaded. */
|
||||
@Suppress("FunctionName")
|
||||
public fun Badge(color: Color, shape: BadgeShape): PainterHint =
|
||||
if (color.isSpecified) BadgeImpl(color, shape) else PainterHint.None
|
||||
|
||||
@@ -12,7 +12,7 @@ import kotlin.math.roundToInt
|
||||
|
||||
@Immutable
|
||||
@GenerateDataFunctions
|
||||
private class PaletteImpl(val map: Map<Color, Color>) : PainterSvgPatchHint {
|
||||
private class ColorBasedReplacementPainterSvgPatchHint(val map: Map<Color, Color>) : PainterSvgPatchHint {
|
||||
|
||||
override fun PainterProviderScope.patch(element: Element) {
|
||||
element.patchPalette(map)
|
||||
@@ -94,5 +94,11 @@ private fun fromHexOrNull(rawColor: String, alpha: Float): Color? {
|
||||
}
|
||||
}
|
||||
|
||||
public fun Palette(map: Map<Color, Color>): 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.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun ColorBasedPaletteReplacement(paletteMap: Map<Color, Color>): PainterHint =
|
||||
if (paletteMap.isEmpty()) PainterHint.None else ColorBasedReplacementPainterSvgPatchHint(paletteMap)
|
||||
@@ -62,8 +62,33 @@ private class StrokeImpl(private val color: Color) : PainterSuffixHint(), Painte
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an SVG image to only draw its borders in the provided
|
||||
* [color]. All fills are removed.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun Stroke(color: Color): PainterHint =
|
||||
if (color.isSpecified) StrokeImpl(color) else PainterHint.None
|
||||
|
||||
/**
|
||||
* Switches between the light and dark variants of an image based on
|
||||
* [isDark]. If no dark image exists, the light image will be used.
|
||||
*
|
||||
* All images that aren't dark images are base, or light, images.
|
||||
*
|
||||
* Dark images must be named in exactly the same way as the corresponding
|
||||
* light image, but add a `_dark` suffix right before the extension. Dark
|
||||
* images must be placed in the same directory and have the same extension
|
||||
* as their light counterparts.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* | Light image name | Dark image name |
|
||||
* |---------------------|--------------------------|
|
||||
* | `my-icon.png` | `my-icon_dark.png` |
|
||||
* | `my-icon@20x20.svg` | `my-icon@20x20_dark.svg` |
|
||||
* | `my-icon@2x.png` | `my-icon@2x_dark.png` |
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun Dark(isDark: Boolean = true): PainterHint =
|
||||
if (isDark) DarkImpl else PainterHint.None
|
||||
@@ -11,12 +11,28 @@ private object HiDpiImpl : PainterSuffixHint() {
|
||||
override fun PainterProviderScope.suffix(): String = "@2x"
|
||||
|
||||
override fun PainterProviderScope.canApply(): Boolean =
|
||||
density > 1f && when (path.substringAfterLast('.').lowercase()) {
|
||||
"svg", "xml" -> false
|
||||
else -> true
|
||||
}
|
||||
density > 1f
|
||||
|
||||
override fun toString(): String = "HiDpi"
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the `@2x` HiDPI variant for bitmap images when running on a
|
||||
* HiDPI screen.
|
||||
*
|
||||
* If an image does not have a HiDPI variant, the base image will be used.
|
||||
*
|
||||
* Note that combining a [Size] with [HiDpi] could lead to unexpected
|
||||
* results and is not supported as of now. Generally speaking, however, the
|
||||
* IntelliJ Platform tends to use only [Size] for SVGs and only [HiDpi]
|
||||
* for PNGs, even though both are in theory supported for all formats.
|
||||
*
|
||||
* | Base image name | HiDPI image name |
|
||||
* |-----------------|------------------|
|
||||
* | `my-icon.png` | `my-icon@2x.png` |
|
||||
* | `my-icon.svg` | N/A |
|
||||
*
|
||||
* @see Size
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun HiDpi(): PainterHint = HiDpiImpl
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
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<String, Color>) : PainterSvgPatchHint {
|
||||
|
||||
override fun PainterProviderScope.patch(element: Element) {
|
||||
element.patchPalette(map)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.patchPalette(replacementColors: Map<String, Color>) {
|
||||
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<String, String> {
|
||||
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.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun KeyBasedPaletteReplacement(paletteMap: Map<String, Color>): PainterHint =
|
||||
if (paletteMap.isEmpty()) PainterHint.None else KeyBasedReplacementPainterSvgPatchHint(paletteMap)
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.jetbrains.jewel.ui.painter.hints
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import org.jetbrains.jewel.foundation.GenerateDataFunctions
|
||||
import org.jetbrains.jewel.ui.painter.PainterHint
|
||||
import org.jetbrains.jewel.ui.painter.PainterPathHint
|
||||
import org.jetbrains.jewel.ui.painter.PainterProviderScope
|
||||
|
||||
@Immutable
|
||||
@GenerateDataFunctions
|
||||
private class OverrideImpl(private val iconOverride: Map<String, String>) : PainterPathHint {
|
||||
|
||||
override fun PainterProviderScope.patch(): String = iconOverride[path] ?: path
|
||||
}
|
||||
|
||||
public fun Override(override: Map<String, String>): PainterHint =
|
||||
if (override.isEmpty()) {
|
||||
PainterHint.None
|
||||
} else {
|
||||
OverrideImpl(override)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.jetbrains.jewel.ui.painter.hints
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import org.jetbrains.jewel.foundation.GenerateDataFunctions
|
||||
import org.jetbrains.jewel.ui.painter.PainterHint
|
||||
import org.jetbrains.jewel.ui.painter.PainterPathHint
|
||||
import org.jetbrains.jewel.ui.painter.PainterProviderScope
|
||||
|
||||
@Immutable
|
||||
@GenerateDataFunctions
|
||||
private class PathOverrideImpl(private val overrides: Map<String, String>) : PainterPathHint {
|
||||
|
||||
override fun PainterProviderScope.patch(): String =
|
||||
overrides[path] ?: path
|
||||
|
||||
override fun toString(): String = "PathOverrideImpl(overrides=$overrides)"
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PainterPathHint] that will override the paths passed as keys in the
|
||||
* [overrides] map with the corresponding map values.
|
||||
*
|
||||
* This is used, for example, to implement the
|
||||
* [New UI Icon Mapping](https://plugins.jetbrains.com/docs/intellij/icons.html#mapping-entries)
|
||||
* when running in standalone mode. In the IntelliJ Platform, this logic is
|
||||
* delegated to the platform by
|
||||
* [org.jetbrains.jewel.bridge.BridgeOverride].
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun PathOverride(overrides: Map<String, String>): PainterHint =
|
||||
if (overrides.isEmpty()) {
|
||||
PainterHint.None
|
||||
} else {
|
||||
PathOverrideImpl(overrides)
|
||||
}
|
||||
@@ -14,7 +14,20 @@ private object SelectedImpl : PainterSuffixHint() {
|
||||
override fun toString(): String = "Selected"
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the "selected" variant of an image when [selected] is true. If
|
||||
* an image does not have a selected variant, the base version will be
|
||||
* used.
|
||||
*
|
||||
* | Base image name | Selected image name |
|
||||
* |-----------------------|-------------------------------|
|
||||
* | `my-icon.png` | `my-iconSelected.png` |
|
||||
* | `myIcon@20x20.svg` | `myIconSelected@20x20.svg` |
|
||||
* | `my-icon@2x_dark.png` | `my-iconSelected@2x_dark.png` |
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun Selected(selected: Boolean = true): PainterHint =
|
||||
if (selected) SelectedImpl else PainterHint.None
|
||||
|
||||
@Suppress("FunctionName")
|
||||
public fun Selected(state: SelectableComponentState): PainterHint = Selected(state.isSelected)
|
||||
|
||||
@@ -35,7 +35,46 @@ private class SizeImpl(
|
||||
}
|
||||
}
|
||||
|
||||
public fun Size(width: Int, height: Int = width): PainterHint {
|
||||
/**
|
||||
* Selects a size variant for the image. If the specific size that was
|
||||
* requested is not available, the base image will be used.
|
||||
*
|
||||
* Note that combining a [Size] with [HiDpi] could lead to unexpected
|
||||
* results and is not supported as of now. Generally speaking, however, the
|
||||
* IntelliJ Platform tends to use only [Size] for SVGs and only [HiDpi]
|
||||
* for PNGs, even though both are in theory supported for all formats.
|
||||
*
|
||||
* | Base image name | Sized image name |
|
||||
* |--------------------|--------------------------|
|
||||
* | `my-icon.svg` | `my-icon@20x20.svg` |
|
||||
* | `my-icon_dark.png` | `my-icon@14x14_dark.png` |
|
||||
*
|
||||
* @see HiDpi
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun Size(size: Int): PainterHint {
|
||||
require(size > 0) { "Size must be positive" }
|
||||
return SizeImpl(size, size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a size variant for the image. If the specific size that was
|
||||
* requested is not available, the base image will be used.
|
||||
*
|
||||
* Note that combining a [Size] with [HiDpi] could lead to unexpected
|
||||
* results and is not supported as of now. Generally speaking, however, the
|
||||
* IntelliJ Platform tends to use only [Size] for SVGs and only [HiDpi]
|
||||
* for PNGs, even though both are in theory supported for all formats.
|
||||
*
|
||||
* | Base image name | Sized image name |
|
||||
* |--------------------|--------------------------|
|
||||
* | `my-icon.svg` | `my-icon@20x20.svg` |
|
||||
* | `my-icon_dark.png` | `my-icon@14x14_dark.png` |
|
||||
*
|
||||
* @see HiDpi
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun Size(width: Int, height: Int): PainterHint {
|
||||
require(width > 0 && height > 0) { "Width and height must be positive" }
|
||||
return SizeImpl(width, height)
|
||||
}
|
||||
|
||||
@@ -25,4 +25,30 @@ private class StatefulImpl(private val state: InteractiveComponentState) : Paint
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a stateful variant of an image, based on the current [state].
|
||||
* Stateful variants are `Focused`, `Pressed`, `Hovered`, and `Disabled`.
|
||||
* If an image does not have the required stateful variant, the base one
|
||||
* will be used.
|
||||
*
|
||||
* If the [state] is a [FocusableComponentState] and its
|
||||
* [`isFocused`][FocusableComponentState.isFocused] property
|
||||
* is true, then the `Focused` variant will be used.
|
||||
*
|
||||
* For the base image name `myIcon.svg`, for example:
|
||||
*
|
||||
* | State | Stateful image names |
|
||||
* |------------------|----------------------|
|
||||
* | Disabled | `myIconDisabled.svg` |
|
||||
* | Enabled, focused | `myIconFocused.svg` |
|
||||
* | Enabled, pressed | `myIconPressed.svg` |
|
||||
* | Enabled, hovered | `myIconHovered.svg` |
|
||||
* | Enabled, at rest | `myIcon.svg` |
|
||||
*
|
||||
* Note that the
|
||||
* [Swing Compat mode][org.jetbrains.jewel.foundation.theme.JewelTheme.isSwingCompatMode]
|
||||
* value might prevent the selection of the pressed and hovered states,
|
||||
* when true.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun Stateful(state: InteractiveComponentState): PainterHint = StatefulImpl(state)
|
||||
|
||||
@@ -4,6 +4,28 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Converts a [java.awt.Color] to a RGBA formatted color `#RRGGBBAA` hex
|
||||
* string; e.g., `#FFFFFF1A` (a translucent white).
|
||||
*/
|
||||
public fun java.awt.Color.toRgbaHexString(): String {
|
||||
val r = Integer.toHexString(red)
|
||||
val g = Integer.toHexString(green)
|
||||
val b = Integer.toHexString(blue)
|
||||
|
||||
return buildString {
|
||||
append('#')
|
||||
append(r.padStart(2, '0'))
|
||||
append(g.padStart(2, '0'))
|
||||
append(b.padStart(2, '0'))
|
||||
|
||||
if (alpha < 255) {
|
||||
val a = Integer.toHexString(alpha)
|
||||
append(a.padStart(2, '0'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a [Color] to a RGBA formatted color `#RRGGBBAA` hex string;
|
||||
* e.g., `#FFFFFF1A` (a translucent white).
|
||||
@@ -46,8 +68,6 @@ public fun Color.Companion.fromRGBAHexStringOrNull(rgba: String): Color? =
|
||||
?.toLongOrNull(radix = 16)
|
||||
?.let { Color(it) }
|
||||
|
||||
/**
|
||||
* Heuristically determines if the color can be thought of as "dark".
|
||||
*/
|
||||
/** Heuristically determines if the color can be thought of as "dark". */
|
||||
public fun Color.isDark(): Boolean =
|
||||
(luminance() + 0.05) / 0.05 < 4.5
|
||||
|
||||
@@ -10,10 +10,10 @@ import org.jetbrains.jewel.ui.painter.PainterHint
|
||||
import org.jetbrains.jewel.ui.painter.PainterPathHint
|
||||
import org.jetbrains.jewel.ui.painter.PainterProviderScope
|
||||
import org.jetbrains.jewel.ui.painter.PainterSvgPatchHint
|
||||
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.Override
|
||||
import org.jetbrains.jewel.ui.painter.hints.Palette
|
||||
import org.jetbrains.jewel.ui.painter.hints.PathOverride
|
||||
import org.jetbrains.jewel.ui.painter.hints.Selected
|
||||
import org.jetbrains.jewel.ui.painter.hints.Size
|
||||
import org.jetbrains.jewel.ui.painter.hints.Stateful
|
||||
@@ -21,6 +21,7 @@ import org.jetbrains.jewel.ui.painter.hints.Stroke
|
||||
import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider
|
||||
import org.jetbrains.jewel.ui.painter.writeToString
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import javax.xml.XMLConstants
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
@@ -41,8 +42,8 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
// must be ignored the None and hit cache too
|
||||
val painter3 by provider.getPainter(PainterHint.None, PainterHint.None)
|
||||
|
||||
Assert.assertEquals(painter1, painter2)
|
||||
Assert.assertEquals(painter3, painter2)
|
||||
assertEquals(painter1, painter2)
|
||||
assertEquals(painter3, painter2)
|
||||
}
|
||||
}) {
|
||||
awaitIdle()
|
||||
@@ -91,79 +92,72 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
fun `dark painter hint should append suffix when isDark is true`() {
|
||||
val basePath = "icons/github.svg"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Dark(true))
|
||||
Assert.assertEquals("icons/github_dark.svg", patchedPath)
|
||||
assertEquals("icons/github_dark.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dark painter hint should not append suffix when isDark is false`() {
|
||||
val basePath = "icons/github.svg"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Dark(false))
|
||||
Assert.assertEquals(basePath, patchedPath)
|
||||
assertEquals(basePath, patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `override painter hint should replace path entirely`() {
|
||||
val basePath = "icons/github.svg"
|
||||
val patchedPath = testScope(basePath)
|
||||
.applyPathHints(Override(mapOf("icons/github.svg" to "icons/search.svg")))
|
||||
Assert.assertEquals("icons/search.svg", patchedPath)
|
||||
.applyPathHints(PathOverride(mapOf("icons/github.svg" to "icons/search.svg")))
|
||||
assertEquals("icons/search.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `override painter hint should not replace path when not matched`() {
|
||||
val basePath = "icons/github.svg"
|
||||
val patchedPath = testScope(basePath)
|
||||
.applyPathHints(Override(mapOf("icons/settings.svg" to "icons/search.svg")))
|
||||
Assert.assertEquals(basePath, patchedPath)
|
||||
.applyPathHints(PathOverride(mapOf("icons/settings.svg" to "icons/search.svg")))
|
||||
assertEquals(basePath, patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selected painter hint should append suffix when selected is true`() {
|
||||
val basePath = "icons/checkbox.svg"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Selected(true))
|
||||
Assert.assertEquals("icons/checkboxSelected.svg", patchedPath)
|
||||
assertEquals("icons/checkboxSelected.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selected painter hint should not append suffix when selected is false`() {
|
||||
val basePath = "icons/checkbox.svg"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Selected(false))
|
||||
Assert.assertEquals(basePath, patchedPath)
|
||||
assertEquals(basePath, patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `size painter hint should append suffix`() {
|
||||
val basePath = "icons/github.svg"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Size(20))
|
||||
Assert.assertEquals("icons/github@20x20.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `highDpi painter hint should not append suffix for svg`() {
|
||||
val basePath = "icons/github.svg"
|
||||
val patchedPath = testScope(basePath, 2f).applyPathHints(HiDpi())
|
||||
Assert.assertEquals(basePath, patchedPath)
|
||||
assertEquals("icons/github@20x20.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `highDpi painter hint should append suffix when isHiDpi is true`() {
|
||||
val basePath = "icons/github.png"
|
||||
val patchedPath = testScope(basePath, 2f).applyPathHints(HiDpi())
|
||||
Assert.assertEquals("icons/github@2x.png", patchedPath)
|
||||
assertEquals("icons/github@2x.png", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `highDpi painter hint should not append suffix when isHiDpi is false`() {
|
||||
val basePath = "icons/github.png"
|
||||
val patchedPath = testScope(basePath, 1f).applyPathHints(HiDpi())
|
||||
Assert.assertEquals(basePath, patchedPath)
|
||||
assertEquals(basePath, patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `size painter hint should not append suffix for bitmap`() {
|
||||
val basePath = "icons/github.png"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Size(20))
|
||||
Assert.assertEquals(basePath, patchedPath)
|
||||
assertEquals(basePath, patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -184,11 +178,11 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
val basePath = "icons/checkbox.svg"
|
||||
val state = CheckboxState.of(toggleableState = ToggleableState.Off)
|
||||
val patchedPath = testScope(basePath).applyPathHints(Stateful(state.copy(enabled = false)))
|
||||
Assert.assertEquals("icons/checkboxDisabled.svg", patchedPath)
|
||||
assertEquals("icons/checkboxDisabled.svg", patchedPath)
|
||||
|
||||
testScope(basePath)
|
||||
.applyPathHints(Stateful(state.copy(enabled = false, pressed = true, hovered = true, focused = true)))
|
||||
.let { Assert.assertEquals("icons/checkboxDisabled.svg", it) }
|
||||
.let { assertEquals("icons/checkboxDisabled.svg", it) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -197,7 +191,7 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
val state = CheckboxState.of(toggleableState = ToggleableState.Off)
|
||||
val patchedPath = testScope(basePath)
|
||||
.applyPathHints(Stateful(state.copy(enabled = false, pressed = true, hovered = true, focused = true)))
|
||||
Assert.assertEquals("icons/checkboxDisabled.svg", patchedPath)
|
||||
assertEquals("icons/checkboxDisabled.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -205,7 +199,7 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
val basePath = "icons/checkbox.svg"
|
||||
val state = CheckboxState.of(toggleableState = ToggleableState.Off)
|
||||
val patchedPath = testScope(basePath).applyPathHints(Stateful(state.copy(focused = true)))
|
||||
Assert.assertEquals("icons/checkboxFocused.svg", patchedPath)
|
||||
assertEquals("icons/checkboxFocused.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -214,7 +208,7 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
val state = CheckboxState.of(toggleableState = ToggleableState.Off)
|
||||
val patchedPath = testScope(basePath)
|
||||
.applyPathHints(Stateful(state.copy(pressed = true, hovered = true, focused = true)))
|
||||
Assert.assertEquals("icons/checkboxFocused.svg", patchedPath)
|
||||
assertEquals("icons/checkboxFocused.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -222,7 +216,7 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
val basePath = "icons/checkbox.svg"
|
||||
val state = CheckboxState.of(toggleableState = ToggleableState.Off)
|
||||
val patchedPath = testScope(basePath).applyPathHints(Stateful(state.copy(pressed = true)))
|
||||
Assert.assertEquals("icons/checkboxPressed.svg", patchedPath)
|
||||
assertEquals("icons/checkboxPressed.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -231,7 +225,7 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
val state = CheckboxState.of(toggleableState = ToggleableState.Off)
|
||||
val patchedPath = testScope(basePath)
|
||||
.applyPathHints(Stateful(state.copy(pressed = true, hovered = true)))
|
||||
Assert.assertEquals("icons/checkboxPressed.svg", patchedPath)
|
||||
assertEquals("icons/checkboxPressed.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -239,38 +233,38 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
val basePath = "icons/checkbox.svg"
|
||||
val state = CheckboxState.of(toggleableState = ToggleableState.Off)
|
||||
val patchedPath = testScope(basePath).applyPathHints(Stateful(state.copy(hovered = true)))
|
||||
Assert.assertEquals("icons/checkboxHovered.svg", patchedPath)
|
||||
assertEquals("icons/checkboxHovered.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `stroke painter hint should append suffix when color is Specified`() {
|
||||
val basePath = "icons/rerun.svg"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Stroke(Color.White))
|
||||
Assert.assertEquals("icons/rerun_stroke.svg", patchedPath)
|
||||
assertEquals("icons/rerun_stroke.svg", patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `stroke painter hint should not append suffix when color is Unspecified`() {
|
||||
val basePath = "icons/rerun.svg"
|
||||
val patchedPath = testScope(basePath).applyPathHints(Stroke(Color.Unspecified))
|
||||
Assert.assertEquals(basePath, patchedPath)
|
||||
assertEquals(basePath, patchedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `palette painter hint should patch colors correctly in SVG`() {
|
||||
val baseSvg =
|
||||
"""
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect fill="#000000" height="20" stroke="#000000" stroke-opacity="0.5" width="20" x="2" y="2"/>
|
||||
<rect fill="#00ff00" height="16" width="16" x="4" y="4"/>
|
||||
<rect fill="#123456" height="12" width="12" x="6" y="6"/>
|
||||
</svg>
|
||||
""".trimIndent()
|
||||
|<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
| <rect fill="#000000" height="20" stroke="#000000" stroke-opacity="0.5" width="20" x="2" y="2"/>
|
||||
| <rect fill="#00ff00" height="16" width="16" x="4" y="4"/>
|
||||
| <rect fill="#123456" height="12" width="12" x="6" y="6"/>
|
||||
|</svg>
|
||||
""".trimMargin()
|
||||
|
||||
val patchedSvg = testScope("fake_icon.svg")
|
||||
.applyPaletteHints(
|
||||
baseSvg,
|
||||
Palette(
|
||||
ColorBasedPaletteReplacement(
|
||||
mapOf(
|
||||
Color(0x80000000) to Color(0xFF123456),
|
||||
Color.Black to Color.White,
|
||||
@@ -278,15 +272,16 @@ class PainterHintTest : BasicJewelUiTest() {
|
||||
),
|
||||
),
|
||||
)
|
||||
.replace("\r\n", "\n")
|
||||
|
||||
Assert.assertEquals(
|
||||
assertEquals(
|
||||
"""
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect fill="#ffffff" height="20" stroke="#123456" stroke-opacity="1.0" width="20" x="2" y="2"/>
|
||||
<rect fill="#ff0000" height="16" width="16" x="4" y="4"/>
|
||||
<rect fill="#123456" height="12" width="12" x="6" y="6"/>
|
||||
</svg>
|
||||
""".trimIndent(),
|
||||
|<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
| <rect fill="#ffffff" height="20" stroke="#123456" stroke-opacity="1.0" width="20" x="2" y="2"/>
|
||||
| <rect fill="#ff0000" height="16" width="16" x="4" y="4"/>
|
||||
| <rect fill="#123456" height="12" width="12" x="6" y="6"/>
|
||||
|</svg>
|
||||
""".trimMargin(),
|
||||
patchedSvg,
|
||||
)
|
||||
}
|
||||
|
||||