diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Checkbox.kt b/core/src/main/kotlin/org/jetbrains/jewel/Checkbox.kt index 2aaed34b7..2f42e4d09 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Checkbox.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Checkbox.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.semantics.Role import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.text.TextStyle @@ -40,6 +39,10 @@ import org.jetbrains.jewel.CommonStateBitMask.Pressed import org.jetbrains.jewel.CommonStateBitMask.Selected import org.jetbrains.jewel.ToggleableComponentState.Companion.readToggleableState import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterSuffixHint +import org.jetbrains.jewel.painter.hints.Selected +import org.jetbrains.jewel.painter.hints.Stateful import org.jetbrains.jewel.styling.CheckboxColors import org.jetbrains.jewel.styling.CheckboxIcons import org.jetbrains.jewel.styling.CheckboxMetrics @@ -48,7 +51,6 @@ import org.jetbrains.jewel.styling.LocalCheckboxStyle @Composable fun Checkbox( checked: Boolean, - resourceLoader: ResourceLoader, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -71,7 +73,6 @@ fun Checkbox( metrics = metrics, icons = icons, textStyle = textStyle, - resourceLoader = resourceLoader, content = null, ) } @@ -79,7 +80,6 @@ fun Checkbox( @Composable fun TriStateCheckbox( state: ToggleableState, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -101,7 +101,6 @@ fun TriStateCheckbox( metrics = metrics, icons = icons, textStyle = textStyle, - resourceLoader = resourceLoader, content = null, ) } @@ -110,7 +109,6 @@ fun TriStateCheckbox( fun TriStateCheckboxRow( text: String, state: ToggleableState, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -131,7 +129,6 @@ fun TriStateCheckboxRow( colors = colors, metrics = metrics, icons = icons, - resourceLoader = resourceLoader, textStyle = textStyle, ) { Text(text) @@ -142,7 +139,6 @@ fun TriStateCheckboxRow( fun CheckboxRow( text: String, checked: Boolean, - resourceLoader: ResourceLoader, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -165,7 +161,6 @@ fun CheckboxRow( colors = colors, metrics = metrics, icons = icons, - resourceLoader = resourceLoader, textStyle = textStyle, ) { Text(text) @@ -175,7 +170,6 @@ fun CheckboxRow( @Composable fun CheckboxRow( checked: Boolean, - resourceLoader: ResourceLoader, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -199,7 +193,6 @@ fun CheckboxRow( colors = colors, metrics = metrics, icons = icons, - resourceLoader = resourceLoader, textStyle = textStyle, content = content, ) @@ -208,7 +201,6 @@ fun CheckboxRow( @Composable fun TriStateCheckboxRow( state: ToggleableState, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -230,7 +222,6 @@ fun TriStateCheckboxRow( colors = colors, metrics = metrics, icons = icons, - resourceLoader = resourceLoader, textStyle = textStyle, content = content, ) @@ -244,7 +235,6 @@ private fun CheckboxImpl( colors: CheckboxColors, metrics: CheckboxMetrics, icons: CheckboxIcons, - resourceLoader: ResourceLoader, modifier: Modifier, enabled: Boolean, outline: Outline, @@ -268,13 +258,17 @@ private fun CheckboxImpl( checkboxState.copy(pressed = false) is HoverInteraction.Enter -> checkboxState = checkboxState.copy(hovered = true) - is HoverInteraction.Exit -> checkboxState = checkboxState.copy(hovered = true) + is HoverInteraction.Exit -> checkboxState = checkboxState.copy(hovered = false) is FocusInteraction.Focus -> checkboxState = checkboxState.copy(focused = true) is FocusInteraction.Unfocus -> checkboxState = checkboxState.copy(focused = false) } } } + if (LocalSwingCompatMode.current) { + checkboxState = checkboxState.copy(hovered = false, pressed = false) + } + val wrapperModifier = modifier.triStateToggleable( state = state, onClick = onClick, @@ -294,7 +288,15 @@ private fun CheckboxImpl( alignment = Stroke.Alignment.Center, ) - val checkboxPainter by icons.checkbox.getPainter(resourceLoader, checkboxState) + val checkboxPainter by icons.checkbox.getPainter( + if (checkboxState.toggleableState == ToggleableState.Indeterminate) { + CheckBoxIndeterminate + } else { + PainterHint.None + }, + Selected(checkboxState), + Stateful(checkboxState), + ) if (content == null) { Box(contentAlignment = Alignment.TopStart) { @@ -323,6 +325,11 @@ private fun CheckboxImpl( } } +private object CheckBoxIndeterminate : PainterSuffixHint() { + + override fun suffix(): String = "Indeterminate" +} + @Composable private fun CheckBoxImage(outerModifier: Modifier, checkboxPainter: Painter, checkBoxModifier: Modifier) { Box(outerModifier, contentAlignment = Alignment.Center) { diff --git a/core/src/main/kotlin/org/jetbrains/jewel/CircularProgressIndicator.kt b/core/src/main/kotlin/org/jetbrains/jewel/CircularProgressIndicator.kt index b605e96af..b10b5dedd 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/CircularProgressIndicator.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/CircularProgressIndicator.kt @@ -10,73 +10,73 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.loadSvgPainter import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import org.jetbrains.jewel.styling.CircularProgressStyle -import org.jetbrains.jewel.util.toHexString +import org.jetbrains.jewel.util.toRgbaHexString @Composable fun CircularProgressIndicator( - svgLoader: SvgLoader, modifier: Modifier = Modifier, style: CircularProgressStyle = IntelliJTheme.circularProgressStyle, ) { CircularProgressIndicatorImpl( modifier = modifier, - svgLoader = svgLoader, iconSize = DpSize(16.dp, 16.dp), style = style, - frameRetriever = { color -> SpinnerProgressIconGenerator.Small.generateSvgFrames(color.toHexString()) }, + frameRetriever = { color -> SpinnerProgressIconGenerator.Small.generateSvgFrames(color.toRgbaHexString()) }, ) } @Composable fun CircularProgressIndicatorBig( - svgLoader: SvgLoader, modifier: Modifier = Modifier, style: CircularProgressStyle = IntelliJTheme.circularProgressStyle, ) { CircularProgressIndicatorImpl( modifier = modifier, - svgLoader = svgLoader, iconSize = DpSize(32.dp, 32.dp), style = style, - frameRetriever = { color -> SpinnerProgressIconGenerator.Big.generateSvgFrames(color.toHexString()) }, + frameRetriever = { color -> SpinnerProgressIconGenerator.Big.generateSvgFrames(color.toRgbaHexString()) }, ) } @Composable private fun CircularProgressIndicatorImpl( modifier: Modifier = Modifier, - svgLoader: SvgLoader, iconSize: DpSize, style: CircularProgressStyle, frameRetriever: (Color) -> List, ) { val defaultColor = if (IntelliJTheme.isDark) Color(0xFF6F737A) else Color(0xFFA8ADBD) var isFrameReady by remember { mutableStateOf(false) } - var currentFrame: Pair by remember { mutableStateOf("" to 0) } + var currentFrame: Painter? by remember { mutableStateOf(null) } + val currentPainter = currentFrame - if (!isFrameReady) { + if (currentPainter == null) { Box(modifier.size(iconSize)) } else { Icon( modifier = modifier.size(iconSize), - painter = svgLoader.loadRawSvg( - currentFrame.first, - "circularProgressIndicator_frame_${currentFrame.second}", - ), + painter = currentPainter, contentDescription = null, ) } - LaunchedEffect(style.color) { + val density = LocalDensity.current + LaunchedEffect(density, style.color) { val frames = frameRetriever(style.color.takeOrElse { defaultColor }) + .map { + loadSvgPainter(it.byteInputStream(), density) + } while (true) { for (i in frames.indices) { - currentFrame = frames[i] to i + currentFrame = frames[i] isFrameReady = true delay(style.frameTime.inWholeMilliseconds) } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/ContextMenu.kt b/core/src/main/kotlin/org/jetbrains/jewel/ContextMenu.kt index 63ef4c16d..30c6d3ed6 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/ContextMenu.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/ContextMenu.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.input.InputMode import androidx.compose.ui.input.InputModeManager import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalInputModeManager -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.window.rememberCursorPositionProvider @@ -34,7 +33,6 @@ object IntelliJContextMenuRepresentation : ContextMenuRepresentation { true }, style = IntelliJTheme.menuStyle, - resourceLoader = LocalResourceLoader.current, ) { contextItems(items) } @@ -45,7 +43,6 @@ object IntelliJContextMenuRepresentation : ContextMenuRepresentation { @Composable internal fun ContextMenu( onDismissRequest: (InputMode) -> Boolean, - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, focusable: Boolean = true, style: MenuStyle = IntelliJTheme.menuStyle, @@ -81,7 +78,6 @@ internal fun ContextMenu( MenuContent( modifier = modifier, content = content, - resourceLoader = resourceLoader, ) } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Dropdown.kt b/core/src/main/kotlin/org/jetbrains/jewel/Dropdown.kt index 96920ff51..e33960ae8 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Dropdown.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Dropdown.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.input.InputModeManager import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalInputModeManager -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.semantics.Role import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties @@ -44,6 +43,7 @@ import org.jetbrains.jewel.CommonStateBitMask.Hovered import org.jetbrains.jewel.CommonStateBitMask.Pressed import org.jetbrains.jewel.foundation.Stroke import org.jetbrains.jewel.foundation.border +import org.jetbrains.jewel.painter.hints.Stateful import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.LocalMenuStyle import org.jetbrains.jewel.styling.MenuStyle @@ -51,7 +51,6 @@ import org.jetbrains.jewel.util.appendIf @Composable fun Dropdown( - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, enabled: Boolean = true, menuModifier: Modifier = Modifier, @@ -135,7 +134,7 @@ fun Dropdown( .align(Alignment.CenterEnd), contentAlignment = Alignment.Center, ) { - val chevronIcon by style.icons.chevronDown.getPainter(resourceLoader, dropdownState) + val chevronIcon by style.icons.chevronDown.getPainter(Stateful(dropdownState)) Icon( painter = chevronIcon, contentDescription = null, @@ -157,7 +156,6 @@ fun Dropdown( style = style.menuStyle, horizontalAlignment = Alignment.Start, content = menuContent, - resourceLoader = resourceLoader, ) } } @@ -167,7 +165,6 @@ fun Dropdown( internal fun DropdownMenu( onDismissRequest: (InputMode) -> Boolean, horizontalAlignment: Alignment.Horizontal, - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, style: MenuStyle, content: MenuScope.() -> Unit, @@ -208,7 +205,6 @@ internal fun DropdownMenu( MenuContent( modifier = modifier, content = content, - resourceLoader = resourceLoader, ) } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/GlobalColors.kt b/core/src/main/kotlin/org/jetbrains/jewel/GlobalColors.kt index 284be2cbe..ea346e27c 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/GlobalColors.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/GlobalColors.kt @@ -12,6 +12,8 @@ interface GlobalColors { val outlines: OutlineColors val infoContent: Color + + val paneBackground: Color } @Immutable diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt b/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt index 05658f0d6..f3dd04ff8 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt @@ -6,6 +6,7 @@ package org.jetbrains.jewel import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.paint @@ -30,9 +31,48 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.painter.rememberResourcePainterProvider import org.xml.sax.InputSource import java.io.InputStream +@Composable +fun Icon( + resource: String, + contentDescription: String?, + iconClass: Class<*>, + colorFilter: ColorFilter?, + modifier: Modifier = Modifier, +) { + val painterProvider = rememberResourcePainterProvider(resource, iconClass) + val painter by painterProvider.getPainter() + + Icon( + painter = painter, + contentDescription = contentDescription, + modifier = modifier, + colorFilter = colorFilter, + ) +} + +@Composable +fun Icon( + resource: String, + contentDescription: String?, + iconClass: Class<*>, + modifier: Modifier = Modifier, + tint: Color = Color.Unspecified, +) { + val painterProvider = rememberResourcePainterProvider(resource, iconClass) + val painter by painterProvider.getPainter() + + Icon( + painter = painter, + contentDescription = contentDescription, + modifier = modifier, + tint = tint, + ) +} + /** * Icon component that draws [imageVector] using [tint], defaulting to * [Color.Unspecified]. @@ -156,7 +196,7 @@ fun Icon( fun painterResource( resourcePath: String, loader: ResourceLoader, -): Painter = when (resourcePath.substringAfterLast(".")) { +): Painter = when (resourcePath.substringAfterLast(".").lowercase()) { "svg" -> rememberSvgResource(resourcePath, loader) "xml" -> rememberVectorXmlResource(resourcePath, loader) else -> rememberBitmapResource(resourcePath, loader) diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IconButton.kt b/core/src/main/kotlin/org/jetbrains/jewel/IconButton.kt index f610e44ac..61b4e4a2f 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/IconButton.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/IconButton.kt @@ -74,9 +74,7 @@ fun IconButton( .border(style.metrics.borderWidth, border, shape), contentAlignment = Alignment.Center, content = { - onBackground(background) { - content(buttonState) - } + content(buttonState) }, ) } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IconMapper.kt b/core/src/main/kotlin/org/jetbrains/jewel/IconMapper.kt deleted file mode 100644 index 3cc7ee849..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/IconMapper.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.jetbrains.jewel - -import androidx.compose.ui.res.ResourceLoader - -interface IconMapper { - - fun mapPath(originalPath: String, iconData: IntelliJThemeIconData, resourceLoader: ResourceLoader): String -} - -object IntelliJIconMapper : IconMapper { - - private const val VERBOSE = false - - override fun mapPath( - originalPath: String, - iconData: IntelliJThemeIconData, - resourceLoader: ResourceLoader, - ): String { - val normalized = "/${originalPath.trimStart('/')}" - val overriddenPath = iconData.iconOverrides[normalized] ?: normalized - - if (overriddenPath != normalized && VERBOSE) { - println("Found theme icon override: '$originalPath' -> '$overriddenPath'") - } - - return overriddenPath - } -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt index 53b478cc7..c4b3f5048 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJComponentStyling.kt @@ -1,5 +1,8 @@ package org.jetbrains.jewel +import androidx.compose.foundation.LocalContextMenuRepresentation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidedValue import androidx.compose.runtime.Stable import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle @@ -13,6 +16,27 @@ import org.jetbrains.jewel.styling.IconButtonStyle import org.jetbrains.jewel.styling.LabelledTextFieldStyle import org.jetbrains.jewel.styling.LazyTreeStyle import org.jetbrains.jewel.styling.LinkStyle +import org.jetbrains.jewel.styling.LocalCheckboxStyle +import org.jetbrains.jewel.styling.LocalChipStyle +import org.jetbrains.jewel.styling.LocalCircularProgressStyle +import org.jetbrains.jewel.styling.LocalDefaultButtonStyle +import org.jetbrains.jewel.styling.LocalDefaultTabStyle +import org.jetbrains.jewel.styling.LocalDividerStyle +import org.jetbrains.jewel.styling.LocalDropdownStyle +import org.jetbrains.jewel.styling.LocalEditorTabStyle +import org.jetbrains.jewel.styling.LocalGroupHeaderStyle +import org.jetbrains.jewel.styling.LocalHorizontalProgressBarStyle +import org.jetbrains.jewel.styling.LocalIconButtonStyle +import org.jetbrains.jewel.styling.LocalLabelledTextFieldStyle +import org.jetbrains.jewel.styling.LocalLazyTreeStyle +import org.jetbrains.jewel.styling.LocalLinkStyle +import org.jetbrains.jewel.styling.LocalMenuStyle +import org.jetbrains.jewel.styling.LocalOutlinedButtonStyle +import org.jetbrains.jewel.styling.LocalRadioButtonStyle +import org.jetbrains.jewel.styling.LocalScrollbarStyle +import org.jetbrains.jewel.styling.LocalTextAreaStyle +import org.jetbrains.jewel.styling.LocalTextFieldStyle +import org.jetbrains.jewel.styling.LocalTooltipStyle import org.jetbrains.jewel.styling.MenuStyle import org.jetbrains.jewel.styling.RadioButtonStyle import org.jetbrains.jewel.styling.ScrollbarStyle @@ -46,6 +70,32 @@ class IntelliJComponentStyling( val iconButtonStyle: IconButtonStyle, ) { + @Composable + fun providedStyles(): Array> = arrayOf( + LocalCheckboxStyle provides checkboxStyle, + LocalChipStyle provides chipStyle, + LocalContextMenuRepresentation provides IntelliJContextMenuRepresentation, + LocalDefaultButtonStyle provides defaultButtonStyle, + LocalDividerStyle provides dividerStyle, + LocalDropdownStyle provides dropdownStyle, + LocalGroupHeaderStyle provides groupHeaderStyle, + LocalHorizontalProgressBarStyle provides horizontalProgressBarStyle, + LocalLabelledTextFieldStyle provides labelledTextFieldStyle, + LocalLazyTreeStyle provides lazyTreeStyle, + LocalLinkStyle provides linkStyle, + LocalMenuStyle provides menuStyle, + LocalOutlinedButtonStyle provides outlinedButtonStyle, + LocalRadioButtonStyle provides radioButtonStyle, + LocalScrollbarStyle provides scrollbarStyle, + LocalTextAreaStyle provides textAreaStyle, + LocalTextFieldStyle provides textFieldStyle, + LocalDefaultTabStyle provides defaultTabStyle, + LocalEditorTabStyle provides editorTabStyle, + LocalCircularProgressStyle provides circularProgressStyle, + LocalTooltipStyle provides tooltipStyle, + LocalIconButtonStyle provides iconButtonStyle, + ) + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt index 697947ecb..af206b823 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.text.TextStyle import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle @@ -47,7 +46,6 @@ import org.jetbrains.jewel.styling.TabStyle import org.jetbrains.jewel.styling.TextAreaStyle import org.jetbrains.jewel.styling.TextFieldStyle import org.jetbrains.jewel.styling.TooltipStyle -import org.jetbrains.jewel.util.isDark interface IntelliJTheme { @@ -222,12 +220,10 @@ fun IntelliJTheme( fun IntelliJTheme(theme: IntelliJThemeDefinition, content: @Composable () -> Unit) { CompositionLocalProvider( LocalIsDarkTheme provides theme.isDark, - LocalOnDarkBackground provides theme.isDark, LocalContentColor provides theme.contentColor, LocalTextStyle provides theme.defaultTextStyle, LocalGlobalColors provides theme.globalColors, LocalGlobalMetrics provides theme.globalMetrics, - *theme.extensionStyles, content = content, ) } @@ -236,10 +232,6 @@ internal val LocalIsDarkTheme = staticCompositionLocalOf { error("No InDarkTheme provided") } -internal val LocalOnDarkBackground = staticCompositionLocalOf { - error("No OnDarkBackground provided") -} - internal val LocalSwingCompatMode = staticCompositionLocalOf { // By default, Swing compat is not enabled false @@ -254,19 +246,9 @@ val LocalIconData = staticCompositionLocalOf { } /** - * Sets the background color of the current area, - * which affects the style(light or dark) of the icon rendered above it, - * by calculating the luminance. - * If the color is not specified, the style will follow the current theme style. - * Transparent color will be ignored. + * Overrides the dark mode of the current area. */ @Composable -fun onBackground(color: Color, content: @Composable () -> Unit) { - val locals = if (color.isSpecified && color.alpha > 0) { - arrayOf(LocalOnDarkBackground provides color.isDark()) - } else { - emptyArray() - } - - CompositionLocalProvider(values = locals, content) +fun OverrideDarkMode(isDark: Boolean, content: @Composable () -> Unit) { + CompositionLocalProvider(LocalIsDarkTheme provides isDark, content = content) } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt index 133d8dcae..31ba5d9f4 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/IntelliJThemeDefinition.kt @@ -1,7 +1,6 @@ package org.jetbrains.jewel import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ProvidedValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle @@ -16,8 +15,4 @@ interface IntelliJThemeDefinition { val colorPalette: IntelliJThemeColorPalette val iconData: IntelliJThemeIconData - - val extensionStyles: Array> - - fun withExtensions(vararg extensions: ProvidedValue<*>): IntelliJThemeDefinition } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/JewelResourceLoader.kt b/core/src/main/kotlin/org/jetbrains/jewel/JewelResourceLoader.kt deleted file mode 100644 index b0f315471..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/JewelResourceLoader.kt +++ /dev/null @@ -1,78 +0,0 @@ -package org.jetbrains.jewel - -import androidx.compose.ui.res.ResourceLoader -import java.io.InputStream - -abstract class JewelResourceLoader : ResourceLoader { - - private val verbose = true - - protected fun loadResourceOrNull(path: String, classLoaders: List): InputStream? { - for (classLoader in classLoaders) { - val stream = classLoader.getResourceAsStream(path) - if (stream != null) { - if (verbose) println("Found resource: '$path'") - return stream - } - - // Didn't work, let's see if we can simplify the icon to a base state - val simplifiedPath = trySimplifyingPath(path) - if (simplifiedPath != null) { - if (verbose) println("Resource not found: '$path'. Trying simplified path: '$simplifiedPath'") - return loadResourceOrNull(simplifiedPath, classLoaders) - } - } - - return null - } - - private fun trySimplifyingPath(originalPath: String): String? { - // Step 1: attempt to remove dark qualifier - val darkIndex = originalPath.lastIndexOf("_dark") - if (darkIndex > 0) { - return originalPath.removeRange(darkIndex, darkIndex + "_dark".length) - } - - // Step 2: attempt to remove extended state qualifiers (pressed, hovered) - val pressedIndex = originalPath.lastIndexOf("Pressed") - if (pressedIndex > 0) { - return originalPath.removeRange(pressedIndex, pressedIndex + "Pressed".length) - } - - val hoveredIndex = originalPath.lastIndexOf("Hovered") - if (hoveredIndex > 0) { - return originalPath.removeRange(hoveredIndex, hoveredIndex + "Hovered".length) - } - - // Step 3: attempt to remove state qualifiers (indeterminate, selected, focused, disabled) - val indeterminateIndex = originalPath.lastIndexOf("Indeterminate") - if (indeterminateIndex > 0) { - return originalPath.removeRange(indeterminateIndex, indeterminateIndex + "Indeterminate".length) - } - - val selectedIndex = originalPath.lastIndexOf("Selected") - if (selectedIndex > 0) { - return originalPath.removeRange(selectedIndex, selectedIndex + "Selected".length) - } - - val focusedIndex = originalPath.lastIndexOf("Focused") - if (focusedIndex > 0) { - return originalPath.removeRange(focusedIndex, focusedIndex + "Focused".length) - } - - val disabledIndex = originalPath.lastIndexOf("Disabled") - if (disabledIndex > 0) { - return originalPath.removeRange(disabledIndex, disabledIndex + "Disabled".length) - } - - // Step 4: attempt to remove density and size qualifiers - val retinaIndex = originalPath.lastIndexOf("@2x") - if (retinaIndex > 0) { - return originalPath.removeRange(retinaIndex, retinaIndex + "@2x".length) - } - - // TODO remove size qualifiers (e.g., "@20x20") - - return null - } -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/JewelSvgLoader.kt b/core/src/main/kotlin/org/jetbrains/jewel/JewelSvgLoader.kt deleted file mode 100644 index ab1df5d01..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/JewelSvgLoader.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.jetbrains.jewel - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.ResourceLoader -import androidx.compose.ui.res.loadSvgPainter -import java.io.InputStream -import java.util.concurrent.ConcurrentHashMap - -@Immutable -class JewelSvgLoader(private val svgPatcher: SvgPatcher) : SvgLoader { - - private val cache = ConcurrentHashMap() - private val rawSvgCache = ConcurrentHashMap() - - @Composable - override fun loadSvgResource( - svgPath: String, - resourceLoader: ResourceLoader, - pathPatcher: @Composable (String) -> String, - ): Painter { - val patchedPath = pathPatcher(svgPath) - cache[patchedPath]?.let { return it } - - val painter = rememberPatchedSvgResource(patchedPath, resourceLoader) - cache[patchedPath] = painter - return painter - } - - @Composable - private fun rememberPatchedSvgResource( - resourcePath: String, - loader: ResourceLoader, - ): Painter { - val density = LocalDensity.current - - val painter = useResource(resourcePath, loader) { - loadSvgPainter(it.patchColors(resourcePath), density) - } - return remember(resourcePath, density, loader) { painter } - } - - @Composable - override fun loadRawSvg(rawSvg: String, key: String): Painter = - rawSvg.byteInputStream().use { loadRawSvg(it, key) } - - @Composable - override fun loadRawSvg(rawSvg: InputStream, key: String): Painter { - rawSvgCache[key]?.let { return it } - - val painter = rememberRawSvgResource(rawSvg, key) - cache[key] = painter - return painter - } - - @Composable - private fun rememberRawSvgResource(rawSvg: InputStream, key: String): Painter { - val density = LocalDensity.current - - val painter = loadSvgPainter(rawSvg, density) - return remember(key, density) { painter } - } - - private fun InputStream.patchColors(resourcePath: String): InputStream = - svgPatcher.patchSvg(this, resourcePath).byteInputStream() - - // Copied from androidx.compose.ui.res.Resources - private inline fun useResource( - resourcePath: String, - loader: ResourceLoader, - block: (InputStream) -> T, - ): T = loader.load(resourcePath).use(block) -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/LazyTree.kt b/core/src/main/kotlin/org/jetbrains/jewel/LazyTree.kt index 3dce4abe2..bc2da6877 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/LazyTree.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/LazyTree.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.res.ResourceLoader import org.jetbrains.jewel.foundation.lazy.SelectableLazyItemScope import org.jetbrains.jewel.foundation.tree.BasicLazyTree import org.jetbrains.jewel.foundation.tree.DefaultTreeViewKeyActions @@ -20,7 +19,6 @@ import org.jetbrains.jewel.styling.LazyTreeStyle @Composable fun LazyTree( tree: Tree, - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, onElementClick: (Tree.Element) -> Unit = {}, treeState: TreeState = rememberTreeState(), @@ -51,7 +49,7 @@ fun LazyTree( keyActions = keyActions, chevronContent = { elementState -> val painterProvider = style.icons.chevron(elementState.isExpanded, elementState.isSelected) - val painter by painterProvider.getPainter(resourceLoader) + val painter by painterProvider.getPainter() Icon(painter = painter, contentDescription = null) }, ) { diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Link.kt b/core/src/main/kotlin/org/jetbrains/jewel/Link.kt index 48ee76234..cef6271f8 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Link.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Link.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.input.InputMode import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.platform.LocalInputModeManager -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily @@ -42,18 +41,18 @@ import org.jetbrains.jewel.CommonStateBitMask.Hovered import org.jetbrains.jewel.CommonStateBitMask.Pressed import org.jetbrains.jewel.IntelliJTheme.Companion.isSwingCompatMode import org.jetbrains.jewel.foundation.onHover +import org.jetbrains.jewel.painter.PainterProvider +import org.jetbrains.jewel.painter.hints.Stateful import org.jetbrains.jewel.styling.LinkStyle import org.jetbrains.jewel.styling.LocalLinkStyle import org.jetbrains.jewel.styling.LocalMenuStyle import org.jetbrains.jewel.styling.MenuStyle -import org.jetbrains.jewel.styling.PainterProvider import org.jetbrains.jewel.util.appendIf import java.awt.Cursor @Composable fun Link( text: String, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -83,7 +82,6 @@ fun Link( lineHeight = lineHeight, interactionSource = interactionSource, style = style, - resourceLoader = resourceLoader, icon = null, ) } @@ -91,7 +89,6 @@ fun Link( @Composable fun ExternalLink( text: String, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -121,7 +118,6 @@ fun ExternalLink( lineHeight = lineHeight, interactionSource = interactionSource, style = style, - resourceLoader = resourceLoader, icon = style.icons.externalLink, ) } @@ -129,7 +125,6 @@ fun ExternalLink( @Composable fun DropdownLink( text: String, - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, enabled: Boolean = true, fontSize: TextUnit = TextUnit.Unspecified, @@ -172,7 +167,6 @@ fun DropdownLink( interactionSource = interactionSource, style = style, icon = style.icons.dropdownChevron, - resourceLoader = resourceLoader, ) if (expanded) { @@ -188,7 +182,6 @@ fun DropdownLink( style = menuStyle, horizontalAlignment = Alignment.Start, content = menuContent, - resourceLoader = resourceLoader, ) } } @@ -198,7 +191,6 @@ fun DropdownLink( private fun LinkImpl( text: String, style: LinkStyle, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier, enabled: Boolean, @@ -211,7 +203,7 @@ private fun LinkImpl( overflow: TextOverflow, lineHeight: TextUnit, interactionSource: MutableInteractionSource, - icon: PainterProvider?, + icon: PainterProvider?, ) { var linkState by remember(interactionSource, enabled) { mutableStateOf(LinkState.of(enabled = enabled)) @@ -285,7 +277,7 @@ private fun LinkImpl( ) if (icon != null) { - val iconPainter by icon.getPainter(resourceLoader, linkState) + val iconPainter by icon.getPainter(Stateful(linkState)) Icon( iconPainter, contentDescription = null, diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt b/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt index cba4e67e2..7569834d5 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Menu.kt @@ -54,7 +54,6 @@ import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalInputModeManager -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection @@ -70,6 +69,7 @@ import org.jetbrains.jewel.CommonStateBitMask.Selected import org.jetbrains.jewel.foundation.Stroke import org.jetbrains.jewel.foundation.border import org.jetbrains.jewel.foundation.onHover +import org.jetbrains.jewel.painter.hints.Stateful import org.jetbrains.jewel.styling.LocalMenuStyle import org.jetbrains.jewel.styling.MenuItemColors import org.jetbrains.jewel.styling.MenuItemMetrics @@ -79,7 +79,6 @@ import org.jetbrains.jewel.styling.MenuStyle fun PopupMenu( onDismissRequest: (InputMode) -> Boolean, horizontalAlignment: Alignment.Horizontal, - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, style: MenuStyle = IntelliJTheme.menuStyle, content: MenuScope.() -> Unit, @@ -120,7 +119,6 @@ fun PopupMenu( MenuContent( modifier = modifier, content = content, - resourceLoader = resourceLoader, ) } } @@ -128,7 +126,6 @@ fun PopupMenu( @Composable internal fun MenuContent( - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, style: MenuStyle = IntelliJTheme.menuStyle, content: MenuScope.() -> Unit, @@ -179,7 +176,6 @@ internal fun MenuContent( enabled = it.isEnabled, submenu = it.submenu, content = it.content, - resourceLoader = resourceLoader, ) } @@ -389,7 +385,6 @@ fun MenuItem( @Composable fun MenuSubmenuItem( - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, @@ -463,7 +458,7 @@ fun MenuSubmenuItem( content() } - val chevronPainter by style.icons.submenuChevron.getPainter(resourceLoader, itemState) + val chevronPainter by style.icons.submenuChevron.getPainter(Stateful(itemState)) Icon( painter = chevronPainter, tint = itemColors.iconTintFor(itemState).value, @@ -485,7 +480,6 @@ fun MenuSubmenuItem( }, style = style, content = submenu, - resourceLoader = resourceLoader, ) } } @@ -521,7 +515,6 @@ private fun Size.subtract(paddingValues: PaddingValues, density: Density, layout @Composable internal fun Submenu( - resourceLoader: ResourceLoader, onDismissRequest: (InputMode) -> Boolean, modifier: Modifier = Modifier, style: MenuStyle = IntelliJTheme.menuStyle, @@ -563,7 +556,6 @@ internal fun Submenu( MenuContent( modifier = modifier, content = content, - resourceLoader = resourceLoader, ) } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/PaletteMapper.kt b/core/src/main/kotlin/org/jetbrains/jewel/PaletteMapper.kt deleted file mode 100644 index 5ebe6666a..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/PaletteMapper.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.jetbrains.jewel - -import androidx.compose.runtime.Immutable -import androidx.compose.ui.graphics.Color - -// Replicates com.intellij.ide.ui.UITheme.PaletteScopeManager's functionality -// (note that in Swing, there is also a RadioButtons scope, but while it gets -// written to, it never gets accessed by the actual color patching, so we ignore -// writing any Radio button-related entries and read the CheckBox values for them) -@Immutable -class PaletteMapper( - private val ui: Scope, - private val checkBoxes: Scope, - private val trees: Scope, -) { - - fun getScopeForPath(path: String?): Scope? { - if (path == null) return ui - if (!path.contains("com/intellij/ide/ui/laf/icons/")) return ui - - val file = path.substringAfterLast('/') - return when { - file == "treeCollapsed.svg" || file == "treeExpanded.svg" -> trees - // ⚠️ This next line is not a copy-paste error — the code in UITheme.PaletteScopeManager.getScopeByPath() - // says they share the same colors - file.startsWith("check") || file.startsWith("radio") -> checkBoxes - else -> null - } - } - - companion object { - - val Empty = PaletteMapper(Scope.Empty, Scope.Empty, Scope.Empty) - } - - @Immutable - @JvmInline - value class Scope(val colorOverrides: Map) { - - fun mapColorOrNull(originalColor: Color): Color? = - colorOverrides[originalColor] - - override fun toString(): String = "PaletteMapper.Scope(colorOverrides=$colorOverrides)" - - companion object { - - val Empty = Scope(emptyMap()) - } - } -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/RadioButton.kt b/core/src/main/kotlin/org/jetbrains/jewel/RadioButton.kt index 051ecf91f..5b3bdb34a 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/RadioButton.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/RadioButton.kt @@ -22,11 +22,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import org.jetbrains.jewel.CommonStateBitMask.Active @@ -36,12 +34,13 @@ import org.jetbrains.jewel.CommonStateBitMask.Hovered import org.jetbrains.jewel.CommonStateBitMask.Pressed import org.jetbrains.jewel.CommonStateBitMask.Selected import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.painter.hints.Selected +import org.jetbrains.jewel.painter.hints.Stateful import org.jetbrains.jewel.styling.RadioButtonStyle @Composable fun RadioButton( selected: Boolean, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -56,7 +55,6 @@ fun RadioButton( modifier = modifier, enabled = enabled, outline = outline, - resourceLoader = resourceLoader, interactionSource = interactionSource, style = style, textStyle = textStyle, @@ -68,7 +66,6 @@ fun RadioButton( fun RadioButtonRow( text: String, selected: Boolean, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -83,7 +80,6 @@ fun RadioButtonRow( modifier = modifier, enabled = enabled, outline = outline, - resourceLoader = resourceLoader, interactionSource = interactionSource, style = style, textStyle = textStyle, @@ -95,7 +91,6 @@ fun RadioButtonRow( @Composable fun RadioButtonRow( selected: Boolean, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, @@ -114,16 +109,13 @@ fun RadioButtonRow( interactionSource = interactionSource, style = style, textStyle = textStyle, - resourceLoader = resourceLoader, content = content, ) } -@OptIn(ExperimentalComposeUiApi::class) @Composable private fun RadioButtonImpl( selected: Boolean, - resourceLoader: ResourceLoader, onClick: () -> Unit, modifier: Modifier, enabled: Boolean, @@ -139,6 +131,7 @@ private fun RadioButtonImpl( remember(selected, enabled) { radioButtonState = radioButtonState.copy(selected = selected, enabled = enabled) } + LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { @@ -155,6 +148,10 @@ private fun RadioButtonImpl( } } + if (LocalSwingCompatMode.current) { + radioButtonState = radioButtonState.copy(hovered = false, pressed = false) + } + val wrapperModifier = modifier.selectable( selected = selected, onClick = onClick, @@ -169,7 +166,10 @@ private fun RadioButtonImpl( val radioButtonModifier = Modifier .size(metrics.radioButtonSize) .outline(radioButtonState, outline, outlineShape = CircleShape, alignment = Stroke.Alignment.Inside) - val radioButtonPainter by style.icons.radioButton.getPainter(resourceLoader, radioButtonState) + val radioButtonPainter by style.icons.radioButton.getPainter( + Selected(radioButtonState), + Stateful(radioButtonState), + ) if (content == null) { RadioButtonImage(wrapperModifier, radioButtonPainter, radioButtonModifier) diff --git a/core/src/main/kotlin/org/jetbrains/jewel/ResourceLoader.kt b/core/src/main/kotlin/org/jetbrains/jewel/ResourceLoader.kt deleted file mode 100644 index cba4ee0eb..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/ResourceLoader.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.jewel - -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.res.ResourceLoader - -val LocalResourceLoader = staticCompositionLocalOf { - ResourceLoader.Default -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/SimpleResourceLoader.kt b/core/src/main/kotlin/org/jetbrains/jewel/SimpleResourceLoader.kt deleted file mode 100644 index c245a0f28..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/SimpleResourceLoader.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.jetbrains.jewel - -import java.io.InputStream - -class SimpleResourceLoader(private val classLoader: ClassLoader) : JewelResourceLoader(), ClassLoaderProvider { - - override val classLoaders - get() = listOf(javaClass.classLoader, classLoader) - - override fun load(resourcePath: String): InputStream { - val path = resourcePath.removePrefix("/") - val parentClassLoader = - StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) - .callerClass - .classLoader - val resource = loadResourceOrNull(path, classLoaders + parentClassLoader) - - return requireNotNull(resource) { "Resource '$resourcePath' not found (tried loading: '$path')" } - } -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/SvgLoader.kt b/core/src/main/kotlin/org/jetbrains/jewel/SvgLoader.kt deleted file mode 100644 index a2b42d81c..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/SvgLoader.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.jetbrains.jewel - -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.ResourceLoader -import java.io.InputStream - -interface SvgLoader { - - /** - * Creates a [Painter] from the provided [svgPath], using the - * [resourceLoader] and [pathPatcher] to locate the correct resource - * file. The icon colors are patched, if needed, and the result is - * cached in memory. - * - * @param svgPath The path to the SVG resource to load. - * @param resourceLoader The [ResourceLoader] to use to load the resource. - * @param pathPatcher A function that can be used to patch the path of - * the resource (e.g., for mapping to New UI icons in the IJ Platform). - */ - @Composable - fun loadSvgResource( - svgPath: String, - resourceLoader: ResourceLoader, - pathPatcher: @Composable (String) -> String, - ): Painter - - /** - * Creates a [Painter] from the provided [rawSvg], using the [key] - * to maintain an in-memory cache of the loaded SVG. - * - * The [rawSvg] stream is **not** automatically closed after being - * consumed. - * - * Note: when loading raw SVGs, icon color patching is not possible. - * The SVG contents are not manipulated in any way before loading. - * - * @param rawSvg An [InputStream] containing a raw SVG. - * @param key A unique name for the SVG that is being loaded, used - * for in-memory caching. - */ - @Composable - fun loadRawSvg(rawSvg: InputStream, key: String): Painter - - /** - * Creates a [Painter] from the provided [rawSvg], using the [key] - * to maintain an in-memory cache of the loaded SVG. - * - * Note: when loading raw SVGs, icon color patching is not possible. - * The SVG contents are not manipulated in any way before loading. - * - * @param rawSvg A [String] containing a raw SVG. - * @param key A unique name for the SVG that is being loaded, used - * for in-memory caching. - */ - @Composable - fun loadRawSvg(rawSvg: String, key: String): Painter -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/SvgPatcher.kt b/core/src/main/kotlin/org/jetbrains/jewel/SvgPatcher.kt deleted file mode 100644 index 2cc427fc5..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/SvgPatcher.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.jewel - -import java.io.InputStream - -interface SvgPatcher { - - fun patchSvg(rawSvg: InputStream, path: String?): String -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/TabStrip.kt b/core/src/main/kotlin/org/jetbrains/jewel/TabStrip.kt index 43818bd5a..7d9210af9 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/TabStrip.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/TabStrip.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalLayoutDirection import org.jetbrains.jewel.foundation.onHover @@ -93,7 +94,7 @@ sealed class TabData { abstract val selected: Boolean abstract val label: String - abstract val tabIconResource: String? // TODO use painters instead + abstract val icon: Painter? abstract val closable: Boolean abstract val onClose: () -> Unit abstract val onClick: () -> Unit @@ -102,7 +103,7 @@ sealed class TabData { class Default( override val selected: Boolean, override val label: String, - override val tabIconResource: String? = null, + override val icon: Painter? = null, override val closable: Boolean = true, override val onClose: () -> Unit = {}, override val onClick: () -> Unit = {}, @@ -116,7 +117,7 @@ sealed class TabData { if (selected != other.selected) return false if (label != other.label) return false - if (tabIconResource != other.tabIconResource) return false + if (icon != other.icon) return false if (closable != other.closable) return false if (onClose != other.onClose) return false if (onClick != other.onClick) return false @@ -127,7 +128,7 @@ sealed class TabData { override fun hashCode(): Int { var result = selected.hashCode() result = 31 * result + label.hashCode() - result = 31 * result + (tabIconResource?.hashCode() ?: 0) + result = 31 * result + (icon?.hashCode() ?: 0) result = 31 * result + closable.hashCode() result = 31 * result + onClose.hashCode() result = 31 * result + onClick.hashCode() @@ -139,7 +140,7 @@ sealed class TabData { class Editor( override val selected: Boolean, override val label: String, - override val tabIconResource: String? = null, + override val icon: Painter? = null, override val closable: Boolean = true, override val onClose: () -> Unit = {}, override val onClick: () -> Unit = {}, @@ -153,7 +154,7 @@ sealed class TabData { if (selected != other.selected) return false if (label != other.label) return false - if (tabIconResource != other.tabIconResource) return false + if (icon != other.icon) return false if (closable != other.closable) return false if (onClose != other.onClose) return false if (onClick != other.onClick) return false @@ -164,7 +165,7 @@ sealed class TabData { override fun hashCode(): Int { var result = selected.hashCode() result = 31 * result + label.hashCode() - result = 31 * result + (tabIconResource?.hashCode() ?: 0) + result = 31 * result + (icon?.hashCode() ?: 0) result = 31 * result + closable.hashCode() result = 31 * result + onClose.hashCode() result = 31 * result + onClick.hashCode() diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt b/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt index ff36c4697..baeb74cf6 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.isTertiary import androidx.compose.ui.input.pointer.onPointerEvent -import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import org.jetbrains.jewel.CommonStateBitMask.Active @@ -43,6 +42,7 @@ import org.jetbrains.jewel.CommonStateBitMask.Focused import org.jetbrains.jewel.CommonStateBitMask.Hovered import org.jetbrains.jewel.CommonStateBitMask.Pressed import org.jetbrains.jewel.CommonStateBitMask.Selected +import org.jetbrains.jewel.painter.hints.Stateful @Composable internal fun TabImpl( @@ -115,9 +115,8 @@ internal fun TabImpl( horizontalArrangement = Arrangement.spacedBy(tabStyle.metrics.closeContentGap), verticalAlignment = Alignment.CenterVertically, ) { - tabData.tabIconResource?.let { icon -> - val iconPainter = painterResource(icon, LocalResourceLoader.current) - Image(modifier = Modifier.alpha(iconAlpha), painter = iconPainter, contentDescription = null) + tabData.icon?.let { icon -> + Image(modifier = Modifier.alpha(iconAlpha), painter = icon, contentDescription = null) } Text( @@ -144,7 +143,7 @@ internal fun TabImpl( } } } - val closePainter by tabStyle.icons.close.getPainter(LocalResourceLoader.current, closeButtonState) + val closePainter by tabStyle.icons.close.getPainter(Stateful(closeButtonState)) Image( modifier = Modifier.clickable( interactionSource = closeActionInteractionSource, diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Text.kt b/core/src/main/kotlin/org/jetbrains/jewel/Text.kt index 467a65642..10e0c9427 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Text.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Text.kt @@ -79,8 +79,8 @@ fun Text( style: TextStyle = IntelliJTheme.textStyle, ) { val textColor = color.takeOrElse { - style.color.takeOrElse { - LocalContentColor.current + LocalContentColor.current.takeOrElse { + style.color } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Tooltip.kt b/core/src/main/kotlin/org/jetbrains/jewel/Tooltip.kt index f504a5e63..90565aa4f 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Tooltip.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Tooltip.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupPositionProvider import org.jetbrains.jewel.styling.TooltipStyle +import org.jetbrains.jewel.util.isDark @Composable fun Tooltip( @@ -63,7 +64,7 @@ fun Tooltip( ) .padding(style.metrics.contentPadding), ) { - onBackground(style.colors.background) { + OverrideDarkMode(style.colors.background.isDark()) { tooltip() } } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt new file mode 100644 index 000000000..342b657c0 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHint.kt @@ -0,0 +1,112 @@ +package org.jetbrains.jewel.painter + +import androidx.compose.runtime.Immutable +import org.w3c.dom.Element + +/** + * A [PainterHint] is a hint for [PainterProvider] on how to load a [Painter][androidx.compose.ui.graphics.painter.Painter]. + * It can used to patch the path of the resource being loaded (e.g., for New UI + * icon path mapping, and handling the dark theme variants), replace colors in + * an SVG based on the theme palette, etc. + * + * Custom implementations are not allowed. There are two types of hints: + * * [PainterPathHint] modifies the path of the resource to load + * * [PainterSvgPatchHint] modifies the contents of SVG resources + * + * @see PainterPathHint + * @see PainterSvgPatchHint + */ +@Immutable +sealed interface PainterHint { + + /** + * An empty [PainterHint], it will be ignored. + */ + companion object None : PainterHint { + + override fun toString(): String = "None" + } +} + +/** + * A [PainterHint] that modifies the path of the resource being loaded. + * Usage examples are applying the New UI icon mappings, or picking up + * dark theme variants of icons. + */ +@Immutable +interface PainterPathHint : PainterHint { + /** + * Replace the entire path with the given value. + */ + fun patch(path: String): String +} + +/** + * A [PainterHint] that modifies the module/jar path of the resource being loaded. + * Main used in bridge mode. + */ +@Immutable +interface PainterResourcePathHint : PainterHint { + + /** + * Patch the resource path with context classLoaders(for bridge module). + */ + fun patch(path: String, classLoaders: List): String +} + +/** + * A [PainterHint] that patches the content of SVG resources. It is only applied + * to SVG resources; it doesn't affect other types of resources. + */ +@Immutable +interface PainterSvgPatchHint : PainterHint { + + /** + * Patch the SVG content. + */ + fun patch(element: Element) +} + +/** + * A [PainterHint] that adds a prefix to a resource file name, without + * changing the rest of the path. + * For example, if the original path is `icons/MyIcon.svg`, and the prefix + * is `Dark`, the patched path will be `icons/DarkMyIcon.svg`. + */ +@Immutable +abstract class PainterPrefixHint : PainterPathHint { + + override fun patch(path: String): String = buildString { + append(path.substringBeforeLast('/', "")) + append('/') + append(prefix()) + append(path.substringBeforeLast('.').substringAfterLast('/')) + + append('.') + append(path.substringAfterLast('.')) + } + + abstract fun prefix(): String +} + +/** + * A [PainterHint] that adds a suffix to a resource file name, without + * changing the rest of the path nor the extension. + * For example, if the original path is `icons/MyIcon.svg`, and the suffix + * is `_dark`, the patched path will be `icons/MyIcon_dark.svg`. + */ +@Immutable +abstract class PainterSuffixHint : PainterPathHint { + + override fun patch(path: String): String = buildString { + append(path.substringBeforeLast('/', "")) + append('/') + append(path.substringBeforeLast('.').substringAfterLast('/')) + append(suffix()) + + append('.') + append(path.substringAfterLast('.')) + } + + abstract fun suffix(): String +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHintsProvider.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHintsProvider.kt new file mode 100644 index 000000000..4f1d42a40 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterHintsProvider.kt @@ -0,0 +1,35 @@ +package org.jetbrains.jewel.painter + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.staticCompositionLocalOf +import org.jetbrains.jewel.IntelliJTheme +import org.jetbrains.jewel.painter.hints.Dark + +/** + * Provides [hints][PainterHint] to a [PainterProvider]. + * + * @see DarkPainterHintsProvider + * @see org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider + * @see org.jetbrains.jewel.intui.standalone.StandalonePainterHintsProvider + */ +@Immutable +interface PainterHintsProvider { + + @Composable + fun hints(path: String): List +} + +/** + * The default [PainterHintsProvider] to load dark theme icon variants. + * It will provide the [Dark] hint when [LocalIsDarkTheme][org.jetbrains.jewel.LocalIsDarkTheme] is true. + */ +object DarkPainterHintsProvider : PainterHintsProvider { + + @Composable + override fun hints(path: String): List = listOf(Dark(IntelliJTheme.isDark)) +} + +val LocalPainterHintsProvider = staticCompositionLocalOf { + DarkPainterHintsProvider +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterProvider.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterProvider.kt new file mode 100644 index 000000000..bcc62bf30 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/PainterProvider.kt @@ -0,0 +1,22 @@ +package org.jetbrains.jewel.painter + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.State +import androidx.compose.ui.graphics.painter.Painter + +/** + * Implementations of this interface should handle the passed [PainterHint]s correctly. + * For now, this means calling [PainterPathHint.patch] and [PainterSvgPatchHint.patch]. + * Most likely, a [PainterProvider] should also hold the resource path and [ClassLoader] + * references. + */ +@Immutable +interface PainterProvider { + + /** + * Provides a [Painter] using the specified [PainterHint]s. + */ + @Composable + fun getPainter(vararg hints: PainterHint): State +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/ResourcePainterProvider.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/ResourcePainterProvider.kt new file mode 100644 index 000000000..0056fe4eb --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/ResourcePainterProvider.kt @@ -0,0 +1,185 @@ +package org.jetbrains.jewel.painter + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.loadImageBitmap +import androidx.compose.ui.res.loadSvgPainter +import androidx.compose.ui.res.loadXmlImageVector +import org.jetbrains.jewel.util.inDebugMode +import org.w3c.dom.Document +import org.xml.sax.InputSource +import java.io.IOException +import java.io.InputStream +import java.io.StringWriter +import java.net.URL +import java.util.concurrent.ConcurrentHashMap +import javax.xml.XMLConstants +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.Transformer +import javax.xml.transform.TransformerException +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +/** + * Provide [Painter] by resources in the module and jars, it use the ResourceResolver to load resources. + * + * It will cache the painter by [PainterHint]s, so it is safe to call [getPainter] multiple times. + */ +@Immutable +class ResourcePainterProvider( + private val basePath: String, + vararg classLoaders: ClassLoader, +) : PainterProvider { + + private val cache = ConcurrentHashMap() + + private val contextClassLoaders = classLoaders.toList() + + private val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance() + .apply { setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) } + + @Composable + override fun getPainter(vararg hints: PainterHint): State { + val resolvedHints = (hints.toList() + LocalPainterHintsProvider.current.hints(basePath)) + .filter { it != PainterHint.None } + + val cacheKey = resolvedHints.hashCode() + + if (inDebugMode && cache[cacheKey] != null) { + println("Cache hit for $basePath(${resolvedHints.joinToString()})") + } + + val painter = cache.getOrPut(cacheKey) { + if (inDebugMode) { + println("Cache miss for $basePath(${resolvedHints.joinToString()})") + } + loadPainter(resolvedHints) + } + + return rememberUpdatedState(painter) + } + + @Composable + private fun loadPainter(hints: List): Painter { + val format = basePath.substringAfterLast(".").lowercase() + + val pathStack = buildSet { + var path = basePath + + add(path) + + hints.forEach { + path = when (it) { + is PainterResourcePathHint -> it.patch(path, contextClassLoaders) + is PainterPathHint -> it.patch(path) + else -> return@forEach + } + add(path) + } + }.reversed() + + val url = pathStack.firstNotNullOfOrNull { + resolveResource(it) + } ?: error("Resource '$basePath(${hints.joinToString()})' not found") + + val density = LocalDensity.current + + return when (format) { + "svg" -> { + remember(url, density, hints) { + patchSvg(url.openStream(), hints).use { + if (inDebugMode) { + println("Load icon $basePath(${hints.joinToString()}) from $url") + } + loadSvgPainter(it, density) + } + } + } + + "xml" -> { + val vector = url.openStream().use { + loadXmlImageVector(InputSource(it), density) + } + rememberVectorPainter(vector) + } + + else -> { + remember(url, density) { + val bitmap = url.openStream().use { + loadImageBitmap(it) + } + BitmapPainter(bitmap) + } + } + } + } + + private fun resolveResource(path: String): URL? { + val normalized = path.removePrefix("/") + + for (classLoader in contextClassLoaders) { + val url = classLoader.getResource(normalized) + if (url != null) { + if (inDebugMode) println("Found resource: '$normalized'") + return url + } + } + + return null + } + + private fun patchSvg(inputStream: InputStream, hints: List): InputStream { + if (hints.all { it !is PainterSvgPatchHint }) { + return inputStream + } + + inputStream.use { + val builder = documentBuilderFactory.newDocumentBuilder() + val document = builder.parse(inputStream) + + hints.forEach { hint -> + if (hint !is PainterSvgPatchHint) return@forEach + hint.patch(document.documentElement) + } + + return document.writeToString().byteInputStream() + } + } + + private fun Document.writeToString(): String { + val tf = TransformerFactory.newInstance() + val transformer: Transformer + + try { + transformer = tf.newTransformer() + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") + + val writer = StringWriter() + transformer.transform(DOMSource(this), StreamResult(writer)) + return writer.buffer.toString() + } catch (e: TransformerException) { + error("Unable to render XML document to string: ${e.message}") + } catch (e: IOException) { + error("Unable to render XML document to string: ${e.message}") + } + } +} + +@Composable +fun rememberResourcePainterProvider( + path: String, + iconClass: Class<*>, +): PainterProvider { + return remember(path, iconClass.classLoader) { + ResourcePainterProvider(path, iconClass.classLoader) + } +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Dark.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Dark.kt new file mode 100644 index 000000000..c5f5a0610 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Dark.kt @@ -0,0 +1,19 @@ +package org.jetbrains.jewel.painter.hints + +import androidx.compose.runtime.Immutable +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterSuffixHint + +@Immutable +private object DarkImpl : PainterSuffixHint() { + + override fun suffix(): String = "_dark" + + override fun toString(): String = "Dark" +} + +fun Dark(isDark: Boolean = true): PainterHint = if (isDark) { + DarkImpl +} else { + PainterHint.None +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Override.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Override.kt new file mode 100644 index 000000000..8c5e8417b --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Override.kt @@ -0,0 +1,28 @@ +package org.jetbrains.jewel.painter.hints + +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterPathHint + +private class OverrideImpl(private val iconOverride: Map) : PainterPathHint { + + override fun patch(path: String): String = iconOverride[path] ?: path + + override fun toString(): String = "Override(${iconOverride.hashCode()})" + + override fun hashCode(): Int = iconOverride.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is OverrideImpl) return false + + if (iconOverride != other.iconOverride) return false + + return true + } +} + +fun Override(override: Map): PainterHint = if (override.isEmpty()) { + PainterHint.None +} else { + OverrideImpl(override) +} diff --git a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntelliJSvgPatcher.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Palette.kt similarity index 51% rename from int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntelliJSvgPatcher.kt rename to core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Palette.kt index dfa88a4a6..c5dd528d5 100644 --- a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntelliJSvgPatcher.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Palette.kt @@ -1,66 +1,39 @@ -package org.jetbrains.jewel.intui.core +package org.jetbrains.jewel.painter.hints import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color -import org.jetbrains.jewel.PaletteMapper -import org.jetbrains.jewel.SvgPatcher -import org.jetbrains.jewel.util.toHexString -import org.w3c.dom.Document +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterSvgPatchHint +import org.jetbrains.jewel.util.toRgbaHexString import org.w3c.dom.Element -import java.io.IOException -import java.io.InputStream -import java.io.StringWriter -import javax.xml.XMLConstants -import javax.xml.parsers.DocumentBuilderFactory -import javax.xml.transform.OutputKeys -import javax.xml.transform.Transformer -import javax.xml.transform.TransformerException -import javax.xml.transform.TransformerFactory -import javax.xml.transform.dom.DOMSource -import javax.xml.transform.stream.StreamResult import kotlin.math.roundToInt @Immutable -class IntelliJSvgPatcher(private val mapper: PaletteMapper) : SvgPatcher { +private class PaletteImpl(val map: Map) : PainterSvgPatchHint { - private val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance() - .apply { setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) } + override fun patch(element: Element) { + element.patchColorAttribute("fill", map) + element.patchColorAttribute("stroke", map) - override fun patchSvg(rawSvg: InputStream, path: String?): String { - val builder = documentBuilderFactory.newDocumentBuilder() - val document = builder.parse(rawSvg) - - val scope = mapper.getScopeForPath(path) - if (scope != null) { - document.documentElement.patchColors(scope) - } - - return document.writeToString() - } - - private fun Element.patchColors(mapperScope: PaletteMapper.Scope) { - patchColorAttribute("fill", mapperScope) - patchColorAttribute("stroke", mapperScope) - - val nodes = childNodes + val nodes = element.childNodes val length = nodes.length for (i in 0 until length) { val item = nodes.item(i) if (item is Element) { - item.patchColors(mapperScope) + patch(item) } } } - private fun Element.patchColorAttribute(attrName: String, mapperScope: PaletteMapper.Scope) { + private fun Element.patchColorAttribute(attrName: String, pattern: Map) { val color = getAttribute(attrName) val opacity = getAttribute("$attrName-opacity") if (color.isNotEmpty()) { val alpha = opacity.toFloatOrNull() ?: 1.0f val originalColor = tryParseColor(color, alpha) ?: return - val newColor = mapperScope.mapColorOrNull(originalColor) ?: return - setAttribute(attrName, newColor.copy(alpha = alpha).toHexString()) + val newColor = pattern[originalColor] ?: return + setAttribute(attrName, newColor.copy(alpha = alpha).toRgbaHexString()) } } @@ -110,34 +83,22 @@ class IntelliJSvgPatcher(private val mapper: PaletteMapper) : SvgPatcher { } } - private fun Document.writeToString(): String { - val tf = TransformerFactory.newInstance() - val transformer: Transformer - - try { - transformer = tf.newTransformer() - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") - - val writer = StringWriter() - transformer.transform(DOMSource(this), StreamResult(writer)) - return writer.buffer.toString() - } catch (e: TransformerException) { - error("Unable to render XML document to string: ${e.message}") - } catch (e: IOException) { - error("Unable to render XML document to string: ${e.message}") - } - } + override fun toString(): String = "Palette(${map.hashCode()})" + + override fun hashCode(): Int = map.hashCode() override fun equals(other: Any?): Boolean { if (this === other) return true - if (javaClass != other?.javaClass) return false + if (other !is PaletteImpl) return false - other as IntelliJSvgPatcher + if (map != other.map) return false - return mapper == other.mapper + return true } +} - override fun hashCode(): Int = mapper.hashCode() - - override fun toString(): String = "IntelliJSvgPatcher(mapper=$mapper)" +fun Palette(map: Map): PainterHint = if (map.isEmpty()) { + PainterHint.None +} else { + PaletteImpl(map) } diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Selected.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Selected.kt new file mode 100644 index 000000000..a2239aaeb --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Selected.kt @@ -0,0 +1,22 @@ +package org.jetbrains.jewel.painter.hints + +import androidx.compose.runtime.Immutable +import org.jetbrains.jewel.SelectableComponentState +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterSuffixHint + +@Immutable +private object SelectedImpl : PainterSuffixHint() { + + override fun suffix(): String = "Selected" + + override fun toString(): String = "Selected" +} + +fun Selected(selected: Boolean = true): PainterHint = if (selected) { + SelectedImpl +} else { + PainterHint.None +} + +fun Selected(state: SelectableComponentState): PainterHint = Selected(state.isSelected) diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt new file mode 100644 index 000000000..e0e645a49 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Size.kt @@ -0,0 +1,33 @@ +package org.jetbrains.jewel.painter.hints + +import androidx.compose.runtime.Immutable +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterSuffixHint + +@Immutable +private class SizeImpl(private val size: String) : PainterSuffixHint() { + + override fun suffix(): String = buildString { + append("@") + append(size) + } + + override fun toString(): String = "Size(size=$size)" + + override fun hashCode(): Int = size.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is SizeImpl) return false + + if (size != other.size) return false + + return true + } +} + +fun Size(size: String?): PainterHint = if (size.isNullOrEmpty()) { + PainterHint.None +} else { + SizeImpl(size) +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Stateful.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Stateful.kt new file mode 100644 index 000000000..2d6763b6e --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Stateful.kt @@ -0,0 +1,38 @@ +package org.jetbrains.jewel.painter.hints + +import androidx.compose.runtime.Immutable +import org.jetbrains.jewel.FocusableComponentState +import org.jetbrains.jewel.InteractiveComponentState +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterSuffixHint + +@Immutable +private class StatefulImpl(private val state: InteractiveComponentState) : PainterSuffixHint() { + + override fun suffix(): String = buildString { + if (state.isEnabled) { + when { + state is FocusableComponentState && state.isFocused -> append("Focused") + state.isPressed -> append("Pressed") + state.isHovered -> append("Hovered") + } + } else { + append("Disabled") + } + } + + override fun toString(): String = "Stateful(state=$state)" + + override fun hashCode(): Int = state.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is StatefulImpl) return false + + if (state != other.state) return false + + return true + } +} + +fun Stateful(state: InteractiveComponentState): PainterHint = StatefulImpl(state) diff --git a/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Stroke.kt b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Stroke.kt new file mode 100644 index 000000000..f836d7795 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Stroke.kt @@ -0,0 +1,19 @@ +package org.jetbrains.jewel.painter.hints + +import androidx.compose.runtime.Immutable +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterSuffixHint + +@Immutable +private object StrokeImpl : PainterSuffixHint() { + + override fun suffix(): String = "_stroke" + + override fun toString(): String = "Stroke" +} + +fun Stroke(stroked: Boolean = true): PainterHint = if (stroked) { + StrokeImpl +} else { + PainterHint.None +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/CheckboxStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/CheckboxStyling.kt index 84ca159ce..744d82c9b 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/CheckboxStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/CheckboxStyling.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpSize import org.jetbrains.jewel.CheckboxState +import org.jetbrains.jewel.painter.PainterProvider @Immutable interface CheckboxStyle { @@ -64,7 +65,7 @@ interface CheckboxMetrics { @Immutable interface CheckboxIcons { - val checkbox: PainterProvider + val checkbox: PainterProvider } val LocalCheckboxStyle = staticCompositionLocalOf { diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/DropdownStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/DropdownStyling.kt index 2604fcfab..04a548866 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/DropdownStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/DropdownStyling.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import org.jetbrains.jewel.DropdownState +import org.jetbrains.jewel.painter.PainterProvider @Stable interface DropdownStyle { @@ -112,7 +113,7 @@ interface DropdownMetrics { @Immutable interface DropdownIcons { - val chevronDown: PainterProvider + val chevronDown: PainterProvider } val LocalDropdownStyle = staticCompositionLocalOf { diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/LazyTreeStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/LazyTreeStyling.kt index 4055bfa92..334bc2950 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/LazyTreeStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/LazyTreeStyling.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import org.jetbrains.jewel.foundation.tree.TreeElementState +import org.jetbrains.jewel.painter.PainterProvider @Stable interface LazyTreeStyle { @@ -56,10 +57,10 @@ interface LazyTreeMetrics { @Immutable interface LazyTreeIcons { - val chevronCollapsed: PainterProvider - val chevronExpanded: PainterProvider - val chevronSelectedCollapsed: PainterProvider - val chevronSelectedExpanded: PainterProvider + val chevronCollapsed: PainterProvider + val chevronExpanded: PainterProvider + val chevronSelectedCollapsed: PainterProvider + val chevronSelectedExpanded: PainterProvider @Composable fun chevron(isExpanded: Boolean, isSelected: Boolean) = diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/LinkStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/LinkStyling.kt index 26f50c505..88916c96c 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/LinkStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/LinkStyling.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import org.jetbrains.jewel.LinkState +import org.jetbrains.jewel.painter.PainterProvider @Immutable interface LinkStyle { @@ -55,8 +56,8 @@ interface LinkMetrics { @Immutable interface LinkIcons { - val dropdownChevron: PainterProvider - val externalLink: PainterProvider + val dropdownChevron: PainterProvider + val externalLink: PainterProvider } @Immutable diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/MenuStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/MenuStyling.kt index fcaf775fd..88637ea43 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/MenuStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/MenuStyling.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import org.jetbrains.jewel.MenuItemState +import org.jetbrains.jewel.painter.PainterProvider @Stable interface MenuStyle { @@ -121,7 +122,7 @@ interface MenuItemColors { @Immutable interface MenuIcons { - val submenuChevron: PainterProvider + val submenuChevron: PainterProvider } val LocalMenuStyle = staticCompositionLocalOf { diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/PainterProvider.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/PainterProvider.kt deleted file mode 100644 index ff0779d9d..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/PainterProvider.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.jetbrains.jewel.styling - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.State -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.ResourceLoader - -@Immutable -interface PainterProvider { - - /** - * Obtain a painter, with no extra data. It is equivalent to calling - * [getPainter] with a `null` value for the `extraData` argument. This - * overload should only be used for stateless painter providers (i.e., when - * [T] is [Unit]). - * - * A [resourceLoader] that allows loading the corresponding resource must - * be loading. For example, if your resource is in module `my-module`'s - * resources, the [resourceLoader] must be pointing to `my-module`s - * classloader. - * - * Passing the wrong [ResourceLoader] will cause your resources not to - * load, and you will get cryptic errors. Please also note that using - * [ResourceLoader.Default] will probably cause loading to fail if you are - * trying to load the icons from a different module. For example, if Jewel - * is running in the IDE and you use [ResourceLoader.Default] to try and - * load a default IDE resource, it will fail. - * - * @see getPainter - */ - @Composable - fun getPainter(resourceLoader: ResourceLoader): State = getPainter(resourceLoader, null) - - /** - * Obtain a painter for the provided [extraData]. - * - * A [resourceLoader] that allows loading the corresponding resource must - * be loading. For example, if your resource is in module `my-module`'s - * resources, the [resourceLoader] must be pointing to `my-module`s - * classloader. - * - * Passing the wrong [ResourceLoader] will cause your resources not to - * load, and you will get cryptic errors. Please also note that using - * [ResourceLoader.Default] will probably cause loading to fail if you are - * trying to load the icons from a different module. For example, if Jewel - * is running in the IDE and you use [ResourceLoader.Default] to try and - * load a default IDE resource, it will fail. - */ - @Composable - fun getPainter(resourceLoader: ResourceLoader, extraData: T?): State -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/RadioButtonStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/RadioButtonStyling.kt index 99cbe6303..03012a417 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/RadioButtonStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/RadioButtonStyling.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import org.jetbrains.jewel.RadioButtonState +import org.jetbrains.jewel.painter.PainterProvider @Immutable interface RadioButtonStyle { @@ -50,7 +51,7 @@ interface RadioButtonMetrics { @Immutable interface RadioButtonIcons { - val radioButton: PainterProvider + val radioButton: PainterProvider } val LocalRadioButtonStyle = staticCompositionLocalOf { diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/ResourcePainterProvider.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/ResourcePainterProvider.kt deleted file mode 100644 index cb98285c6..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/ResourcePainterProvider.kt +++ /dev/null @@ -1,118 +0,0 @@ -package org.jetbrains.jewel.styling - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.ResourceLoader -import org.jetbrains.jewel.IconMapper -import org.jetbrains.jewel.IntelliJIconMapper -import org.jetbrains.jewel.IntelliJThemeIconData -import org.jetbrains.jewel.InteractiveComponentState -import org.jetbrains.jewel.InternalJewelApi -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.SvgLoader -import org.jetbrains.jewel.painterResource - -open class ResourcePainterProvider @InternalJewelApi constructor( - private val basePath: String, - private val svgLoader: SvgLoader, - private val iconMapper: IconMapper, - private val iconData: IntelliJThemeIconData, - private val pathPatcher: ResourcePathPatcher, -) : PainterProvider { - - @Composable - override fun getPainter(resourceLoader: ResourceLoader, extraData: T?): State { - val isSvg = basePath.endsWith(".svg", ignoreCase = true) - val painter = if (isSvg) { - svgLoader.loadSvgResource(basePath, resourceLoader) { - patchPath(basePath, resourceLoader, extraData) - } - } else { - val patchedPath = patchPath(basePath, resourceLoader, extraData) - painterResource(patchedPath, resourceLoader) - } - - return rememberUpdatedState(painter) - } - - @Composable - protected open fun patchPath( - basePath: String, - resourceLoader: ResourceLoader, - extraData: T?, - ): String { - val patched = pathPatcher.patchVariant(basePath, resourceLoader, extraData) - val override = iconMapper.mapPath(patched, iconData, resourceLoader) - return pathPatcher.patchTheme(override, resourceLoader) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ResourcePainterProvider<*> - - if (basePath != other.basePath) return false - if (svgLoader != other.svgLoader) return false - if (iconMapper != other.iconMapper) return false - if (iconData != other.iconData) return false - if (pathPatcher != other.pathPatcher) return false - - return true - } - - override fun hashCode(): Int { - var result = basePath.hashCode() - result = 31 * result + svgLoader.hashCode() - result = 31 * result + iconMapper.hashCode() - result = 31 * result + iconData.hashCode() - result = 31 * result + pathPatcher.hashCode() - return result - } - - override fun toString(): String = - "ResourcePainterProvider(basePath='$basePath', svgLoader=$svgLoader, iconMapper=$iconMapper, iconData=$iconData, pathPatcher=$pathPatcher)" - - @OptIn(InternalJewelApi::class) // These are the public constructors - companion object Factory { - - fun stateless(basePath: String, svgLoader: SvgLoader, iconData: IntelliJThemeIconData) = - ResourcePainterProvider( - basePath, - svgLoader, - IntelliJIconMapper, - iconData, - SimpleResourcePathPatcher(), - ) - - fun stateful( - basePath: String, - svgLoader: SvgLoader, - iconData: IntelliJThemeIconData, - pathPatcher: ResourcePathPatcher = StatefulResourcePathPatcher(), - ) = - ResourcePainterProvider(basePath, svgLoader, IntelliJIconMapper, iconData, pathPatcher) - } -} - -@Composable -fun rememberStatelessPainterProvider( - basePath: String, - svgLoader: SvgLoader, - iconData: IntelliJThemeIconData = LocalIconData.current, -): ResourcePainterProvider = remember(basePath, iconData) { - ResourcePainterProvider.stateless(basePath, svgLoader, iconData) -} - -@Composable -fun rememberStatefulPainterProvider( - basePath: String, - svgLoader: SvgLoader, - pathPatcher: ResourcePathPatcher = StatefulResourcePathPatcher(), - iconData: IntelliJThemeIconData = LocalIconData.current, -): ResourcePainterProvider = remember(basePath, iconData, pathPatcher) { - ResourcePainterProvider.stateful(basePath, svgLoader, iconData, pathPatcher) -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/ResourcePathPatcher.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/ResourcePathPatcher.kt deleted file mode 100644 index 1974a9b92..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/ResourcePathPatcher.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.jetbrains.jewel.styling - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.ResourceLoader - -interface ResourcePathPatcher { - - @Composable - fun patchVariant(basePath: String, resourceLoader: ResourceLoader, extraData: T?): String - - @Composable - fun patchTheme(basePath: String, resourceLoader: ResourceLoader): String -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/SimpleResourcePathPatcher.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/SimpleResourcePathPatcher.kt deleted file mode 100644 index 2575a62ac..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/SimpleResourcePathPatcher.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.jetbrains.jewel.styling - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.ResourceLoader -import org.jetbrains.jewel.LocalOnDarkBackground - -open class SimpleResourcePathPatcher : ResourcePathPatcher { - - @Composable - final override fun patchVariant(basePath: String, resourceLoader: ResourceLoader, extraData: T?) = - buildString { - append(basePath.substringBeforeLast('/', "")) - append('/') - append(basePath.substringBeforeLast('.').substringAfterLast('/')) - - append(injectVariantTokens(extraData)) - - append('.') - append(basePath.substringAfterLast('.')) - } - - @Composable - final override fun patchTheme(basePath: String, resourceLoader: ResourceLoader): String = buildString { - append(basePath.substringBeforeLast('/', "")) - append('/') - append(basePath.substringBeforeLast('.').substringAfterLast('/')) - - // TODO load HiDPI rasterized images ("@2x") - // TODO load sized SVG images (e.g., "@20x20") - - if (LocalOnDarkBackground.current) { - append("_dark") - } - - append('.') - append(basePath.substringAfterLast('.')) - } - - @Composable - protected open fun injectVariantTokens(extraData: T? = null): String = "" -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/StatefulResourcePathPatcher.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/StatefulResourcePathPatcher.kt deleted file mode 100644 index 6291d40cc..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/StatefulResourcePathPatcher.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.jetbrains.jewel.styling - -import androidx.compose.runtime.Composable -import org.jetbrains.jewel.FocusableComponentState -import org.jetbrains.jewel.IntelliJTheme -import org.jetbrains.jewel.InteractiveComponentState -import org.jetbrains.jewel.SelectableComponentState - -class StatefulResourcePathPatcher( - private val prefixTokensProvider: (state: T) -> String = { "" }, - private val suffixTokensProvider: (state: T) -> String = { "" }, -) : SimpleResourcePathPatcher() { - - @Composable - override fun injectVariantTokens(extraData: T?): String = buildString { - if (extraData == null) return@buildString - - append(prefixTokensProvider(extraData)) - - if (extraData is SelectableComponentState && extraData.isSelected) { - append("Selected") - } - - if (extraData.isEnabled) { - when { - extraData is FocusableComponentState && extraData.isFocused -> append("Focused") - !IntelliJTheme.isSwingCompatMode && extraData.isPressed -> append("Pressed") - !IntelliJTheme.isSwingCompatMode && extraData.isHovered -> append("Hovered") - } - } else { - append("Disabled") - } - - append(suffixTokensProvider(extraData)) - } -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/styling/TabStyling.kt b/core/src/main/kotlin/org/jetbrains/jewel/styling/TabStyling.kt index 30e47e17e..142505fe7 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/styling/TabStyling.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/styling/TabStyling.kt @@ -8,8 +8,8 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp -import org.jetbrains.jewel.ButtonState import org.jetbrains.jewel.TabState +import org.jetbrains.jewel.painter.PainterProvider @Stable interface TabStyle { @@ -23,7 +23,7 @@ interface TabStyle { @Immutable interface TabIcons { - val close: PainterProvider + val close: PainterProvider } @Stable diff --git a/core/src/main/kotlin/org/jetbrains/jewel/themes/PaletteMapperFactory.kt b/core/src/main/kotlin/org/jetbrains/jewel/themes/PaletteMapperFactory.kt deleted file mode 100644 index 915a52a72..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/themes/PaletteMapperFactory.kt +++ /dev/null @@ -1,85 +0,0 @@ -package org.jetbrains.jewel.themes - -import androidx.compose.ui.graphics.Color -import org.jetbrains.jewel.PaletteMapper - -abstract class PaletteMapperFactory { - - protected fun createInternal( - iconColorPalette: Map, - keyPalette: Map, - themeColors: Map, - isDark: Boolean, - ): PaletteMapper { - // This partially emulates what com.intellij.ide.ui.UITheme.loadFromJson does - val ui = mutableMapOf() - val checkBoxes = mutableMapOf() - val trees = mutableMapOf() - - for ((key, value) in iconColorPalette) { - 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 = themeColors[value]?.let { rawColor -> - when (rawColor) { - is Int -> Color(rawColor) - is String -> rawColor.toColorOrNull() - else -> null - } - } - - // If either the key or the resolved value aren't valid colors, ignore the entry - val keyAsColor = resolveKeyColor(key, keyPalette, isDark) ?: continue - val resolvedColor = namedColor ?: value?.toColorOrNull() ?: continue - - // Save the new entry (oldColor -> newColor) in the map - map[keyAsColor] = resolvedColor - } - - return PaletteMapper( - ui = PaletteMapper.Scope(ui), - checkBoxes = PaletteMapper.Scope(checkBoxes), - trees = PaletteMapper.Scope(trees), - ) - } - - // See com.intellij.ide.ui.UITheme.toColorString - private fun resolveKeyColor(key: String, keyPalette: Map, isDark: Boolean): Color? { - val darkKey = "$key.Dark" - val resolvedKey = if (isDark && keyPalette.containsKey(darkKey)) darkKey else key - return keyPalette[resolvedKey]?.toColorOrNull() - } - - private fun selectMap( - key: String, - checkBoxes: MutableMap, - trees: MutableMap, - ui: MutableMap, - ) = when { - key.startsWith("Checkbox.") -> checkBoxes - key.startsWith("Tree.iconColor.") -> trees - key.startsWith("Objects.") || key.startsWith("Actions.") || key.startsWith("#") -> ui - else -> { - logInfo("No PaletteMapperScope defined for key '$key'") - null - } - } - - private fun String.toColorOrNull() = - lowercase() - .removePrefix("#") - .removePrefix("0x") - .let { - when (it.length) { - 3 -> "ff${it[0]}${it[0]}${it[1]}${it[1]}${it[2]}${it[2]}" - 4 -> "${it[0]}${it[0]}${it[1]}${it[1]}${it[2]}${it[2]}${it[3]}${it[3]}" - 6 -> "ff$it" - 8 -> it - else -> null - } - } - ?.toLongOrNull(radix = 16) - ?.let { Color(it) } - - abstract fun logInfo(message: String) -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/themes/StandalonePaletteMapperFactory.kt b/core/src/main/kotlin/org/jetbrains/jewel/themes/StandalonePaletteMapperFactory.kt deleted file mode 100644 index 2c0687e91..000000000 --- a/core/src/main/kotlin/org/jetbrains/jewel/themes/StandalonePaletteMapperFactory.kt +++ /dev/null @@ -1,82 +0,0 @@ -package org.jetbrains.jewel.themes - -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import org.jetbrains.jewel.IntelliJThemeColorPalette -import org.jetbrains.jewel.IntelliJThemeIconData -import org.jetbrains.jewel.PaletteMapper - -object StandalonePaletteMapperFactory : PaletteMapperFactory() { - - // Extracted from com.intellij.ide.ui.UITheme#colorPalette - private val colorsToMap = mapOf( - "Actions.Red" to "#DB5860", - "Actions.Red.Dark" to "#C75450", - "Actions.Yellow" to "#EDA200", - "Actions.Yellow.Dark" to "#F0A732", - "Actions.Green" to "#59A869", - "Actions.Green.Dark" to "#499C54", - "Actions.Blue" to "#389FD6", - "Actions.Blue.Dark" to "#3592C4", - "Actions.Grey" to "#6E6E6E", - "Actions.Grey.Dark" to "#AFB1B3", - "Actions.GreyInline" to "#7F8B91", - "Actions.GreyInline.Dark" to "#7F8B91", - "Objects.Grey" to "#9AA7B0", - "Objects.Blue" to "#40B6E0", - "Objects.Green" to "#62B543", - "Objects.Yellow" to "#F4AF3D", - "Objects.YellowDark" to "#D9A343", - "Objects.Purple" to "#B99BF8", - "Objects.Pink" to "#F98B9E", - "Objects.Red" to "#F26522", - "Objects.RedStatus" to "#E05555", - "Objects.GreenAndroid" to "#3DDC84", - "Objects.BlackText" to "#231F20", - "Checkbox.Background.Default" to "#FFFFFF", - "Checkbox.Background.Default.Dark" to "#43494A", - "Checkbox.Background.Disabled" to "#F2F2F2", - "Checkbox.Background.Disabled.Dark" to "#3C3F41", - "Checkbox.Border.Default" to "#b0b0b0", - "Checkbox.Border.Default.Dark" to "#6B6B6B", - "Checkbox.Border.Disabled" to "#BDBDBD", - "Checkbox.Border.Disabled.Dark" to "#545556", - "Checkbox.Focus.Thin.Default" to "#7B9FC7", - "Checkbox.Focus.Thin.Default.Dark" to "#466D94", - "Checkbox.Focus.Wide" to "#97C3F3", - "Checkbox.Focus.Wide.Dark" to "#3D6185", - "Checkbox.Foreground.Disabled" to "#ABABAB", - "Checkbox.Foreground.Disabled.Dark" to "#606060", - "Checkbox.Background.Selected" to "#4F9EE3", - "Checkbox.Background.Selected.Dark" to "#43494A", - "Checkbox.Border.Selected" to "#4B97D9", - "Checkbox.Border.Selected.Dark" to "#6B6B6B", - "Checkbox.Foreground.Selected" to "#FEFEFE", - "Checkbox.Foreground.Selected.Dark" to "#A7A7A7", - "Checkbox.Focus.Thin.Selected" to "#ACCFF7", - "Checkbox.Focus.Thin.Selected.Dark" to "#466D94", - "Tree.iconColor" to "#808080", - "Tree.iconColor.Dark" to "#AFB1B3", - ) - - fun create( - isDark: Boolean, - iconData: IntelliJThemeIconData, - colorPalette: IntelliJThemeColorPalette, - ): PaletteMapper = - createInternal( - iconColorPalette = iconData.colorPalette, - keyPalette = colorsToMap, - themeColors = colorPalette.rawMap.asColorStringsMap(), - isDark = isDark, - ) - - private fun Map.asColorStringsMap(): Map = - mapValues { (_, color) -> - color.toArgb() - } - - override fun logInfo(message: String) { - println("[${javaClass.simpleName}] $message") - } -} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/util/ColorExtensions.kt b/core/src/main/kotlin/org/jetbrains/jewel/util/ColorExtensions.kt index 1f847f407..13e24ad92 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/util/ColorExtensions.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/util/ColorExtensions.kt @@ -4,7 +4,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import kotlin.math.roundToInt -fun Color.toHexString(): String { +/** + * Converts a [Color] to a RGBA formatted color('#RRGGBBAA') hex string, etc. #FFFFFF1A. + */ +fun Color.toRgbaHexString(): String { val r = Integer.toHexString((red * 255).roundToInt()) val g = Integer.toHexString((green * 255).roundToInt()) val b = Integer.toHexString((blue * 255).roundToInt()) @@ -22,4 +25,22 @@ fun Color.toHexString(): String { } } +/** + * Converts a RGBA formatted color('#RRGGBBAA') hex string to a [Color], etc. #FFFFFF1A. + */ +fun Color.Companion.fromRGBAHexString(rgba: String) = + rgba.lowercase() + .removePrefix("#") + .let { + when (it.length) { + 3 -> "ff${it[0]}${it[0]}${it[1]}${it[1]}${it[2]}${it[2]}" + 4 -> "${it[3]}${it[3]}${it[0]}${it[0]}${it[1]}${it[1]}${it[2]}${it[2]}" + 6 -> "ff$it" + 8 -> "${it[6]}${it[7]}${it[0]}${it[1]}${it[2]}${it[3]}${it[4]}${it[5]}" + else -> null + } + } + ?.toLongOrNull(radix = 16) + ?.let { Color(it) } + fun Color.isDark(): Boolean = (luminance() + 0.05) / 0.05 < 4.5 diff --git a/core/src/main/kotlin/org/jetbrains/jewel/util/Debug.kt b/core/src/main/kotlin/org/jetbrains/jewel/util/Debug.kt new file mode 100644 index 000000000..80124508a --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/util/Debug.kt @@ -0,0 +1,8 @@ +package org.jetbrains.jewel.util + +import org.jetbrains.jewel.InternalJewelApi + +@InternalJewelApi +val inDebugMode by lazy { + System.getProperty("org.jetbrains.jewel.debug")?.toBoolean() ?: false +} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt index ae684bc0b..9a0de460d 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt @@ -17,9 +17,10 @@ import com.jetbrains.JBR import org.jetbrains.jewel.Icon import org.jetbrains.jewel.IconButton import org.jetbrains.jewel.IntelliJTheme -import org.jetbrains.jewel.LocalResourceLoader +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterProvider +import org.jetbrains.jewel.painter.PainterSuffixHint import org.jetbrains.jewel.styling.IconButtonStyle -import org.jetbrains.jewel.styling.PainterProvider import org.jetbrains.jewel.window.styling.TitleBarStyle import java.awt.Frame import java.awt.event.MouseEvent @@ -84,7 +85,7 @@ import java.awt.event.WindowEvent @Composable private fun TitleBarScope.ControlButton( onClick: () -> Unit, state: DecoratedWindowState, - painterProvider: PainterProvider, + painterProvider: PainterProvider, description: String, style: TitleBarStyle = IntelliJTheme.defaultTitleBarStyle, iconButtonStyle: IconButtonStyle = style.paneButtonStyle, @@ -96,6 +97,13 @@ import java.awt.event.WindowEvent .size(style.metrics.titlePaneButtonSize), style = iconButtonStyle, ) { - Icon(painterProvider.getPainter(LocalResourceLoader.current, state).value, description) + Icon(painterProvider.getPainter(if (state.isActive) PainterHint.None else Inactive).value, description) } } + +private object Inactive : PainterSuffixHint() { + + override fun suffix(): String = "Inactive" + + override fun toString(): String = "Inactive" +} diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt index a3ebffa25..4db0f8ab5 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.focusProperties @@ -41,9 +40,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.LocalContentColor -import org.jetbrains.jewel.onBackground +import org.jetbrains.jewel.OverrideDarkMode import org.jetbrains.jewel.styling.LocalDropdownStyle import org.jetbrains.jewel.styling.LocalIconButtonStyle +import org.jetbrains.jewel.util.isDark import org.jetbrains.jewel.window.styling.TitleBarStyle import org.jetbrains.jewel.window.utils.DesktopPlatform import org.jetbrains.jewel.window.utils.macos.MacUtil @@ -106,7 +106,7 @@ internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__" LocalIconButtonStyle provides style.iconButtonStyle, LocalDropdownStyle provides style.dropdownStyle, ) { - onBackground(background) { + OverrideDarkMode(background.isDark()) { val scope = TitleBarScopeImpl(titleBarInfo.title, titleBarInfo.icon) scope.content(state) } diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt index 8f9790258..ef2712480 100644 --- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt +++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt @@ -8,9 +8,9 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.IconButtonStyle -import org.jetbrains.jewel.styling.PainterProvider import org.jetbrains.jewel.window.DecoratedWindowState @Stable @@ -77,13 +77,13 @@ interface TitleBarMetrics { @Immutable interface TitleBarIcons { - val minimizeButton: PainterProvider + val minimizeButton: PainterProvider - val maximizeButton: PainterProvider + val maximizeButton: PainterProvider - val restoreButton: PainterProvider + val restoreButton: PainterProvider - val closeButton: PainterProvider + val closeButton: PainterProvider } val LocalTitleBarStyle = staticCompositionLocalOf { diff --git a/ide-laf-bridge/build.gradle.kts b/ide-laf-bridge/build.gradle.kts index 5ebda090e..186521f93 100644 --- a/ide-laf-bridge/build.gradle.kts +++ b/ide-laf-bridge/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } dependencies { - api(projects.intUi.intUiCore) { + api(projects.intUi.intUiStandalone) { exclude(group = "org.jetbrains.kotlinx") } when (supportedIJVersion()) { diff --git a/ide-laf-bridge/ide-laf-bridge-232/build.gradle.kts b/ide-laf-bridge/ide-laf-bridge-232/build.gradle.kts index cb3d89359..aef584e2d 100644 --- a/ide-laf-bridge/ide-laf-bridge-232/build.gradle.kts +++ b/ide-laf-bridge/ide-laf-bridge-232/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - api(projects.intUi.intUiStandalone) + api(projects.intUi.intUiCore) compileOnly(libs.bundles.idea232) } diff --git a/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt b/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt new file mode 100644 index 000000000..821241065 --- /dev/null +++ b/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt @@ -0,0 +1,34 @@ +package org.jetbrains.jewel.bridge + +import com.intellij.util.ui.DirProvider +import org.jetbrains.jewel.painter.PainterResourcePathHint + +internal object BridgeOverride : PainterResourcePathHint { + + private val dirProvider = DirProvider() + + private val patchIconPath by lazy { + val clazz = Class.forName("com.intellij.ui.icons.CachedImageIconKt") + val patchIconPath = clazz.getMethod("patchIconPath", String::class.java, ClassLoader::class.java) + patchIconPath.isAccessible = true + patchIconPath + } + + override fun patch(path: String, classLoaders: List): String { + // For all provided classloaders, we try to get the patched path, both using + // the original path, and an "abridged" path that has gotten the icon path prefix + // removed (the classloader is set up differently in prod IDEs and when running + // from Gradle, and the icon could be in either place depending on the environment) + val fallbackPath = path.removePrefix(dirProvider.dir()) + val patchedPath = classLoaders.firstNotNullOfOrNull { classLoader -> + val patchedPathAndClassLoader = + patchIconPath.invoke(null, path.removePrefix("/"), classLoader) + ?: patchIconPath.invoke(null, fallbackPath, classLoader) + patchedPathAndClassLoader as? Pair<*, *> + }?.first as? String + + return patchedPath ?: path + } + + override fun toString(): String = "BridgeOverride" +} diff --git a/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt b/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt new file mode 100644 index 000000000..861af5ce4 --- /dev/null +++ b/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt @@ -0,0 +1,52 @@ +package org.jetbrains.jewel.bridge + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.intellij.ide.ui.UITheme +import com.intellij.openapi.diagnostic.thisLogger +import org.jetbrains.jewel.InternalJewelApi +import org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.util.fromRGBAHexString + +@InternalJewelApi +class BridgePainterHintsProvider private constructor( + isDark: Boolean, + intellijIconPalette: Map = emptyMap(), + themeIconPalette: Map = emptyMap(), + themeColorPalette: Map = emptyMap(), +) : IntUiPainterHintsProvider( + isDark, + intellijIconPalette, + themeIconPalette, + themeColorPalette, +) { + + @Composable + override fun hints(path: String): List = buildList { + add(getPaletteHint(path)) + add(BridgeOverride) + } + + companion object { + + private val logger = thisLogger() + + operator fun invoke(isDark: Boolean): IntUiPainterHintsProvider { + val uiTheme = currentUiThemeOrNull() ?: return BridgePainterHintsProvider(isDark) + logger.info("Parsing theme info from theme ${uiTheme.name} (id: ${uiTheme.id}, isDark: ${uiTheme.isDark})") + + val iconColorPalette = uiTheme.iconColorPalette + val keyPalette = UITheme.getColorPalette() + val themeColors = uiTheme.colors.orEmpty().mapValues { (k, v) -> + when (v) { + is Int -> Color(v) + is String -> Color.fromRGBAHexString(v) + else -> null + } + } + + return BridgePainterHintsProvider(isDark, keyPalette, iconColorPalette, themeColors) + } + } +} diff --git a/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePaletteMapperFactory.kt b/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePaletteMapperFactory.kt deleted file mode 100644 index 3d902a98d..000000000 --- a/ide-laf-bridge/ide-laf-bridge-232/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePaletteMapperFactory.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.jetbrains.jewel.bridge - -import com.intellij.ide.ui.UITheme -import com.intellij.openapi.diagnostic.thisLogger -import org.jetbrains.jewel.PaletteMapper -import org.jetbrains.jewel.themes.PaletteMapperFactory - -object BridgePaletteMapperFactory : PaletteMapperFactory() { - - private val logger = thisLogger() - - fun create(isDark: Boolean): PaletteMapper { - // If we can't read the current theme, no mapping is possible - val uiTheme = currentUiThemeOrNull() ?: return PaletteMapper.Empty - logger.info("Parsing theme info from theme ${uiTheme.name} (id: ${uiTheme.id}, isDark: ${uiTheme.isDark})") - - val iconColorPalette = uiTheme.iconColorPalette - val keyPalette = UITheme.getColorPalette() - val themeColors = uiTheme.colors.orEmpty() - - return createInternal(iconColorPalette, keyPalette, themeColors, isDark) - } - - override fun logInfo(message: String) { - logger.info(message) - } -} diff --git a/ide-laf-bridge/ide-laf-bridge-233/build.gradle.kts b/ide-laf-bridge/ide-laf-bridge-233/build.gradle.kts index 743a7e8e7..207e7d71e 100644 --- a/ide-laf-bridge/ide-laf-bridge-233/build.gradle.kts +++ b/ide-laf-bridge/ide-laf-bridge-233/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - api(projects.intUi.intUiStandalone) + api(projects.intUi.intUiCore) compileOnly(libs.bundles.idea233) } diff --git a/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt b/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt new file mode 100644 index 000000000..6c1528ab0 --- /dev/null +++ b/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeOverride.kt @@ -0,0 +1,30 @@ +package org.jetbrains.jewel.bridge + +import com.intellij.ui.icons.patchIconPath +import com.intellij.util.ui.DirProvider +import org.jetbrains.jewel.painter.PainterResourcePathHint + +internal object BridgeOverride : PainterResourcePathHint { + + private val dirProvider = DirProvider() + + override fun patch(path: String, classLoaders: List): String { + // For all provided classloaders, we try to get the patched path, both using + // the original path, and an "abridged" path that has gotten the icon path prefix + // removed (the classloader is set up differently in prod IDEs and when running + // from Gradle, and the icon could be in either place depending on the environment) + val fallbackPath = path.removePrefix(dirProvider.dir()) + + for (classLoader in classLoaders) { + val patchedPath = patchIconPath(path.removePrefix("/"), classLoader)?.first + ?: patchIconPath(fallbackPath, classLoader)?.first + + if (patchedPath != null) { + return patchedPath + } + } + return path + } + + override fun toString(): String = "BridgeOverride" +} diff --git a/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt b/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt new file mode 100644 index 000000000..eb7baee81 --- /dev/null +++ b/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt @@ -0,0 +1,53 @@ +package org.jetbrains.jewel.bridge + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.intellij.ide.ui.UITheme +import com.intellij.openapi.diagnostic.thisLogger +import org.jetbrains.jewel.InternalJewelApi +import org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider +import org.jetbrains.jewel.painter.PainterHint + +@InternalJewelApi +class BridgePainterHintsProvider private constructor( + isDark: Boolean, + intellijIconPalette: Map = emptyMap(), + themeIconPalette: Map = emptyMap(), + themeColorPalette: Map = emptyMap(), +) : IntUiPainterHintsProvider( + isDark, + intellijIconPalette, + themeIconPalette, + themeColorPalette, +) { + + @Composable + override fun hints(path: String): List = buildList { + add(getPaletteHint(path)) + add(BridgeOverride) + } + + companion object { + + private val logger = thisLogger() + + operator fun invoke(isDark: Boolean): IntUiPainterHintsProvider { + val uiTheme = currentUiThemeOrNull() ?: return BridgePainterHintsProvider(isDark) + logger.info("Parsing theme info from theme ${uiTheme.name} (id: ${uiTheme.id}, isDark: ${uiTheme.isDark})") + + val bean = uiTheme.describe() + val iconColorPalette = (bean.colorPalette as Map).mapValues { + when (val value = it.value) { + is String -> value + else -> null + } + } + val keyPalette = UITheme.getColorPalette() + val themeColors = bean.colors.mapValues { (k, v) -> + Color(v) + } + + return BridgePainterHintsProvider(isDark, keyPalette, iconColorPalette, themeColors) + } + } +} diff --git a/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePaletteMapperFactory.kt b/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePaletteMapperFactory.kt deleted file mode 100644 index d29ed438f..000000000 --- a/ide-laf-bridge/ide-laf-bridge-233/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePaletteMapperFactory.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.jetbrains.jewel.bridge - -import com.intellij.ide.ui.UITheme -import com.intellij.openapi.diagnostic.thisLogger -import org.jetbrains.jewel.PaletteMapper -import org.jetbrains.jewel.themes.PaletteMapperFactory - -object BridgePaletteMapperFactory : PaletteMapperFactory() { - - private val logger = thisLogger() - - @Suppress("UnstableApiUsage") - fun create(isDark: Boolean): PaletteMapper { - // If we can't read the current theme, no mapping is possible - val uiTheme = currentUiThemeOrNull() ?: return PaletteMapper.Empty - logger.info("Parsing theme info from theme ${uiTheme.name} (id: ${uiTheme.id}, isDark: ${uiTheme.isDark})") - - val bean = uiTheme.describe() - - // TODO: in New UI + Dark theme values can be non String, but IJColorUIResource - // since these are deserialized values simply casted to Map. - // Let's handle it while it is not fixed in platform - val iconColorPalette = (bean.colorPalette as Map).mapValues { - when (val value = it.value) { - is String -> value - else -> null - } - } - val keyPalette = UITheme.getColorPalette() - val themeColors = bean.colors - - return createInternal(iconColorPalette, keyPalette, themeColors, isDark) - } - - override fun logInfo(message: String) { - logger.info(message) - } -} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeGlobalColors.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeGlobalColors.kt index 24dfbb876..0f9d77908 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeGlobalColors.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeGlobalColors.kt @@ -11,6 +11,7 @@ internal class BridgeGlobalColors( override val borders: BorderColors, override val outlines: OutlineColors, @SwingLafKey("*.infoForeground") override val infoContent: Color, + @SwingLafKey("Panel.background") override val paneBackground: Color, ) : GlobalColors { override fun equals(other: Any?): Boolean { @@ -22,6 +23,7 @@ internal class BridgeGlobalColors( if (borders != other.borders) return false if (outlines != other.outlines) return false if (infoContent != other.infoContent) return false + if (paneBackground != other.paneBackground) return false return true } @@ -30,11 +32,12 @@ internal class BridgeGlobalColors( var result = borders.hashCode() result = 31 * result + outlines.hashCode() result = 31 * result + infoContent.hashCode() + result = 31 * result + paneBackground.hashCode() return result } override fun toString(): String = - "BridgeGlobalColors(borders=$borders, outlines=$outlines, infoContent=$infoContent)" + "BridgeGlobalColors(borders=$borders, outlines=$outlines, infoContent=$infoContent, paneBackground=$paneBackground)" companion object { @@ -42,6 +45,7 @@ internal class BridgeGlobalColors( borders = BridgeBorderColors.readFromLaF(), outlines = BridgeOutlineColors.readFromLaF(), infoContent = retrieveColorOrUnspecified("*.infoForeground"), + paneBackground = retrieveColorOrUnspecified("Panel.background"), ) } } diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeIconMapper.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeIconMapper.kt deleted file mode 100644 index d7f3cbbe0..000000000 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeIconMapper.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.jetbrains.jewel.bridge - -import androidx.compose.ui.res.ResourceLoader -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.util.ui.DirProvider -import org.jetbrains.jewel.ClassLoaderProvider -import org.jetbrains.jewel.IconMapper -import org.jetbrains.jewel.IntelliJThemeIconData -import org.jetbrains.jewel.InternalJewelApi - -internal object BridgeIconMapper : IconMapper { - - private val logger = thisLogger() - - private val dirProvider = DirProvider() - - @OptIn(InternalJewelApi::class) - override fun mapPath( - originalPath: String, - iconData: IntelliJThemeIconData, - resourceLoader: ResourceLoader, - ): String { - val classLoaders = (resourceLoader as? ClassLoaderProvider)?.classLoaders - if (classLoaders == null) { - logger.warn( - "Tried loading a resource but the provided ResourceLoader is now a JewelResourceLoader; " + - "this is probably a bug. Make sure you always use JewelResourceLoaders.", - ) - return originalPath - } - - val patchedPath = getPatchedIconPath(dirProvider, originalPath, classLoaders) - val path = if (patchedPath != null) { - logger.info("Found icon mapping: '$originalPath' -> '$patchedPath'") - patchedPath - } else { - logger.debug("Icon '$originalPath' has no available mapping") - originalPath - } - - val overriddenPath = iconData.iconOverrides[path] ?: path - if (overriddenPath != path) { - logger.info("Found theme icon override: '$path' -> '$overriddenPath'") - } - - return overriddenPath - } -} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourceLoader.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourceLoader.kt deleted file mode 100644 index e070efdec..000000000 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourceLoader.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.jetbrains.jewel.bridge - -import com.intellij.util.ui.DirProvider -import org.jetbrains.jewel.ClassLoaderProvider -import org.jetbrains.jewel.JewelResourceLoader -import java.io.InputStream - -object BridgeResourceLoader : JewelResourceLoader(), ClassLoaderProvider { - - private val dirProvider = DirProvider() - - override val classLoaders - get() = listOf(dirProvider::class.java.classLoader, javaClass.classLoader) - - override fun load(resourcePath: String): InputStream { - val normalizedPath = resourcePath.removePrefix("/") - val fallbackPath = resourcePath.removePrefix(dirProvider.dir()) - val resource = loadResourceOrNull(normalizedPath, classLoaders) - ?: loadResourceOrNull(fallbackPath, classLoaders) - - return requireNotNull(resource) { - "Resource '$resourcePath' not found (tried using '$normalizedPath' and '$fallbackPath')" - } - } -} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourcePainterProvider.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourcePainterProvider.kt deleted file mode 100644 index cdbb3de33..000000000 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourcePainterProvider.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.jetbrains.jewel.bridge - -import org.jetbrains.jewel.IconMapper -import org.jetbrains.jewel.IntelliJThemeIconData -import org.jetbrains.jewel.InteractiveComponentState -import org.jetbrains.jewel.InternalJewelApi -import org.jetbrains.jewel.SvgLoader -import org.jetbrains.jewel.styling.ResourcePainterProvider -import org.jetbrains.jewel.styling.ResourcePathPatcher -import org.jetbrains.jewel.styling.SimpleResourcePathPatcher -import org.jetbrains.jewel.styling.StatefulResourcePathPatcher - -@OptIn(InternalJewelApi::class) -internal class BridgeResourcePainterProvider @InternalJewelApi constructor( - basePath: String, - svgLoader: SvgLoader, - pathPatcher: ResourcePathPatcher, - iconMapper: IconMapper, - iconData: IntelliJThemeIconData, -) : ResourcePainterProvider(basePath, svgLoader, iconMapper, iconData, pathPatcher) { - - companion object Factory { - - fun stateless(basePath: String, svgLoader: SvgLoader, iconData: IntelliJThemeIconData) = - BridgeResourcePainterProvider( - basePath, - svgLoader, - SimpleResourcePathPatcher(), - BridgeIconMapper, - iconData, - ) - - fun stateful( - iconPath: String, - svgLoader: SvgLoader, - iconData: IntelliJThemeIconData, - prefixTokensProvider: (state: T) -> String = { "" }, - suffixTokensProvider: (state: T) -> String = { "" }, - pathPatcher: ResourcePathPatcher = StatefulResourcePathPatcher( - prefixTokensProvider, - suffixTokensProvider, - ), - ) = - BridgeResourcePainterProvider(iconPath, svgLoader, pathPatcher, BridgeIconMapper, iconData) - } -} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourceResolver.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourceResolver.kt new file mode 100644 index 000000000..d369ac81c --- /dev/null +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeResourceResolver.kt @@ -0,0 +1,10 @@ +package org.jetbrains.jewel.bridge + +import com.intellij.util.ui.DirProvider +import org.jetbrains.jewel.painter.ResourcePainterProvider + +/** + * [ResourceResolver] to resolve resource in Intellij Module and Bridge module. + */ +fun bridgePainterProvider(path: String) = + ResourcePainterProvider(path, DirProvider::class.java.classLoader, SwingBridgeService::class.java.classLoader) diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt index f1aaedc9e..f29c0fd8d 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt @@ -26,10 +26,6 @@ import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBInsets import com.intellij.util.ui.JBValue import kotlinx.coroutines.runBlocking -import org.jetbrains.jewel.IntelliJThemeIconData -import org.jetbrains.jewel.InteractiveComponentState -import org.jetbrains.jewel.SvgLoader -import org.jetbrains.jewel.styling.PainterProvider import org.jetbrains.skia.Typeface import org.jetbrains.skiko.DependsOnJBR import org.jetbrains.skiko.awt.font.AwtFontManager @@ -221,25 +217,3 @@ internal operator fun TextUnit.plus(delta: Float) = isEm -> TextUnit(value + delta, type) else -> this } - -fun retrieveStatefulIcon( - iconPath: String, - svgLoader: SvgLoader, - iconData: IntelliJThemeIconData, - prefixTokensProvider: (state: T) -> String = { "" }, - suffixTokensProvider: (state: T) -> String = { "" }, -): PainterProvider = - BridgeResourcePainterProvider.stateful( - iconPath = iconPath, - svgLoader, - iconData, - prefixTokensProvider, - suffixTokensProvider, - ) - -fun retrieveStatelessIcon( - iconPath: String, - svgLoader: SvgLoader, - iconData: IntelliJThemeIconData, -): PainterProvider = - BridgeResourcePainterProvider.stateless(iconPath, svgLoader, iconData) diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt index f79e1f3c6..e968ed35f 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/IntUiBridge.kt @@ -6,7 +6,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpSize @@ -22,10 +21,7 @@ import com.intellij.util.ui.DirProvider import com.intellij.util.ui.JBUI import com.intellij.util.ui.NamedColorUtil import com.intellij.util.ui.StatusText -import org.jetbrains.jewel.CheckboxState import org.jetbrains.jewel.IntelliJComponentStyling -import org.jetbrains.jewel.IntelliJThemeIconData -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.IntUiThemeDefinition import org.jetbrains.jewel.intui.standalone.styling.IntUiButtonColors import org.jetbrains.jewel.intui.standalone.styling.IntUiButtonMetrics @@ -129,10 +125,8 @@ internal fun createBridgeIntUiDefinition(textStyle: TextStyle): IntUiThemeDefini @OptIn(DependsOnJBR::class) internal suspend fun createSwingIntUiComponentStyling( theme: IntUiThemeDefinition, - svgLoader: SvgLoader, ): IntelliJComponentStyling = createSwingIntUiComponentStyling( theme = theme, - svgLoader = svgLoader, textAreaTextStyle = retrieveTextStyle("TextArea.font", "TextArea.foreground"), textFieldTextStyle = retrieveTextStyle("TextField.font", "TextField.foreground"), dropdownTextStyle = retrieveTextStyle("ComboBox.font"), @@ -142,7 +136,6 @@ internal suspend fun createSwingIntUiComponentStyling( internal fun createSwingIntUiComponentStyling( theme: IntUiThemeDefinition, - svgLoader: SvgLoader, textFieldTextStyle: TextStyle, textAreaTextStyle: TextStyle, dropdownTextStyle: TextStyle, @@ -152,24 +145,24 @@ internal fun createSwingIntUiComponentStyling( logger.debug("Obtaining Int UI component styling from Swing...") val textFieldStyle = readTextFieldStyle(textFieldTextStyle) - val menuStyle = readMenuStyle(theme.iconData, svgLoader) + val menuStyle = readMenuStyle() return IntelliJComponentStyling( - checkboxStyle = readCheckboxStyle(theme.iconData, svgLoader), + checkboxStyle = readCheckboxStyle(), chipStyle = readChipStyle(), defaultButtonStyle = readDefaultButtonStyle(), - defaultTabStyle = readDefaultTabStyle(theme.iconData, svgLoader), + defaultTabStyle = readDefaultTabStyle(), dividerStyle = readDividerStyle(), - dropdownStyle = readDropdownStyle(theme.iconData, svgLoader, menuStyle, dropdownTextStyle), - editorTabStyle = readEditorTabStyle(theme.iconData, svgLoader), + dropdownStyle = readDropdownStyle(menuStyle, dropdownTextStyle), + editorTabStyle = readEditorTabStyle(), groupHeaderStyle = readGroupHeaderStyle(), horizontalProgressBarStyle = readHorizontalProgressBarStyle(), labelledTextFieldStyle = readLabelledTextFieldStyle(textFieldStyle, labelTextStyle), - lazyTreeStyle = readLazyTreeStyle(theme.iconData, svgLoader), - linkStyle = readLinkStyle(theme.iconData, svgLoader, linkTextStyle), + lazyTreeStyle = readLazyTreeStyle(), + linkStyle = readLinkStyle(linkTextStyle), menuStyle = menuStyle, outlinedButtonStyle = readOutlinedButtonStyle(), - radioButtonStyle = readRadioButtonStyle(theme.iconData, svgLoader), + radioButtonStyle = readRadioButtonStyle(), scrollbarStyle = readScrollbarStyle(theme.isDark), textAreaStyle = readTextAreaStyle(textAreaTextStyle, textFieldStyle.metrics), circularProgressStyle = readCircularProgressStyle(theme.isDark), @@ -260,7 +253,7 @@ private fun readOutlinedButtonStyle(): IntUiButtonStyle { private val iconsBasePath get() = DirProvider().dir() -private fun readCheckboxStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader): IntUiCheckboxStyle { +private fun readCheckboxStyle(): IntUiCheckboxStyle { val background = retrieveColorOrUnspecified("CheckBox.background") val textColor = retrieveColorOrUnspecified("CheckBox.foreground") val colors = IntUiCheckboxColors( @@ -282,14 +275,7 @@ private fun readCheckboxStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoa iconContentGap = 5.dp, // See DarculaCheckBoxUI#textIconGap ), icons = IntUiCheckboxIcons( - checkbox = retrieveStatefulIcon( - iconPath = "${iconsBasePath}checkBox.svg", - svgLoader = svgLoader, - iconData = iconData, - prefixTokensProvider = { state: CheckboxState -> - if (state.toggleableState == ToggleableState.Indeterminate) "Indeterminate" else "" - }, - ), + checkbox = bridgePainterProvider("${iconsBasePath}checkBox.svg"), ), ) } @@ -359,8 +345,6 @@ private fun readDividerStyle() = ) private fun readDropdownStyle( - iconData: IntelliJThemeIconData, - svgLoader: SvgLoader, menuStyle: IntUiMenuStyle, dropdownTextStyle: TextStyle, ): IntUiDropdownStyle { @@ -406,11 +390,7 @@ private fun readDropdownStyle( borderWidth = DarculaUIUtil.BW.dp, ), icons = IntUiDropdownIcons( - chevronDown = retrieveStatefulIcon( - iconPath = "${iconsBasePath}general/chevron-down.svg", - iconData = iconData, - svgLoader = svgLoader, - ), + chevronDown = bridgePainterProvider("${iconsBasePath}general/chevron-down.svg"), ), textStyle = dropdownTextStyle, menuStyle = menuStyle, @@ -493,8 +473,6 @@ private fun readLabelledTextFieldStyle( } private fun readLinkStyle( - iconData: IntelliJThemeIconData, - svgLoader: SvgLoader, linkTextStyle: TextStyle, ): IntUiLinkStyle { val normalContent = @@ -521,16 +499,8 @@ private fun readLinkStyle( iconSize = DpSize.Unspecified, ), icons = IntUiLinkIcons( - dropdownChevron = retrieveStatefulIcon( - iconPath = "${iconsBasePath}general/chevron-down.svg", - iconData = iconData, - svgLoader = svgLoader, - ), - externalLink = retrieveStatefulIcon( - iconPath = "${iconsBasePath}ide/external_link_arrow.svg", - iconData = iconData, - svgLoader = svgLoader, - ), + dropdownChevron = bridgePainterProvider("${iconsBasePath}general/chevron-down.svg"), + externalLink = bridgePainterProvider("${iconsBasePath}ide/external_link_arrow.svg"), ), textStyles = IntUiLinkTextStyles( normal = linkTextStyle, @@ -543,7 +513,7 @@ private fun readLinkStyle( ) } -private fun readMenuStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader): IntUiMenuStyle { +private fun readMenuStyle(): IntUiMenuStyle { val backgroundSelected = retrieveColorOrUnspecified("MenuItem.selectionBackground") val foregroundSelected = retrieveColorOrUnspecified("MenuItem.selectionForeground") @@ -595,16 +565,12 @@ private fun readMenuStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader) ), ), icons = IntUiMenuIcons( - submenuChevron = retrieveStatefulIcon( - iconPath = "${iconsBasePath}general/chevron-down.svg", - iconData = iconData, - svgLoader = svgLoader, - ), + submenuChevron = bridgePainterProvider("${iconsBasePath}general/chevron-down.svg"), ), ) } -private fun readRadioButtonStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader): IntUiRadioButtonStyle { +private fun readRadioButtonStyle(): IntUiRadioButtonStyle { val normalContent = retrieveColorOrUnspecified("RadioButton.foreground") val disabledContent = retrieveColorOrUnspecified("RadioButton.disabledText") val colors = IntUiRadioButtonColors( @@ -623,11 +589,7 @@ private fun readRadioButtonStyle(iconData: IntelliJThemeIconData, svgLoader: Svg iconContentGap = retrieveIntAsDpOrUnspecified("RadioButton.textIconGap").takeOrElse { 4.dp }, ), icons = IntUiRadioButtonIcons( - radioButton = retrieveStatefulIcon( - iconPath = "${iconsBasePath}radio.svg", - iconData = iconData, - svgLoader = svgLoader, - ), + radioButton = bridgePainterProvider("${iconsBasePath}radio.svg"), ), ) } @@ -736,7 +698,7 @@ private fun readTextFieldStyle(textFieldStyle: TextStyle): IntUiTextFieldStyle { ) } -private fun readLazyTreeStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader): IntUiLazyTreeStyle { +private fun readLazyTreeStyle(): IntUiLazyTreeStyle { val normalContent = retrieveColorOrUnspecified("Tree.foreground") val selectedContent = retrieveColorOrUnspecified("Tree.selectionForeground") val selectedElementBackground = retrieveColorOrUnspecified("Tree.selectionBackground") @@ -752,16 +714,8 @@ private fun readLazyTreeStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoa elementBackgroundSelectedFocused = selectedElementBackground, ) - val chevronCollapsed = retrieveStatelessIcon( - iconPath = "${iconsBasePath}general/chevron-right.svg", - iconData = iconData, - svgLoader = svgLoader, - ) - val chevronExpanded = retrieveStatelessIcon( - iconPath = "${iconsBasePath}general/chevron-down.svg", - iconData = iconData, - svgLoader = svgLoader, - ) + val chevronCollapsed = bridgePainterProvider("${iconsBasePath}general/chevron-right.svg") + val chevronExpanded = bridgePainterProvider("${iconsBasePath}general/chevron-down.svg") return IntUiLazyTreeStyle( colors = colors, @@ -784,7 +738,7 @@ private fun readLazyTreeStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoa } // See com.intellij.ui.tabs.impl.themes.DefaultTabTheme -private fun readDefaultTabStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader): IntUiTabStyle { +private fun readDefaultTabStyle(): IntUiTabStyle { val normalBackground = JBUI.CurrentTheme.DefaultTabs.background().toComposeColor() val selectedBackground = JBUI.CurrentTheme.DefaultTabs.underlinedTabBackground().toComposeColorOrUnspecified() val normalContent = retrieveColorOrUnspecified("TabbedPane.foreground") @@ -820,11 +774,7 @@ private fun readDefaultTabStyle(iconData: IntelliJThemeIconData, svgLoader: SvgL tabHeight = retrieveIntAsDpOrUnspecified("TabbedPane.tabHeight").takeOrElse { 24.dp }, ), icons = IntUiTabIcons( - close = retrieveStatefulIcon( - iconPath = "${iconsBasePath}expui/general/closeSmall.svg", - iconData = iconData, - svgLoader = svgLoader, - ), + close = bridgePainterProvider("${iconsBasePath}expui/general/closeSmall.svg"), ), contentAlpha = IntUiTabContentAlpha( iconNormal = 1f, @@ -843,7 +793,7 @@ private fun readDefaultTabStyle(iconData: IntelliJThemeIconData, svgLoader: SvgL ) } -private fun readEditorTabStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLoader): IntUiTabStyle { +private fun readEditorTabStyle(): IntUiTabStyle { val normalBackground = JBUI.CurrentTheme.EditorTabs.background().toComposeColor() val selectedBackground = JBUI.CurrentTheme.EditorTabs.underlinedTabBackground().toComposeColorOrUnspecified() val normalContent = retrieveColorOrUnspecified("TabbedPane.foreground") @@ -879,11 +829,7 @@ private fun readEditorTabStyle(iconData: IntelliJThemeIconData, svgLoader: SvgLo tabHeight = retrieveIntAsDpOrUnspecified("TabbedPane.tabHeight").takeOrElse { 24.dp }, ), icons = IntUiTabIcons( - close = retrieveStatefulIcon( - iconPath = "${iconsBasePath}expui/general/closeSmall.svg", - iconData = iconData, - svgLoader = svgLoader, - ), + close = bridgePainterProvider("${iconsBasePath}expui/general/closeSmall.svg"), ), contentAlpha = IntUiTabContentAlpha( iconNormal = .7f, diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt index 158555004..d564ea912 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt @@ -1,8 +1,5 @@ package org.jetbrains.jewel.bridge -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.text.TextStyle import com.intellij.openapi.Disposable import com.intellij.openapi.components.Service @@ -19,14 +16,11 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import org.jetbrains.jewel.IntelliJComponentStyling -import org.jetbrains.jewel.JewelSvgLoader -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.IntUiThemeDefinition -import org.jetbrains.jewel.intui.core.IntelliJSvgPatcher import kotlin.time.Duration.Companion.milliseconds @Service(Level.APP) -class SwingBridgeService : Disposable { +internal class SwingBridgeService : Disposable { private val logger = thisLogger() @@ -40,13 +34,6 @@ class SwingBridgeService : Disposable { .mapLatest { tryGettingThemeData() } .stateIn(coroutineScope, SharingStarted.Eagerly, BridgeThemeData.DEFAULT) - val svgLoader: SvgLoader - @Composable - get() { - val data by currentBridgeThemeData.collectAsState() - return data.svgLoader - } - private suspend fun tryGettingThemeData(): BridgeThemeData { var counter = 0 while (counter < 20) { @@ -66,11 +53,9 @@ class SwingBridgeService : Disposable { } val themeDefinition = createBridgeIntUiDefinition() - val svgLoader = createSvgLoader(themeDefinition) return BridgeThemeData( themeDefinition = createBridgeIntUiDefinition(), - svgLoader = svgLoader, - componentStyling = createSwingIntUiComponentStyling(themeDefinition, svgLoader), + componentStyling = createSwingIntUiComponentStyling(themeDefinition), ) } @@ -80,7 +65,6 @@ class SwingBridgeService : Disposable { internal data class BridgeThemeData( val themeDefinition: IntUiThemeDefinition, - val svgLoader: SvgLoader, val componentStyling: IntelliJComponentStyling, ) { @@ -88,13 +72,10 @@ class SwingBridgeService : Disposable { val DEFAULT = run { val themeDefinition = createBridgeIntUiDefinition(TextStyle.Default) - val svgLoader = createSvgLoader(themeDefinition) BridgeThemeData( themeDefinition = createBridgeIntUiDefinition(TextStyle.Default), - svgLoader = createSvgLoader(themeDefinition), componentStyling = createSwingIntUiComponentStyling( theme = themeDefinition, - svgLoader = svgLoader, textAreaTextStyle = TextStyle.Default, textFieldTextStyle = TextStyle.Default, dropdownTextStyle = TextStyle.Default, @@ -106,9 +87,3 @@ class SwingBridgeService : Disposable { } } } - -private fun createSvgLoader(theme: IntUiThemeDefinition): SvgLoader { - val paletteMapper = BridgePaletteMapperFactory.create(theme.isDark) - val svgPatcher = IntelliJSvgPatcher(paletteMapper) - return JewelSvgLoader(svgPatcher) -} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeTheme.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeTheme.kt index d05162840..d4e2f61fa 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeTheme.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeTheme.kt @@ -6,8 +6,8 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import com.intellij.openapi.components.service import org.jetbrains.jewel.ExperimentalJewelApi -import org.jetbrains.jewel.LocalResourceLoader -import org.jetbrains.jewel.intui.standalone.IntUiTheme +import org.jetbrains.jewel.intui.core.BaseIntUiTheme +import org.jetbrains.jewel.painter.LocalPainterHintsProvider private val bridgeService get() = service() @@ -18,8 +18,10 @@ fun SwingBridgeTheme(content: @Composable () -> Unit) { val themeData by bridgeService.currentBridgeThemeData.collectAsState() // TODO handle non-Int UI themes, too - IntUiTheme(themeData.themeDefinition, themeData.componentStyling, swingCompatMode = true) { - CompositionLocalProvider(LocalResourceLoader provides BridgeResourceLoader) { + BaseIntUiTheme(themeData.themeDefinition, { + themeData.componentStyling.providedStyles() + }, swingCompatMode = true) { + CompositionLocalProvider(LocalPainterHintsProvider provides BridgePainterHintsProvider(themeData.themeDefinition.isDark)) { content() } } diff --git a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt index 01464dec5..b93ff499d 100644 --- a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt +++ b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/BaseIntUiTheme.kt @@ -1,16 +1,14 @@ package org.jetbrains.jewel.intui.core -import androidx.compose.foundation.LocalContextMenuRepresentation import androidx.compose.foundation.LocalIndication import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ProvidedValue import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import org.jetbrains.jewel.GlobalColors import org.jetbrains.jewel.GlobalMetrics -import org.jetbrains.jewel.IntelliJComponentStyling -import org.jetbrains.jewel.IntelliJContextMenuRepresentation import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.IntelliJThemeIconData import org.jetbrains.jewel.LocalColorPalette @@ -27,27 +25,6 @@ import org.jetbrains.jewel.styling.HorizontalProgressBarStyle import org.jetbrains.jewel.styling.LabelledTextFieldStyle import org.jetbrains.jewel.styling.LazyTreeStyle import org.jetbrains.jewel.styling.LinkStyle -import org.jetbrains.jewel.styling.LocalCheckboxStyle -import org.jetbrains.jewel.styling.LocalChipStyle -import org.jetbrains.jewel.styling.LocalCircularProgressStyle -import org.jetbrains.jewel.styling.LocalDefaultButtonStyle -import org.jetbrains.jewel.styling.LocalDefaultTabStyle -import org.jetbrains.jewel.styling.LocalDividerStyle -import org.jetbrains.jewel.styling.LocalDropdownStyle -import org.jetbrains.jewel.styling.LocalEditorTabStyle -import org.jetbrains.jewel.styling.LocalGroupHeaderStyle -import org.jetbrains.jewel.styling.LocalHorizontalProgressBarStyle -import org.jetbrains.jewel.styling.LocalIconButtonStyle -import org.jetbrains.jewel.styling.LocalLabelledTextFieldStyle -import org.jetbrains.jewel.styling.LocalLazyTreeStyle -import org.jetbrains.jewel.styling.LocalLinkStyle -import org.jetbrains.jewel.styling.LocalMenuStyle -import org.jetbrains.jewel.styling.LocalOutlinedButtonStyle -import org.jetbrains.jewel.styling.LocalRadioButtonStyle -import org.jetbrains.jewel.styling.LocalScrollbarStyle -import org.jetbrains.jewel.styling.LocalTextAreaStyle -import org.jetbrains.jewel.styling.LocalTextFieldStyle -import org.jetbrains.jewel.styling.LocalTooltipStyle import org.jetbrains.jewel.styling.MenuStyle import org.jetbrains.jewel.styling.RadioButtonStyle import org.jetbrains.jewel.styling.ScrollbarStyle @@ -196,7 +173,7 @@ interface BaseIntUiTheme : IntelliJTheme { @Composable fun BaseIntUiTheme( theme: IntUiThemeDefinition, - componentStyling: IntelliJComponentStyling, + componentStyling: @Composable () -> Array>, content: @Composable () -> Unit, ) { BaseIntUiTheme(theme, componentStyling, swingCompatMode = false, content) @@ -205,37 +182,17 @@ fun BaseIntUiTheme( @Composable fun BaseIntUiTheme( theme: IntUiThemeDefinition, - componentStyling: IntelliJComponentStyling, + componentStyling: @Composable () -> Array>, swingCompatMode: Boolean = false, content: @Composable () -> Unit, ) { - CompositionLocalProvider( - LocalColorPalette provides theme.colorPalette, - LocalIconData provides theme.iconData, - LocalCheckboxStyle provides componentStyling.checkboxStyle, - LocalChipStyle provides componentStyling.chipStyle, - LocalContextMenuRepresentation provides IntelliJContextMenuRepresentation, - LocalDefaultButtonStyle provides componentStyling.defaultButtonStyle, - LocalDividerStyle provides componentStyling.dividerStyle, - LocalDropdownStyle provides componentStyling.dropdownStyle, - LocalGroupHeaderStyle provides componentStyling.groupHeaderStyle, - LocalHorizontalProgressBarStyle provides componentStyling.horizontalProgressBarStyle, - LocalLabelledTextFieldStyle provides componentStyling.labelledTextFieldStyle, - LocalLazyTreeStyle provides componentStyling.lazyTreeStyle, - LocalLinkStyle provides componentStyling.linkStyle, - LocalMenuStyle provides componentStyling.menuStyle, - LocalOutlinedButtonStyle provides componentStyling.outlinedButtonStyle, - LocalRadioButtonStyle provides componentStyling.radioButtonStyle, - LocalScrollbarStyle provides componentStyling.scrollbarStyle, - LocalTextAreaStyle provides componentStyling.textAreaStyle, - LocalTextFieldStyle provides componentStyling.textFieldStyle, - LocalDefaultTabStyle provides componentStyling.defaultTabStyle, - LocalEditorTabStyle provides componentStyling.editorTabStyle, - LocalIndication provides NoIndication, - LocalCircularProgressStyle provides componentStyling.circularProgressStyle, - LocalTooltipStyle provides componentStyling.tooltipStyle, - LocalIconButtonStyle provides componentStyling.iconButtonStyle, - ) { - IntelliJTheme(theme, swingCompatMode, content) + IntelliJTheme(theme, swingCompatMode) { + CompositionLocalProvider( + LocalColorPalette provides theme.colorPalette, + LocalIconData provides theme.iconData, + LocalIndication provides NoIndication, + ) { + CompositionLocalProvider(values = componentStyling(), content = content) + } } } diff --git a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiPainterHintsProvider.kt b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiPainterHintsProvider.kt new file mode 100644 index 000000000..b2e8855b2 --- /dev/null +++ b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiPainterHintsProvider.kt @@ -0,0 +1,77 @@ +package org.jetbrains.jewel.intui.core + +import androidx.compose.ui.graphics.Color +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.PainterHintsProvider +import org.jetbrains.jewel.painter.hints.Palette +import org.jetbrains.jewel.util.fromRGBAHexString + +abstract class IntUiPainterHintsProvider( + isDark: Boolean, + intellijIconPalette: Map, + themeIconPalette: Map, + themeColorPalette: Map, +) : PainterHintsProvider { + + private val checkBoxPaletteHint: PainterHint + private val treePaletteHint: PainterHint + private val uiPaletteHint: PainterHint + + init { + val ui = mutableMapOf() + val checkBoxes = mutableMapOf() + val trees = mutableMapOf() + + @Suppress("LoopWithTooManyJumpStatements") + for ((key, value) in themeIconPalette) { + value ?: continue + val map = selectMap(key, checkBoxes, trees, ui) ?: continue + + // If the value is one of the named colors in the theme, use that named color's value + val namedColor = themeColorPalette[value] + + // If either the key or the resolved value aren't valid colors, ignore the entry + val keyAsColor = resolveKeyColor(key, intellijIconPalette, isDark) ?: continue + val resolvedColor = namedColor ?: Color.fromRGBAHexString(value) ?: continue + + // Save the new entry (oldColor -> newColor) in the map + map[keyAsColor] = resolvedColor + } + + checkBoxPaletteHint = Palette(checkBoxes) + treePaletteHint = Palette(trees) + uiPaletteHint = Palette(ui) + } + + private fun selectMap( + key: String, + checkBoxes: MutableMap, + trees: MutableMap, + ui: MutableMap, + ) = when { + key.startsWith("Checkbox.") -> checkBoxes + key.startsWith("Tree.iconColor.") -> trees + key.startsWith("Objects.") || key.startsWith("Actions.") || key.startsWith("#") -> ui + else -> null + } + + // See com.intellij.ide.ui.UITheme.toColorString + private fun resolveKeyColor(key: String, keyPalette: Map, isDark: Boolean): Color? { + val darkKey = "$key.Dark" + val resolvedKey = if (isDark && keyPalette.containsKey(darkKey)) darkKey else key + return Color.fromRGBAHexString(keyPalette[resolvedKey] ?: return null) + } + + protected fun getPaletteHint(path: String): PainterHint { + if (!path.contains("com/intellij/ide/ui/laf/icons/")) return uiPaletteHint + + val file = path.substringAfterLast('/') + return when { + file == "treeCollapsed.svg" || file == "treeExpanded.svg" -> treePaletteHint + // ⚠️ This next line is not a copy-paste error — the code in UITheme.PaletteScopeManager.getScopeByPath() + // says they share the same colors + file.startsWith("check") || file.startsWith("radio") -> checkBoxPaletteHint + else -> PainterHint.None + } + } +} diff --git a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt index 420289191..2e53e5213 100644 --- a/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt +++ b/int-ui/int-ui-core/src/main/kotlin/org/jetbrains/jewel/intui/core/IntUiThemeDefinition.kt @@ -1,7 +1,6 @@ package org.jetbrains.jewel.intui.core import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ProvidedValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import org.jetbrains.jewel.GlobalColors @@ -18,12 +17,8 @@ class IntUiThemeDefinition( override val globalMetrics: GlobalMetrics, override val defaultTextStyle: TextStyle, override val contentColor: Color, - override val extensionStyles: Array> = emptyArray(), ) : IntelliJThemeDefinition { - override fun withExtensions(vararg extensions: ProvidedValue<*>): IntUiThemeDefinition = - copy(extensionStyles = extensionStyles + extensions) - fun copy( isDark: Boolean = this.isDark, globalColors: GlobalColors = this.globalColors, @@ -32,7 +27,6 @@ class IntUiThemeDefinition( globalMetrics: GlobalMetrics = this.globalMetrics, defaultTextStyle: TextStyle = this.defaultTextStyle, contentColor: Color = this.contentColor, - extensionStyles: Array> = this.extensionStyles, ): IntUiThemeDefinition = IntUiThemeDefinition( isDark = isDark, globalColors = globalColors, @@ -41,7 +35,6 @@ class IntUiThemeDefinition( globalMetrics = globalMetrics, defaultTextStyle = defaultTextStyle, contentColor = contentColor, - extensionStyles = extensionStyles, ) override fun equals(other: Any?): Boolean { @@ -57,7 +50,6 @@ class IntUiThemeDefinition( if (globalMetrics != other.globalMetrics) return false if (defaultTextStyle != other.defaultTextStyle) return false if (contentColor != other.contentColor) return false - if (!extensionStyles.contentEquals(other.extensionStyles)) return false return true } @@ -70,7 +62,6 @@ class IntUiThemeDefinition( result = 31 * result + globalMetrics.hashCode() result = 31 * result + defaultTextStyle.hashCode() result = 31 * result + contentColor.hashCode() - result = 31 * result + extensionStyles.contentHashCode() return result } } diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiDecoratedWindowResourceResolver.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiDecoratedWindowResourceResolver.kt new file mode 100644 index 000000000..53fcdeb22 --- /dev/null +++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiDecoratedWindowResourceResolver.kt @@ -0,0 +1,11 @@ +package org.jetbrains.jewel.intui.window + +import org.jetbrains.jewel.intui.standalone.IntUiTheme +import org.jetbrains.jewel.intui.window.styling.IntUiDecoratedWindowStyle +import org.jetbrains.jewel.painter.ResourcePainterProvider + +/** + * Create [PainterProvider][org.jetbrains.jewel.painter.PainterProvider] for decorated window module resource. + */ +fun decoratedWindowPainterProvider(path: String) = + ResourcePainterProvider(path, IntUiDecoratedWindowStyle::class.java.classLoader, IntUiTheme::class.java.classLoader) diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt index e9f753358..169e470cc 100644 --- a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt +++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/IntUiTheme.kt @@ -1,32 +1,27 @@ package org.jetbrains.jewel.intui.window import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidedValue import org.jetbrains.jewel.intui.core.IntUiThemeDefinition import org.jetbrains.jewel.intui.window.styling.IntUiDecoratedWindowStyle import org.jetbrains.jewel.intui.window.styling.IntUiTitleBarStyle -import org.jetbrains.jewel.window.styling.DecoratedWindowStyle import org.jetbrains.jewel.window.styling.LocalDecoratedWindowStyle import org.jetbrains.jewel.window.styling.LocalTitleBarStyle import org.jetbrains.jewel.window.styling.TitleBarStyle @Composable -fun IntUiThemeDefinition.decoratedWindowStyle(): DecoratedWindowStyle = - if (isDark) { +fun IntUiThemeDefinition.decoratedWindowComponentStyling( + windowStyle: IntUiDecoratedWindowStyle = if (isDark) { IntUiDecoratedWindowStyle.dark() } else { IntUiDecoratedWindowStyle.light() - } - -@Composable -fun IntUiThemeDefinition.withDecoratedWindow( + }, titleBarStyle: TitleBarStyle = if (isDark) { IntUiTitleBarStyle.dark() } else { IntUiTitleBarStyle.light() }, -): IntUiThemeDefinition { - return withExtensions( - LocalDecoratedWindowStyle provides decoratedWindowStyle(), - LocalTitleBarStyle provides titleBarStyle, - ) -} +): Array> = arrayOf( + LocalDecoratedWindowStyle provides windowStyle, + LocalTitleBarStyle provides titleBarStyle, +) diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt index 013d3179b..9b3470f06 100644 --- a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt +++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt @@ -9,13 +9,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.IntelliJIconMapper -import org.jetbrains.jewel.InternalJewelApi -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme -import org.jetbrains.jewel.intui.standalone.rememberSvgLoader import org.jetbrains.jewel.intui.standalone.styling.IntUiDropdownColors import org.jetbrains.jewel.intui.standalone.styling.IntUiDropdownMetrics import org.jetbrains.jewel.intui.standalone.styling.IntUiDropdownStyle @@ -23,13 +18,11 @@ import org.jetbrains.jewel.intui.standalone.styling.IntUiIconButtonColors import org.jetbrains.jewel.intui.standalone.styling.IntUiIconButtonMetrics import org.jetbrains.jewel.intui.standalone.styling.IntUiIconButtonStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiMenuStyle +import org.jetbrains.jewel.intui.window.decoratedWindowPainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.IconButtonStyle import org.jetbrains.jewel.styling.MenuStyle -import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.styling.ResourcePainterProvider -import org.jetbrains.jewel.styling.SimpleResourcePathPatcher -import org.jetbrains.jewel.window.DecoratedWindowState import org.jetbrains.jewel.window.styling.TitleBarColors import org.jetbrains.jewel.window.styling.TitleBarIcons import org.jetbrains.jewel.window.styling.TitleBarMetrics @@ -83,13 +76,11 @@ class IntUiTitleBarStyle( @Composable private fun titleBarDropdownStyle( - svgLoader: SvgLoader, content: Color, hoverBackground: Color, pressBackground: Color, menuStyle: MenuStyle, ) = IntUiDropdownStyle.undecorated( - svgLoader, IntUiDropdownColors.undecorated( backgroundHovered = hoverBackground, backgroundPressed = pressBackground, @@ -129,20 +120,18 @@ class IntUiTitleBarStyle( @Composable fun light( - svgLoader: SvgLoader = rememberSvgLoader(false).value, colors: IntUiTitleBarColors = IntUiTitleBarColors.light(), metrics: IntUiTitleBarMetrics = IntUiTitleBarMetrics(), - icons: IntUiTitleBarIcons = intUiTitleBarIcons(svgLoader), + icons: IntUiTitleBarIcons = intUiTitleBarIcons(), ): IntUiTitleBarStyle = IntUiTitleBarStyle( colors = colors, metrics = metrics, icons = icons, dropdownStyle = titleBarDropdownStyle( - svgLoader, colors.content, colors.dropdownHoverBackground, colors.dropdownPressBackground, - IntUiMenuStyle.light(svgLoader), + IntUiMenuStyle.light(), ), iconButtonStyle = titleBarIconButtonStyle( colors.iconButtonHoverBackground, @@ -163,20 +152,18 @@ class IntUiTitleBarStyle( @Composable fun lightWithLightHeader( - svgLoader: SvgLoader = rememberSvgLoader(false).value, colors: IntUiTitleBarColors = IntUiTitleBarColors.lightWithLightHeader(), metrics: IntUiTitleBarMetrics = IntUiTitleBarMetrics(), - icons: IntUiTitleBarIcons = intUiTitleBarIcons(svgLoader), + icons: IntUiTitleBarIcons = intUiTitleBarIcons(), ): IntUiTitleBarStyle = IntUiTitleBarStyle( colors = colors, metrics = metrics, icons = icons, dropdownStyle = titleBarDropdownStyle( - svgLoader, colors.content, colors.dropdownHoverBackground, colors.dropdownPressBackground, - IntUiMenuStyle.light(svgLoader), + IntUiMenuStyle.light(), ), iconButtonStyle = titleBarIconButtonStyle( colors.iconButtonHoverBackground, @@ -197,20 +184,18 @@ class IntUiTitleBarStyle( @Composable fun dark( - svgLoader: SvgLoader = rememberSvgLoader(true).value, colors: IntUiTitleBarColors = IntUiTitleBarColors.dark(), metrics: IntUiTitleBarMetrics = IntUiTitleBarMetrics(), - icons: IntUiTitleBarIcons = intUiTitleBarIcons(svgLoader), + icons: IntUiTitleBarIcons = intUiTitleBarIcons(), ): IntUiTitleBarStyle = IntUiTitleBarStyle( colors = colors, metrics = metrics, icons = icons, dropdownStyle = titleBarDropdownStyle( - svgLoader, colors.content, colors.dropdownHoverBackground, colors.dropdownPressBackground, - IntUiMenuStyle.dark(svgLoader), + IntUiMenuStyle.dark(), ), iconButtonStyle = titleBarIconButtonStyle( colors.iconButtonHoverBackground, @@ -450,10 +435,10 @@ class IntUiTitleBarMetrics( } class IntUiTitleBarIcons( - override val minimizeButton: PainterProvider, - override val maximizeButton: PainterProvider, - override val restoreButton: PainterProvider, - override val closeButton: PainterProvider, + override val minimizeButton: PainterProvider, + override val maximizeButton: PainterProvider, + override val restoreButton: PainterProvider, + override val closeButton: PainterProvider, ) : TitleBarIcons { override fun hashCode(): Int { @@ -481,86 +466,34 @@ class IntUiTitleBarIcons( return true } - @OptIn(InternalJewelApi::class) companion object { @Composable fun minimize( - svgLoader: SvgLoader, basePath: String = "icons/intui/window/minimize.svg", - ): PainterProvider = ResourcePainterProvider( - basePath, - svgLoader, - IntelliJIconMapper, - LocalIconData.current, - TitleBarResourcePathPatcher(), - ) + ): PainterProvider = decoratedWindowPainterProvider(basePath) @Composable fun maximize( - svgLoader: SvgLoader, basePath: String = "icons/intui/window/maximize.svg", - ): PainterProvider = - ResourcePainterProvider( - basePath, - svgLoader, - IntelliJIconMapper, - LocalIconData.current, - TitleBarResourcePathPatcher(), - ) + ): PainterProvider = decoratedWindowPainterProvider(basePath) @Composable fun restore( - svgLoader: SvgLoader, basePath: String = "icons/intui/window/restore.svg", - ): PainterProvider = - ResourcePainterProvider( - basePath, - svgLoader, - IntelliJIconMapper, - LocalIconData.current, - TitleBarResourcePathPatcher(), - ) + ): PainterProvider = decoratedWindowPainterProvider(basePath) @Composable fun close( - svgLoader: SvgLoader, basePath: String = "icons/intui/window/close.svg", - ): PainterProvider = - ResourcePainterProvider( - basePath, - svgLoader, - IntelliJIconMapper, - LocalIconData.current, - TitleBarResourcePathPatcher(), - ) + ): PainterProvider = decoratedWindowPainterProvider(basePath) } } @Composable fun intUiTitleBarIcons( - svgLoader: SvgLoader, - minimize: PainterProvider = IntUiTitleBarIcons.minimize(svgLoader), - maximize: PainterProvider = IntUiTitleBarIcons.maximize(svgLoader), - restore: PainterProvider = IntUiTitleBarIcons.restore(svgLoader), - close: PainterProvider = IntUiTitleBarIcons.close(svgLoader), + minimize: PainterProvider = IntUiTitleBarIcons.minimize(), + maximize: PainterProvider = IntUiTitleBarIcons.maximize(), + restore: PainterProvider = IntUiTitleBarIcons.restore(), + close: PainterProvider = IntUiTitleBarIcons.close(), ) = IntUiTitleBarIcons(minimize, maximize, restore, close) - -private class TitleBarResourcePathPatcher( - private val prefixTokensProvider: (state: DecoratedWindowState) -> String = { "" }, - private val suffixTokensProvider: (state: DecoratedWindowState) -> String = { "" }, -) : SimpleResourcePathPatcher() { - - @Composable - override fun injectVariantTokens(extraData: DecoratedWindowState?): String = buildString { - if (extraData == null) return@buildString - - append(prefixTokensProvider(extraData)) - - if (!extraData.isActive) { - append("Inactive") - } - - append(suffixTokensProvider(extraData)) - } -} diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiGlobalColors.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiGlobalColors.kt index 4eeea0d03..5dc1a01d4 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiGlobalColors.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiGlobalColors.kt @@ -14,6 +14,7 @@ class IntUiGlobalColors( override val borders: BorderColors, override val outlines: OutlineColors, override val infoContent: Color, + override val paneBackground: Color, ) : GlobalColors { override fun equals(other: Any?): Boolean { @@ -25,6 +26,7 @@ class IntUiGlobalColors( if (borders != other.borders) return false if (outlines != other.outlines) return false if (infoContent != other.infoContent) return false + if (paneBackground != other.paneBackground) return false return true } @@ -33,11 +35,12 @@ class IntUiGlobalColors( var result = borders.hashCode() result = 31 * result + outlines.hashCode() result = 31 * result + infoContent.hashCode() + result = 31 * result + paneBackground.hashCode() return result } override fun toString(): String = - "IntUiGlobalColors(borders=$borders, outlines=$outlines, infoContent=$infoContent)" + "IntUiGlobalColors(borders=$borders, outlines=$outlines, infoContent=$infoContent, paneBackground=$paneBackground)" companion object { @@ -45,22 +48,26 @@ class IntUiGlobalColors( fun light( borders: BorderColors = IntUiBorderColors.light(), outlines: OutlineColors = IntUiOutlineColors.light(), - infoContent: Color = IntUiTheme.colorPalette.grey(7), + infoContent: Color = IntUiLightTheme.colors.grey(7), + paneBackground: Color = IntUiLightTheme.colors.grey(13), ) = IntUiGlobalColors( borders = borders, outlines = outlines, infoContent = infoContent, + paneBackground = paneBackground, ) @Composable fun dark( borders: BorderColors = IntUiBorderColors.dark(), outlines: OutlineColors = IntUiOutlineColors.dark(), - infoContent: Color = IntUiTheme.colorPalette.grey(7), + infoContent: Color = IntUiDarkTheme.colors.grey(7), + paneBackground: Color = IntUiDarkTheme.colors.grey(2), ) = IntUiGlobalColors( borders = borders, outlines = outlines, infoContent = infoContent, + paneBackground = paneBackground, ) } } diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt index f3d94c5d0..22e8e2f2c 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiTheme.kt @@ -2,9 +2,8 @@ package org.jetbrains.jewel.intui.standalone import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ProvidedValue import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily @@ -15,20 +14,9 @@ import org.jetbrains.jewel.GlobalColors import org.jetbrains.jewel.GlobalMetrics import org.jetbrains.jewel.IntelliJComponentStyling import org.jetbrains.jewel.IntelliJThemeIconData -import org.jetbrains.jewel.JewelSvgLoader -import org.jetbrains.jewel.LocalColorPalette -import org.jetbrains.jewel.LocalContentColor -import org.jetbrains.jewel.LocalGlobalColors -import org.jetbrains.jewel.LocalGlobalMetrics -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.LocalResourceLoader -import org.jetbrains.jewel.LocalTextStyle -import org.jetbrains.jewel.SimpleResourceLoader -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.BaseIntUiTheme import org.jetbrains.jewel.intui.core.IntUiThemeColorPalette import org.jetbrains.jewel.intui.core.IntUiThemeDefinition -import org.jetbrains.jewel.intui.core.IntelliJSvgPatcher import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme import org.jetbrains.jewel.intui.standalone.IntUiTheme.defaultComponentStyling @@ -51,6 +39,7 @@ import org.jetbrains.jewel.intui.standalone.styling.IntUiTabStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiTextAreaStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiTextFieldStyle import org.jetbrains.jewel.intui.standalone.styling.IntUiTooltipStyle +import org.jetbrains.jewel.painter.LocalPainterHintsProvider import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle import org.jetbrains.jewel.styling.ChipStyle @@ -68,7 +57,6 @@ import org.jetbrains.jewel.styling.RadioButtonStyle import org.jetbrains.jewel.styling.ScrollbarStyle import org.jetbrains.jewel.styling.TabStyle import org.jetbrains.jewel.styling.TextFieldStyle -import org.jetbrains.jewel.themes.StandalonePaletteMapperFactory object IntUiTheme : BaseIntUiTheme { @@ -99,92 +87,74 @@ object IntUiTheme : BaseIntUiTheme { contentColor: Color = IntUiDarkTheme.colors.grey(12), ) = IntUiThemeDefinition(isDark = true, colors, palette, icons, metrics, defaultTextStyle, contentColor) - @Composable - fun defaultComponentStyling(theme: IntUiThemeDefinition, svgLoader: SvgLoader): IntelliJComponentStyling { - lateinit var styling: IntelliJComponentStyling - CompositionLocalProvider( - LocalColorPalette provides theme.colorPalette, - LocalTextStyle provides theme.defaultTextStyle, - LocalContentColor provides theme.defaultTextStyle.color, - LocalIconData provides theme.iconData, - LocalGlobalColors provides theme.globalColors, - LocalGlobalMetrics provides theme.globalMetrics, - ) { - styling = if (theme.isDark) darkComponentStyling(svgLoader) else lightComponentStyling(svgLoader) - } - return styling - } + @Composable fun defaultComponentStyling(theme: IntUiThemeDefinition): IntelliJComponentStyling = + if (theme.isDark) darkComponentStyling() else lightComponentStyling() - @Composable - fun darkComponentStyling( - svgLoader: SvgLoader, + @Composable fun darkComponentStyling( defaultButtonStyle: ButtonStyle = IntUiButtonStyle.Default.dark(), outlinedButtonStyle: ButtonStyle = IntUiButtonStyle.Outlined.dark(), - checkboxStyle: CheckboxStyle = IntUiCheckboxStyle.dark(svgLoader), + checkboxStyle: CheckboxStyle = IntUiCheckboxStyle.dark(), chipStyle: ChipStyle = IntUiChipStyle.dark(), dividerStyle: DividerStyle = IntUiDividerStyle.dark(), - dropdownStyle: DropdownStyle = IntUiDropdownStyle.dark(svgLoader), + dropdownStyle: DropdownStyle = IntUiDropdownStyle.dark(), groupHeaderStyle: GroupHeaderStyle = IntUiGroupHeaderStyle.dark(), labelledTextFieldStyle: LabelledTextFieldStyle = IntUiLabelledTextFieldStyle.dark(), - linkStyle: LinkStyle = IntUiLinkStyle.dark(svgLoader), - menuStyle: MenuStyle = IntUiMenuStyle.dark(svgLoader), + linkStyle: LinkStyle = IntUiLinkStyle.dark(), + menuStyle: MenuStyle = IntUiMenuStyle.dark(), horizontalProgressBarStyle: HorizontalProgressBarStyle = IntUiHorizontalProgressBarStyle.dark(), - radioButtonStyle: RadioButtonStyle = IntUiRadioButtonStyle.dark(svgLoader), + radioButtonStyle: RadioButtonStyle = IntUiRadioButtonStyle.dark(), scrollbarStyle: ScrollbarStyle = IntUiScrollbarStyle.dark(), textAreaStyle: IntUiTextAreaStyle = IntUiTextAreaStyle.dark(), textFieldStyle: TextFieldStyle = IntUiTextFieldStyle.dark(), - lazyTreeStyle: LazyTreeStyle = IntUiLazyTreeStyle.dark(svgLoader), - defaultTabStyle: TabStyle = IntUiTabStyle.Default.dark(svgLoader), - editorTabStyle: TabStyle = IntUiTabStyle.Editor.dark(svgLoader), + lazyTreeStyle: LazyTreeStyle = IntUiLazyTreeStyle.dark(), + defaultTabStyle: TabStyle = IntUiTabStyle.Default.dark(), + editorTabStyle: TabStyle = IntUiTabStyle.Editor.dark(), circularProgressStyle: CircularProgressStyle = IntUiCircularProgressStyle.dark(), tooltipStyle: IntUiTooltipStyle = IntUiTooltipStyle.dark(), iconButtonStyle: IconButtonStyle = IntUiIconButtonStyle.dark(), - ) = - IntelliJComponentStyling( - checkboxStyle = checkboxStyle, - chipStyle = chipStyle, - defaultButtonStyle = defaultButtonStyle, - defaultTabStyle = defaultTabStyle, - dividerStyle = dividerStyle, - dropdownStyle = dropdownStyle, - editorTabStyle = editorTabStyle, - groupHeaderStyle = groupHeaderStyle, - horizontalProgressBarStyle = horizontalProgressBarStyle, - labelledTextFieldStyle = labelledTextFieldStyle, - lazyTreeStyle = lazyTreeStyle, - linkStyle = linkStyle, - menuStyle = menuStyle, - outlinedButtonStyle = outlinedButtonStyle, - radioButtonStyle = radioButtonStyle, - scrollbarStyle = scrollbarStyle, - textAreaStyle = textAreaStyle, - textFieldStyle = textFieldStyle, - circularProgressStyle = circularProgressStyle, - tooltipStyle = tooltipStyle, - iconButtonStyle = iconButtonStyle, - ) + ) = IntelliJComponentStyling( + checkboxStyle = checkboxStyle, + chipStyle = chipStyle, + defaultButtonStyle = defaultButtonStyle, + defaultTabStyle = defaultTabStyle, + dividerStyle = dividerStyle, + dropdownStyle = dropdownStyle, + editorTabStyle = editorTabStyle, + groupHeaderStyle = groupHeaderStyle, + horizontalProgressBarStyle = horizontalProgressBarStyle, + labelledTextFieldStyle = labelledTextFieldStyle, + lazyTreeStyle = lazyTreeStyle, + linkStyle = linkStyle, + menuStyle = menuStyle, + outlinedButtonStyle = outlinedButtonStyle, + radioButtonStyle = radioButtonStyle, + scrollbarStyle = scrollbarStyle, + textAreaStyle = textAreaStyle, + textFieldStyle = textFieldStyle, + circularProgressStyle = circularProgressStyle, + tooltipStyle = tooltipStyle, + iconButtonStyle = iconButtonStyle, + ) - @Composable - fun lightComponentStyling( - svgLoader: SvgLoader, + @Composable fun lightComponentStyling( defaultButtonStyle: ButtonStyle = IntUiButtonStyle.Default.light(), outlinedButtonStyle: ButtonStyle = IntUiButtonStyle.Outlined.light(), - checkboxStyle: CheckboxStyle = IntUiCheckboxStyle.light(svgLoader), + checkboxStyle: CheckboxStyle = IntUiCheckboxStyle.light(), chipStyle: ChipStyle = IntUiChipStyle.light(), dividerStyle: DividerStyle = IntUiDividerStyle.light(), - dropdownStyle: DropdownStyle = IntUiDropdownStyle.light(svgLoader), + dropdownStyle: DropdownStyle = IntUiDropdownStyle.light(), groupHeaderStyle: GroupHeaderStyle = IntUiGroupHeaderStyle.light(), labelledTextFieldStyle: LabelledTextFieldStyle = IntUiLabelledTextFieldStyle.light(), - linkStyle: LinkStyle = IntUiLinkStyle.light(svgLoader), - menuStyle: MenuStyle = IntUiMenuStyle.light(svgLoader), + linkStyle: LinkStyle = IntUiLinkStyle.light(), + menuStyle: MenuStyle = IntUiMenuStyle.light(), horizontalProgressBarStyle: HorizontalProgressBarStyle = IntUiHorizontalProgressBarStyle.light(), - radioButtonStyle: RadioButtonStyle = IntUiRadioButtonStyle.light(svgLoader), + radioButtonStyle: RadioButtonStyle = IntUiRadioButtonStyle.light(), scrollbarStyle: ScrollbarStyle = IntUiScrollbarStyle.light(), textAreaStyle: IntUiTextAreaStyle = IntUiTextAreaStyle.light(), textFieldStyle: TextFieldStyle = IntUiTextFieldStyle.light(), - lazyTreeStyle: LazyTreeStyle = IntUiLazyTreeStyle.light(svgLoader), - defaultTabStyle: TabStyle = IntUiTabStyle.Default.light(svgLoader), - editorTabStyle: TabStyle = IntUiTabStyle.Editor.light(svgLoader), + lazyTreeStyle: LazyTreeStyle = IntUiLazyTreeStyle.light(), + defaultTabStyle: TabStyle = IntUiTabStyle.Default.light(), + editorTabStyle: TabStyle = IntUiTabStyle.Editor.light(), circularProgressStyle: CircularProgressStyle = IntUiCircularProgressStyle.light(), tooltipStyle: IntUiTooltipStyle = IntUiTooltipStyle.light(), iconButtonStyle: IconButtonStyle = IntUiIconButtonStyle.light(), @@ -213,55 +183,18 @@ object IntUiTheme : BaseIntUiTheme { ) } -@Composable -fun IntUiTheme( - themeDefinition: IntUiThemeDefinition, - swingCompatMode: Boolean = false, - content: @Composable () -> Unit, -) { - val svgLoader by rememberSvgLoader( - isDark = themeDefinition.isDark, - iconData = themeDefinition.iconData, - colorPalette = themeDefinition.colorPalette, - ) - - val componentStyling = defaultComponentStyling(themeDefinition, svgLoader) - IntUiTheme(themeDefinition, componentStyling, swingCompatMode, content) -} - -/** - * Create and remember an instance of [SvgLoader]. - * - * Note that since [SvgLoader] may cache the loaded images, and that - * creating it may be somewhat expensive, you should only create it once at - * the top level, and pass it around. - */ -@Composable -fun rememberSvgLoader( - isDark: Boolean = IntUiTheme.isDark, - iconData: IntelliJThemeIconData = IntUiTheme.iconData, - colorPalette: IntUiThemeColorPalette = IntUiTheme.colorPalette, -) = - remember(isDark, iconData, colorPalette) { - val paletteMapper = - StandalonePaletteMapperFactory.create( - isDark, - iconData, - colorPalette, - ) - val svgPatcher = IntelliJSvgPatcher(paletteMapper) - mutableStateOf(JewelSvgLoader(svgPatcher)) - } - -@Composable -fun IntUiTheme( +@Composable fun IntUiTheme( theme: IntUiThemeDefinition, - componentStyling: IntelliJComponentStyling, + componentStyling: @Composable () -> Array>, swingCompatMode: Boolean = false, content: @Composable () -> Unit, ) { - BaseIntUiTheme(theme, componentStyling, swingCompatMode) { - CompositionLocalProvider(LocalResourceLoader provides SimpleResourceLoader(IntUiTheme.javaClass.classLoader)) { + BaseIntUiTheme(theme, { + defaultComponentStyling(theme).providedStyles() + componentStyling() + }, swingCompatMode) { + CompositionLocalProvider( + LocalPainterHintsProvider provides StandalonePainterHintsProvider(theme), + ) { content() } } diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/PainterProvider.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/PainterProvider.kt new file mode 100644 index 000000000..779161d58 --- /dev/null +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/PainterProvider.kt @@ -0,0 +1,10 @@ +package org.jetbrains.jewel.intui.standalone + +import org.jetbrains.jewel.painter.ResourcePainterProvider + +/** + * Create a [PainterProvider][org.jetbrains.jewel.painter.PainterProvider] to load a + * resource from the classpath. + */ +fun standalonePainterProvider(path: String) = + ResourcePainterProvider(path, IntUiTheme::class.java.classLoader) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider.kt new file mode 100644 index 000000000..53ca57477 --- /dev/null +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/StandalonePainterHintsProvider.kt @@ -0,0 +1,90 @@ +package org.jetbrains.jewel.intui.standalone + +import androidx.compose.runtime.Composable +import org.jetbrains.jewel.IntelliJTheme +import org.jetbrains.jewel.intui.core.IntUiPainterHintsProvider +import org.jetbrains.jewel.intui.core.IntUiThemeDefinition +import org.jetbrains.jewel.painter.PainterHint +import org.jetbrains.jewel.painter.hints.Dark +import org.jetbrains.jewel.painter.hints.Override + +class StandalonePainterHintsProvider( + theme: IntUiThemeDefinition, +) : IntUiPainterHintsProvider( + theme.isDark, + intellijColorPalette, + theme.iconData.colorPalette, + theme.colorPalette.rawMap, +) { + + private val overrideHint: PainterHint + + init { + overrideHint = Override( + theme.iconData.iconOverrides.entries.associate { (k, v) -> + k.removePrefix("/") to v.removePrefix("/") + }, + ) + } + + @Composable + override fun hints(path: String): List = buildList { + add(getPaletteHint(path)) + add(overrideHint) + add(Dark(IntelliJTheme.isDark)) + } + + companion object { + + // Extracted from com.intellij.ide.ui.UITheme#colorPalette + private val intellijColorPalette = mapOf( + "Actions.Red" to "#DB5860", + "Actions.Red.Dark" to "#C75450", + "Actions.Yellow" to "#EDA200", + "Actions.Yellow.Dark" to "#F0A732", + "Actions.Green" to "#59A869", + "Actions.Green.Dark" to "#499C54", + "Actions.Blue" to "#389FD6", + "Actions.Blue.Dark" to "#3592C4", + "Actions.Grey" to "#6E6E6E", + "Actions.Grey.Dark" to "#AFB1B3", + "Actions.GreyInline" to "#7F8B91", + "Actions.GreyInline.Dark" to "#7F8B91", + "Objects.Grey" to "#9AA7B0", + "Objects.Blue" to "#40B6E0", + "Objects.Green" to "#62B543", + "Objects.Yellow" to "#F4AF3D", + "Objects.YellowDark" to "#D9A343", + "Objects.Purple" to "#B99BF8", + "Objects.Pink" to "#F98B9E", + "Objects.Red" to "#F26522", + "Objects.RedStatus" to "#E05555", + "Objects.GreenAndroid" to "#3DDC84", + "Objects.BlackText" to "#231F20", + "Checkbox.Background.Default" to "#FFFFFF", + "Checkbox.Background.Default.Dark" to "#43494A", + "Checkbox.Background.Disabled" to "#F2F2F2", + "Checkbox.Background.Disabled.Dark" to "#3C3F41", + "Checkbox.Border.Default" to "#b0b0b0", + "Checkbox.Border.Default.Dark" to "#6B6B6B", + "Checkbox.Border.Disabled" to "#BDBDBD", + "Checkbox.Border.Disabled.Dark" to "#545556", + "Checkbox.Focus.Thin.Default" to "#7B9FC7", + "Checkbox.Focus.Thin.Default.Dark" to "#466D94", + "Checkbox.Focus.Wide" to "#97C3F3", + "Checkbox.Focus.Wide.Dark" to "#3D6185", + "Checkbox.Foreground.Disabled" to "#ABABAB", + "Checkbox.Foreground.Disabled.Dark" to "#606060", + "Checkbox.Background.Selected" to "#4F9EE3", + "Checkbox.Background.Selected.Dark" to "#43494A", + "Checkbox.Border.Selected" to "#4B97D9", + "Checkbox.Border.Selected.Dark" to "#6B6B6B", + "Checkbox.Foreground.Selected" to "#FEFEFE", + "Checkbox.Foreground.Selected.Dark" to "#A7A7A7", + "Checkbox.Focus.Thin.Selected" to "#ACCFF7", + "Checkbox.Focus.Thin.Selected.Dark" to "#466D94", + "Tree.iconColor" to "#808080", + "Tree.iconColor.Dark" to "#AFB1B3", + ) + } +} diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt index b35d06664..9e0b173fa 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiCheckboxStyling.kt @@ -4,23 +4,18 @@ import androidx.compose.foundation.shape.CornerSize import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable 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 androidx.compose.ui.unit.dp -import org.jetbrains.jewel.CheckboxState -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme +import org.jetbrains.jewel.intui.standalone.standalonePainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.CheckboxColors import org.jetbrains.jewel.styling.CheckboxIcons import org.jetbrains.jewel.styling.CheckboxMetrics import org.jetbrains.jewel.styling.CheckboxStyle -import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.styling.ResourcePainterProvider -import org.jetbrains.jewel.styling.StatefulResourcePathPatcher @Immutable data class IntUiCheckboxStyle( override val colors: IntUiCheckboxColors, @@ -31,17 +26,15 @@ import org.jetbrains.jewel.styling.StatefulResourcePathPatcher companion object { @Composable fun light( - svgLoader: SvgLoader, colors: IntUiCheckboxColors = IntUiCheckboxColors.light(), metrics: IntUiCheckboxMetrics = IntUiCheckboxMetrics(), - icons: IntUiCheckboxIcons = IntUiCheckboxIcons.light(svgLoader), + icons: IntUiCheckboxIcons = IntUiCheckboxIcons.light(), ) = IntUiCheckboxStyle(colors, metrics, icons) @Composable fun dark( - svgLoader: SvgLoader, colors: IntUiCheckboxColors = IntUiCheckboxColors.dark(), metrics: IntUiCheckboxMetrics = IntUiCheckboxMetrics(), - icons: IntUiCheckboxIcons = IntUiCheckboxIcons.dark(svgLoader), + icons: IntUiCheckboxIcons = IntUiCheckboxIcons.dark(), ) = IntUiCheckboxStyle(colors, metrics, icons) } } @@ -100,42 +93,24 @@ import org.jetbrains.jewel.styling.StatefulResourcePathPatcher ) : CheckboxMetrics @Immutable data class IntUiCheckboxIcons( - override val checkbox: PainterProvider, + override val checkbox: PainterProvider, ) : CheckboxIcons { companion object { @Composable fun checkbox( - svgLoader: SvgLoader, basePath: String = "com/intellij/ide/ui/laf/icons/intellij/checkBox.svg", - ): PainterProvider = ResourcePainterProvider.stateful( - basePath, - svgLoader, - LocalIconData.current, - pathPatcher = StatefulResourcePathPatcher( - prefixTokensProvider = { state: CheckboxState -> - if (state.toggleableState == ToggleableState.Indeterminate) "Indeterminate" else "" - }, - ), - ) + ): PainterProvider = standalonePainterProvider(basePath) @Composable fun light( - svgLoader: SvgLoader, - checkbox: PainterProvider = checkbox( - svgLoader, - "com/intellij/ide/ui/laf/icons/intellij/checkBox.svg", - ), + checkbox: PainterProvider = checkbox("com/intellij/ide/ui/laf/icons/intellij/checkBox.svg"), ) = IntUiCheckboxIcons(checkbox) @Composable fun dark( - svgLoader: SvgLoader, - checkbox: PainterProvider = checkbox( - svgLoader, - "com/intellij/ide/ui/laf/icons/darcula/checkBox.svg", - ), + checkbox: PainterProvider = checkbox("com/intellij/ide/ui/laf/icons/darcula/checkBox.svg"), ) = IntUiCheckboxIcons(checkbox) } } diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiDropdownStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiDropdownStyling.kt index b4e82bb24..4817a2fb2 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiDropdownStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiDropdownStyling.kt @@ -10,19 +10,16 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.DropdownState -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme import org.jetbrains.jewel.intui.standalone.IntUiTheme +import org.jetbrains.jewel.intui.standalone.standalonePainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.DropdownColors import org.jetbrains.jewel.styling.DropdownIcons import org.jetbrains.jewel.styling.DropdownMetrics import org.jetbrains.jewel.styling.DropdownStyle import org.jetbrains.jewel.styling.MenuStyle -import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.styling.ResourcePainterProvider @Stable data class IntUiDropdownStyle( @@ -37,32 +34,29 @@ data class IntUiDropdownStyle( @Composable fun undecorated( - svgLoader: SvgLoader, colors: IntUiDropdownColors, metrics: IntUiDropdownMetrics = IntUiDropdownMetrics(borderWidth = 0.dp), - icons: IntUiDropdownIcons = intUiDropdownIcons(svgLoader), + icons: IntUiDropdownIcons = intUiDropdownIcons(), textStyle: TextStyle = IntUiTheme.defaultTextStyle, - menuStyle: MenuStyle = IntUiMenuStyle.light(svgLoader), + menuStyle: MenuStyle = IntUiMenuStyle.light(), ) = IntUiDropdownStyle(colors, metrics, icons, textStyle, menuStyle) @Composable fun light( - svgLoader: SvgLoader, colors: IntUiDropdownColors = IntUiDropdownColors.light(), metrics: IntUiDropdownMetrics = IntUiDropdownMetrics(), - icons: IntUiDropdownIcons = intUiDropdownIcons(svgLoader), + icons: IntUiDropdownIcons = intUiDropdownIcons(), textStyle: TextStyle = IntUiTheme.defaultTextStyle, - menuStyle: MenuStyle = IntUiMenuStyle.light(svgLoader), + menuStyle: MenuStyle = IntUiMenuStyle.light(), ) = IntUiDropdownStyle(colors, metrics, icons, textStyle, menuStyle) @Composable fun dark( - svgLoader: SvgLoader, colors: IntUiDropdownColors = IntUiDropdownColors.dark(), metrics: IntUiDropdownMetrics = IntUiDropdownMetrics(), - icons: IntUiDropdownIcons = intUiDropdownIcons(svgLoader), + icons: IntUiDropdownIcons = intUiDropdownIcons(), textStyle: TextStyle = IntUiTheme.defaultTextStyle, - menuStyle: MenuStyle = IntUiMenuStyle.dark(svgLoader), + menuStyle: MenuStyle = IntUiMenuStyle.dark(), ) = IntUiDropdownStyle(colors, metrics, icons, textStyle, menuStyle) } } @@ -227,23 +221,20 @@ data class IntUiDropdownMetrics( @Immutable data class IntUiDropdownIcons( - override val chevronDown: PainterProvider, + override val chevronDown: PainterProvider, ) : DropdownIcons { companion object { @Composable fun chevronDown( - svgLoader: SvgLoader, basePath: String = "expui/general/chevronDown.svg", - ): PainterProvider = - ResourcePainterProvider.stateful(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) } } @Composable fun intUiDropdownIcons( - svgLoader: SvgLoader, - chevronDown: PainterProvider = IntUiDropdownIcons.chevronDown(svgLoader), + chevronDown: PainterProvider = IntUiDropdownIcons.chevronDown(), ) = IntUiDropdownIcons(chevronDown) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLazyTreeStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLazyTreeStyling.kt index dec1abdf9..d71a5cbb2 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLazyTreeStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLazyTreeStyling.kt @@ -8,16 +8,14 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme +import org.jetbrains.jewel.intui.standalone.standalonePainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.LazyTreeColors import org.jetbrains.jewel.styling.LazyTreeIcons import org.jetbrains.jewel.styling.LazyTreeMetrics import org.jetbrains.jewel.styling.LazyTreeStyle -import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.styling.ResourcePainterProvider @Stable data class IntUiLazyTreeStyle( @@ -30,18 +28,16 @@ data class IntUiLazyTreeStyle( @Composable fun light( - svgLoader: SvgLoader, colors: IntUiLazyTreeColors = IntUiLazyTreeColors.light(), metrics: IntUiLazyTreeMetrics = IntUiLazyTreeMetrics(), - icons: IntUiLazyTreeIcons = intUiLazyTreeIcons(svgLoader), + icons: IntUiLazyTreeIcons = intUiLazyTreeIcons(), ) = IntUiLazyTreeStyle(colors, metrics, icons) @Composable fun dark( - svgLoader: SvgLoader, colors: IntUiLazyTreeColors = IntUiLazyTreeColors.dark(), metrics: IntUiLazyTreeMetrics = IntUiLazyTreeMetrics(), - icons: IntUiLazyTreeIcons = intUiLazyTreeIcons(svgLoader), + icons: IntUiLazyTreeIcons = intUiLazyTreeIcons(), ) = IntUiLazyTreeStyle(colors, metrics, icons) } } @@ -111,39 +107,34 @@ data class IntUiLazyTreeMetrics( @Immutable data class IntUiLazyTreeIcons( - override val chevronCollapsed: PainterProvider, - override val chevronExpanded: PainterProvider, - override val chevronSelectedCollapsed: PainterProvider, - override val chevronSelectedExpanded: PainterProvider, + override val chevronCollapsed: PainterProvider, + override val chevronExpanded: PainterProvider, + override val chevronSelectedCollapsed: PainterProvider, + override val chevronSelectedExpanded: PainterProvider, ) : LazyTreeIcons { companion object { @Composable fun chevronCollapsed( - svgLoader: SvgLoader, basePath: String = "expui/general/chevronRight.svg", - ): PainterProvider = - ResourcePainterProvider.stateless(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) @Composable fun chevronExpanded( - svgLoader: SvgLoader, basePath: String = "expui/general/chevronDown.svg", - ): PainterProvider = - ResourcePainterProvider.stateless(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) } } @Composable fun intUiLazyTreeIcons( - svgLoader: SvgLoader, - chevronCollapsed: PainterProvider = - IntUiLazyTreeIcons.chevronCollapsed(svgLoader), - chevronExpanded: PainterProvider = - IntUiLazyTreeIcons.chevronExpanded(svgLoader), - chevronSelectedCollapsed: PainterProvider = - IntUiLazyTreeIcons.chevronCollapsed(svgLoader), - chevronSelectedExpanded: PainterProvider = - IntUiLazyTreeIcons.chevronExpanded(svgLoader), + chevronCollapsed: PainterProvider = + IntUiLazyTreeIcons.chevronCollapsed(), + chevronExpanded: PainterProvider = + IntUiLazyTreeIcons.chevronExpanded(), + chevronSelectedCollapsed: PainterProvider = + IntUiLazyTreeIcons.chevronCollapsed(), + chevronSelectedExpanded: PainterProvider = + IntUiLazyTreeIcons.chevronExpanded(), ) = IntUiLazyTreeIcons(chevronCollapsed, chevronExpanded, chevronSelectedCollapsed, chevronSelectedExpanded) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLinkStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLinkStyling.kt index 74af02e39..7bfa49ec0 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLinkStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiLinkStyling.kt @@ -9,19 +9,16 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.LinkState -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme import org.jetbrains.jewel.intui.standalone.IntUiTheme +import org.jetbrains.jewel.intui.standalone.standalonePainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.LinkColors import org.jetbrains.jewel.styling.LinkIcons import org.jetbrains.jewel.styling.LinkMetrics import org.jetbrains.jewel.styling.LinkStyle import org.jetbrains.jewel.styling.LinkTextStyles -import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.styling.ResourcePainterProvider @Immutable data class IntUiLinkStyle( @@ -35,19 +32,17 @@ data class IntUiLinkStyle( @Composable fun light( - svgLoader: SvgLoader, colors: IntUiLinkColors = IntUiLinkColors.light(), metrics: IntUiLinkMetrics = IntUiLinkMetrics(), - icons: IntUiLinkIcons = intUiLinkIcons(svgLoader), + icons: IntUiLinkIcons = intUiLinkIcons(), textStyles: IntUiLinkTextStyles = IntUiLinkTextStyles.light(), ) = IntUiLinkStyle(colors, metrics, icons, textStyles) @Composable fun dark( - svgLoader: SvgLoader, colors: IntUiLinkColors = IntUiLinkColors.dark(), metrics: IntUiLinkMetrics = IntUiLinkMetrics(), - icons: IntUiLinkIcons = intUiLinkIcons(svgLoader), + icons: IntUiLinkIcons = intUiLinkIcons(), textStyles: IntUiLinkTextStyles = IntUiLinkTextStyles.dark(), ) = IntUiLinkStyle(colors, metrics, icons, textStyles) } @@ -110,33 +105,28 @@ data class IntUiLinkMetrics( @Immutable data class IntUiLinkIcons( - override val dropdownChevron: PainterProvider, - override val externalLink: PainterProvider, + override val dropdownChevron: PainterProvider, + override val externalLink: PainterProvider, ) : LinkIcons { companion object { @Composable fun dropdownChevron( - svgLoader: SvgLoader, basePath: String = "expui/general/chevronDown.svg", - ): PainterProvider = - ResourcePainterProvider.stateful(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) @Composable fun externalLink( - svgLoader: SvgLoader, basePath: String = "expui/ide/externalLink.svg", - ): PainterProvider = - ResourcePainterProvider.stateful(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) } } @Composable fun intUiLinkIcons( - svgLoader: SvgLoader, - dropdownChevron: PainterProvider = IntUiLinkIcons.dropdownChevron(svgLoader), - externalLink: PainterProvider = IntUiLinkIcons.externalLink(svgLoader), + dropdownChevron: PainterProvider = IntUiLinkIcons.dropdownChevron(), + externalLink: PainterProvider = IntUiLinkIcons.externalLink(), ) = IntUiLinkIcons(dropdownChevron, externalLink) @Immutable diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiMenuStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiMenuStyling.kt index 16cbc77c3..461fdd35e 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiMenuStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiMenuStyling.kt @@ -9,19 +9,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.MenuItemState -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme +import org.jetbrains.jewel.intui.standalone.standalonePainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.MenuColors import org.jetbrains.jewel.styling.MenuIcons import org.jetbrains.jewel.styling.MenuItemColors import org.jetbrains.jewel.styling.MenuItemMetrics import org.jetbrains.jewel.styling.MenuMetrics import org.jetbrains.jewel.styling.MenuStyle -import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.styling.ResourcePainterProvider import org.jetbrains.jewel.styling.SubmenuMetrics @Stable @@ -35,18 +32,16 @@ data class IntUiMenuStyle( @Composable fun light( - svgLoader: SvgLoader, colors: IntUiMenuColors = IntUiMenuColors.light(), metrics: IntUiMenuMetrics = IntUiMenuMetrics(), - icons: IntUiMenuIcons = intUiMenuIcons(svgLoader), + icons: IntUiMenuIcons = intUiMenuIcons(), ) = IntUiMenuStyle(colors, metrics, icons) @Composable fun dark( - svgLoader: SvgLoader, colors: IntUiMenuColors = IntUiMenuColors.dark(), metrics: IntUiMenuMetrics = IntUiMenuMetrics(), - icons: IntUiMenuIcons = intUiMenuIcons(svgLoader), + icons: IntUiMenuIcons = intUiMenuIcons(), ) = IntUiMenuStyle(colors, metrics, icons) } } @@ -205,23 +200,20 @@ data class IntUiSubmenuMetrics( @Immutable data class IntUiMenuIcons( - override val submenuChevron: PainterProvider, + override val submenuChevron: PainterProvider, ) : MenuIcons { companion object { @Composable fun submenuChevron( - svgLoader: SvgLoader, basePath: String = "expui/general/chevronRight.svg", - ): PainterProvider = - ResourcePainterProvider.stateful(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) } } @Composable fun intUiMenuIcons( - svgLoader: SvgLoader, - submenuChevron: PainterProvider = IntUiMenuIcons.submenuChevron(svgLoader), + submenuChevron: PainterProvider = IntUiMenuIcons.submenuChevron(), ) = IntUiMenuIcons(submenuChevron) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt index 3622b9361..9bdc7b1cc 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButtonStyling.kt @@ -6,17 +6,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.RadioButtonState -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme -import org.jetbrains.jewel.styling.PainterProvider +import org.jetbrains.jewel.intui.standalone.standalonePainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.RadioButtonColors import org.jetbrains.jewel.styling.RadioButtonIcons import org.jetbrains.jewel.styling.RadioButtonMetrics import org.jetbrains.jewel.styling.RadioButtonStyle -import org.jetbrains.jewel.styling.ResourcePainterProvider @Immutable data class IntUiRadioButtonStyle( @@ -29,18 +26,16 @@ data class IntUiRadioButtonStyle( @Composable fun light( - svgLoader: SvgLoader, colors: IntUiRadioButtonColors = IntUiRadioButtonColors.light(), metrics: IntUiRadioButtonMetrics = IntUiRadioButtonMetrics(), - icons: IntUiRadioButtonIcons = IntUiRadioButtonIcons.light(svgLoader), + icons: IntUiRadioButtonIcons = IntUiRadioButtonIcons.light(), ) = IntUiRadioButtonStyle(colors, metrics, icons) @Composable fun dark( - svgLoader: SvgLoader, colors: IntUiRadioButtonColors = IntUiRadioButtonColors.dark(), metrics: IntUiRadioButtonMetrics = IntUiRadioButtonMetrics(), - icons: IntUiRadioButtonIcons = IntUiRadioButtonIcons.dark(svgLoader), + icons: IntUiRadioButtonIcons = IntUiRadioButtonIcons.dark(), ) = IntUiRadioButtonStyle(colors, metrics, icons) } } @@ -101,34 +96,24 @@ data class IntUiRadioButtonMetrics( @Immutable data class IntUiRadioButtonIcons( - override val radioButton: PainterProvider, + override val radioButton: PainterProvider, ) : RadioButtonIcons { companion object { @Composable fun radioButton( - svgLoader: SvgLoader, basePath: String = "com/intellij/ide/ui/laf/icons/intellij/radio.svg", - ): PainterProvider = - ResourcePainterProvider.stateful(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) @Composable fun light( - svgLoader: SvgLoader, - radioButton: PainterProvider = radioButton( - svgLoader, - "com/intellij/ide/ui/laf/icons/intellij/radio.svg", - ), + radioButton: PainterProvider = radioButton("com/intellij/ide/ui/laf/icons/intellij/radio.svg"), ) = IntUiRadioButtonIcons(radioButton) @Composable fun dark( - svgLoader: SvgLoader, - radioButton: PainterProvider = radioButton( - svgLoader, - "com/intellij/ide/ui/laf/icons/darcula/radio.svg", - ), + radioButton: PainterProvider = radioButton("com/intellij/ide/ui/laf/icons/darcula/radio.svg"), ) = IntUiRadioButtonIcons(radioButton) } } diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStyling.kt index f75dce47f..21b12c7da 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStyling.kt @@ -6,13 +6,10 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.ButtonState -import org.jetbrains.jewel.LocalIconData -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme -import org.jetbrains.jewel.styling.PainterProvider -import org.jetbrains.jewel.styling.ResourcePainterProvider +import org.jetbrains.jewel.intui.standalone.standalonePainterProvider +import org.jetbrains.jewel.painter.PainterProvider import org.jetbrains.jewel.styling.TabColors import org.jetbrains.jewel.styling.TabContentAlpha import org.jetbrains.jewel.styling.TabIcons @@ -30,19 +27,17 @@ data class IntUiTabStyle( @Composable fun light( - svgLoader: SvgLoader, colors: TabColors = IntUiTabColors.Default.light(), metrics: TabMetrics = IntUiTabMetrics(), - icons: TabIcons = intUiTabIcons(svgLoader), + icons: TabIcons = intUiTabIcons(), contentAlpha: TabContentAlpha = IntUiTabContentAlpha.default(), ) = IntUiTabStyle(colors, metrics, icons, contentAlpha) @Composable fun dark( - svgLoader: SvgLoader, colors: TabColors = IntUiTabColors.Default.dark(), metrics: TabMetrics = IntUiTabMetrics(), - icons: TabIcons = intUiTabIcons(svgLoader), + icons: TabIcons = intUiTabIcons(), contentAlpha: TabContentAlpha = IntUiTabContentAlpha.default(), ) = IntUiTabStyle(colors, metrics, icons, contentAlpha) } @@ -51,19 +46,17 @@ data class IntUiTabStyle( @Composable fun light( - svgLoader: SvgLoader, colors: TabColors = IntUiTabColors.Editor.light(), metrics: TabMetrics = IntUiTabMetrics(), - icons: TabIcons = intUiTabIcons(svgLoader), + icons: TabIcons = intUiTabIcons(), contentAlpha: TabContentAlpha = IntUiTabContentAlpha.editor(), ) = IntUiTabStyle(colors, metrics, icons, contentAlpha) @Composable fun dark( - svgLoader: SvgLoader, colors: TabColors = IntUiTabColors.Editor.dark(), metrics: TabMetrics = IntUiTabMetrics(), - icons: TabIcons = intUiTabIcons(svgLoader), + icons: TabIcons = intUiTabIcons(), contentAlpha: TabContentAlpha = IntUiTabContentAlpha.editor(), ) = IntUiTabStyle(colors, metrics, icons, contentAlpha) } @@ -346,22 +339,19 @@ data class IntUiTabContentAlpha( } data class IntUiTabIcons( - override val close: PainterProvider, + override val close: PainterProvider, ) : TabIcons { companion object { @Composable fun close( - svgLoader: SvgLoader, basePath: String = "expui/general/closeSmall.svg", - ): PainterProvider = - ResourcePainterProvider.stateful(basePath, svgLoader, LocalIconData.current) + ): PainterProvider = standalonePainterProvider(basePath) } } @Composable fun intUiTabIcons( - svgLoader: SvgLoader, - close: PainterProvider = IntUiTabIcons.close(svgLoader), + close: PainterProvider = IntUiTabIcons.close(), ) = IntUiTabIcons(close) diff --git a/samples/ide-plugin/build.gradle.kts b/samples/ide-plugin/build.gradle.kts index 839531cb0..81a57cb8c 100644 --- a/samples/ide-plugin/build.gradle.kts +++ b/samples/ide-plugin/build.gradle.kts @@ -43,4 +43,8 @@ tasks { buildSearchableOptions { enabled = false } + + runIde { + this.systemProperties["org.jetbrains.jewel.debug"] = "true" + } } diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt index 41d35cae2..ca3286e48 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt @@ -22,9 +22,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.unit.dp -import com.intellij.openapi.components.service +import com.intellij.icons.AllIcons import com.intellij.ui.JBColor import org.jetbrains.jewel.CheckboxRow import org.jetbrains.jewel.CircularProgressIndicator @@ -33,42 +32,32 @@ import org.jetbrains.jewel.DefaultButton import org.jetbrains.jewel.Icon import org.jetbrains.jewel.IconButton import org.jetbrains.jewel.LazyTree -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.OutlinedButton import org.jetbrains.jewel.RadioButtonRow import org.jetbrains.jewel.Text import org.jetbrains.jewel.TextField import org.jetbrains.jewel.Tooltip -import org.jetbrains.jewel.bridge.SwingBridgeService import org.jetbrains.jewel.bridge.SwingBridgeTheme -import org.jetbrains.jewel.bridge.retrieveStatelessIcon import org.jetbrains.jewel.bridge.toComposeColor import org.jetbrains.jewel.foundation.tree.buildTree import org.jetbrains.jewel.intui.standalone.IntUiTheme -@Composable -internal fun ComponentShowcaseTab() { +@Composable internal fun ComponentShowcaseTab() { SwingBridgeTheme { - val resourceLoader = LocalResourceLoader.current val bgColor by remember(IntUiTheme.isDark) { mutableStateOf(JBColor.PanelBackground.toComposeColor()) } val scrollState = rememberScrollState() Row( - modifier = Modifier - .fillMaxSize() - .background(bgColor) - .verticalScroll(scrollState) - .padding(16.dp), + modifier = Modifier.fillMaxSize().background(bgColor).verticalScroll(scrollState).padding(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - ColumnOne(resourceLoader) - ColumnTwo(resourceLoader) + ColumnOne() + ColumnTwo() } } } -@Composable -private fun RowScope.ColumnOne(resourceLoader: ResourceLoader) { +@Composable private fun RowScope.ColumnOne() { Column( Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp), @@ -108,60 +97,55 @@ private fun RowScope.ColumnOne(resourceLoader: ResourceLoader) { CheckboxRow( checked = checked, onCheckedChange = { checked = it }, - resourceLoader = resourceLoader, ) { Text("Hello, I am a themed checkbox") } Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { var index by remember { mutableStateOf(0) } - RadioButtonRow(selected = index == 0, resourceLoader, onClick = { index = 0 }) { + RadioButtonRow(selected = index == 0, onClick = { index = 0 }) { Text("I am number one") } - RadioButtonRow(selected = index == 1, resourceLoader, onClick = { index = 1 }) { + RadioButtonRow(selected = index == 1, onClick = { index = 1 }) { Text("Sad second") } } - val svgLoader = service().svgLoader Row { - val painterProvider = retrieveStatelessIcon("actions/close.svg", svgLoader, IntUiTheme.iconData) - val painter by painterProvider.getPainter(resourceLoader) - Icon(painter = painter, modifier = Modifier.border(1.dp, Color.Magenta), contentDescription = "An icon") + Icon( + "actions/close.svg", + iconClass = AllIcons::class.java, + modifier = Modifier.border(1.dp, Color.Magenta), + contentDescription = "An icon", + ) } Row { IconButton(onClick = { }) { - val painterProvider = retrieveStatelessIcon("actions/close.svg", svgLoader, IntUiTheme.iconData) - val painter by painterProvider.getPainter(resourceLoader) - Icon(painter = painter, contentDescription = "An icon") + Icon("actions/close.svg", contentDescription = "An icon", AllIcons::class.java) } } Row { Text("Circular progress small: ") - CircularProgressIndicator(svgLoader) + CircularProgressIndicator() } Row(verticalAlignment = Alignment.CenterVertically) { Text("Circular progress big: ") - CircularProgressIndicatorBig(svgLoader) + CircularProgressIndicatorBig() } Row(verticalAlignment = Alignment.CenterVertically) { Tooltip(tooltip = { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - val painterProvider = - retrieveStatelessIcon("general/showInfos.svg", svgLoader, IntUiTheme.iconData) - val painter by painterProvider.getPainter(resourceLoader) - Icon(painter = painter, contentDescription = null) + Icon("general/showInfos.svg", contentDescription = null, AllIcons::class.java) Text("This is a tooltip") } }) { Text( - modifier = Modifier.border(1.dp, IntUiTheme.globalColors.borders.normal) - .padding(12.dp, 8.dp), + modifier = Modifier.border(1.dp, IntUiTheme.globalColors.borders.normal).padding(12.dp, 8.dp), text = "Hover Me!", ) } @@ -169,8 +153,7 @@ private fun RowScope.ColumnOne(resourceLoader: ResourceLoader) { } } -@Composable -private fun RowScope.ColumnTwo(resourceLoader: ResourceLoader) { +@Composable private fun RowScope.ColumnTwo() { Column( Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp), @@ -196,7 +179,6 @@ private fun RowScope.ColumnTwo(resourceLoader: ResourceLoader) { } LazyTree( tree = tree, - resourceLoader = resourceLoader, modifier = Modifier.height(200.dp).fillMaxWidth(), onElementClick = {}, onElementDoubleClick = {}, diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt index fe755282f..473d12f2e 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt @@ -53,8 +53,6 @@ import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.ResourceLoader -import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale @@ -62,6 +60,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.intellij.icons.AllIcons import com.intellij.ide.ui.laf.darcula.DarculaUIUtil import com.intellij.openapi.components.service import com.intellij.openapi.project.Project @@ -69,22 +68,19 @@ import com.intellij.ui.NewUI import com.intellij.ui.RelativeFont import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI +import icons.JewelIcons import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.datetime.toJavaLocalDate import org.jetbrains.jewel.HorizontalSplitLayout import org.jetbrains.jewel.Icon -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.PopupMenu -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.Text import org.jetbrains.jewel.TextField import org.jetbrains.jewel.VerticalScrollbar -import org.jetbrains.jewel.bridge.SwingBridgeService import org.jetbrains.jewel.bridge.SwingBridgeTheme import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified -import org.jetbrains.jewel.bridge.retrieveStatelessIcon import org.jetbrains.jewel.bridge.retrieveTextStyle import org.jetbrains.jewel.bridge.toComposeColor import org.jetbrains.jewel.bridge.toFontFamily @@ -96,6 +92,7 @@ import org.jetbrains.jewel.foundation.onHover import org.jetbrains.jewel.foundation.utils.thenIf import org.jetbrains.jewel.intui.standalone.IntUiTheme import org.jetbrains.jewel.items +import org.jetbrains.jewel.painter.rememberResourcePainterProvider import org.jetbrains.skiko.DependsOnJBR import java.awt.Font import java.time.format.DateTimeFormatter @@ -106,14 +103,11 @@ import kotlin.time.Duration.Companion.seconds @Composable fun ReleasesSampleCompose(project: Project) { SwingBridgeTheme { - val svgLoader = service().svgLoader - var selectedItem: ContentItem? by remember { mutableStateOf(null) } HorizontalSplitLayout( first = { modifier -> LeftColumn( project = project, - svgLoader = svgLoader, modifier = modifier.fillMaxSize(), onSelectedItemChange = { selectedItem = it }, ) @@ -135,7 +129,6 @@ fun ReleasesSampleCompose(project: Project) { @Composable fun LeftColumn( project: Project, - svgLoader: SvgLoader, modifier: Modifier = Modifier, onSelectedItemChange: (ContentItem?) -> Unit, ) { @@ -153,12 +146,11 @@ fun LeftColumn( Spacer(Modifier.width(8.dp)) - val resourceLoader = LocalResourceLoader.current - SearchBar(service, svgLoader, resourceLoader, Modifier.weight(1f)) + SearchBar(service, Modifier.weight(1f)) Spacer(Modifier.width(4.dp)) - OverflowMenu(currentContentSource, svgLoader, resourceLoader) { + OverflowMenu(currentContentSource) { service.setContentSource(it) } } @@ -279,15 +271,10 @@ private enum class ItemType { @Composable private fun SearchBar( service: ReleasesSampleService, - svgLoader: SvgLoader, - resourceLoader: ResourceLoader, modifier: Modifier = Modifier, ) { val filterText by service.filter.collectAsState() - val searchIconProvider = retrieveStatelessIcon("actions/search.svg", svgLoader, IntUiTheme.iconData) - val searchIcon by searchIconProvider.getPainter(resourceLoader) - val focusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { @@ -299,11 +286,11 @@ private fun SearchBar( onValueChange = { service.filterContent(it) }, modifier = modifier.focusRequester(focusRequester), leadingIcon = { - Icon(searchIcon, null, Modifier.padding(end = 8.dp)) + Icon("actions/search.svg", null, AllIcons::class.java, Modifier.padding(end = 8.dp)) }, trailingIcon = { if (filterText.isNotBlank()) { - CloseIconButton(svgLoader, resourceLoader, service) + CloseIconButton(service) } }, ) @@ -311,8 +298,6 @@ private fun SearchBar( @Composable private fun CloseIconButton( - svgLoader: SvgLoader, - resourceLoader: ResourceLoader, service: ReleasesSampleService, ) { val interactionSource = remember { MutableInteractionSource() } @@ -327,12 +312,12 @@ private fun CloseIconButton( } } - val closeIconProvider = retrieveStatelessIcon("actions/close.svg", svgLoader, IntUiTheme.iconData) - val closeIcon by closeIconProvider.getPainter(resourceLoader) + val closeIconProvider = rememberResourcePainterProvider("actions/close.svg", AllIcons::class.java) + val closeIcon by closeIconProvider.getPainter() val hoveredCloseIconProvider = - retrieveStatelessIcon("actions/closeHovered.svg", svgLoader, IntUiTheme.iconData) - val hoveredCloseIcon by hoveredCloseIconProvider.getPainter(resourceLoader) + rememberResourcePainterProvider("actions/closeHovered.svg", AllIcons::class.java) + val hoveredCloseIcon by hoveredCloseIconProvider.getPainter() Icon( painter = if (hovered) hoveredCloseIcon else closeIcon, @@ -350,14 +335,8 @@ private fun CloseIconButton( @Composable private fun OverflowMenu( currentContentSource: ContentSource<*>, - svgLoader: SvgLoader, - resourceLoader: ResourceLoader, onContentSourceChange: (ContentSource<*>) -> Unit, ) { - val iconProvider = - retrieveStatelessIcon("actions/more.svg", svgLoader, IntUiTheme.iconData) - val icon by iconProvider.getPainter(resourceLoader) - val interactionSource = remember { MutableInteractionSource() } var hovered by remember { mutableStateOf(false) } var pressed by remember { mutableStateOf(false) } @@ -385,7 +364,8 @@ private fun OverflowMenu( // TODO use IconButton when it exists Icon( - painter = icon, + resource = "actions/more.svg", + iconClass = AllIcons::class.java, contentDescription = "Select data source", modifier = Modifier .fillMaxHeight() @@ -398,9 +378,8 @@ private fun OverflowMenu( } if (menuVisible) { - val checkedIconProvider = - retrieveStatelessIcon("actions/checked.svg", svgLoader, IntUiTheme.iconData) - val checkedIcon by checkedIconProvider.getPainter(resourceLoader) + val checkedIconProvider = rememberResourcePainterProvider("actions/checked.svg", AllIcons::class.java) + val checkedIcon by checkedIconProvider.getPainter() PopupMenu( onDismissRequest = { @@ -434,7 +413,6 @@ private fun OverflowMenu( } } }, - resourceLoader = resourceLoader, ) } } @@ -470,7 +448,8 @@ fun RightColumn( @Composable private fun ReleaseImage(imagePath: String) { - val painter = painterResource(imagePath, LocalResourceLoader.current) + val painterProvider = rememberResourcePainterProvider(imagePath, JewelIcons::class.java) + val painter by painterProvider.getPainter() val transition = rememberInfiniteTransition("HoloFoil") val offset by transition.animateFloat( initialValue = -1f, diff --git a/samples/standalone/build.gradle.kts b/samples/standalone/build.gradle.kts index e2fa0ace1..8f2063a42 100644 --- a/samples/standalone/build.gradle.kts +++ b/samples/standalone/build.gradle.kts @@ -19,6 +19,8 @@ compose.desktop { application { mainClass = "org.jetbrains.jewel.samples.standalone.MainKt" + jvmArgs("-Dorg.jetbrains.jewel.debug=true") + nativeDistributions { targetFormats(TargetFormat.Dmg) packageName = "Jewel Sample" diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt index 4f3990c6e..e9dadce9e 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt @@ -36,16 +36,13 @@ import org.jetbrains.jewel.Divider import org.jetbrains.jewel.Dropdown import org.jetbrains.jewel.Icon import org.jetbrains.jewel.IconButton -import org.jetbrains.jewel.JewelSvgLoader -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.Orientation import org.jetbrains.jewel.Text import org.jetbrains.jewel.Tooltip import org.jetbrains.jewel.VerticalScrollbar import org.jetbrains.jewel.intui.standalone.IntUiTheme -import org.jetbrains.jewel.intui.standalone.rememberSvgLoader +import org.jetbrains.jewel.intui.window.decoratedWindowComponentStyling import org.jetbrains.jewel.intui.window.styling.IntUiTitleBarStyle -import org.jetbrains.jewel.intui.window.withDecoratedWindow import org.jetbrains.jewel.samples.standalone.components.Borders import org.jetbrains.jewel.samples.standalone.components.Buttons import org.jetbrains.jewel.samples.standalone.components.Checkboxes @@ -60,7 +57,6 @@ import org.jetbrains.jewel.samples.standalone.components.TextAreas import org.jetbrains.jewel.samples.standalone.components.TextFields import org.jetbrains.jewel.samples.standalone.components.Tooltips import org.jetbrains.jewel.separator -import org.jetbrains.jewel.styling.rememberStatelessPainterProvider import org.jetbrains.jewel.window.DecoratedWindow import org.jetbrains.jewel.window.TitleBar import org.jetbrains.jewel.window.newFullscreenControls @@ -84,18 +80,18 @@ fun main() { ) IntUiTheme( - theme.withDecoratedWindow( - titleBarStyle = when (intUiTheme) { - IntUiThemes.Light -> IntUiTitleBarStyle.light() - IntUiThemes.LightWithLightHeader -> IntUiTitleBarStyle.lightWithLightHeader() - IntUiThemes.Dark -> IntUiTitleBarStyle.dark() - }, - ), + theme, + { + theme.decoratedWindowComponentStyling( + titleBarStyle = when (intUiTheme) { + IntUiThemes.Light -> IntUiTitleBarStyle.light() + IntUiThemes.LightWithLightHeader -> IntUiTitleBarStyle.lightWithLightHeader() + IntUiThemes.Dark -> IntUiTitleBarStyle.dark() + }, + ) + }, swingCompat, ) { - val resourceLoader = LocalResourceLoader.current - val svgLoader by rememberSvgLoader() - DecoratedWindow( onCloseRequest = { exitApplication() }, title = "Jewel component catalog", @@ -107,11 +103,8 @@ fun main() { IntUiTheme.colorPalette.grey(14) } TitleBar(Modifier.newFullscreenControls(), gradientStartColor = projectColor) { - val jewelLogoProvider = rememberStatelessPainterProvider("icons/jewel-logo.svg", svgLoader) - val jewelLogo by jewelLogoProvider.getPainter(resourceLoader) - Row(Modifier.align(Alignment.Start)) { - Dropdown(resourceLoader, Modifier.height(30.dp), menuContent = { + Dropdown(Modifier.height(30.dp), menuContent = { selectableItem(false, { }) { Text("New Project...") @@ -126,7 +119,12 @@ fun main() { horizontalArrangement = Arrangement.spacedBy(3.dp), verticalAlignment = Alignment.CenterVertically, ) { - Icon(jewelLogo, "Jewel Logo", Modifier.padding(horizontal = 4.dp).size(20.dp)) + Icon( + "icons/jewel-logo.svg", + "Jewel Logo", + StandaloneSampleIcons::class.java, + Modifier.padding(horizontal = 4.dp).size(20.dp), + ) Text("jewel") } } @@ -141,8 +139,7 @@ fun main() { IconButton({ Desktop.getDesktop().browse(URI.create("https://github.com/JetBrains/jewel")) }, Modifier.size(40.dp).padding(5.dp)) { - val iconProvider = rememberStatelessPainterProvider("icons/github@20x20.svg", svgLoader) - Icon(iconProvider.getPainter(resourceLoader).value, "Github") + Icon("icons/github@20x20.svg", "Github", StandaloneSampleIcons::class.java) } } @@ -160,13 +157,11 @@ fun main() { IntUiThemes.Dark -> IntUiThemes.Light } }, Modifier.size(40.dp).padding(5.dp)) { - val lightThemeIcon = - rememberStatelessPainterProvider("icons/lightTheme@20x20.svg", svgLoader) - val darkThemeIcon = - rememberStatelessPainterProvider("icons/darkTheme@20x20.svg", svgLoader) - - val iconProvider = if (intUiTheme.isDark()) darkThemeIcon else lightThemeIcon - Icon(iconProvider.getPainter(resourceLoader).value, "Themes") + if (intUiTheme.isDark()) { + Icon("icons/darkTheme@20x20.svg", "Themes", StandaloneSampleIcons::class.java) + } else { + Icon("icons/lightTheme@20x20.svg", "Themes", StandaloneSampleIcons::class.java) + } } } } @@ -178,12 +173,12 @@ fun main() { horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically, ) { - CheckboxRow("Swing compat", swingCompat, resourceLoader, { swingCompat = it }) + CheckboxRow("Swing compat", swingCompat, { swingCompat = it }) } Divider(Orientation.Horizontal, Modifier.fillMaxWidth()) - ComponentShowcase(svgLoader, resourceLoader) + ComponentShowcase() } } } @@ -191,7 +186,7 @@ fun main() { } @Composable -private fun ComponentShowcase(svgLoader: JewelSvgLoader, resourceLoader: ResourceLoader) { +private fun ComponentShowcase() { val verticalScrollState = rememberScrollState() Box(Modifier.fillMaxSize()) { Column( @@ -202,18 +197,18 @@ private fun ComponentShowcase(svgLoader: JewelSvgLoader, resourceLoader: Resourc horizontalAlignment = Alignment.Start, ) { Borders() - Buttons(svgLoader, resourceLoader) + Buttons() Dropdowns() Checkboxes() RadioButtons() Links() Tooltips() - TextFields(svgLoader, resourceLoader) + TextFields() TextAreas() - ProgressBar(svgLoader) + ProgressBar() ChipsAndTree() - Tabs(svgLoader, resourceLoader) - Icons(svgLoader, resourceLoader) + Tabs() + Icons() } VerticalScrollbar( diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/StandaloneSampleIcons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/StandaloneSampleIcons.kt new file mode 100644 index 000000000..bbf0bc6e9 --- /dev/null +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/StandaloneSampleIcons.kt @@ -0,0 +1,3 @@ +package org.jetbrains.jewel.samples.standalone + +object StandaloneSampleIcons diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Borders.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Borders.kt index d0b9e4557..5d465a9d1 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Borders.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Borders.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp import org.jetbrains.jewel.GroupHeader -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.OutlinedButton import org.jetbrains.jewel.RadioButtonRow import org.jetbrains.jewel.Text @@ -28,7 +27,6 @@ import org.jetbrains.jewel.intui.standalone.IntUiTheme internal fun Borders() { GroupHeader("Borders") var borderAlignment by remember { mutableStateOf(Stroke.Alignment.Center) } - val resourceLoader = LocalResourceLoader.current Row( horizontalArrangement = Arrangement.spacedBy(10.dp), @@ -37,19 +35,16 @@ internal fun Borders() { RadioButtonRow( text = "Inside", selected = borderAlignment == Stroke.Alignment.Inside, - resourceLoader = resourceLoader, onClick = { borderAlignment = Stroke.Alignment.Inside }, ) RadioButtonRow( text = "Center", selected = borderAlignment == Stroke.Alignment.Center, - resourceLoader = resourceLoader, onClick = { borderAlignment = Stroke.Alignment.Center }, ) RadioButtonRow( text = "Outside", selected = borderAlignment == Stroke.Alignment.Outside, - resourceLoader = resourceLoader, onClick = { borderAlignment = Stroke.Alignment.Outside }, ) } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Buttons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Buttons.kt index fc9213cd5..05dc89976 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Buttons.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Buttons.kt @@ -7,19 +7,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.unit.dp import org.jetbrains.jewel.DefaultButton import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.Icon import org.jetbrains.jewel.IconButton -import org.jetbrains.jewel.JewelSvgLoader import org.jetbrains.jewel.OutlinedButton import org.jetbrains.jewel.Text -import org.jetbrains.jewel.styling.rememberStatelessPainterProvider +import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons @Composable -fun Buttons(svgLoader: JewelSvgLoader, resourceLoader: ResourceLoader) { +fun Buttons() { GroupHeader("Buttons") Row( modifier = Modifier.fillMaxWidth(), @@ -43,11 +41,10 @@ fun Buttons(svgLoader: JewelSvgLoader, resourceLoader: ResourceLoader) { } IconButton(onClick = {}) { - val iconProvider = rememberStatelessPainterProvider("icons/close.svg", svgLoader) - val iconPainter by iconProvider.getPainter(resourceLoader) Icon( - painter = iconPainter, + resource = "icons/close.svg", "icon", + StandaloneSampleIcons::class.java, ) } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Checkboxes.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Checkboxes.kt index 878464534..3937172b5 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Checkboxes.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Checkboxes.kt @@ -11,7 +11,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp import org.jetbrains.jewel.GroupHeader -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.Outline import org.jetbrains.jewel.TriStateCheckboxRow @@ -22,29 +21,28 @@ fun Checkboxes() { horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically, ) { - val resourceLoader = LocalResourceLoader.current var checked by remember { mutableStateOf(ToggleableState.On) } - TriStateCheckboxRow("Checkbox", checked, resourceLoader, { + TriStateCheckboxRow("Checkbox", checked, { checked = when (checked) { ToggleableState.On -> ToggleableState.Off ToggleableState.Off -> ToggleableState.Indeterminate ToggleableState.Indeterminate -> ToggleableState.On } }) - TriStateCheckboxRow("Error", checked, resourceLoader, { + TriStateCheckboxRow("Error", checked, { checked = when (checked) { ToggleableState.On -> ToggleableState.Off ToggleableState.Off -> ToggleableState.Indeterminate ToggleableState.Indeterminate -> ToggleableState.On } }, outline = Outline.Error) - TriStateCheckboxRow("Warning", checked, resourceLoader, { + TriStateCheckboxRow("Warning", checked, { checked = when (checked) { ToggleableState.On -> ToggleableState.Off ToggleableState.Off -> ToggleableState.Indeterminate ToggleableState.Indeterminate -> ToggleableState.On } }, outline = Outline.Warning) - TriStateCheckboxRow("Disabled", checked, resourceLoader, {}, enabled = false) + TriStateCheckboxRow("Disabled", checked, {}, enabled = false) } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ChipsAndTree.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ChipsAndTree.kt index e9c98a668..7782975f4 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ChipsAndTree.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ChipsAndTree.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp import org.jetbrains.jewel.Chip import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.LazyTree -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.RadioButtonChip import org.jetbrains.jewel.Text import org.jetbrains.jewel.ToggleableChip @@ -171,7 +170,6 @@ fun TreeSample(modifier: Modifier = Modifier) { } } - val resourceLoader = LocalResourceLoader.current val borderColor = if (IntUiTheme.isDark) { IntUiTheme.colorPalette.grey(3) @@ -182,7 +180,6 @@ fun TreeSample(modifier: Modifier = Modifier) { Box(modifier.border(1.dp, borderColor, RoundedCornerShape(2.dp))) { LazyTree( tree = tree, - resourceLoader = resourceLoader, modifier = Modifier.size(200.dp, 200.dp), onElementClick = {}, onElementDoubleClick = {}, diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Dropdowns.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Dropdowns.kt index a097d2291..31ad70bd7 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Dropdowns.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Dropdowns.kt @@ -11,7 +11,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.unit.dp import org.jetbrains.jewel.Dropdown import org.jetbrains.jewel.GroupHeader -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.Outline import org.jetbrains.jewel.Text import org.jetbrains.jewel.separator @@ -34,18 +33,15 @@ fun Dropdowns() { ) } var selected by remember { mutableStateOf(items.first()) } - val resourceLoader = LocalResourceLoader.current Dropdown( enabled = false, - resourceLoader = resourceLoader, menuContent = { }, ) { Text("Disabled") } Dropdown( - resourceLoader = resourceLoader, menuContent = { items.forEach { if (it == "---") { @@ -95,7 +91,6 @@ fun Dropdowns() { Text(selected) } Dropdown( - resourceLoader = resourceLoader, outline = Outline.Error, menuContent = { items.forEach { diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Icons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Icons.kt index 71c494d84..bc7fc45d1 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Icons.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Icons.kt @@ -11,28 +11,32 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.unit.dp import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.Icon -import org.jetbrains.jewel.SvgLoader -import org.jetbrains.jewel.styling.rememberStatelessPainterProvider +import org.jetbrains.jewel.painter.rememberResourcePainterProvider +import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons @Composable -internal fun Icons(svgLoader: SvgLoader, resourceLoader: ResourceLoader) { +internal fun Icons() { GroupHeader("Icons") Row( modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - val jewelLogoProvider = rememberStatelessPainterProvider("icons/jewel-logo.svg", svgLoader) - val jewelLogo by jewelLogoProvider.getPainter(resourceLoader) + val iconProvider = rememberResourcePainterProvider("icons/jewel-logo.svg", StandaloneSampleIcons::class.java) + val logo by iconProvider.getPainter() - Icon(jewelLogo, "Jewel Logo", Modifier.size(16.dp)) - Icon(jewelLogo, "Jewel Logo", Modifier.size(32.dp)) - Icon(jewelLogo, "Jewel Logo", Modifier.size(64.dp)) - Icon(jewelLogo, "Jewel Logo", Modifier.size(128.dp)) - Icon(jewelLogo, "Jewel Logo", ColorFilter.tint(Color.Magenta, BlendMode.Multiply), Modifier.size(128.dp)) + Icon(logo, "Jewel Logo", Modifier.size(16.dp)) + Icon(logo, "Jewel Logo", Modifier.size(32.dp)) + Icon(logo, "Jewel Logo", Modifier.size(64.dp)) + Icon(logo, "Jewel Logo", Modifier.size(128.dp)) + Icon( + logo, + "Jewel Logo", + ColorFilter.tint(Color.Magenta, BlendMode.Multiply), + Modifier.size(128.dp), + ) } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Links.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Links.kt index c75d39f34..710853398 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Links.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Links.kt @@ -13,7 +13,6 @@ import org.jetbrains.jewel.DropdownLink import org.jetbrains.jewel.ExternalLink import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.Link -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.Text import org.jetbrains.jewel.separator @@ -21,14 +20,13 @@ import org.jetbrains.jewel.separator fun Links() { GroupHeader("Links") - val resourceLoader = LocalResourceLoader.current Row( horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically, ) { - Link("Link", resourceLoader, {}) + Link("Link", {}) - ExternalLink("ExternalLink", resourceLoader, {}) + ExternalLink("ExternalLink", {}) val items = remember { listOf( @@ -41,7 +39,7 @@ fun Links() { ) } var selected by remember { mutableStateOf(items.first()) } - DropdownLink("DropdownLink", resourceLoader) { + DropdownLink("DropdownLink") { items.forEach { if (it == "---") { separator() @@ -59,11 +57,11 @@ fun Links() { horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically, ) { - Link("Link", resourceLoader, {}, enabled = false) + Link("Link", {}, enabled = false) - ExternalLink("ExternalLink", resourceLoader, {}, enabled = false) + ExternalLink("ExternalLink", {}, enabled = false) - DropdownLink("DropdownLink", resourceLoader, enabled = false) { + DropdownLink("DropdownLink", enabled = false) { } } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt index 89e944f0d..004327e31 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/ProgressBar.kt @@ -23,11 +23,10 @@ import org.jetbrains.jewel.CircularProgressIndicatorBig import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.HorizontalProgressBar import org.jetbrains.jewel.IndeterminateHorizontalProgressBar -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.Text @Composable -fun ProgressBar(svgLoader: SvgLoader) { +fun ProgressBar() { GroupHeader("Progress bars") val transition = rememberInfiniteTransition() val currentOffset by transition.animateFloat( @@ -104,7 +103,7 @@ fun ProgressBar(svgLoader: SvgLoader) { verticalAlignment = Alignment.CenterVertically, ) { Text("CircularProgress (16x16)") - CircularProgressIndicator(svgLoader) + CircularProgressIndicator() } Row( Modifier.width(600.dp), @@ -112,7 +111,7 @@ fun ProgressBar(svgLoader: SvgLoader) { verticalAlignment = Alignment.CenterVertically, ) { Text("CircularProgressBig (32x32) - Big") - CircularProgressIndicatorBig(svgLoader) + CircularProgressIndicatorBig() } } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/RadioButtons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/RadioButtons.kt index a2bc75696..9481ad763 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/RadioButtons.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/RadioButtons.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.unit.dp import org.jetbrains.jewel.GroupHeader -import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.Outline import org.jetbrains.jewel.RadioButtonRow @@ -21,13 +20,11 @@ fun RadioButtons() { horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically, ) { - val resourceLoader = LocalResourceLoader.current var index by remember { mutableStateOf(0) } RadioButtonRow( text = "Default", selected = index == 0, onClick = { index = 0 }, - resourceLoader = resourceLoader, ) RadioButtonRow( @@ -35,7 +32,6 @@ fun RadioButtons() { selected = index == 1, onClick = { index = 1 }, outline = Outline.Error, - resourceLoader = resourceLoader, ) RadioButtonRow( @@ -43,7 +39,6 @@ fun RadioButtons() { selected = index == 2, onClick = { index = 2 }, outline = Outline.Warning, - resourceLoader = resourceLoader, ) RadioButtonRow( @@ -51,7 +46,6 @@ fun RadioButtons() { selected = index == 3, onClick = { index = 3 }, enabled = false, - resourceLoader = resourceLoader, ) } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Tabs.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Tabs.kt index cf7015d06..e8825e622 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Tabs.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Tabs.kt @@ -22,40 +22,32 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.Icon import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.NoIndication -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.TabData import org.jetbrains.jewel.TabStrip import org.jetbrains.jewel.Text import org.jetbrains.jewel.intui.standalone.IntUiTheme -import org.jetbrains.jewel.styling.rememberStatelessPainterProvider +import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons import kotlin.math.max @Composable -fun Tabs( - svgLoader: SvgLoader, - resourceLoader: ResourceLoader, -) { +fun Tabs() { GroupHeader("Tabs") Text("Default tabs", Modifier.fillMaxWidth()) - DefaultTabShowcase(svgLoader, resourceLoader) + DefaultTabShowcase() Spacer(Modifier.height(16.dp)) Text("Editor tabs", Modifier.fillMaxWidth()) - EditorTabShowcase(svgLoader, resourceLoader) + EditorTabShowcase() } @Composable -private fun DefaultTabShowcase( - svgLoader: SvgLoader, - resourceLoader: ResourceLoader, -) { +private fun DefaultTabShowcase() { var selectedTabIndex by remember { mutableStateOf(0) } var tabIds by remember { mutableStateOf((1..12).toList()) } @@ -79,7 +71,7 @@ private fun DefaultTabShowcase( } } - TabStripWithAddButton(tabs, svgLoader, resourceLoader) { + TabStripWithAddButton(tabs) { val insertionIndex = (selectedTabIndex + 1).coerceIn(0..tabIds.size) val nextTabId = maxId + 1 @@ -90,10 +82,7 @@ private fun DefaultTabShowcase( } @Composable -private fun EditorTabShowcase( - svgLoader: SvgLoader, - resourceLoader: ResourceLoader, -) { +private fun EditorTabShowcase() { var selectedTabIndex by remember { mutableStateOf(0) } var tabIds by remember { mutableStateOf((1..12).toList()) } @@ -117,7 +106,7 @@ private fun EditorTabShowcase( } } - TabStripWithAddButton(tabs, svgLoader, resourceLoader) { + TabStripWithAddButton(tabs) { val insertionIndex = (selectedTabIndex + 1).coerceIn(0..tabIds.size) val nextTabId = maxId + 1 @@ -130,8 +119,6 @@ private fun EditorTabShowcase( @Composable private fun TabStripWithAddButton( tabs: List, - svgLoader: SvgLoader, - resourceLoader: ResourceLoader, onAddClick: () -> Unit, ) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -170,10 +157,7 @@ private fun TabStripWithAddButton( .background(backgroundColor), contentAlignment = Alignment.Center, ) { - val addIconProvider = rememberStatelessPainterProvider("expui/general/add.svg", svgLoader) - val addIcon by addIconProvider.getPainter(resourceLoader) - - Icon(addIcon, contentDescription = "Add a tab") + Icon(resource = "expui/general/add.svg", contentDescription = "Add a tab", StandaloneSampleIcons::class.java) } } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt index 83af58ef1..9101f43ea 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt @@ -10,20 +10,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.unit.dp import org.jetbrains.jewel.GroupHeader import org.jetbrains.jewel.Icon import org.jetbrains.jewel.LabelledTextField -import org.jetbrains.jewel.LocalIconData import org.jetbrains.jewel.Outline -import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.Text import org.jetbrains.jewel.TextField -import org.jetbrains.jewel.styling.rememberStatelessPainterProvider +import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons @Composable -fun TextFields(svgLoader: SvgLoader, resourceLoader: ResourceLoader) { +fun TextFields() { GroupHeader("TextFields") Row( horizontalArrangement = Arrangement.spacedBy(10.dp), @@ -72,12 +69,7 @@ fun TextFields(svgLoader: SvgLoader, resourceLoader: ResourceLoader) { ) { var text by remember { mutableStateOf("With leading icon") } TextField(text, { text = it }, enabled = true, leadingIcon = { - val iconData = LocalIconData.current - val searchIcon by rememberStatelessPainterProvider("icons/search.svg", svgLoader, iconData) - .getPainter( - resourceLoader, - ) - Icon(searchIcon, "SearchIcon", Modifier.size(16.dp)) + Icon("icons/search.svg", "SearchIcon", StandaloneSampleIcons::class.java, Modifier.size(16.dp)) }) } }