From 551d8245f4cf8f464e12d71e602eb63293572e1f Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Fri, 29 Sep 2023 13:46:09 +0200 Subject: [PATCH] Show how to create icons in standalone sample (#139) Also clean up related code --- .../main/kotlin/org/jetbrains/jewel/Icon.kt | 16 ---- .../org/jetbrains/jewel/NoIndication.kt | 21 +++++ .../main/kotlin/org/jetbrains/jewel/Tabs.kt | 19 ----- .../jewel/intui/core/BaseIntUiTheme.kt | 19 +---- .../jewel/intui/standalone/IntUiTheme.kt | 35 +++++++-- .../jewel/samples/standalone/Main.kt | 11 ++- .../samples/standalone/components/Icons.kt | 39 ++++++++++ .../samples/standalone/components/Tabs.kt | 76 ++++++++++++++++--- 8 files changed, 160 insertions(+), 76 deletions(-) create mode 100644 core/src/main/kotlin/org/jetbrains/jewel/NoIndication.kt create mode 100644 samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Icons.kt diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt b/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt index 8208ab47d..05658f0d6 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Icon.kt @@ -90,22 +90,6 @@ fun Icon( ) } -@Composable -fun Icon( - resource: String, - contentDescription: String?, - modifier: Modifier = Modifier, - resourceLoader: ResourceLoader = LocalResourceLoader.current, - tint: Color = Color.Unspecified, -) { - Icon( - painter = painterResource(resource, resourceLoader), - contentDescription = contentDescription, - modifier = modifier, - tint = tint, - ) -} - /** * Icon component that draws a [painter] using [tint], defaulting to * [Color.Unspecified] diff --git a/core/src/main/kotlin/org/jetbrains/jewel/NoIndication.kt b/core/src/main/kotlin/org/jetbrains/jewel/NoIndication.kt new file mode 100644 index 000000000..c099ab0cf --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/jewel/NoIndication.kt @@ -0,0 +1,21 @@ +package org.jetbrains.jewel + +import androidx.compose.foundation.Indication +import androidx.compose.foundation.IndicationInstance +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.drawscope.ContentDrawScope + +object NoIndication : Indication { + + private object NoIndicationInstance : IndicationInstance { + + override fun ContentDrawScope.drawIndication() { + drawContent() + } + } + + @Composable + override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance = + NoIndicationInstance +} diff --git a/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt b/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt index f9ed07314..8674cb072 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/Tabs.kt @@ -1,13 +1,10 @@ package org.jetbrains.jewel import androidx.compose.foundation.Image -import androidx.compose.foundation.Indication -import androidx.compose.foundation.IndicationInstance import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.HoverInteraction -import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement @@ -33,7 +30,6 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.isTertiary import androidx.compose.ui.input.pointer.onPointerEvent @@ -165,21 +161,6 @@ internal fun TabImpl( } } -private object NoIndication : Indication { - private object NoIndicationInstance : IndicationInstance { - - override fun ContentDrawScope.drawIndication() { - drawContent() - } - } - - @Suppress("ExpressionBodySyntax") - @Composable - override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { - return NoIndicationInstance - } -} - @Immutable @JvmInline value class TabState(val state: ULong) : SelectableComponentState { 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 1d3d569ca..478bbc0c1 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,15 +1,11 @@ package org.jetbrains.jewel.intui.core -import androidx.compose.foundation.Indication -import androidx.compose.foundation.IndicationInstance import androidx.compose.foundation.LocalContextMenuRepresentation import androidx.compose.foundation.LocalIndication -import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.text.TextStyle import org.jetbrains.jewel.GlobalColors import org.jetbrains.jewel.GlobalMetrics @@ -19,6 +15,7 @@ import org.jetbrains.jewel.IntelliJTheme import org.jetbrains.jewel.IntelliJThemeIconData import org.jetbrains.jewel.LocalColorPalette import org.jetbrains.jewel.LocalIconData +import org.jetbrains.jewel.NoIndication import org.jetbrains.jewel.styling.ButtonStyle import org.jetbrains.jewel.styling.CheckboxStyle import org.jetbrains.jewel.styling.ChipStyle @@ -225,17 +222,3 @@ fun BaseIntUiTheme( IntelliJTheme(theme, swingCompatMode, content) } } - -private object NoIndication : Indication { - - private object NoIndicationInstance : IndicationInstance { - - override fun ContentDrawScope.drawIndication() { - drawContent() - } - } - - @Composable - override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance = - NoIndicationInstance -} 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 0b5fb934b..cd5776461 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 @@ -197,21 +197,40 @@ fun IntUiTheme( swingCompatMode: Boolean = false, content: @Composable () -> Unit, ) { - val svgLoader by remember(themeDefinition.isDark, themeDefinition.iconData, themeDefinition.colorPalette) { + 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( - themeDefinition.isDark, - themeDefinition.iconData, - themeDefinition.colorPalette, + isDark, + iconData, + colorPalette, ) val svgPatcher = IntelliJSvgPatcher(paletteMapper) mutableStateOf(JewelSvgLoader(svgPatcher)) } - val componentStyling = defaultComponentStyling(themeDefinition, svgLoader) - IntUiTheme(themeDefinition, componentStyling, swingCompatMode, content) -} - @Composable fun IntUiTheme( theme: IntUiThemeDefinition, 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 53812b9f0..23e1cf70d 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 @@ -29,14 +29,17 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.singleWindowApplication import org.jetbrains.jewel.CheckboxRow import org.jetbrains.jewel.Divider +import org.jetbrains.jewel.JewelSvgLoader import org.jetbrains.jewel.LocalResourceLoader import org.jetbrains.jewel.VerticalScrollbar import org.jetbrains.jewel.intui.standalone.IntUiTheme +import org.jetbrains.jewel.intui.standalone.rememberSvgLoader import org.jetbrains.jewel.samples.standalone.components.Borders import org.jetbrains.jewel.samples.standalone.components.Buttons import org.jetbrains.jewel.samples.standalone.components.Checkboxes import org.jetbrains.jewel.samples.standalone.components.ChipsAndTree import org.jetbrains.jewel.samples.standalone.components.Dropdowns +import org.jetbrains.jewel.samples.standalone.components.Icons import org.jetbrains.jewel.samples.standalone.components.Links import org.jetbrains.jewel.samples.standalone.components.ProgressBar import org.jetbrains.jewel.samples.standalone.components.RadioButtons @@ -57,6 +60,7 @@ fun main() { IntUiTheme(theme, swingCompat) { val resourceLoader = LocalResourceLoader.current + val svgLoader by rememberSvgLoader() val windowBackground = if (isDark) { IntUiTheme.colorPalette.grey(1) @@ -76,14 +80,14 @@ fun main() { Divider(Modifier.fillMaxWidth()) - ComponentShowcase() + ComponentShowcase(svgLoader, resourceLoader) } } } } @Composable -private fun ComponentShowcase() { +private fun ComponentShowcase(svgLoader: JewelSvgLoader, resourceLoader: ResourceLoader) { val verticalScrollState = rememberScrollState() Box(Modifier.fillMaxSize()) { @@ -104,7 +108,8 @@ private fun ComponentShowcase() { TextAreas() ProgressBar() ChipsAndTree() - Tabs() + Tabs(svgLoader, resourceLoader) + Icons(svgLoader, resourceLoader) } VerticalScrollbar( 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 new file mode 100644 index 000000000..2e6a9c743 --- /dev/null +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/Icons.kt @@ -0,0 +1,39 @@ +package org.jetbrains.jewel.samples.standalone.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.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.ResourcePainterProvider + +@Composable +internal fun Icons(svgLoader: SvgLoader, resourceLoader: ResourceLoader) { + GroupHeader("Icons") + + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + val jewelLogoProvider = remember { ResourcePainterProvider.stateless("icons/jewel-logo.svg", svgLoader) } + val jewelLogo by jewelLogoProvider.getPainter(resourceLoader) + + 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)) + } +} 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 1e4612b60..feea106a7 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 @@ -2,14 +2,19 @@ package org.jetbrains.jewel.samples.standalone.components +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.HoverInteraction +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.material.IconButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -17,28 +22,40 @@ 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.ResourcePainterProvider import kotlin.math.max @Composable -fun Tabs() { +fun Tabs( + svgLoader: SvgLoader, + resourceLoader: ResourceLoader, +) { GroupHeader("Tabs") Text("Default tabs", Modifier.fillMaxWidth()) - DefaultTabShowcase() + DefaultTabShowcase(svgLoader, resourceLoader) Spacer(Modifier.height(16.dp)) Text("Editor tabs", Modifier.fillMaxWidth()) - EditorTabShowcase() + EditorTabShowcase(svgLoader, resourceLoader) } @Composable -private fun DefaultTabShowcase() { +private fun DefaultTabShowcase( + svgLoader: SvgLoader, + resourceLoader: ResourceLoader, +) { var selectedTabIndex by remember { mutableStateOf(0) } var tabIds by remember { mutableStateOf((1..12).toList()) } @@ -62,7 +79,7 @@ private fun DefaultTabShowcase() { } } - TabStripWithAddButton(tabs = tabs) { + TabStripWithAddButton(tabs, svgLoader, resourceLoader) { val insertionIndex = (selectedTabIndex + 1).coerceIn(0..tabIds.size) val nextTabId = maxId + 1 @@ -73,7 +90,10 @@ private fun DefaultTabShowcase() { } @Composable -private fun EditorTabShowcase() { +private fun EditorTabShowcase( + svgLoader: SvgLoader, + resourceLoader: ResourceLoader, +) { var selectedTabIndex by remember { mutableStateOf(0) } var tabIds by remember { mutableStateOf((1..12).toList()) } @@ -97,7 +117,7 @@ private fun EditorTabShowcase() { } } - TabStripWithAddButton(tabs = tabs) { + TabStripWithAddButton(tabs, svgLoader, resourceLoader) { val insertionIndex = (selectedTabIndex + 1).coerceIn(0..tabIds.size) val nextTabId = maxId + 1 @@ -110,6 +130,8 @@ private fun EditorTabShowcase() { @Composable private fun TabStripWithAddButton( tabs: List, + svgLoader: SvgLoader, + resourceLoader: ResourceLoader, onAddClick: () -> Unit, ) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -117,11 +139,41 @@ private fun TabStripWithAddButton( Spacer(Modifier.width(8.dp)) - IconButton( - onClick = onAddClick, - modifier = Modifier.size(IntelliJTheme.defaultTabStyle.metrics.tabHeight), + var isHovered by remember { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + + LaunchedEffect(interactionSource) { + interactionSource.interactions.collect { interaction -> + when (interaction) { + is HoverInteraction.Enter -> isHovered = true + is HoverInteraction.Exit -> isHovered = false + } + } + } + + // TODO create an IconButton instead of this hack + val backgroundColor = if (isHovered) { + IntUiTheme.defaultTabStyle.colors.backgroundHovered + } else { + IntUiTheme.defaultTabStyle.colors.background + } + + Box( + modifier = Modifier.size(IntelliJTheme.defaultTabStyle.metrics.tabHeight) + .clickable( + onClick = onAddClick, + onClickLabel = "Add a tab", + role = Role.Button, + interactionSource = interactionSource, + indication = NoIndication, + ) + .background(backgroundColor), + contentAlignment = Alignment.Center, ) { - Icon("icons/intui/add.svg", contentDescription = "Add a tab") + val addIconProvider = remember { ResourcePainterProvider.stateless("icons/intui/add.svg", svgLoader) } + val addIcon by addIconProvider.getPainter(resourceLoader) + + Icon(addIcon, contentDescription = "Add a tab") } } }