From 92901f5213131604a4c9e6b61e40de9068d20eba Mon Sep 17 00:00:00 2001 From: Daniel Bertoldi Date: Thu, 5 Mar 2026 15:38:48 -0300 Subject: [PATCH 1/2] [JEWEL-1285] Update JDialogRenderer to Support Custom Window Shape add missing jbpopuprenderer to commits --- .../jewel/bridge/component/JBPopupRenderer.kt | 1 + .../intui/standalone/popup/JDialogRenderer.kt | 64 +++++++ platform/jewel/ui/api-dump.txt | 10 +- .../org/jetbrains/jewel/ui/component/Popup.kt | 157 +++++++++++++++++- 4 files changed, 229 insertions(+), 3 deletions(-) diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/component/JBPopupRenderer.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/component/JBPopupRenderer.kt index 6c99e89f025f3..271d3bc24968a 100644 --- a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/component/JBPopupRenderer.kt +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/component/JBPopupRenderer.kt @@ -60,6 +60,7 @@ import org.jetbrains.jewel.foundation.LocalComponent import org.jetbrains.jewel.ui.component.PopupRenderer internal object JBPopupRenderer : PopupRenderer { + @Suppress("OVERRIDE_DEPRECATION") @Composable override fun Popup( popupPositionProvider: PopupPositionProvider, diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/popup/JDialogRenderer.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/popup/JDialogRenderer.kt index 330aea3c06888..abb0ca5f432ed 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/popup/JDialogRenderer.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/popup/JDialogRenderer.kt @@ -93,6 +93,7 @@ import org.jetbrains.skiko.hostOs * possible issues. */ internal object JDialogRenderer : PopupRenderer { + @Suppress("OVERRIDE_DEPRECATION") @Composable override fun Popup( popupPositionProvider: PopupPositionProvider, @@ -102,6 +103,29 @@ internal object JDialogRenderer : PopupRenderer { onKeyEvent: ((KeyEvent) -> Boolean)?, cornerSize: CornerSize, content: @Composable () -> Unit, + ) { + Popup( + popupPositionProvider = popupPositionProvider, + properties = properties, + onDismissRequest = onDismissRequest, + onPreviewKeyEvent = onPreviewKeyEvent, + onKeyEvent = onKeyEvent, + cornerSize = cornerSize, + windowShape = null, + content = content, + ) + } + + @Composable + override fun Popup( + popupPositionProvider: PopupPositionProvider, + properties: PopupProperties, + onDismissRequest: (() -> Unit)?, + onPreviewKeyEvent: ((KeyEvent) -> Boolean)?, + onKeyEvent: ((KeyEvent) -> Boolean)?, + cornerSize: CornerSize, + windowShape: ((IntSize) -> java.awt.Shape)?, + content: @Composable () -> Unit, ) { val isJBREnvironment = remember { JBR.isAvailable() && JBR.isRoundedCornersManagerSupported() } val supportBlending = remember { @@ -147,6 +171,7 @@ internal object JDialogRenderer : PopupRenderer { onKeyEvent = onKeyEvent, cornerSize = cornerSize, blendingEnabled = supportBlending, + windowShape = windowShape, content = content, ) } @@ -163,6 +188,7 @@ private fun JPopupImpl( onKeyEvent: ((KeyEvent) -> Boolean)?, cornerSize: CornerSize, blendingEnabled: Boolean, + windowShape: ((IntSize) -> java.awt.Shape)?, content: @Composable () -> Unit, ) { val component = LocalComponent.current @@ -173,6 +199,7 @@ private fun JPopupImpl( val currentOnKeyEvent by rememberUpdatedState(onKeyEvent) val currentOnPreviewKeyEvent by rememberUpdatedState(onPreviewKeyEvent) val currentProperties by rememberUpdatedState(properties) + val windowShapeState = rememberUpdatedState(windowShape) val compositionLocalContext by rememberUpdatedState(currentCompositionLocalContext) @@ -240,6 +267,33 @@ private fun JPopupImpl( JPopupMeasurePolicy(dialog, currentPopupPositionProvider, parentBounds) { position, size -> popupRectangle = Rectangle(position.x, position.y, size.width, size.height) + val currentWindowShape = windowShapeState.value + if (currentWindowShape != null) { + if (blendingEnabled) { + // When blending is active, the window is already in + // java.awt.GraphicsDevice.WindowTranslucency.PERPIXEL_TRANSLUCENT mode + // (per-pixel alpha). Calling Window.setShape() would switch it to + // PERPIXEL_TRANSPARENT (hard pixel clip), breaking antialiasing at the edges. + // Compose's own drawing + transparent background is sufficient. + return@JPopupMeasurePolicy + } + // Without blending, fall back to Window.setShape() to at least + // clip the rectangular window boundary to the balloon outline. + // Note: this uses PERPIXEL_TRANSPARENT mode, which has known + // antialiasing limitations at concave corners (e.g. arrow junction). + val logicalSize = + IntSize( + floor(size.width / dialog.density()).toInt(), + floor(size.height / dialog.density()).toInt(), + ) + try { + dialog.shape = currentWindowShape(logicalSize) + } catch (_: UnsupportedOperationException) { + applyRoundedCorners(dialog, cornerSize, size) + } + return@JPopupMeasurePolicy + } + if (blendingEnabled) { // If any of the blending logic is enabled, we don't need to use JBR APIs // to set the rounded corners and fix the background. @@ -253,6 +307,7 @@ private fun JPopupImpl( cornerSize.toPx(size.toSize(), Density(dialog.density())) / dialog.density(), ) } + applyRoundedCorners(dialog, cornerSize, size) } }, ) @@ -394,6 +449,15 @@ private class JPopupMeasurePolicy( } } +private fun applyRoundedCorners(dialog: Window, cornerSize: CornerSize, size: IntSize) { + if (cornerSize == ZeroCornerSize) return + JBR.getRoundedCornersManager() + .setRoundedCorners( + dialog, + cornerSize.toPx(size.toSize(), Density(dialog.density())) / dialog.density(), + ) +} + // Based on implementation from JBUIScale and ScreenUtil private fun IntSize.Companion.screenSize(window: Component): IntSize { val windowConfiguration = window.graphicsConfiguration.device.defaultConfiguration diff --git a/platform/jewel/ui/api-dump.txt b/platform/jewel/ui/api-dump.txt index 887fca91be845..6c4902537cdf7 100644 --- a/platform/jewel/ui/api-dump.txt +++ b/platform/jewel/ui/api-dump.txt @@ -573,8 +573,10 @@ f:org.jetbrains.jewel.ui.component.PopupContainerKt - bsf:PopupContainer(kotlin.jvm.functions.Function0,androidx.compose.ui.Alignment$Horizontal,androidx.compose.ui.Modifier,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,androidx.compose.ui.window.PopupProperties,androidx.compose.ui.window.PopupPositionProvider,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V - sf:PopupContainer(kotlin.jvm.functions.Function0,androidx.compose.ui.Alignment$Horizontal,androidx.compose.ui.Modifier,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,androidx.compose.ui.window.PopupProperties,androidx.compose.ui.window.PopupPositionProvider,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V f:org.jetbrains.jewel.ui.component.PopupKt -- sf:Popup(androidx.compose.ui.window.PopupPositionProvider,androidx.compose.foundation.shape.CornerSize,kotlin.jvm.functions.Function0,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V -- sf:Popup(androidx.compose.ui.window.PopupPositionProvider,kotlin.jvm.functions.Function0,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V +- sf:Popup(androidx.compose.ui.window.PopupPositionProvider,androidx.compose.foundation.shape.CornerSize,kotlin.jvm.functions.Function0,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V +- bsf:Popup(androidx.compose.ui.window.PopupPositionProvider,androidx.compose.foundation.shape.CornerSize,kotlin.jvm.functions.Function0,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V +- sf:Popup(androidx.compose.ui.window.PopupPositionProvider,kotlin.jvm.functions.Function0,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V +- bsf:Popup(androidx.compose.ui.window.PopupPositionProvider,kotlin.jvm.functions.Function0,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V - sf:getLocalPopupRenderer():androidx.compose.runtime.ProvidableCompositionLocal f:org.jetbrains.jewel.ui.component.PopupManager - sf:$stable:I @@ -590,8 +592,12 @@ f:org.jetbrains.jewel.ui.component.PopupManager - f:togglePopupVisibility():V org.jetbrains.jewel.ui.component.PopupRenderer - sf:Companion:org.jetbrains.jewel.ui.component.PopupRenderer$Companion +- Popup(androidx.compose.ui.window.PopupPositionProvider,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,androidx.compose.foundation.shape.CornerSize,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I):V +- b:Popup(androidx.compose.ui.window.PopupPositionProvider,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,androidx.compose.foundation.shape.CornerSize,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V - a:Popup(androidx.compose.ui.window.PopupPositionProvider,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,androidx.compose.foundation.shape.CornerSize,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I):V f:org.jetbrains.jewel.ui.component.PopupRenderer$Companion +f:org.jetbrains.jewel.ui.component.PopupRenderer$ComposeDefaultImpls +- sf:Popup$default(androidx.compose.ui.window.PopupPositionProvider,androidx.compose.ui.window.PopupProperties,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,androidx.compose.foundation.shape.CornerSize,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function2,org.jetbrains.jewel.ui.component.PopupRenderer,androidx.compose.runtime.Composer,I,I):V f:org.jetbrains.jewel.ui.component.RadioButtonKt - sf:RadioButton(Z,kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,Z,org.jetbrains.jewel.ui.Outline,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,androidx.compose.ui.text.TextStyle,androidx.compose.ui.Alignment$Vertical,androidx.compose.runtime.Composer,I,I):V - bsf:RadioButtonRow(java.lang.String,Z,kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,Z,org.jetbrains.jewel.ui.Outline,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,androidx.compose.ui.text.TextStyle,androidx.compose.ui.Alignment$Vertical,androidx.compose.runtime.Composer,I,I):V diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Popup.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Popup.kt index cbd63c0718ee3..f24b5b92505ec 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Popup.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Popup.kt @@ -46,22 +46,151 @@ import org.jetbrains.jewel.foundation.JewelFlags * consume the event. * @param onKeyEvent Callback invoked for key events after they are dispatched to children. Return `true` to consume the * event. + * @param content The composable content to be displayed inside the popup. + */ +@Deprecated(message = "Please use the overload with windowShape.", level = DeprecationLevel.HIDDEN) +@Composable +public fun Popup( + popupPositionProvider: PopupPositionProvider, + onDismissRequest: (() -> Unit)? = null, + properties: PopupProperties = PopupProperties(), + onPreviewKeyEvent: ((KeyEvent) -> Boolean)? = null, + onKeyEvent: ((KeyEvent) -> Boolean)? = null, + content: @Composable () -> Unit, +) { + Popup( + popupPositionProvider, + ZeroCornerSize, + onDismissRequest, + properties, + onPreviewKeyEvent, + onKeyEvent, + null, + content, + ) +} + +/** + * Displays a popup with the provided content at a position determined by the given [PopupPositionProvider]. + * + * This function behavior is influenced by the 'jewel.customPopupRender' system property. If set to `true`, it allows + * using a custom popup rendering implementation; otherwise, it defaults to the standard Compose popup. + * + * If running on the IntelliJ Platform and setting the [JewelFlags.useCustomPopupRenderer] property to `true`, the + * plugin will use the JBPopup implementation for rendering popups. This is useful if your composable content is small, + * but you need to display a popup that is bigger than the component size. + * + * @param popupPositionProvider Determines the position of the popup on the screen. + * @param onDismissRequest Callback invoked when a dismiss event is requested, typically when the popup is dismissed. + * @param properties Configuration parameters for the popup, such as whether it should consume touch events or focusable + * behavior. + * @param onPreviewKeyEvent Callback invoked for key events before they are dispatched to children. Return `true` to + * consume the event. + * @param onKeyEvent Callback invoked for key events after they are dispatched to children. Return `true` to consume the + * event. + * @param windowShape An optional factory that produces the [java.awt.Shape] used to clip the native popup window. The + * lambda receives the window's measured size in AWT logical units and **must** return a shape in the same coordinate + * system. Only applied by JDialogRenderer when `useCustomPopupRenderer = true` and the panel is not transparent (be + * it by enabling `compose.interop.blending` or setting the alpha channel of the panel's background to 0); all other + * renderers ignore it. When null, window clipping falls back to the `cornerSize`-based rounded corners (via JBR) if + * the platform supports it. + * @param content The composable content to be displayed inside the popup. + */ +@Composable +public fun Popup( + popupPositionProvider: PopupPositionProvider, + onDismissRequest: (() -> Unit)? = null, + properties: PopupProperties = PopupProperties(), + onPreviewKeyEvent: ((KeyEvent) -> Boolean)? = null, + onKeyEvent: ((KeyEvent) -> Boolean)? = null, + windowShape: ((IntSize) -> java.awt.Shape)? = null, + content: @Composable () -> Unit, +) { + Popup( + popupPositionProvider, + ZeroCornerSize, + onDismissRequest, + properties, + onPreviewKeyEvent, + onKeyEvent, + windowShape, + content, + ) +} + +/** + * Displays a popup with the provided content at a position determined by the given [PopupPositionProvider]. + * + * This function behavior is influenced by the 'jewel.customPopupRender' system property. If set to `true`, it allows + * using a custom popup rendering implementation; otherwise, it defaults to the standard Compose popup. + * + * If running on the IntelliJ Platform and setting the [JewelFlags.useCustomPopupRenderer] property to `true`, the + * plugin will use the JBPopup implementation for rendering popups. This is useful if your composable content is small, + * but you need to display a popup that is bigger than the component size. + * + * @param popupPositionProvider Determines the position of the popup on the screen. * @param cornerSize The size of the popup's rounded corners. This value gets ignored if the popup's implementation used * is the default Compose popup. + * @param onDismissRequest Callback invoked when a dismiss event is requested, typically when the popup is dismissed. + * @param properties Configuration parameters for the popup, such as whether it should consume touch events or focusable + * behavior. + * @param onPreviewKeyEvent Callback invoked for key events before they are dispatched to children. Return `true` to + * consume the event. + * @param onKeyEvent Callback invoked for key events after they are dispatched to children. Return `true` to consume the + * event. * @param content The composable content to be displayed inside the popup. */ +@Deprecated(message = "Please use the overload with windowShape.", level = DeprecationLevel.HIDDEN) @Composable public fun Popup( popupPositionProvider: PopupPositionProvider, + cornerSize: CornerSize, onDismissRequest: (() -> Unit)? = null, properties: PopupProperties = PopupProperties(), onPreviewKeyEvent: ((KeyEvent) -> Boolean)? = null, onKeyEvent: ((KeyEvent) -> Boolean)? = null, content: @Composable () -> Unit, ) { - Popup(popupPositionProvider, ZeroCornerSize, onDismissRequest, properties, onPreviewKeyEvent, onKeyEvent, content) + Popup( + popupPositionProvider, + cornerSize, + onDismissRequest, + properties, + onPreviewKeyEvent, + onKeyEvent, + windowShape = null, + content, + ) } +/** + * Displays a popup with the provided content at a position determined by the given [PopupPositionProvider]. + * + * This function behavior is influenced by the 'jewel.customPopupRender' system property. If set to `true`, it allows + * using a custom popup rendering implementation; otherwise, it defaults to the standard Compose popup. + * + * If running on the IntelliJ Platform and setting the [JewelFlags.useCustomPopupRenderer] property to `true`, the + * plugin will use the JBPopup implementation for rendering popups. This is useful if your composable content is small, + * but you need to display a popup that is bigger than the component size. + * + * @param popupPositionProvider Determines the position of the popup on the screen. + * @param cornerSize The size of the popup's rounded corners. This value gets ignored if the popup's implementation used + * is the default Compose popup. + * @param onDismissRequest Callback invoked when a dismiss event is requested, typically when the popup is dismissed. + * @param properties Configuration parameters for the popup, such as whether it should consume touch events or focusable + * behavior. + * @param onPreviewKeyEvent Callback invoked for key events before they are dispatched to children. Return `true` to + * consume the event. + * @param onKeyEvent Callback invoked for key events after they are dispatched to children. Return `true` to consume the + * event. + * @param windowShape An optional factory that produces the [java.awt.Shape] used to clip the native popup window. The + * lambda receives the window's measured size in AWT logical units and must return a shape in the same coordinate + * system. Only applied by JDialogRenderer when `useCustomPopupRenderer = true` and the panel is not transparent (be + * it by enabling `compose.interop.blending` or setting the alpha channel of the panel's background to 0); all other + * renderers ignore it. When null, window clipping falls back to the `cornerSize`-based rounded corners (via JBR) if + * the platform supports it. + * @param content The composable content to be displayed inside the popup. + */ @Composable public fun Popup( popupPositionProvider: PopupPositionProvider, @@ -70,6 +199,7 @@ public fun Popup( properties: PopupProperties = PopupProperties(), onPreviewKeyEvent: ((KeyEvent) -> Boolean)? = null, onKeyEvent: ((KeyEvent) -> Boolean)? = null, + windowShape: ((IntSize) -> java.awt.Shape)? = null, content: @Composable () -> Unit, ) { if (JewelFlags.useCustomPopupRenderer) { @@ -80,6 +210,7 @@ public fun Popup( onPreviewKeyEvent = onPreviewKeyEvent, onKeyEvent = onKeyEvent, cornerSize = cornerSize, + windowShape = windowShape, content = content, ) } else { @@ -102,6 +233,7 @@ public fun Popup( * [JewelFlags.useCustomPopupRenderer] flag to use it. */ public interface PopupRenderer { + @Deprecated(message = "Please use the overload with windowShape.") @Composable public fun Popup( popupPositionProvider: PopupPositionProvider, @@ -113,6 +245,28 @@ public interface PopupRenderer { content: @Composable () -> Unit, ) + @Composable + public fun Popup( + popupPositionProvider: PopupPositionProvider, + properties: PopupProperties, + onDismissRequest: (() -> Unit)?, + onPreviewKeyEvent: ((KeyEvent) -> Boolean)?, + onKeyEvent: ((KeyEvent) -> Boolean)?, + cornerSize: CornerSize, + windowShape: ((IntSize) -> java.awt.Shape)? = null, + content: @Composable () -> Unit, + ) { + Popup( + popupPositionProvider = popupPositionProvider, + properties = properties, + onDismissRequest = onDismissRequest, + onPreviewKeyEvent = onPreviewKeyEvent, + onKeyEvent = onKeyEvent, + cornerSize = cornerSize, + content = content, + ) + } + public companion object } @@ -126,6 +280,7 @@ public val LocalPopupRenderer: ProvidableCompositionLocal = stati } private object DefaultPopupRenderer : PopupRenderer { + @Suppress("OVERRIDE_DEPRECATION") @Composable override fun Popup( popupPositionProvider: PopupPositionProvider, From 1115d0175192d4869e22ce58aafd80c59c321cb5 Mon Sep 17 00:00:00 2001 From: Daniel Bertoldi Date: Fri, 6 Mar 2026 16:58:53 -0300 Subject: [PATCH 2/2] [JEWEL-1150] Create Got It Tooltip Component --- .../jewel/bridge/theme/IntUiBridge.kt | 2 + .../bridge/theme/IntUiBridgeGotItTooltip.kt | 93 ++ .../int-ui/int-ui-standalone/api-dump.txt | 37 +- .../styling/IntUiGotItTooltipStyling.kt | 188 ++++ .../intui/standalone/theme/IntUiTheme.kt | 214 +++- platform/jewel/samples/showcase/api-dump.txt | 1 + .../jewel/samples/showcase/ShowcaseIcons.kt | 1 + .../samples/showcase/components/GotIt.kt | 976 ++++++++++++++++++ .../showcase/views/ComponentsViewModel.kt | 2 + .../main/resources/drawables/cool_jewel.png | Bin 0 -> 117088 bytes .../resources/icons/components/balloon.svg | 7 + .../icons/components/balloon_dark.svg | 7 + .../jewel/samples/standalone/build.gradle.kts | 1 + .../gotit/GotItIconTooltipOrStepTest.kt | 60 ++ ...GotItTooltipBalloonPositionProviderTest.kt | 139 +++ .../gotit/GotItTooltipBalloonShapeTest.kt | 133 +++ .../component/gotit/GotItTooltipBodyTest.kt | 229 ++++ .../ui/component/gotit/GotItTooltipTest.kt | 677 ++++++++++++ .../test/resources/drawables/test_gotit.png | Bin 0 -> 945 bytes platform/jewel/ui/api-dump.txt | 146 ++- .../jewel/ui/DefaultComponentStyling.kt | 116 ++- .../jewel/ui/component/gotit/GotItTooltip.kt | 971 +++++++++++++++++ .../gotit/GotItTooltipBalloonInternals.kt | 234 +++++ .../ui/component/gotit/GotItTooltipBody.kt | 252 +++++ .../ui/component/gotit/GotItTooltipStyling.kt | 225 ++++ .../jetbrains/jewel/ui/theme/JewelTheme.kt | 9 + .../messages/DevkitComposeBundle.properties | 1 + .../src/demo/SwingComparisonTabPanel.kt | 91 +- 28 files changed, 4796 insertions(+), 16 deletions(-) create mode 100644 platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeGotItTooltip.kt create mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiGotItTooltipStyling.kt create mode 100644 platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/GotIt.kt create mode 100644 platform/jewel/samples/showcase/src/main/resources/drawables/cool_jewel.png create mode 100644 platform/jewel/samples/showcase/src/main/resources/icons/components/balloon.svg create mode 100644 platform/jewel/samples/showcase/src/main/resources/icons/components/balloon_dark.svg create mode 100644 platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItIconTooltipOrStepTest.kt create mode 100644 platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonPositionProviderTest.kt create mode 100644 platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonShapeTest.kt create mode 100644 platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBodyTest.kt create mode 100644 platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipTest.kt create mode 100644 platform/jewel/ui-tests/src/test/resources/drawables/test_gotit.png create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltip.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonInternals.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBody.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipStyling.kt diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt index 5a72ece55b6bf..22bb3953a3a9d 100644 --- a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt @@ -109,6 +109,8 @@ internal fun createBridgeComponentStyling(theme: ThemeDefinition): ComponentStyl popupAdStyle = readPopupAdStyle(), defaultSlimButtonStyle = readDefaultSlimButtonStyle(defaultButtonStyle.colors), outlinedSlimButtonStyle = readOutlinedSlimButtonStyle(outlinedButtonStyle.colors), + gotItTooltipStyle = readGotItTooltipStyle(), + gotItButtonStyle = readGotItButtonStyle(), ) } diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeGotItTooltip.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeGotItTooltip.kt new file mode 100644 index 0000000000000..6b842fe00c126 --- /dev/null +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeGotItTooltip.kt @@ -0,0 +1,93 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.bridge.theme + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import com.intellij.util.ui.JBUI +import org.jetbrains.jewel.bridge.dp +import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified +import org.jetbrains.jewel.bridge.safeValue +import org.jetbrains.jewel.bridge.toComposeColor +import org.jetbrains.jewel.bridge.toPaddingValues +import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.ui.component.gotit.GotItColors +import org.jetbrains.jewel.ui.component.gotit.GotItMetrics +import org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +import org.jetbrains.jewel.ui.component.styling.ButtonColors +import org.jetbrains.jewel.ui.component.styling.ButtonMetrics +import org.jetbrains.jewel.ui.component.styling.ButtonStyle + +internal fun readGotItTooltipStyle(): GotItTooltipStyle = + GotItTooltipStyle( + colors = + GotItColors( + foreground = JBUI.CurrentTheme.GotItTooltip.foreground(false).toComposeColor(), + background = JBUI.CurrentTheme.GotItTooltip.background(false).toComposeColor(), + stepForeground = JBUI.CurrentTheme.GotItTooltip.stepForeground(false).toComposeColor(), + secondaryActionForeground = + JBUI.CurrentTheme.GotItTooltip.secondaryActionForeground(false).toComposeColor(), + headerForeground = JBUI.CurrentTheme.GotItTooltip.headerForeground(false).toComposeColor(), + balloonBorderColor = JBUI.CurrentTheme.GotItTooltip.borderColor(false).toComposeColor(), + imageBorderColor = JBUI.CurrentTheme.GotItTooltip.imageBorderColor(false).toComposeColor(), + link = JBUI.CurrentTheme.GotItTooltip.linkForeground(false).toComposeColor(), + codeForeground = JBUI.CurrentTheme.GotItTooltip.codeForeground(false).toComposeColor(), + codeBackground = JBUI.CurrentTheme.GotItTooltip.codeBackground(false).toComposeColor(), + ), + metrics = + GotItMetrics( + contentPadding = JBUI.CurrentTheme.GotItTooltip.insets().toPaddingValues(), + textPadding = JBUI.CurrentTheme.GotItTooltip.TEXT_INSET.dp.safeValue(), + buttonPadding = + PaddingValues( + top = JBUI.CurrentTheme.GotItTooltip.BUTTON_TOP_INSET.dp.safeValue(), + bottom = JBUI.CurrentTheme.GotItTooltip.BUTTON_BOTTOM_INSET.dp.safeValue(), + ), + iconPadding = JBUI.CurrentTheme.GotItTooltip.ICON_INSET.dp.safeValue(), + imagePadding = + PaddingValues( + top = JBUI.CurrentTheme.GotItTooltip.IMAGE_TOP_INSET.dp.safeValue(), + bottom = JBUI.CurrentTheme.GotItTooltip.IMAGE_BOTTOM_INSET.dp.safeValue(), + ), + cornerRadius = JBUI.CurrentTheme.GotItTooltip.CORNER_RADIUS.dp.safeValue(), + ), + ) + +internal fun readGotItButtonStyle(): ButtonStyle { + val background = SolidColor(retrieveColorOrUnspecified("GotItTooltip.Button.startBackground")) + val border = SolidColor(retrieveColorOrUnspecified("GotItTooltip.Button.startBorderColor")) + val content = JBUI.CurrentTheme.GotItTooltip.buttonForeground().toComposeColor() + + return ButtonStyle( + colors = + ButtonColors( + background = background, + backgroundDisabled = SolidColor(Color.Unspecified), + backgroundFocused = background, + backgroundPressed = background, + backgroundHovered = background, + content = content, + contentDisabled = retrieveColorOrUnspecified("Button.disabledText"), + contentFocused = content, + contentPressed = content, + contentHovered = content, + border = border, + borderDisabled = SolidColor(Color.Unspecified), + borderFocused = border, + borderPressed = border, + borderHovered = border, + ), + metrics = + ButtonMetrics( + cornerSize = CornerSize(JBUI.CurrentTheme.GotItTooltip.CORNER_RADIUS.dp.safeValue()), + padding = PaddingValues(horizontal = 12.dp, vertical = 6.dp), + minSize = DpSize(72.dp, 28.dp), + borderWidth = 1.dp, + focusOutlineExpand = 0.dp, + ), + focusOutlineAlignment = Stroke.Alignment.Center, + ) +} diff --git a/platform/jewel/int-ui/int-ui-standalone/api-dump.txt b/platform/jewel/int-ui/int-ui-standalone/api-dump.txt index 8986387419cd9..3be900ee9a3c0 100644 --- a/platform/jewel/int-ui/int-ui-standalone/api-dump.txt +++ b/platform/jewel/int-ui/int-ui-standalone/api-dump.txt @@ -257,6 +257,35 @@ f:org.jetbrains.jewel.intui.standalone.styling.IntUiErrorBannerColorFactory - bs:dark--OWjLjI$default(org.jetbrains.jewel.intui.standalone.styling.IntUiErrorBannerColorFactory,J,J,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.BannerColors - f:light--OWjLjI(J,J):org.jetbrains.jewel.ui.component.styling.BannerColors - bs:light--OWjLjI$default(org.jetbrains.jewel.intui.standalone.styling.IntUiErrorBannerColorFactory,J,J,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.BannerColors +f:org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonColorFactory +- sf:$stable:I +- sf:INSTANCE:org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonColorFactory +- f:dark-jhmft08(androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,J,J,J,J,J,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush):org.jetbrains.jewel.ui.component.styling.ButtonColors +- bs:dark-jhmft08$default(org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonColorFactory,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,J,J,J,J,J,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ButtonColors +- f:light-jhmft08(androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,J,J,J,J,J,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush):org.jetbrains.jewel.ui.component.styling.ButtonColors +- bs:light-jhmft08$default(org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonColorFactory,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,J,J,J,J,J,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,androidx.compose.ui.graphics.Brush,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ButtonColors +f:org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonStyleFactory +- sf:$stable:I +- sf:INSTANCE:org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonStyleFactory +- f:dark(org.jetbrains.jewel.ui.component.styling.ButtonColors,org.jetbrains.jewel.ui.component.styling.ButtonMetrics,org.jetbrains.jewel.foundation.Stroke$Alignment):org.jetbrains.jewel.ui.component.styling.ButtonStyle +- bs:dark$default(org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonStyleFactory,org.jetbrains.jewel.ui.component.styling.ButtonColors,org.jetbrains.jewel.ui.component.styling.ButtonMetrics,org.jetbrains.jewel.foundation.Stroke$Alignment,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ButtonStyle +- f:light(org.jetbrains.jewel.ui.component.styling.ButtonColors,org.jetbrains.jewel.ui.component.styling.ButtonMetrics,org.jetbrains.jewel.foundation.Stroke$Alignment):org.jetbrains.jewel.ui.component.styling.ButtonStyle +- bs:light$default(org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonStyleFactory,org.jetbrains.jewel.ui.component.styling.ButtonColors,org.jetbrains.jewel.ui.component.styling.ButtonMetrics,org.jetbrains.jewel.foundation.Stroke$Alignment,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ButtonStyle +f:org.jetbrains.jewel.intui.standalone.styling.IntUiGotItTooltipStylingKt +- sf:dark(org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle$Companion,org.jetbrains.jewel.ui.component.gotit.GotItColors,org.jetbrains.jewel.ui.component.gotit.GotItMetrics):org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +- bs:dark$default(org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle$Companion,org.jetbrains.jewel.ui.component.gotit.GotItColors,org.jetbrains.jewel.ui.component.gotit.GotItMetrics,I,java.lang.Object):org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +- sf:dark-SKfS8CY(org.jetbrains.jewel.ui.component.gotit.GotItColors$Companion,J,J,J,J,J,J,J,J,J,J):org.jetbrains.jewel.ui.component.gotit.GotItColors +- bs:dark-SKfS8CY$default(org.jetbrains.jewel.ui.component.gotit.GotItColors$Companion,J,J,J,J,J,J,J,J,J,J,I,java.lang.Object):org.jetbrains.jewel.ui.component.gotit.GotItColors +- sf:dark-u5Pfa8Y(org.jetbrains.jewel.ui.component.gotit.GotItMetrics$Companion,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F):org.jetbrains.jewel.ui.component.gotit.GotItMetrics +- bs:dark-u5Pfa8Y$default(org.jetbrains.jewel.ui.component.gotit.GotItMetrics$Companion,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F,I,java.lang.Object):org.jetbrains.jewel.ui.component.gotit.GotItMetrics +- sf:getGotIt(org.jetbrains.jewel.ui.component.styling.ButtonColors$Companion):org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonColorFactory +- sf:getGotIt(org.jetbrains.jewel.ui.component.styling.ButtonStyle$Companion):org.jetbrains.jewel.intui.standalone.styling.IntUiGotItButtonStyleFactory +- sf:light(org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle$Companion,org.jetbrains.jewel.ui.component.gotit.GotItColors,org.jetbrains.jewel.ui.component.gotit.GotItMetrics):org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +- bs:light$default(org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle$Companion,org.jetbrains.jewel.ui.component.gotit.GotItColors,org.jetbrains.jewel.ui.component.gotit.GotItMetrics,I,java.lang.Object):org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +- sf:light-SKfS8CY(org.jetbrains.jewel.ui.component.gotit.GotItColors$Companion,J,J,J,J,J,J,J,J,J,J):org.jetbrains.jewel.ui.component.gotit.GotItColors +- bs:light-SKfS8CY$default(org.jetbrains.jewel.ui.component.gotit.GotItColors$Companion,J,J,J,J,J,J,J,J,J,J,I,java.lang.Object):org.jetbrains.jewel.ui.component.gotit.GotItColors +- sf:light-u5Pfa8Y(org.jetbrains.jewel.ui.component.gotit.GotItMetrics$Companion,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F):org.jetbrains.jewel.ui.component.gotit.GotItMetrics +- bs:light-u5Pfa8Y$default(org.jetbrains.jewel.ui.component.gotit.GotItMetrics$Companion,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F,androidx.compose.foundation.layout.PaddingValues,F,I,java.lang.Object):org.jetbrains.jewel.ui.component.gotit.GotItMetrics f:org.jetbrains.jewel.intui.standalone.styling.IntUiGroupHeaderStylingKt - sf:dark(org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle$Companion,org.jetbrains.jewel.ui.component.styling.GroupHeaderColors,org.jetbrains.jewel.ui.component.styling.GroupHeaderMetrics):org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle - bs:dark$default(org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle$Companion,org.jetbrains.jewel.ui.component.styling.GroupHeaderColors,org.jetbrains.jewel.ui.component.styling.GroupHeaderMetrics,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle @@ -722,12 +751,14 @@ f:org.jetbrains.jewel.intui.standalone.theme.IntUiThemeKt - bsf:dark(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle):org.jetbrains.jewel.ui.ComponentStyling - bsf:dark(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle):org.jetbrains.jewel.ui.ComponentStyling - bsf:dark(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle):org.jetbrains.jewel.ui.ComponentStyling -- sf:dark(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):org.jetbrains.jewel.ui.ComponentStyling +- bsf:dark(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):org.jetbrains.jewel.ui.ComponentStyling +- sf:dark(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):org.jetbrains.jewel.ui.ComponentStyling - bsf:dark(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle):org.jetbrains.jewel.ui.ComponentStyling - bs:dark$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:dark$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:dark$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:dark$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling +- bs:dark$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:dark$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bsf:darkThemeDefinition-aKPr-nQ(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,org.jetbrains.jewel.foundation.GlobalColors,org.jetbrains.jewel.foundation.GlobalMetrics,org.jetbrains.jewel.foundation.theme.ThemeColorPalette,org.jetbrains.jewel.foundation.theme.ThemeIconData,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,J):org.jetbrains.jewel.foundation.theme.ThemeDefinition - bs:darkThemeDefinition-aKPr-nQ$default(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,org.jetbrains.jewel.foundation.GlobalColors,org.jetbrains.jewel.foundation.GlobalMetrics,org.jetbrains.jewel.foundation.theme.ThemeColorPalette,org.jetbrains.jewel.foundation.theme.ThemeIconData,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,J,I,java.lang.Object):org.jetbrains.jewel.foundation.theme.ThemeDefinition @@ -737,12 +768,14 @@ f:org.jetbrains.jewel.intui.standalone.theme.IntUiThemeKt - bsf:light(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle):org.jetbrains.jewel.ui.ComponentStyling - bsf:light(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle):org.jetbrains.jewel.ui.ComponentStyling - bsf:light(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle):org.jetbrains.jewel.ui.ComponentStyling -- sf:light(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):org.jetbrains.jewel.ui.ComponentStyling +- bsf:light(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):org.jetbrains.jewel.ui.ComponentStyling +- sf:light(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):org.jetbrains.jewel.ui.ComponentStyling - bsf:light(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle):org.jetbrains.jewel.ui.ComponentStyling - bs:light$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:light$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:light$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:light$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling +- bs:light$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,I,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bs:light$default(org.jetbrains.jewel.ui.ComponentStyling,org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,I,java.lang.Object):org.jetbrains.jewel.ui.ComponentStyling - bsf:lightThemeDefinition-aKPr-nQ(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,org.jetbrains.jewel.foundation.GlobalColors,org.jetbrains.jewel.foundation.GlobalMetrics,org.jetbrains.jewel.foundation.theme.ThemeColorPalette,org.jetbrains.jewel.foundation.theme.ThemeIconData,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,J):org.jetbrains.jewel.foundation.theme.ThemeDefinition - bs:lightThemeDefinition-aKPr-nQ$default(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,org.jetbrains.jewel.foundation.GlobalColors,org.jetbrains.jewel.foundation.GlobalMetrics,org.jetbrains.jewel.foundation.theme.ThemeColorPalette,org.jetbrains.jewel.foundation.theme.ThemeIconData,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,androidx.compose.ui.text.TextStyle,J,I,java.lang.Object):org.jetbrains.jewel.foundation.theme.ThemeDefinition diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiGotItTooltipStyling.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiGotItTooltipStyling.kt new file mode 100644 index 0000000000000..4181274ee1652 --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiGotItTooltipStyling.kt @@ -0,0 +1,188 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.intui.standalone.styling + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme +import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme +import org.jetbrains.jewel.ui.component.gotit.GotItColors +import org.jetbrains.jewel.ui.component.gotit.GotItMetrics +import org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +import org.jetbrains.jewel.ui.component.styling.ButtonColors +import org.jetbrains.jewel.ui.component.styling.ButtonMetrics +import org.jetbrains.jewel.ui.component.styling.ButtonStyle + +public val ButtonStyle.Companion.GotIt: IntUiGotItButtonStyleFactory + get() = IntUiGotItButtonStyleFactory + +public object IntUiGotItButtonStyleFactory { + public fun light( + colors: ButtonColors = ButtonColors.GotIt.light(), + metrics: ButtonMetrics = ButtonMetrics.default(focusOutlineExpand = 0.dp, borderWidth = 0.dp), + focusOutlineAlignment: Stroke.Alignment = Stroke.Alignment.Center, + ): ButtonStyle = ButtonStyle(colors, metrics, focusOutlineAlignment) + + public fun dark( + colors: ButtonColors = ButtonColors.GotIt.dark(), + metrics: ButtonMetrics = ButtonMetrics.default(focusOutlineExpand = 0.dp), + focusOutlineAlignment: Stroke.Alignment = Stroke.Alignment.Center, + ): ButtonStyle = ButtonStyle(colors, metrics, focusOutlineAlignment) +} + +public val ButtonColors.Companion.GotIt: IntUiGotItButtonColorFactory + get() = IntUiGotItButtonColorFactory + +public object IntUiGotItButtonColorFactory { + public fun light( + background: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + backgroundDisabled: Brush = SolidColor(Color.Unspecified), + backgroundFocused: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + backgroundPressed: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + backgroundHovered: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + content: Color = IntUiLightTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + contentDisabled: Color = IntUiLightTheme.colors.grayOrNull(7) ?: Color(0xFF818594), + contentFocused: Color = IntUiLightTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + contentPressed: Color = IntUiLightTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + contentHovered: Color = IntUiLightTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + border: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + borderDisabled: Brush = SolidColor(Color.Unspecified), + borderFocused: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + borderPressed: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + borderHovered: Brush = SolidColor(IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57)), + ): ButtonColors = + ButtonColors( + background = background, + backgroundDisabled = backgroundDisabled, + backgroundFocused = backgroundFocused, + backgroundPressed = backgroundPressed, + backgroundHovered = backgroundHovered, + content = content, + contentDisabled = contentDisabled, + contentFocused = contentFocused, + contentPressed = contentPressed, + contentHovered = contentHovered, + border = border, + borderDisabled = borderDisabled, + borderFocused = borderFocused, + borderPressed = borderPressed, + borderHovered = borderHovered, + ) + + public fun dark( + background: Brush = SolidColor(IntUiDarkTheme.colors.blueOrNull(4) ?: Color(0xFF375FAD)), + backgroundDisabled: Brush = SolidColor(Color.Unspecified), + backgroundFocused: Brush = SolidColor(IntUiDarkTheme.colors.blueOrNull(4) ?: Color(0xFF375FAD)), + backgroundPressed: Brush = SolidColor(IntUiDarkTheme.colors.blueOrNull(4) ?: Color(0xFF375FAD)), + backgroundHovered: Brush = SolidColor(IntUiDarkTheme.colors.blueOrNull(4) ?: Color(0xFF375FAD)), + content: Color = IntUiDarkTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + contentDisabled: Color = IntUiDarkTheme.colors.gray(6), + contentFocused: Color = IntUiDarkTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + contentPressed: Color = IntUiDarkTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + contentHovered: Color = IntUiDarkTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + border: Brush = SolidColor(Color(0x80FFFFFF)), + borderDisabled: Brush = SolidColor(Color.Unspecified), + borderFocused: Brush = SolidColor(Color(0x80FFFFFF)), + borderPressed: Brush = SolidColor(Color(0x80FFFFFF)), + borderHovered: Brush = SolidColor(Color(0x80FFFFFF)), + ): ButtonColors = + ButtonColors( + background = background, + backgroundDisabled = backgroundDisabled, + backgroundFocused = backgroundFocused, + backgroundPressed = backgroundPressed, + backgroundHovered = backgroundHovered, + content = content, + contentDisabled = contentDisabled, + contentFocused = contentFocused, + contentPressed = contentPressed, + contentHovered = contentHovered, + border = border, + borderDisabled = borderDisabled, + borderFocused = borderFocused, + borderPressed = borderPressed, + borderHovered = borderHovered, + ) +} + +public fun GotItTooltipStyle.Companion.light( + colors: GotItColors = GotItColors.light(), + metrics: GotItMetrics = GotItMetrics.light(), +): GotItTooltipStyle = GotItTooltipStyle(colors, metrics) + +public fun GotItColors.Companion.light( + foreground: Color = IntUiLightTheme.colors.grayOrNull(9) ?: Color(0xFFC9CCD6), + background: Color = IntUiLightTheme.colors.grayOrNull(2) ?: Color(0xFF27282E), + stepForeground: Color = IntUiLightTheme.colors.grayOrNull(7) ?: Color(0xFF818594), + secondaryActionForeground: Color = IntUiLightTheme.colors.grayOrNull(7) ?: Color(0xFF818594), + headerForeground: Color = IntUiLightTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + balloonBorderColor: Color = IntUiLightTheme.colors.grayOrNull(2) ?: Color(0xFF27282E), + imageBorderColor: Color = IntUiLightTheme.colors.grayOrNull(4) ?: Color(0xFF494B57), + link: Color = IntUiLightTheme.colors.blueOrNull(8) ?: Color(0xFF88ADF7), + codeForeground: Color = IntUiLightTheme.colors.grayOrNull(9) ?: Color(0xFFC9CCD6), + codeBackground: Color = IntUiLightTheme.colors.grayOrNull(3) ?: Color(0xFF393B40), +): GotItColors = + GotItColors( + foreground, + background, + stepForeground, + secondaryActionForeground, + headerForeground, + balloonBorderColor, + imageBorderColor, + link, + codeForeground, + codeBackground, + ) + +public fun GotItMetrics.Companion.light( + contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + textPadding: Dp = 4.dp, + buttonPadding: PaddingValues = PaddingValues(top = 12.dp, bottom = 6.dp), + iconPadding: Dp = 6.dp, + imagePadding: PaddingValues = PaddingValues(top = 4.dp, bottom = 12.dp), + cornerRadius: Dp = 8.dp, +): GotItMetrics = GotItMetrics(contentPadding, textPadding, buttonPadding, iconPadding, imagePadding, cornerRadius) + +public fun GotItTooltipStyle.Companion.dark( + colors: GotItColors = GotItColors.dark(), + metrics: GotItMetrics = GotItMetrics.dark(), +): GotItTooltipStyle = GotItTooltipStyle(colors, metrics) + +public fun GotItColors.Companion.dark( + foreground: Color = Color(0xCCFFFFFF), + background: Color = IntUiDarkTheme.colors.blueOrNull(4) ?: Color(0xFF375FAD), + stepForeground: Color = IntUiDarkTheme.colors.blueOrNull(11) ?: Color(0xFF99BBFF), + secondaryActionForeground: Color = IntUiDarkTheme.colors.blueOrNull(11) ?: Color(0xFF99BBFF), + headerForeground: Color = IntUiDarkTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + borderColor: Color = IntUiDarkTheme.colors.blueOrNull(4) ?: Color(0xFF366ACE), + imageBorderColor: Color = IntUiDarkTheme.colors.grayOrNull(3) ?: Color(0xFF393B40), + link: Color = Color(0xCCFFFFFF), + codeForeground: Color = IntUiDarkTheme.colors.grayOrNull(14) ?: Color(0xFFFFFFFF), + codeBackground: Color = IntUiDarkTheme.colors.blueOrNull(4) ?: Color(0xFF375FAD), +): GotItColors = + GotItColors( + foreground, + background, + stepForeground, + secondaryActionForeground, + headerForeground, + borderColor, + imageBorderColor, + link, + codeForeground, + codeBackground, + ) + +public fun GotItMetrics.Companion.dark( + contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + textPadding: Dp = 4.dp, + buttonPadding: PaddingValues = PaddingValues(top = 12.dp, bottom = 6.dp), + iconPadding: Dp = 6.dp, + imagePadding: PaddingValues = PaddingValues(top = 4.dp, bottom = 12.dp), + cornerRadius: Dp = 8.dp, +): GotItMetrics = GotItMetrics(contentPadding, textPadding, buttonPadding, iconPadding, imagePadding, cornerRadius) diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt index 3cb1e3d962880..ccae3d3be1709 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt @@ -25,6 +25,7 @@ import org.jetbrains.jewel.intui.standalone.menuShortcut.StandaloneMenuItemShort import org.jetbrains.jewel.intui.standalone.menuShortcut.StandaloneShortcutProvider import org.jetbrains.jewel.intui.standalone.styling.Default import org.jetbrains.jewel.intui.standalone.styling.Editor +import org.jetbrains.jewel.intui.standalone.styling.GotIt import org.jetbrains.jewel.intui.standalone.styling.Outlined import org.jetbrains.jewel.intui.standalone.styling.Slim import org.jetbrains.jewel.intui.standalone.styling.Undecorated @@ -37,6 +38,7 @@ import org.jetbrains.jewel.ui.DefaultComponentStyling import org.jetbrains.jewel.ui.LocalMenuItemShortcutHintProvider import org.jetbrains.jewel.ui.LocalMenuItemShortcutProvider import org.jetbrains.jewel.ui.LocalTypography +import org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle import org.jetbrains.jewel.ui.component.styling.ButtonStyle import org.jetbrains.jewel.ui.component.styling.CheckboxStyle import org.jetbrains.jewel.ui.component.styling.ChipStyle @@ -285,6 +287,8 @@ public fun ComponentStyling.dark( popupAdStyle: PopupAdStyle = PopupAdStyle.dark(), defaultSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Default.dark(), outlinedSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Outlined.dark(), + gotItTooltipStyle: GotItTooltipStyle = GotItTooltipStyle.dark(), + gotItButtonStyle: ButtonStyle = ButtonStyle.GotIt.dark(), ): ComponentStyling = DefaultComponentStyling( checkboxStyle = checkboxStyle, @@ -325,9 +329,96 @@ public fun ComponentStyling.dark( popupAdStyle = popupAdStyle, defaultSlimButtonStyle = defaultSlimButtonStyle, outlinedSlimButtonStyle = outlinedSlimButtonStyle, + gotItTooltipStyle = gotItTooltipStyle, + gotItButtonStyle = gotItButtonStyle, ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") +@Suppress("UnusedReceiverParameter") +@Deprecated(message = "Use the variant with gotItStyle.", level = DeprecationLevel.HIDDEN) +public fun ComponentStyling.dark( + checkboxStyle: CheckboxStyle = CheckboxStyle.dark(), + chipStyle: ChipStyle = ChipStyle.dark(), + circularProgressStyle: CircularProgressStyle = CircularProgressStyle.dark(), + defaultBannerStyle: DefaultBannerStyles = DefaultBannerStyles.Default.dark(), + comboBoxStyle: ComboBoxStyle = ComboBoxStyle.Default.dark(), + defaultButtonStyle: ButtonStyle = ButtonStyle.Default.dark(), + defaultSplitButtonStyle: SplitButtonStyle = SplitButtonStyle.Default.dark(), + defaultTabStyle: TabStyle = TabStyle.Default.dark(), + dividerStyle: DividerStyle = DividerStyle.dark(), + dropdownStyle: DropdownStyle = DropdownStyle.Default.dark(), + editorTabStyle: TabStyle = TabStyle.Editor.dark(), + groupHeaderStyle: GroupHeaderStyle = GroupHeaderStyle.dark(), + horizontalProgressBarStyle: HorizontalProgressBarStyle = HorizontalProgressBarStyle.dark(), + iconButtonStyle: IconButtonStyle = IconButtonStyle.dark(), + transparentIconButtonStyle: IconButtonStyle = IconButtonStyle.darkTransparentBackground(), + inlineBannerStyle: InlineBannerStyles = InlineBannerStyles.Default.dark(), + lazyTreeStyle: LazyTreeStyle = LazyTreeStyle.dark(), + linkStyle: LinkStyle = LinkStyle.dark(), + menuStyle: MenuStyle = MenuStyle.dark(), + outlinedButtonStyle: ButtonStyle = ButtonStyle.Outlined.dark(), + popupContainerStyle: PopupContainerStyle = PopupContainerStyle.dark(), + outlinedSplitButtonStyle: SplitButtonStyle = SplitButtonStyle.Outlined.dark(), + radioButtonStyle: RadioButtonStyle = RadioButtonStyle.dark(), + scrollbarStyle: ScrollbarStyle = ScrollbarStyle.dark(), + segmentedControlButtonStyle: SegmentedControlButtonStyle = SegmentedControlButtonStyle.dark(), + segmentedControlStyle: SegmentedControlStyle = SegmentedControlStyle.dark(), + selectableLazyColumnStyle: SelectableLazyColumnStyle = SelectableLazyColumnStyle.dark(), + sliderStyle: SliderStyle = SliderStyle.dark(), + simpleListItemStyle: SimpleListItemStyle = SimpleListItemStyle.dark(), + textAreaStyle: TextAreaStyle = TextAreaStyle.dark(), + textFieldStyle: TextFieldStyle = TextFieldStyle.dark(), + tooltipStyle: TooltipStyle = TooltipStyle.dark(autoHideBehavior = TooltipAutoHideBehavior.Normal), + undecoratedDropdownStyle: DropdownStyle = DropdownStyle.Undecorated.dark(), + speedSearchStyle: SpeedSearchStyle = SpeedSearchStyle.dark(), + searchMatchStyle: SearchMatchStyle = SearchMatchStyle.dark(), + popupAdStyle: PopupAdStyle = PopupAdStyle.dark(), + defaultSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Default.dark(), + outlinedSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Outlined.dark(), +): ComponentStyling = + DefaultComponentStyling( + checkboxStyle = checkboxStyle, + chipStyle = chipStyle, + circularProgressStyle = circularProgressStyle, + defaultBannerStyle = defaultBannerStyle, + comboBoxStyle = comboBoxStyle, + defaultButtonStyle = defaultButtonStyle, + defaultDropdownStyle = dropdownStyle, + defaultSplitButtonStyle = defaultSplitButtonStyle, + defaultTabStyle = defaultTabStyle, + dividerStyle = dividerStyle, + editorTabStyle = editorTabStyle, + groupHeaderStyle = groupHeaderStyle, + horizontalProgressBarStyle = horizontalProgressBarStyle, + iconButtonStyle = iconButtonStyle, + transparentIconButtonStyle = transparentIconButtonStyle, + inlineBannerStyle = inlineBannerStyle, + lazyTreeStyle = lazyTreeStyle, + linkStyle = linkStyle, + menuStyle = menuStyle, + outlinedButtonStyle = outlinedButtonStyle, + popupContainerStyle = popupContainerStyle, + outlinedSplitButtonStyle = outlinedSplitButtonStyle, + radioButtonStyle = radioButtonStyle, + scrollbarStyle = scrollbarStyle, + segmentedControlButtonStyle = segmentedControlButtonStyle, + segmentedControlStyle = segmentedControlStyle, + selectableLazyColumnStyle = selectableLazyColumnStyle, + simpleListItemStyle = simpleListItemStyle, + sliderStyle = sliderStyle, + textAreaStyle = textAreaStyle, + textFieldStyle = textFieldStyle, + tooltipStyle = tooltipStyle, + undecoratedDropdownStyle = undecoratedDropdownStyle, + speedSearchStyle = speedSearchStyle, + searchMatchStyle = searchMatchStyle, + popupAdStyle = popupAdStyle, + defaultSlimButtonStyle = defaultSlimButtonStyle, + outlinedSlimButtonStyle = outlinedSlimButtonStyle, + gotItTooltipStyle = GotItTooltipStyle.dark(), + gotItButtonStyle = ButtonStyle.GotIt.dark(), + ) + +@Suppress("UnusedReceiverParameter") @Deprecated( message = "Use the variant with defaultSlimButtonStyle and outlinedSlimButtonStyle.", level = DeprecationLevel.HIDDEN, @@ -409,9 +500,11 @@ public fun ComponentStyling.dark( popupAdStyle = popupAdStyle, defaultSlimButtonStyle = ButtonStyle.Slim.Default.dark(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.dark(), + gotItTooltipStyle = GotItTooltipStyle.dark(), + gotItButtonStyle = ButtonStyle.GotIt.dark(), ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") +@Suppress("UnusedReceiverParameter") @Deprecated("Use the variant with popupAdTextStyle.", level = DeprecationLevel.HIDDEN) public fun ComponentStyling.dark( checkboxStyle: CheckboxStyle = CheckboxStyle.dark(), @@ -489,9 +582,11 @@ public fun ComponentStyling.dark( popupAdStyle = PopupAdStyle.dark(), defaultSlimButtonStyle = ButtonStyle.Slim.Default.dark(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.dark(), + gotItTooltipStyle = GotItTooltipStyle.dark(), + gotItButtonStyle = ButtonStyle.GotIt.dark(), ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") +@Suppress("UnusedReceiverParameter") @Deprecated("Use the variant with speedSearchStyle.", level = DeprecationLevel.HIDDEN) public fun ComponentStyling.dark( checkboxStyle: CheckboxStyle = CheckboxStyle.dark(), @@ -567,9 +662,11 @@ public fun ComponentStyling.dark( popupAdStyle = PopupAdStyle.dark(), defaultSlimButtonStyle = ButtonStyle.Slim.Default.dark(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.dark(), + gotItTooltipStyle = GotItTooltipStyle.dark(), + gotItButtonStyle = ButtonStyle.GotIt.dark(), ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") +@Suppress("UnusedReceiverParameter") @Deprecated("Use the variant with transparentIconButtonStyle.", level = DeprecationLevel.HIDDEN) public fun ComponentStyling.dark( checkboxStyle: CheckboxStyle = CheckboxStyle.dark(), @@ -644,6 +741,8 @@ public fun ComponentStyling.dark( popupAdStyle = PopupAdStyle.dark(), defaultSlimButtonStyle = ButtonStyle.Slim.Default.dark(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.dark(), + gotItTooltipStyle = GotItTooltipStyle.dark(), + gotItButtonStyle = ButtonStyle.GotIt.dark(), ) @Suppress("UnusedReceiverParameter") @@ -686,6 +785,8 @@ public fun ComponentStyling.light( popupAdStyle: PopupAdStyle = PopupAdStyle.light(), defaultSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Default.light(), outlinedSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Outlined.light(), + gotItTooltipStyle: GotItTooltipStyle = GotItTooltipStyle.light(), + gotItButtonStyle: ButtonStyle = ButtonStyle.GotIt.light(), ): ComponentStyling = DefaultComponentStyling( checkboxStyle = checkboxStyle, @@ -726,9 +827,96 @@ public fun ComponentStyling.light( popupAdStyle = popupAdStyle, defaultSlimButtonStyle = defaultSlimButtonStyle, outlinedSlimButtonStyle = outlinedSlimButtonStyle, + gotItTooltipStyle = gotItTooltipStyle, + gotItButtonStyle = gotItButtonStyle, ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") +@Suppress("UnusedReceiverParameter") +@Deprecated(message = "Use the variant with gotItStyle.", level = DeprecationLevel.HIDDEN) +public fun ComponentStyling.light( + checkboxStyle: CheckboxStyle = CheckboxStyle.light(), + chipStyle: ChipStyle = ChipStyle.light(), + circularProgressStyle: CircularProgressStyle = CircularProgressStyle.light(), + defaultBannerStyle: DefaultBannerStyles = DefaultBannerStyles.Default.light(), + comboBoxStyle: ComboBoxStyle = ComboBoxStyle.Default.light(), + defaultButtonStyle: ButtonStyle = ButtonStyle.Default.light(), + defaultSplitButtonStyle: SplitButtonStyle = SplitButtonStyle.Default.light(), + defaultTabStyle: TabStyle = TabStyle.Default.light(), + dividerStyle: DividerStyle = DividerStyle.light(), + dropdownStyle: DropdownStyle = DropdownStyle.Default.light(), + editorTabStyle: TabStyle = TabStyle.Editor.light(), + groupHeaderStyle: GroupHeaderStyle = GroupHeaderStyle.light(), + horizontalProgressBarStyle: HorizontalProgressBarStyle = HorizontalProgressBarStyle.light(), + iconButtonStyle: IconButtonStyle = IconButtonStyle.light(), + transparentIconButtonStyle: IconButtonStyle = IconButtonStyle.lightTransparentBackground(), + inlineBannerStyle: InlineBannerStyles = InlineBannerStyles.Default.light(), + lazyTreeStyle: LazyTreeStyle = LazyTreeStyle.light(), + linkStyle: LinkStyle = LinkStyle.light(), + menuStyle: MenuStyle = MenuStyle.light(), + popupContainerStyle: PopupContainerStyle = PopupContainerStyle.light(), + outlinedButtonStyle: ButtonStyle = ButtonStyle.Outlined.light(), + outlinedSplitButtonStyle: SplitButtonStyle = SplitButtonStyle.Outlined.light(), + radioButtonStyle: RadioButtonStyle = RadioButtonStyle.light(), + scrollbarStyle: ScrollbarStyle = ScrollbarStyle.light(), + segmentedControlButtonStyle: SegmentedControlButtonStyle = SegmentedControlButtonStyle.light(), + segmentedControlStyle: SegmentedControlStyle = SegmentedControlStyle.light(), + sliderStyle: SliderStyle = SliderStyle.light(), + selectableLazyColumnStyle: SelectableLazyColumnStyle = SelectableLazyColumnStyle.light(), + simpleListItemStyle: SimpleListItemStyle = SimpleListItemStyle.light(), + textAreaStyle: TextAreaStyle = TextAreaStyle.light(), + textFieldStyle: TextFieldStyle = TextFieldStyle.light(), + tooltipStyle: TooltipStyle = TooltipStyle.light(autoHideBehavior = TooltipAutoHideBehavior.Normal), + undecoratedDropdownStyle: DropdownStyle = DropdownStyle.Undecorated.light(), + speedSearchStyle: SpeedSearchStyle = SpeedSearchStyle.light(), + searchMatchStyle: SearchMatchStyle = SearchMatchStyle.light(), + popupAdStyle: PopupAdStyle = PopupAdStyle.light(), + defaultSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Default.light(), + outlinedSlimButtonStyle: ButtonStyle = ButtonStyle.Slim.Outlined.light(), +): ComponentStyling = + DefaultComponentStyling( + checkboxStyle = checkboxStyle, + chipStyle = chipStyle, + circularProgressStyle = circularProgressStyle, + comboBoxStyle = comboBoxStyle, + defaultBannerStyle = defaultBannerStyle, + defaultButtonStyle = defaultButtonStyle, + defaultDropdownStyle = dropdownStyle, + defaultSplitButtonStyle = defaultSplitButtonStyle, + defaultTabStyle = defaultTabStyle, + dividerStyle = dividerStyle, + editorTabStyle = editorTabStyle, + groupHeaderStyle = groupHeaderStyle, + horizontalProgressBarStyle = horizontalProgressBarStyle, + iconButtonStyle = iconButtonStyle, + transparentIconButtonStyle = transparentIconButtonStyle, + inlineBannerStyle = inlineBannerStyle, + lazyTreeStyle = lazyTreeStyle, + linkStyle = linkStyle, + menuStyle = menuStyle, + outlinedButtonStyle = outlinedButtonStyle, + popupContainerStyle = popupContainerStyle, + outlinedSplitButtonStyle = outlinedSplitButtonStyle, + radioButtonStyle = radioButtonStyle, + scrollbarStyle = scrollbarStyle, + segmentedControlButtonStyle = segmentedControlButtonStyle, + segmentedControlStyle = segmentedControlStyle, + selectableLazyColumnStyle = selectableLazyColumnStyle, + sliderStyle = sliderStyle, + simpleListItemStyle = simpleListItemStyle, + textAreaStyle = textAreaStyle, + textFieldStyle = textFieldStyle, + tooltipStyle = tooltipStyle, + undecoratedDropdownStyle = undecoratedDropdownStyle, + speedSearchStyle = speedSearchStyle, + searchMatchStyle = searchMatchStyle, + popupAdStyle = popupAdStyle, + defaultSlimButtonStyle = defaultSlimButtonStyle, + outlinedSlimButtonStyle = outlinedSlimButtonStyle, + gotItTooltipStyle = GotItTooltipStyle.light(), + gotItButtonStyle = ButtonStyle.GotIt.light(), + ) + +@Suppress("UnusedReceiverParameter") @Deprecated( message = "Use the variant with defaultSlimButtonStyle and outlinedSlimButtonStyle.", level = DeprecationLevel.HIDDEN, @@ -810,10 +998,12 @@ public fun ComponentStyling.light( popupAdStyle = popupAdStyle, defaultSlimButtonStyle = ButtonStyle.Slim.Default.light(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.light(), + gotItTooltipStyle = GotItTooltipStyle.light(), + gotItButtonStyle = ButtonStyle.GotIt.light(), ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") -@Deprecated("Use the variant with popupAdTextStyle.", level = DeprecationLevel.HIDDEN) +@Suppress("UnusedReceiverParameter") +@Deprecated("Use the variant with popupAdTextStyle and gotItTooltipStyle.", level = DeprecationLevel.HIDDEN) public fun ComponentStyling.light( checkboxStyle: CheckboxStyle = CheckboxStyle.light(), chipStyle: ChipStyle = ChipStyle.light(), @@ -890,9 +1080,11 @@ public fun ComponentStyling.light( popupAdStyle = PopupAdStyle.light(), defaultSlimButtonStyle = ButtonStyle.Slim.Default.light(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.light(), + gotItTooltipStyle = GotItTooltipStyle.light(), + gotItButtonStyle = ButtonStyle.GotIt.light(), ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") +@Suppress("UnusedReceiverParameter") @Deprecated("Use the variant with speedSearchStyle.", level = DeprecationLevel.HIDDEN) public fun ComponentStyling.light( checkboxStyle: CheckboxStyle = CheckboxStyle.light(), @@ -968,9 +1160,11 @@ public fun ComponentStyling.light( popupAdStyle = PopupAdStyle.light(), defaultSlimButtonStyle = ButtonStyle.Slim.Default.light(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.light(), + gotItTooltipStyle = GotItTooltipStyle.light(), + gotItButtonStyle = ButtonStyle.GotIt.light(), ) -@Suppress("UnusedReceiverParameter", "DEPRECATION_ERROR") +@Suppress("UnusedReceiverParameter") @Deprecated("Use the variant with transparentIconButtonStyle.", level = DeprecationLevel.HIDDEN) public fun ComponentStyling.light( checkboxStyle: CheckboxStyle = CheckboxStyle.light(), @@ -1045,6 +1239,8 @@ public fun ComponentStyling.light( popupAdStyle = PopupAdStyle.light(), defaultSlimButtonStyle = ButtonStyle.Slim.Default.light(), outlinedSlimButtonStyle = ButtonStyle.Slim.Outlined.light(), + gotItTooltipStyle = GotItTooltipStyle.light(), + gotItButtonStyle = ButtonStyle.GotIt.light(), ) @Composable diff --git a/platform/jewel/samples/showcase/api-dump.txt b/platform/jewel/samples/showcase/api-dump.txt index 2fc43487d30f0..ae0812ae90605 100644 --- a/platform/jewel/samples/showcase/api-dump.txt +++ b/platform/jewel/samples/showcase/api-dump.txt @@ -20,6 +20,7 @@ f:org.jetbrains.jewel.samples.showcase.ShowcaseIcons$Components - f:getButton():org.jetbrains.jewel.ui.icon.PathIconKey - f:getCheckbox():org.jetbrains.jewel.ui.icon.PathIconKey - f:getComboBox():org.jetbrains.jewel.ui.icon.PathIconKey +- f:getGotIt():org.jetbrains.jewel.ui.icon.PathIconKey - f:getLinks():org.jetbrains.jewel.ui.icon.PathIconKey - f:getMenu():org.jetbrains.jewel.ui.icon.PathIconKey - f:getProgressBar():org.jetbrains.jewel.ui.icon.PathIconKey diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt index d1b9612b217bb..ce19251d125f3 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt @@ -23,6 +23,7 @@ public object ShowcaseIcons { public val button: PathIconKey = PathIconKey("icons/components/button.svg", ShowcaseIcons::class.java) public val checkbox: PathIconKey = PathIconKey("icons/components/checkBox.svg", ShowcaseIcons::class.java) public val comboBox: PathIconKey = PathIconKey("icons/components/comboBox.svg", ShowcaseIcons::class.java) + public val gotIt: PathIconKey = PathIconKey("icons/components/balloon.svg", ShowcaseIcons::class.java) public val links: PathIconKey = PathIconKey("icons/components/links.svg", ShowcaseIcons::class.java) public val menu: PathIconKey = PathIconKey("icons/components/menu.svg", ShowcaseIcons::class.java) public val progressBar: PathIconKey = PathIconKey("icons/components/progressbar.svg", ShowcaseIcons::class.java) diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/GotIt.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/GotIt.kt new file mode 100644 index 0000000000000..5c956ca9d6a34 --- /dev/null +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/GotIt.kt @@ -0,0 +1,976 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.samples.showcase.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.byValue +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAlignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds +import org.jetbrains.jewel.ui.component.CheckboxRow +import org.jetbrains.jewel.ui.component.DefaultButton +import org.jetbrains.jewel.ui.component.GroupHeader +import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.component.InfoText +import org.jetbrains.jewel.ui.component.RadioButtonRow +import org.jetbrains.jewel.ui.component.Slider +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.TextField +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer +import org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition +import org.jetbrains.jewel.ui.component.gotit.GotItBody +import org.jetbrains.jewel.ui.component.gotit.GotItButton +import org.jetbrains.jewel.ui.component.gotit.GotItButtons +import org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep +import org.jetbrains.jewel.ui.component.gotit.GotItImage +import org.jetbrains.jewel.ui.component.gotit.GotItLink +import org.jetbrains.jewel.ui.component.gotit.GotItTooltip +import org.jetbrains.jewel.ui.component.gotit.buildGotItBody +import org.jetbrains.jewel.ui.component.scrollbarContentSafePadding +import org.jetbrains.jewel.ui.icon.IntelliJIconKey +import org.jetbrains.jewel.ui.icons.AllIconsKeys + +private const val JEWEL_README = "https://github.com/JetBrains/intellij-community/blob/master/platform/jewel/README.md" + +private enum class TimeoutUnit { + MILLISECONDS, + SECONDS, + MINUTES, +} + +@Composable +internal fun GotItTooltipShowcase() { + var gotItShowcaseBody by remember { mutableStateOf(buildGotItBody {}) } + + var showImage by remember { mutableStateOf(false) } + var showImageBorder by remember { mutableStateOf(false) } + + var showIconOrStep by remember { mutableStateOf(false) } + var isIconMode by remember { mutableStateOf(false) } + var chosenIconOption by remember { mutableStateOf(IconOption.entries.first()) } + val stepNumberState = rememberTextFieldState("1") + val stepValue by remember { derivedStateOf { stepNumberState.textAsString.toIntOrNull()?.coerceIn(1, 99) ?: 1 } } + val iconOrStep by + remember(isIconMode, chosenIconOption, stepValue) { + derivedStateOf { + if (isIconMode) { + GotItIconOrStep.Icon { Icon(chosenIconOption.icon, chosenIconOption.contentDescription) } + } else { + GotItIconOrStep.Step(stepValue) + } + } + } + + var showHeader by remember { mutableStateOf(false) } + val headerText = rememberTextFieldState("This is the header text") + + var setMaxWidth by remember { mutableStateOf(false) } + val maxWidthValue = rememberTextFieldState("200") + + val possibleAnchors = + mapOf( + "Start" to Alignment.CenterStart, + "Top" to Alignment.TopCenter, + "End" to Alignment.CenterEnd, + "Bottom" to Alignment.BottomCenter, + "Custom" to Alignment.Center, + ) + var currentSelectedAnchor by remember { mutableStateOf(possibleAnchors.entries.first()) } + var horizontalBias by remember { mutableFloatStateOf(0f) } + var verticalBias by remember { mutableFloatStateOf(0f) } + val effectiveAnchor by remember { + derivedStateOf { + if (currentSelectedAnchor.key == "Custom") { + BiasAlignment(horizontalBias, verticalBias) + } else { + currentSelectedAnchor.value + } + } + } + val possibleGotItBalloonPositions = + mapOf( + "Start" to GotItBalloonPosition.START, + "Top" to GotItBalloonPosition.ABOVE, + "End" to GotItBalloonPosition.END, + "Bottom" to GotItBalloonPosition.BELOW, + ) + var currentSelectedBalloonPosition by remember { mutableStateOf(possibleGotItBalloonPositions.entries.first()) } + val componentOffset = rememberTextFieldState("0") + val offsetValueDp by remember { derivedStateOf { (componentOffset.textAsString.toIntOrNull() ?: 0).dp } } + + var showLink by remember { mutableStateOf(false) } + var isExternalLink by remember { mutableStateOf(false) } + val linkLabel = rememberTextFieldState("This is a link") + val linkPrint = rememberTextFieldState("You just clicked the link!") + val externalLinkUri = rememberTextFieldState(JEWEL_README) + val linkType by remember { + derivedStateOf { + if (isExternalLink) { + GotItLink.Browser(linkLabel.textAsString, externalLinkUri.textAsString) { + println(linkPrint.textAsString) + } + } else { + GotItLink.Regular(linkLabel.textAsString) { println(linkPrint.textAsString) } + } + } + } + + var addTimeout by remember { mutableStateOf(false) } + val timeoutSecondsState = rememberTextFieldState("5") + var timeoutUnit by remember { mutableStateOf(TimeoutUnit.SECONDS) } + val timeoutDuration by remember { + derivedStateOf { + val n = timeoutSecondsState.textAsString.toIntOrNull() ?: 5 + if (timeoutUnit == TimeoutUnit.MILLISECONDS) { + n.milliseconds + } else if (timeoutUnit == TimeoutUnit.SECONDS) { + n.seconds + } else { + n.minutes + } + } + } + + val onShownText = rememberTextFieldState("The component was just shown!") + + var setEscapePressed by remember { mutableStateOf(false) } + val onEscapePressedText = rememberTextFieldState("You just pressed Escape key") + + var showPrimaryButton by remember { mutableStateOf(true) } + var usePrimaryDefault by remember { mutableStateOf(true) } + val primaryLabelState = rememberTextFieldState("Got it") + val primaryPrintState = rememberTextFieldState("Primary button clicked!") + var showSecondaryButton by remember { mutableStateOf(false) } + val secondaryLabelState = rememberTextFieldState("Skip All") + val secondaryPrintState = rememberTextFieldState("Secondary button clicked!") + val buttons by remember { + derivedStateOf { + val primary = + when { + !showPrimaryButton -> null + usePrimaryDefault -> GotItButton.Default + else -> GotItButton(primaryLabelState.textAsString) { println(primaryPrintState.textAsString) } + } + val secondary = + if (showSecondaryButton) { + GotItButton(secondaryLabelState.textAsString) { println(secondaryPrintState.textAsString) } + } else { + null + } + GotItButtons(primary = primary, secondary = secondary) + } + } + + var isVisible by remember { mutableStateOf(false) } + + VerticallyScrollableContainer { + Column(modifier = Modifier.fillMaxWidth().padding(end = scrollbarContentSafePadding(), bottom = 2.dp)) { + Column( + modifier = Modifier.fillMaxWidth().padding(vertical = 50.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + GotItTooltip( + body = gotItShowcaseBody, + visible = isVisible, + onDismiss = { isVisible = false }, + header = if (showHeader) headerText.textAsString else null, + iconOrStep = if (showIconOrStep) iconOrStep else null, + buttons = buttons, + link = if (showLink) linkType else null, + image = if (showImage) GotItImage("drawables/cool_jewel.png", null, showImageBorder) else null, + maxWidth = if (setMaxWidth) maxWidthValue.textAsString.toIntOrNull()?.dp else null, + timeout = if (addTimeout) timeoutDuration else Duration.INFINITE, + gotItBalloonPosition = currentSelectedBalloonPosition.value, + anchor = effectiveAnchor, + onShow = { println(onShownText.textAsString) }, + onEscapePress = if (setEscapePressed) ({ println(onEscapePressedText.textAsString) }) else null, + offset = offsetValueDp, + ) { + DefaultButton( + modifier = + Modifier.onKeyEvent { keyEvent -> + when (keyEvent.key) { + Key.J if keyEvent.type == KeyEventType.KeyDown -> { + val entries = possibleGotItBalloonPositions.entries.toList() + val idx = entries.indexOf(currentSelectedBalloonPosition) + currentSelectedBalloonPosition = entries[(idx + 1) % entries.size] + true + } + + Key.H if keyEvent.type == KeyEventType.KeyDown -> { + val entries = possibleAnchors.entries.filter { it.key != "Custom" }.toList() + val idx = entries.indexOf(currentSelectedAnchor).coerceAtLeast(0) + currentSelectedAnchor = entries[(idx + 1) % entries.size] + true + } + + else -> false + } + }, + onClick = { isVisible = !isVisible }, + ) { + Text("Show GotIt Component") + } + } + } + + Text("Playground", fontSize = 16.sp, fontWeight = FontWeight.Bold) + + GroupHeader("Placement", Modifier.padding(vertical = 12.dp)) + Column(modifier = Modifier.padding(start = 8.dp)) { + AnchorAndPositionSection( + anchors = possibleAnchors, + currentAnchor = currentSelectedAnchor, + onAnchorChange = { currentSelectedAnchor = it }, + horizontalBias = horizontalBias, + onHorizontalBiasChange = { horizontalBias = it }, + verticalBias = verticalBias, + onVerticalBiasChange = { verticalBias = it }, + positions = possibleGotItBalloonPositions, + currentPosition = currentSelectedBalloonPosition, + onPositionChange = { currentSelectedBalloonPosition = it }, + ) + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Balloon offset: ") + TextField( + modifier = Modifier.padding(start = 4.dp), + state = componentOffset, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + inputTransformation = remember { intInputTransformation() }, + ) + } + } + + GroupHeader("Appearance", Modifier.padding(vertical = 12.dp)) + + Column(modifier = Modifier.padding(start = 8.dp)) { + GroupHeader("Body") + TextControls(gotItText = { type -> gotItShowcaseBody = type }) + + Row( + modifier = Modifier.padding(bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + CheckboxRow(text = "Show image", checked = showImage, onCheckedChange = { showImage = it }) + + AnimatedVisibility(showImage) { + CheckboxRow( + text = "Show border", + checked = showImageBorder, + onCheckedChange = { showImageBorder = it }, + ) + } + } + + ShowHeaderRow( + showHeader = showHeader, + onShowHeaderChange = { showHeader = it }, + headerText = headerText, + ) + + ShowIconOrStepSection( + showIconOrStep = showIconOrStep, + onShowChange = { showIconOrStep = it }, + isIconMode = isIconMode, + onModeChange = { isIconMode = it }, + stepNumberState = stepNumberState, + chosenIcon = chosenIconOption, + onIconChange = { chosenIconOption = it }, + ) + + SetMaxWidthRow( + setMaxWidth = setMaxWidth, + onSetChange = { setMaxWidth = it }, + maxWidthValue = maxWidthValue, + ) + + GroupHeader("Behavior", Modifier.padding(vertical = 12.dp)) + LinkSection( + showLink = showLink, + onShowChange = { showLink = it }, + isExternalLink = isExternalLink, + onTypeChange = { isExternalLink = it }, + linkLabel = linkLabel, + linkPrint = linkPrint, + externalLinkUri = externalLinkUri, + ) + + TimeoutSection( + addTimeout = addTimeout, + onAddChange = { addTimeout = it }, + timeoutSecondsState = timeoutSecondsState, + timeoutUnit = timeoutUnit, + onUnitChange = { timeoutUnit = it }, + ) + + EscapePressedSection( + setEscapePressed = setEscapePressed, + onSetChange = { setEscapePressed = it }, + onEscapePressedText = onEscapePressedText, + ) + + Row(modifier = Modifier.padding(bottom = 4.dp), verticalAlignment = Alignment.CenterVertically) { + Text("onShown text: ") + TextField(state = onShownText, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)) + } + + GroupHeader("Buttons", Modifier.padding(vertical = 12.dp)) + ButtonsSection( + showPrimaryButton = showPrimaryButton, + onShowPrimaryChange = { showPrimaryButton = it }, + usePrimaryDefault = usePrimaryDefault, + onUsePrimaryDefaultChange = { usePrimaryDefault = it }, + primaryLabelState = primaryLabelState, + primaryPrintState = primaryPrintState, + showSecondaryButton = showSecondaryButton, + onShowSecondaryChange = { showSecondaryButton = it }, + secondaryLabelState = secondaryLabelState, + secondaryPrintState = secondaryPrintState, + ) + } + } + } +} + +@Composable +private fun ShowHeaderRow(showHeader: Boolean, onShowHeaderChange: (Boolean) -> Unit, headerText: TextFieldState) { + Row(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow(text = "Show header: ", checked = showHeader, onCheckedChange = onShowHeaderChange) + + AnimatedVisibility(showHeader) { + TextField( + modifier = Modifier.padding(start = 4.dp), + state = headerText, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } +} + +@Composable +private fun ShowIconOrStepSection( + showIconOrStep: Boolean, + onShowChange: (Boolean) -> Unit, + isIconMode: Boolean, + onModeChange: (Boolean) -> Unit, + stepNumberState: TextFieldState, + chosenIcon: IconOption, + onIconChange: (IconOption) -> Unit, +) { + Column(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow(text = "Show Icon or Step", checked = showIconOrStep, onCheckedChange = onShowChange) + + AnimatedVisibility(showIconOrStep) { + Column(modifier = Modifier.padding(start = 24.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RadioButtonRow(text = "Step", selected = !isIconMode, onClick = { onModeChange(false) }) + RadioButtonRow(text = "Icon", selected = isIconMode, onClick = { onModeChange(true) }) + } + + AnimatedVisibility(!isIconMode) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Step number (between 1 and 99): ") + TextField( + modifier = Modifier.padding(start = 4.dp), + state = stepNumberState, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + inputTransformation = remember { intInputTransformation(1, 99) }, + ) + } + } + + AnimatedVisibility(isIconMode) { + Row { + IconOption.entries.forEach { option -> + RadioButtonRow( + text = option.type, + selected = option == chosenIcon, + onClick = { onIconChange(option) }, + ) + } + } + } + } + } + } +} + +@Composable +private fun SetMaxWidthRow(setMaxWidth: Boolean, onSetChange: (Boolean) -> Unit, maxWidthValue: TextFieldState) { + Column(modifier = Modifier.padding(bottom = 4.dp)) { + Row { + CheckboxRow(text = "Set max width: ", checked = setMaxWidth, onCheckedChange = onSetChange) + + AnimatedVisibility(setMaxWidth) { + Column { + TextField( + modifier = Modifier.padding(start = 4.dp), + state = maxWidthValue, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + inputTransformation = remember { intInputTransformation() }, + ) + } + } + } + AnimatedVisibility(setMaxWidth) { + Column { InfoText("Note: this will only take effect if you don't show an image.") } + } + } +} + +@Suppress("MultipleEmitters") +@Composable +private fun AnchorAndPositionSection( + anchors: Map, + currentAnchor: Map.Entry, + onAnchorChange: (Map.Entry) -> Unit, + horizontalBias: Float, + onHorizontalBiasChange: (Float) -> Unit, + verticalBias: Float, + onVerticalBiasChange: (Float) -> Unit, + positions: Map, + currentPosition: Map.Entry, + onPositionChange: (Map.Entry) -> Unit, +) { + Row(modifier = Modifier.padding(bottom = 4.dp), verticalAlignment = Alignment.CenterVertically) { + Text("Component Anchor: ") + + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + anchors.forEach { anchor -> + RadioButtonRow( + text = anchor.key, + selected = currentAnchor == anchor, + onClick = { onAnchorChange(anchor) }, + ) + } + } + } + + AnimatedVisibility(currentAnchor.key == "Custom") { + Column(modifier = Modifier.padding(start = 24.dp, bottom = 4.dp)) { + // Labels are kept in separate rows from sliders so the dynamic text content + // doesn't change the slider's measured width, which would recreate draggableState + // (keyed on maxPx) and cancel the active drag gesture on every value update. + Text("Horizontal bias: ${horizontalBias.toDisplayString()}") + Slider( + value = horizontalBias, + onValueChange = onHorizontalBiasChange, + modifier = Modifier.fillMaxWidth(), + valueRange = -1f..1f, + ) + Text("Vertical bias: ${verticalBias.toDisplayString()}") + Slider( + value = verticalBias, + onValueChange = onVerticalBiasChange, + modifier = Modifier.fillMaxWidth(), + valueRange = -1f..1f, + ) + } + } + + Row(modifier = Modifier.padding(bottom = 4.dp), verticalAlignment = Alignment.CenterVertically) { + Text("Balloon Position: ") + + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + positions.forEach { position -> + RadioButtonRow( + text = position.key, + selected = currentPosition == position, + onClick = { onPositionChange(position) }, + ) + } + } + } +} + +private fun Float.toDisplayString() = "%.2f".format(this) + +@Composable +private fun LinkSection( + showLink: Boolean, + onShowChange: (Boolean) -> Unit, + isExternalLink: Boolean, + onTypeChange: (Boolean) -> Unit, + linkLabel: TextFieldState, + linkPrint: TextFieldState, + externalLinkUri: TextFieldState, +) { + CheckboxRow( + modifier = Modifier.padding(top = 4.dp), + text = "Add a Link:", + checked = showLink, + onCheckedChange = onShowChange, + ) + + AnimatedVisibility(showLink) { + Column(modifier = Modifier.padding(top = 4.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RadioButtonRow(text = "Regular Link", selected = !isExternalLink, onClick = { onTypeChange(false) }) + RadioButtonRow(text = "External Link", selected = isExternalLink, onClick = { onTypeChange(true) }) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Link Label: ") + TextField( + modifier = Modifier.padding(vertical = 4.dp), + state = linkLabel, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + + Text("Link Action Text: ") + TextField( + modifier = Modifier.padding(start = 8.dp), + state = linkPrint, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + + AnimatedVisibility(isExternalLink) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Link Browser URI: ") + TextField( + modifier = Modifier.padding(start = 4.dp), + state = externalLinkUri, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + } + } + } +} + +@Composable +private fun TimeoutSection( + addTimeout: Boolean, + onAddChange: (Boolean) -> Unit, + timeoutSecondsState: TextFieldState, + timeoutUnit: TimeoutUnit, + onUnitChange: (TimeoutUnit) -> Unit, +) { + Column(modifier = Modifier.padding(top = 4.dp)) { + CheckboxRow(text = "Set a timeout", checked = addTimeout, onCheckedChange = onAddChange) + + AnimatedVisibility(addTimeout) { + Column(modifier = Modifier.padding(top = 8.dp)) { + TextField( + state = timeoutSecondsState, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + inputTransformation = remember { intInputTransformation() }, + ) + + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + RadioButtonRow( + text = "Milliseconds", + selected = timeoutUnit == TimeoutUnit.MILLISECONDS, + onClick = { onUnitChange(TimeoutUnit.MILLISECONDS) }, + ) + RadioButtonRow( + text = "Seconds", + selected = timeoutUnit == TimeoutUnit.SECONDS, + onClick = { onUnitChange(TimeoutUnit.SECONDS) }, + ) + RadioButtonRow( + text = "Minutes", + selected = timeoutUnit == TimeoutUnit.MINUTES, + onClick = { onUnitChange(TimeoutUnit.MINUTES) }, + ) + } + } + } + + AnimatedVisibility(addTimeout) { + InfoText("Note: by setting a timeout, the primary and secondary buttons won't appear.") + } + } +} + +@Composable +private fun EscapePressedSection( + setEscapePressed: Boolean, + onSetChange: (Boolean) -> Unit, + onEscapePressedText: TextFieldState, +) { + Column(modifier = Modifier.padding(top = 4.dp)) { + Row { + CheckboxRow(text = "Set onEscapePressed: ", checked = setEscapePressed, onCheckedChange = onSetChange) + + AnimatedVisibility(setEscapePressed) { + TextField( + modifier = Modifier.padding(start = 4.dp), + state = onEscapePressedText, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + + AnimatedVisibility(setEscapePressed) { + InfoText( + "Note: by setting this parameter, users can press Esc and the popup will be dismissed. " + + "This only works if the popup is currently focused." + ) + } + } +} + +@Suppress("MultipleEmitters") +@Composable +private fun ButtonsSection( + showPrimaryButton: Boolean, + onShowPrimaryChange: (Boolean) -> Unit, + usePrimaryDefault: Boolean, + onUsePrimaryDefaultChange: (Boolean) -> Unit, + primaryLabelState: TextFieldState, + primaryPrintState: TextFieldState, + showSecondaryButton: Boolean, + onShowSecondaryChange: (Boolean) -> Unit, + secondaryLabelState: TextFieldState, + secondaryPrintState: TextFieldState, +) { + Column(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow(text = "Show primary button", checked = showPrimaryButton, onCheckedChange = onShowPrimaryChange) + + AnimatedVisibility(showPrimaryButton) { + Column(modifier = Modifier.padding(start = 24.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RadioButtonRow( + text = "Default (\"Got it\")", + selected = usePrimaryDefault, + onClick = { onUsePrimaryDefaultChange(true) }, + ) + RadioButtonRow( + text = "Custom", + selected = !usePrimaryDefault, + onClick = { onUsePrimaryDefaultChange(false) }, + ) + } + + AnimatedVisibility(!usePrimaryDefault) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Label: ") + TextField( + modifier = Modifier.padding(vertical = 4.dp), + state = primaryLabelState, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + Text("Action text: ") + TextField( + modifier = Modifier.padding(start = 4.dp), + state = primaryPrintState, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + } + } + } + + Column(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow( + text = "Show secondary button", + checked = showSecondaryButton, + onCheckedChange = onShowSecondaryChange, + ) + + AnimatedVisibility(showSecondaryButton) { + Row(modifier = Modifier.padding(start = 24.dp), verticalAlignment = Alignment.CenterVertically) { + Text("Label: ") + TextField( + modifier = Modifier.padding(vertical = 4.dp), + state = secondaryLabelState, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + Text("Action text: ") + TextField( + modifier = Modifier.padding(start = 4.dp), + state = secondaryPrintState, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + } +} + +@Composable +private fun TextControls(gotItText: (GotItBody) -> Unit) { + val currentGotItText by rememberUpdatedState(gotItText) + var isRichBody by remember { mutableStateOf(false) } + + val simpleTextState = + rememberTextFieldState( + "Hi, this is a simple text :) Pro tip: while the button is focused, you can press J to change " + + "the balloon position and H to change the anchor" + ) + var richBodyState by remember { mutableStateOf(buildGotItBody {}) } + + val body by + remember(isRichBody, simpleTextState.text, richBodyState) { + derivedStateOf { + if (isRichBody) richBodyState else buildGotItBody { append(simpleTextState.textAsString) } + } + } + + LaunchedEffect(body) { currentGotItText(body) } + + Column(modifier = Modifier.padding(top = 8.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + RadioButtonRow(text = "Simple Text", selected = !isRichBody, onClick = { isRichBody = false }) + RadioButtonRow(text = "Rich Text", selected = isRichBody, onClick = { isRichBody = true }) + } + + Column(modifier = Modifier.padding(vertical = 4.dp)) { + if (!isRichBody) { + TextField( + modifier = Modifier.fillMaxWidth(), + state = simpleTextState, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } else { + RichBodyControls(richBodyChanged = { updatedBody -> richBodyState = updatedBody }) + } + } + } +} + +@Composable +private fun RichBodyControls(richBodyChanged: (GotItBody) -> Unit) { + val currentRichBodyChanged by rememberUpdatedState(richBodyChanged) + val richBodyText = rememberTextFieldState("This is an example of a rich body text.") + + var richBodyCodeChecked by remember { mutableStateOf(false) } + val richBodyCodeText = rememberTextFieldState("./gradlew is cool") + + var richBodyIconChecked by remember { mutableStateOf(false) } + var richBodyChosenIcon by remember { mutableStateOf(IconOption.entries.first()) } + + var richBodyBoldChecked by remember { mutableStateOf(false) } + val richBodyBoldText = rememberTextFieldState("This is pretty important because it's bold") + + var richBodyLinkChecked by remember { mutableStateOf(false) } + val richBodyLinkText = rememberTextFieldState("Click this link!") + val richBodyLinkPrint = rememberTextFieldState("You just clicked the link :)") + + var richBodyBrowserLinkChecked by remember { mutableStateOf(false) } + val richBodyExternalLinkText = rememberTextFieldState("This opens an external link") + val richBodyExternalLinkUri = rememberTextFieldState(JEWEL_README) + + val finalRichBodyText by + remember( + richBodyText, + richBodyCodeChecked, + richBodyIconChecked, + richBodyChosenIcon, + richBodyBoldChecked, + richBodyLinkChecked, + richBodyBrowserLinkChecked, + ) { + derivedStateOf { + buildGotItBody { + append(richBodyText.textAsString) + if (richBodyCodeChecked) { + append(" You can add small code snippets here, like: ") + code(richBodyCodeText.textAsString) + append(".") + } + if (richBodyIconChecked) { + append(" You can add any icon you want: ") + icon(richBodyChosenIcon.type) { + Icon( + key = richBodyChosenIcon.icon, + contentDescription = richBodyChosenIcon.contentDescription, + ) + } + append(".") + } + if (richBodyBoldChecked) { + append(" You can add bold text: ") + bold(richBodyBoldText.textAsString) + append(".") + } + if (richBodyLinkChecked) { + append(" You can add a link: ") + link(richBodyLinkText.textAsString) { println(richBodyLinkPrint.text) } + append(".") + } + if (richBodyBrowserLinkChecked) { + append(" You can add external links too: ") + browserLink(richBodyExternalLinkText.textAsString, richBodyExternalLinkUri.textAsString) + append(".") + } + } + } + } + + LaunchedEffect(finalRichBodyText) { currentRichBodyChanged(finalRichBodyText) } + + Column(modifier = Modifier.fillMaxWidth()) { + TextField( + modifier = Modifier.padding(bottom = 4.dp).fillMaxWidth(), + state = richBodyText, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + + Row(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow( + text = "Add code: ", + checked = richBodyCodeChecked, + onCheckedChange = { richBodyCodeChecked = it }, + ) + + AnimatedVisibility(richBodyCodeChecked) { + TextField( + modifier = Modifier.padding(start = 4.dp), + state = richBodyCodeText, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + + Row(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow( + text = "Add icon: ", + checked = richBodyIconChecked, + onCheckedChange = { richBodyIconChecked = it }, + ) + + AnimatedVisibility(richBodyIconChecked) { + Row( + modifier = Modifier.padding(start = 4.dp), + horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically, + ) { + IconOption.entries.forEach { option -> + RadioButtonRow( + text = option.type, + selected = option == richBodyChosenIcon, + onClick = { richBodyChosenIcon = option }, + ) + } + } + } + } + + Row(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow( + text = "Add bold text: ", + checked = richBodyBoldChecked, + onCheckedChange = { richBodyBoldChecked = it }, + ) + + AnimatedVisibility(richBodyBoldChecked) { + Row(verticalAlignment = Alignment.CenterVertically) { + TextField( + modifier = Modifier.padding(start = 4.dp), + state = richBodyBoldText, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + } + + Column(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow( + text = "Add link:", + checked = richBodyLinkChecked, + onCheckedChange = { richBodyLinkChecked = it }, + ) + + AnimatedVisibility(richBodyLinkChecked) { + Row(horizontalArrangement = Arrangement.SpaceAround, verticalAlignment = Alignment.CenterVertically) { + Text("Link text") + TextField( + modifier = Modifier.padding(vertical = 4.dp), + state = richBodyLinkText, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + + Text("Link click text: ") + TextField( + modifier = Modifier.padding(start = 4.dp), + state = richBodyLinkPrint, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + } + + Column(modifier = Modifier.padding(bottom = 4.dp)) { + CheckboxRow( + text = "Add external link", + checked = richBodyBrowserLinkChecked, + onCheckedChange = { richBodyBrowserLinkChecked = it }, + ) + + AnimatedVisibility(richBodyBrowserLinkChecked) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("External Link Text") + TextField( + modifier = Modifier.padding(vertical = 4.dp), + state = richBodyExternalLinkText, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + + Text("External Link URI: ") + TextField( + modifier = Modifier.padding(start = 4.dp), + state = richBodyExternalLinkUri, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + ) + } + } + } + } +} + +private enum class IconOption(val type: String, val icon: IntelliJIconKey, val contentDescription: String) { + INFORMATION("Balloon Information", AllIconsKeys.General.BalloonInformation, "Balloon Info"), + RESUME("Resume", AllIconsKeys.Actions.Resume, "Resume icon"), + ARROW_UP("Arrow Up", AllIconsKeys.General.ArrowUp, "Arrow up icon"), + KOTLIN("Kotlin", AllIconsKeys.Language.Kotlin, "Kotlin icon"), +} + +private fun intInputTransformation(min: Int = 0, max: Int = Int.MAX_VALUE) = + InputTransformation.byValue { current, proposed -> + val text = proposed.toString() + if (text.isEmpty()) return@byValue proposed + + val intValue = text.toIntOrNull() + if (intValue == null || intValue !in min..max) { + current + } else { + proposed + } + } + +private val TextFieldState.textAsString + get() = this.text.toString() diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/views/ComponentsViewModel.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/views/ComponentsViewModel.kt index d5d8fb7226138..dc59a98f96bcc 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/views/ComponentsViewModel.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/views/ComponentsViewModel.kt @@ -15,6 +15,7 @@ import org.jetbrains.jewel.samples.showcase.components.Buttons import org.jetbrains.jewel.samples.showcase.components.Checkboxes import org.jetbrains.jewel.samples.showcase.components.ChipsAndTrees import org.jetbrains.jewel.samples.showcase.components.ComboBoxes +import org.jetbrains.jewel.samples.showcase.components.GotItTooltipShowcase import org.jetbrains.jewel.samples.showcase.components.Icons import org.jetbrains.jewel.samples.showcase.components.Links import org.jetbrains.jewel.samples.showcase.components.Menus @@ -111,6 +112,7 @@ public class ComponentsViewModel( iconKey = ShowcaseIcons.Components.speedSearch, content = { SpeedSearches() }, ), + ViewInfo(title = "Got It", iconKey = ShowcaseIcons.Components.gotIt, content = { GotItTooltipShowcase() }), ) private var _currentView: ViewInfo by mutableStateOf(views.first()) diff --git a/platform/jewel/samples/showcase/src/main/resources/drawables/cool_jewel.png b/platform/jewel/samples/showcase/src/main/resources/drawables/cool_jewel.png new file mode 100644 index 0000000000000000000000000000000000000000..1cadaf083c2de8e436816050af199f17069cbaed GIT binary patch literal 117088 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 Ska4mA0000 + + + + + + diff --git a/platform/jewel/samples/showcase/src/main/resources/icons/components/balloon_dark.svg b/platform/jewel/samples/showcase/src/main/resources/icons/components/balloon_dark.svg new file mode 100644 index 0000000000000..b592f6909cae1 --- /dev/null +++ b/platform/jewel/samples/showcase/src/main/resources/icons/components/balloon_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/platform/jewel/samples/standalone/build.gradle.kts b/platform/jewel/samples/standalone/build.gradle.kts index 4b36f6f0222ef..e603a4b5a4b76 100644 --- a/platform/jewel/samples/standalone/build.gradle.kts +++ b/platform/jewel/samples/standalone/build.gradle.kts @@ -69,5 +69,6 @@ tasks { javaLauncher = project.javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(jdkLevel) } setExecutable(javaLauncher.map { it.executablePath.asFile.absolutePath }.get()) } + jvmArgs("-Dcompose.interop.blending=true") } } diff --git a/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItIconTooltipOrStepTest.kt b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItIconTooltipOrStepTest.kt new file mode 100644 index 0000000000000..3def56ca2b9af --- /dev/null +++ b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItIconTooltipOrStepTest.kt @@ -0,0 +1,60 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import org.junit.Assert.assertEquals +import org.junit.Test + +class GotItIconTooltipOrStepTest { + @Test(expected = IllegalArgumentException::class) + fun `Step with 0 throws`() { + GotItIconOrStep.Step(0) + } + + @Test(expected = IllegalArgumentException::class) + fun `Step with 100 throws`() { + GotItIconOrStep.Step(100) + } + + @Test(expected = IllegalArgumentException::class) + fun `Step with negative number throws`() { + GotItIconOrStep.Step(-1) + } + + @Test + fun `Step with 1 is valid`() { + val step = GotItIconOrStep.Step(1) + assertEquals(1, step.number) + } + + @Test + fun `Step with 99 is valid`() { + val step = GotItIconOrStep.Step(99) + assertEquals(99, step.number) + } + + @Test + fun `Step with 50 is valid`() { + val step = GotItIconOrStep.Step(50) + assertEquals(50, step.number) + } + + @Test + fun `Step 1 formats as 01`() { + assertEquals("01", GotItIconOrStep.Step(1).formattedText) + } + + @Test + fun `Step 9 formats as 09`() { + assertEquals("09", GotItIconOrStep.Step(9).formattedText) + } + + @Test + fun `Step 10 formats as 10`() { + assertEquals("10", GotItIconOrStep.Step(10).formattedText) + } + + @Test + fun `Step 99 formats as 99`() { + assertEquals("99", GotItIconOrStep.Step(99).formattedText) + } +} diff --git a/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonPositionProviderTest.kt b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonPositionProviderTest.kt new file mode 100644 index 0000000000000..a268b4691ea85 --- /dev/null +++ b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonPositionProviderTest.kt @@ -0,0 +1,139 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.ui.Alignment +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import org.jetbrains.jewel.foundation.InternalJewelApi +import org.junit.Assert.assertEquals +import org.junit.Test + +@OptIn(InternalJewelApi::class) +class GotItTooltipBalloonPositionProviderTest { + // anchorBounds: left=100, top=200, right=300, bottom=250 → width=200, height=50 + private val anchorBounds = IntRect(left = 100, top = 200, right = 300, bottom = 250) + private val arrowOffset = 24 + private val popupSize = IntSize(width = 280, height = 120) + + @Test + fun `BELOW BottomCenter positions popup at anchorY with arrowOffset applied to x`() { + // BottomCenter.align on 200×50 → (100, 50) → anchorX = 200, anchorY = 250 + val result = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.BELOW, + anchor = Alignment.BottomCenter, + arrowOffsetPx = arrowOffset, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Ltr, + popupContentSize = popupSize, + ) + assertEquals(IntOffset(x = 176, y = 250), result) + } + + @Test + fun `ABOVE BottomCenter positions popup above anchor`() { + // anchorX = 200, anchorY = 250 → y = 250 - 120 = 130 + val result = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.ABOVE, + anchor = Alignment.BottomCenter, + arrowOffsetPx = arrowOffset, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Ltr, + popupContentSize = popupSize, + ) + assertEquals(IntOffset(x = 176, y = 130), result) + } + + @Test + fun `START CenterStart positions popup to the left`() { + // CenterStart.align on 200×50 → (0, 25) → anchorX = 100, anchorY = 225 + // x = 100 - 280 = -180, y = 225 - 24 = 201 + val result = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.START, + anchor = Alignment.CenterStart, + arrowOffsetPx = arrowOffset, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Ltr, + popupContentSize = popupSize, + ) + assertEquals(IntOffset(x = -180, y = 201), result) + } + + @Test + fun `END CenterEnd positions popup to the right`() { + // CenterEnd.align on 200×50 → (200, 25) → anchorX = 300, anchorY = 225 + // x = 300, y = 225 - 24 = 201 + val result = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.END, + anchor = Alignment.CenterEnd, + arrowOffsetPx = arrowOffset, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Ltr, + popupContentSize = popupSize, + ) + assertEquals(IntOffset(x = 300, y = 201), result) + } + + @Test + fun `BELOW with TopCenter anchor uses top of anchor as Y`() { + // TopCenter.align on 200×50 → (100, 0) → anchorX = 200, anchorY = 200 + val result = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.BELOW, + anchor = Alignment.TopCenter, + arrowOffsetPx = arrowOffset, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Ltr, + popupContentSize = popupSize, + ) + assertEquals(IntOffset(x = 176, y = 200), result) + } + + @Test + fun `BELOW with CenterStart anchor and arrowOffset equal to anchorX from left gives x of zero`() { + // CenterStart.align on 200×50 → (0, 25) → anchorX = 100, anchorY = 225 + // arrowOffset = anchorX = 100 → x = 100 - 100 = 0 + val result = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.BELOW, + anchor = Alignment.CenterStart, + arrowOffsetPx = 100, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Ltr, + popupContentSize = popupSize, + ) + assertEquals(IntOffset(x = 0, y = 225), result) + } + + @Test + fun `RTL layout is handled by Alignment align`() { + // TopEnd in LTR → x=200 → anchorX=300; TopEnd in RTL → x=0 → anchorX=100 + val ltrResult = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.BELOW, + anchor = Alignment.TopEnd, + arrowOffsetPx = arrowOffset, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Ltr, + popupContentSize = popupSize, + ) + val rtlResult = + calculateBalloonPosition( + gotItBalloonPosition = GotItBalloonPosition.BELOW, + anchor = Alignment.TopEnd, + arrowOffsetPx = arrowOffset, + anchorBounds = anchorBounds, + layoutDirection = LayoutDirection.Rtl, + popupContentSize = popupSize, + ) + // LTR: anchorX = 100+200=300, y=200 → IntOffset(276, 200) + assertEquals(IntOffset(x = 276, y = 200), ltrResult) + // RTL: anchorX = 100+0=100, y=200 → IntOffset(76, 200) + assertEquals(IntOffset(x = 76, y = 200), rtlResult) + } +} diff --git a/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonShapeTest.kt b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonShapeTest.kt new file mode 100644 index 0000000000000..6f990f4b8c964 --- /dev/null +++ b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonShapeTest.kt @@ -0,0 +1,133 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.foundation.InternalJewelApi +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +@OptIn(InternalJewelApi::class) +class GotItTooltipBalloonShapeTest { + private val density = Density(1f) + private val size = Size(300f, 200f) + private val arrowWidth = 16.dp + private val arrowHeight = 8.dp + private val cornerRadius = 8.dp + private val arrowOffset = 24.dp + + private fun outline( + position: GotItBalloonPosition, + layoutDirection: LayoutDirection = LayoutDirection.Ltr, + offset: Dp = arrowOffset, + ): Outline = + createBalloonOutline( + size = size, + layoutDirection = layoutDirection, + density = density, + arrowWidth = arrowWidth, + arrowHeight = arrowHeight, + cornerRadius = cornerRadius, + arrowPosition = position, + arrowOffset = offset, + ) + + @Test + fun `outline is non-empty for all four positions`() { + for (position in GotItBalloonPosition.entries) { + val o = outline(position) + assertTrue("Outline for $position should be Outline.Generic", o is Outline.Generic) + val bounds = (o as Outline.Generic).path.getBounds() + assertFalse("Path bounds for $position should be non-empty", bounds.isEmpty) + } + } + + @Test + fun `BELOW path bounds span the full size`() { + val o = outline(GotItBalloonPosition.BELOW) + val bounds = (o as Outline.Generic).path.getBounds() + // The arrow tip protrudes to y=0; the rect bottom is at y=size.height + assertEquals(0f, bounds.top, 0.01f) + assertEquals(size.height, bounds.bottom, 0.01f) + assertEquals(0f, bounds.left, 0.01f) + assertEquals(size.width, bounds.right, 0.01f) + } + + @Test + fun `ABOVE path bounds span the full size`() { + val o = outline(GotItBalloonPosition.ABOVE) + val bounds = (o as Outline.Generic).path.getBounds() + // The rect top is at y=0; the arrow tip protrudes to y=size.height + assertEquals(0f, bounds.top, 0.01f) + assertEquals(size.height, bounds.bottom, 0.01f) + assertEquals(0f, bounds.left, 0.01f) + assertEquals(size.width, bounds.right, 0.01f) + } + + @Test + fun `START path bounds span the full size`() { + val o = outline(GotItBalloonPosition.START) + val bounds = (o as Outline.Generic).path.getBounds() + // Rect extends from (0, 0) to (width-arrowHeight, height); arrow tip at x=width + assertEquals(0f, bounds.top, 0.01f) + assertEquals(size.height, bounds.bottom, 0.01f) + assertEquals(0f, bounds.left, 0.01f) + assertEquals(size.width, bounds.right, 0.01f) + } + + @Test + fun `END path bounds span the full size`() { + val o = outline(GotItBalloonPosition.END) + val bounds = (o as Outline.Generic).path.getBounds() + // Rect starts at x=arrowHeight; arrow tip at x=0 + assertEquals(0f, bounds.top, 0.01f) + assertEquals(size.height, bounds.bottom, 0.01f) + assertEquals(0f, bounds.left, 0.01f) + assertEquals(size.width, bounds.right, 0.01f) + } + + @Test + fun `BELOW RTL produces a valid non-empty outline`() { + val ltr = outline(GotItBalloonPosition.BELOW, LayoutDirection.Ltr) + val rtl = outline(GotItBalloonPosition.BELOW, LayoutDirection.Rtl) + + assertTrue(ltr is Outline.Generic) + assertTrue(rtl is Outline.Generic) + assertFalse((ltr as Outline.Generic).path.getBounds().isEmpty) + assertFalse((rtl as Outline.Generic).path.getBounds().isEmpty) + } + + @Test + fun `arrow center is coerced away from corners for very small offset`() { + val o = outline(GotItBalloonPosition.BELOW, offset = 0.dp) + assertTrue(o is Outline.Generic) + assertFalse((o as Outline.Generic).path.getBounds().isEmpty) + } + + @Test + fun `arrow center is coerced away from corners for very large offset`() { + val o = outline(GotItBalloonPosition.BELOW, offset = 1000.dp) + assertTrue(o is Outline.Generic) + assertFalse((o as Outline.Generic).path.getBounds().isEmpty) + } + + @Test + fun `arrow coercion for vertical positions uses very small offset`() { + val o = outline(GotItBalloonPosition.END, offset = 0.dp) + assertTrue(o is Outline.Generic) + assertFalse((o as Outline.Generic).path.getBounds().isEmpty) + } + + @Test + fun `arrow coercion for vertical positions uses very large offset`() { + val o = outline(GotItBalloonPosition.START, offset = 1000.dp) + assertTrue(o is Outline.Generic) + assertFalse((o as Outline.Generic).path.getBounds().isEmpty) + } +} diff --git a/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBodyTest.kt b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBodyTest.kt new file mode 100644 index 0000000000000..ba911d8d4e802 --- /dev/null +++ b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBodyTest.kt @@ -0,0 +1,229 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.font.FontWeight +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +@OptIn(ExperimentalTextApi::class) +class GotItTooltipBodyTest { + private val linkColor = Color(0xFFFF0000) + private val codeColor = Color(0xFF00FF00) + private val codeBg = Color(0xFF0000FF) + + private val colors = + GotItColors( + foreground = Color.Black, + background = Color.White, + stepForeground = Color.Black, + secondaryActionForeground = Color.Black, + headerForeground = Color.Black, + balloonBorderColor = Color.Black, + imageBorderColor = Color.Black, + link = linkColor, + codeForeground = codeColor, + codeBackground = codeBg, + ) + + @Test + fun `empty body produces empty annotated string`() { + val body = buildGotItBody {} + val result = buildBodyAnnotatedString(body, colors) + assertEquals("", result.text) + } + + @Test + fun `plain segment produces its text`() { + val body = buildGotItBody { append("Hello") } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("Hello", result.text) + } + + @Test + fun `bold segment text is included`() { + val body = buildGotItBody { bold("World") } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("World", result.text) + } + + @Test + fun `code segment text is included`() { + val body = buildGotItBody { code("fn()") } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("fn()", result.text) + } + + @Test + fun `should not append code segment if the text is empty`() { + val body = buildGotItBody { + append("empty code: ") + code("") + } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("empty code: ", result.text) + } + + @Test + fun `inline link segment text is included`() { + val body = buildGotItBody { link("click") {} } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("click", result.text) + } + + @Test + fun `browser link segment text is included`() { + val body = buildGotItBody { browserLink("docs", "https://example.com") } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("docs ↗", result.text) + } + + @Test + fun `multiple segments concatenate in order`() { + val body = buildGotItBody { + append("A") + bold("B") + code("C") + } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("ABC", result.text) + } + + @Test + fun `plain segment has no span styles`() { + val body = buildGotItBody { append("Hello") } + val result = buildBodyAnnotatedString(body, colors) + assertTrue(result.spanStyles.isEmpty()) + } + + @Test + fun `bold segment has FontWeight Bold span covering the full text`() { + val body = buildGotItBody { bold("World") } + val result = buildBodyAnnotatedString(body, colors) + assertEquals(1, result.spanStyles.size) + assertEquals(FontWeight.Bold, result.spanStyles[0].item.fontWeight) + assertEquals(0, result.spanStyles[0].start) + assertEquals("World".length, result.spanStyles[0].end) + } + + @Test + fun `code segment produces an inline content annotation spanning its text`() { + val body = buildGotItBody { code("fn()") } + val result = buildBodyAnnotatedString(body, colors) + val annotations = result.getStringAnnotations(0, result.length) + assertEquals(1, annotations.size) + assertEquals(0, annotations[0].start) + assertEquals("fn()".length, annotations[0].end) + } + + @Test + fun `code segment has no span styles`() { + val body = buildGotItBody { code("fn()") } + val result = buildBodyAnnotatedString(body, colors) + assertTrue(result.spanStyles.isEmpty()) + } + + @Test + fun `bold and plain segments produce only one span scoped to the bold text`() { + val body = buildGotItBody { + append("plain") + bold("bold") + } + val result = buildBodyAnnotatedString(body, colors) + assertEquals(1, result.spanStyles.size) + assertEquals("plain".length, result.spanStyles[0].start) + assertEquals("plain".length + "bold".length, result.spanStyles[0].end) + } + + @Test + fun `inline link has a Clickable link annotation`() { + val body = buildGotItBody { link("click me") {} } + val result = buildBodyAnnotatedString(body, colors) + val links = result.getLinkAnnotations(0, result.length) + assertEquals(1, links.size) + assertTrue(links[0].item is LinkAnnotation.Clickable) + } + + @Test + fun `inline link Clickable tag equals segment text`() { + val body = buildGotItBody { link("click me") {} } + val result = buildBodyAnnotatedString(body, colors) + val links = result.getLinkAnnotations(0, result.length) + assertEquals("click me", (links[0].item as LinkAnnotation.Clickable).tag) + } + + @Test + fun `inline link annotation spans the link text exactly`() { + val body = buildGotItBody { link("click me") {} } + val result = buildBodyAnnotatedString(body, colors) + val links = result.getLinkAnnotations(0, result.length) + assertEquals(0, links[0].start) + assertEquals("click me".length, links[0].end) + } + + @Test + fun `browser link has a Url link annotation`() { + val body = buildGotItBody { browserLink("docs", "https://example.com") } + val result = buildBodyAnnotatedString(body, colors) + val links = result.getLinkAnnotations(0, result.length) + assertEquals(1, links.size) + assertTrue(links[0].item is LinkAnnotation.Url) + } + + @Test + fun `browser link Url matches the provided uri`() { + val body = buildGotItBody { browserLink("docs", "https://example.com") } + val result = buildBodyAnnotatedString(body, colors) + val links = result.getLinkAnnotations(0, result.length) + assertEquals("https://example.com", (links[0].item as LinkAnnotation.Url).url) + } + + @Test + fun `icon segment text uses contentDescription as alternate text`() { + val body = buildGotItBody { icon("my icon") {} } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("my icon", result.text) + } + + @Test + fun `icon segment with null contentDescription uses replacement char`() { + val body = buildGotItBody { icon(null) {} } + val result = buildBodyAnnotatedString(body, colors) + assertEquals("\uFFFD", result.text) + } + + @Test + fun `icon segment produces an inline content annotation spanning the alternate text`() { + val body = buildGotItBody { icon("star") {} } + val result = buildBodyAnnotatedString(body, colors) + val annotations = result.getStringAnnotations(0, result.length) + assertEquals(1, annotations.size) + assertEquals(0, annotations[0].start) + assertEquals("star".length, annotations[0].end) + } + + @Test + fun `icon segment has no span styles`() { + val body = buildGotItBody { icon("star") {} } + val result = buildBodyAnnotatedString(body, colors) + assertTrue(result.spanStyles.isEmpty()) + } + + @Test + fun `two icon segments at different positions get distinct annotation ids`() { + val body = buildGotItBody { + icon("first") {} + icon("second") {} + } + val result = buildBodyAnnotatedString(body, colors) + val annotations = result.getStringAnnotations(0, result.length) + assertEquals(2, annotations.size) + assertTrue( + "Expected distinct annotation ids but got: ${annotations.map { it.item }}", + annotations[0].item != annotations[1].item, + ) + } +} diff --git a/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipTest.kt b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipTest.kt new file mode 100644 index 0000000000000..ffe912461afd8 --- /dev/null +++ b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipTest.kt @@ -0,0 +1,677 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.getBoundsInRoot +import androidx.compose.ui.test.hasAnyAncestor +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.isPopup +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.requestFocus +import androidx.compose.ui.unit.dp +import kotlin.time.Duration.Companion.milliseconds +import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme +import org.jetbrains.jewel.ui.component.interactions.performKeyPress +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test + +@OptIn(ExperimentalTestApi::class) +class GotItTooltipTest { + @get:Rule val rule = createComposeRule() + + @Test + fun `should not show tooltip popup when visible is false`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "Body text", visible = false, onDismiss = {}) {} } } + + inPopup("Body text").assertDoesNotExist() + } + + @Test + fun `should show tooltip popup when visible is true`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "Body text", visible = true, onDismiss = {}) {} } } + + inPopup("Body text").assertIsDisplayed() + } + + @Test + fun `should hide tooltip when visible changes to false`() { + var visible by mutableStateOf(true) + rule.setContent { IntUiTheme { GotItTooltip(text = "Body text", visible = visible, onDismiss = {}) {} } } + + inPopup("Body text").assertIsDisplayed() + + visible = false + rule.waitForIdle() + + inPopup("Body text").assertDoesNotExist() + } + + @Test + fun `tooltip appears when visible changes from false to true`() { + var visible by mutableStateOf(false) + rule.setContent { IntUiTheme { GotItTooltip(text = "Body text", visible = visible, onDismiss = {}) {} } } + + inPopup("Body text").assertDoesNotExist() + + visible = true + rule.waitForIdle() + + inPopup("Body text").assertIsDisplayed() + } + + @Test + fun `should render text in popup for plain string overload`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "Hello world", visible = true, onDismiss = {}) {} } } + + inPopup("Hello world").assertIsDisplayed() + } + + @Test + fun `plain string overload renders correctly when body is empty string`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "", visible = true, onDismiss = {}) {} } } + // The popup exists but the body text node may be empty — just verify popup is shown + + rule.onNode(isPopup()).assertIsDisplayed() + } + + @Test + fun `should render plain segment text`() { + rule.setContent { + IntUiTheme { + GotItTooltip(body = buildGotItBody { append("Plain segment") }, visible = true, onDismiss = {}) {} + } + } + + inPopup("Plain segment").assertIsDisplayed() + } + + @Test + fun `body with bold segment text appears in popup`() { + rule.setContent { + IntUiTheme { GotItTooltip(body = buildGotItBody { bold("Bold text") }, visible = true, onDismiss = {}) {} } + } + + inPopup("Bold text").assertIsDisplayed() + } + + @Test + fun `body with code segment text appears in popup`() { + rule.setContent { + IntUiTheme { + GotItTooltip(body = buildGotItBody { code("someFunction()") }, visible = true, onDismiss = {}) {} + } + } + + inPopup("someFunction()").assertIsDisplayed() + } + + @Test + fun `body with inline link segment text appears in popup`() { + rule.setContent { + IntUiTheme { + GotItTooltip(body = buildGotItBody { link("click here") {} }, visible = true, onDismiss = {}) {} + } + } + + inPopup("click here").assertIsDisplayed() + } + + @Test + fun `should show browser link text segment in body when declared`() { + rule.setContent { + IntUiTheme { + GotItTooltip( + body = buildGotItBody { browserLink("open docs", "https://example.com") }, + visible = true, + onDismiss = {}, + ) {} + } + } + + inPopup("open docs ↗").assertIsDisplayed() + } + + @Test + fun `should render all texts when body has multiple mixed segments`() { + rule.setContent { + IntUiTheme { + GotItTooltip( + body = + buildGotItBody { + append("Press ") + bold("Resume") + append(" to continue") + }, + visible = true, + onDismiss = {}, + ) {} + } + } + + // The three segments are merged into a single AnnotatedString in one Text node + inPopup("Press Resume to continue").assertIsDisplayed() + } + + @Test + fun `should preserve segment order when body is built with chained calls`() { + rule.setContent { + IntUiTheme { + GotItTooltip( + body = + buildGotItBody { + append("A") + bold("B") + code("C") + }, + visible = true, + onDismiss = {}, + ) {} + } + } + + inPopup("ABC").assertIsDisplayed() + } + + @Test + fun `should show header text when non-empty`() { + rule.setContent { + IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = {}, header = "My Header") {} } + } + + inPopup("My Header").assertIsDisplayed() + } + + @Test + fun `should not render header when it is an empty string`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = {}, header = "") {} } } + + // Popup is shown but there's no header node + inPopup("Body").assertIsDisplayed() + rule.onNodeWithText("").assertDoesNotExist() + } + + @Test + fun `should show default Got it button if button not provided`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = {}) {} } } + + inPopup("Got it").assertIsDisplayed() + } + + @Test + fun `should show correct custom primary button label`() { + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = {}, + buttons = GotItButtons(primary = GotItButton("Understood")), + ) {} + } + } + + inPopup("Understood").assertIsDisplayed() + } + + @Test + fun `should display secondary button when provided`() { + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = {}, + buttons = GotItButtons(primary = GotItButton.Default, secondary = GotItButton("Learn more")), + ) {} + } + } + + inPopup("Learn more").assertIsDisplayed() + } + + @Test + fun `should not render primary button when button primary is null`() { + rule.setContent { + IntUiTheme { + GotItTooltip(text = "Body", visible = true, onDismiss = {}, buttons = GotItButtons(primary = null)) {} + } + } + + inPopup("Got it").assertDoesNotExist() + } + + @Test + fun `should call onDismiss when primary button is clicked`() { + var dismissed = false + rule.setContent { + IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = { dismissed = true }) {} } + } + + inPopup("Got it").performClick() + + rule.waitForIdle() + + assertTrue(dismissed) + } + + @Test + fun `clicking secondary button calls onDismiss`() { + var dismissed = false + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = { dismissed = true }, + buttons = GotItButtons(primary = GotItButton.Default, secondary = GotItButton("Skip")), + ) {} + } + } + + inPopup("Skip").performClick() + + rule.waitForIdle() + + assertTrue(dismissed) + } + + @Test + fun `primary button side effect runs before onDismiss`() { + val order = mutableListOf() + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = { order.add("dismiss") }, + buttons = GotItButtons(primary = GotItButton("OK") { order.add("side_effect") }), + ) {} + } + } + + inPopup("OK").performClick() + + rule.waitForIdle() + + assertEquals(listOf("side_effect", "dismiss"), order) + } + + @Test + fun `secondary button side effect runs before onDismiss`() { + val order = mutableListOf() + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = { order.add("dismiss") }, + buttons = + GotItButtons( + primary = GotItButton.Default, + secondary = GotItButton("Skip") { order.add("side_effect") }, + ), + ) {} + } + } + + inPopup("Skip").performClick() + + rule.waitForIdle() + + assertEquals(listOf("side_effect", "dismiss"), order) + } + + @Test + fun `regular link label is shown when link is provided`() { + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = {}, + link = GotItLink.Regular("Learn more") {}, + ) {} + } + } + + inPopup("Learn more").assertIsDisplayed() + } + + @Test + fun `browser link label is shown when link is provided`() { + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = {}, + link = GotItLink.Browser("Open docs", "https://example.com"), + ) {} + } + } + + inPopup("Open docs").assertIsDisplayed() + } + + @Test + fun `no link section when link is null`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = {}, link = null) {} } } + + // Popup is present but no extra link node + inPopup("Body").assertIsDisplayed() + rule.onNodeWithText("Learn more").assertDoesNotExist() + } + + @Test + fun `clicking regular link calls its action`() { + var clicked = false + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = {}, + link = GotItLink.Regular("Click me") { clicked = true }, + ) {} + } + } + + inPopup("Click me").performClick() + + rule.waitForIdle() + + assertTrue(clicked) + } + + @Test + fun `buttons are hidden when timeout is set`() { + rule.setContent { + IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = {}, timeout = 5000.milliseconds) {} } + } + + inPopup("Got it").assertDoesNotExist() + } + + @Test + fun `onDismiss is called after timeout elapses`() { + var dismissed = false + rule.mainClock.autoAdvance = false + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = { dismissed = true }, + timeout = 500.milliseconds, + ) {} + } + } + rule.waitForIdle() + + assertFalse(dismissed) + + rule.mainClock.advanceTimeBy(600) + rule.waitForIdle() + + assertTrue(dismissed) + } + + @Test + fun `onShown is called when tooltip becomes visible`() { + var shown = false + rule.setContent { + IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = {}, onShow = { shown = true }) {} } + } + rule.waitForIdle() + + assertTrue(shown) + } + + @Test + fun `onShown is not called when tooltip starts hidden`() { + var shown = false + rule.setContent { + IntUiTheme { GotItTooltip(text = "Body", visible = false, onDismiss = {}, onShow = { shown = true }) {} } + } + rule.waitForIdle() + + assertFalse(shown) + } + + @Test + fun `escape key triggers onEscapePressed and onDismiss when set`() { + var escapedPressed = false + var dismissed = false + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = { dismissed = true }, + onEscapePress = { escapedPressed = true }, + ) { + Box(Modifier.size(1.dp).semantics { testTag = "anchor" }.focusable()) + } + } + } + rule.waitForIdle() + rule.onNodeWithTag("anchor").requestFocus() + rule.waitForIdle() + rule.onNodeWithTag("anchor").performKeyPress(Key.Escape) + rule.waitForIdle() + + assertTrue(escapedPressed) + assertTrue(dismissed) + } + + @Test + fun `escape key calls onDismiss when no buttons are shown and onEscapePress is not set`() { + var dismissed = false + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = { dismissed = true }, + buttons = GotItButtons.None, + ) { + Box(Modifier.size(1.dp).semantics { testTag = "anchor" }.focusable()) + } + } + } + rule.waitForIdle() + rule.onNodeWithTag("anchor").requestFocus() + rule.waitForIdle() + rule.onNodeWithTag("anchor").performKeyPress(Key.Escape) + rule.waitForIdle() + + assertTrue(dismissed) + } + + @Test + fun `escape key calls onEscapePress and onDismiss when no buttons and onEscapePress is set`() { + var escapePressed = false + var dismissed = false + rule.setContent { + IntUiTheme { + GotItTooltip( + text = "Body", + visible = true, + onDismiss = { dismissed = true }, + buttons = GotItButtons.None, + onEscapePress = { escapePressed = true }, + ) { + Box(Modifier.size(1.dp).semantics { testTag = "anchor" }.focusable()) + } + } + } + rule.waitForIdle() + rule.onNodeWithTag("anchor").requestFocus() + rule.waitForIdle() + rule.onNodeWithTag("anchor").performKeyPress(Key.Escape) + rule.waitForIdle() + + assertTrue(escapePressed) + assertTrue(dismissed) + } + + @Test + fun `escape key does not dismiss when buttons are shown and onEscapePress is not set`() { + var dismissed = false + rule.setContent { + IntUiTheme { GotItTooltip(text = "Body", visible = true, onDismiss = { dismissed = true }) {} } + } + rule.waitForIdle() + rule.onNode(isPopup()).performKeyPress(Key.Escape) + rule.waitForIdle() + + assertFalse(dismissed) + } + + @Test + fun `step number is rendered inside the popup`() { + rule.setContent { + IntUiTheme { + GotItTooltip(text = "Body", visible = true, onDismiss = {}, iconOrStep = GotItIconOrStep.Step(3)) {} + } + } + + inPopup("03").assertIsDisplayed() + } + + @Test + fun `default width is 280dp for short content`() { + rule.setContent { IntUiTheme { GotItTooltip(text = "Short", visible = true, onDismiss = {}) {} } } + rule.waitForIdle() + // The body text node is constrained to currentWidth (280dp for short content). + val bounds = rule.onNode(hasText("Short").and(hasAnyAncestor(isPopup()))).getBoundsInRoot() + val nodeWidth = bounds.right - bounds.left + + assertTrue("Expected text width to be less than 328dp for short content, was $nodeWidth", nodeWidth < 328.dp) + } + + @Test + fun `width extends to 328dp when body text spans 5 or more lines`() { + // Long repeating text guarantees 5+ lines at 280dp and fills lines close to the extended 328dp max + val longText = "The quick brown fox jumps over the lazy dog. ".repeat(10) + rule.setContent { + IntUiTheme { GotItTooltip(body = buildGotItBody { append(longText) }, visible = true, onDismiss = {}) {} } + } + rule.waitForIdle() + val bounds = rule.onNode(hasText(longText).and(hasAnyAncestor(isPopup()))).getBoundsInRoot() + val nodeWidth = bounds.right - bounds.left + + // Extension was triggered (width exceeded 280dp) and stayed within the extended max of 328dp + assertTrue("Expected text wider than 280dp after extension, was $nodeWidth", nodeWidth > 280.dp) + assertTrue("Expected text within 328dp extended max, was $nodeWidth", nodeWidth <= 328.dp) + } + + @Test + fun `width stays at maxWidth when maxWidth is explicitly set`() { + // maxWidth disables auto-extension, so the content is capped at maxWidth regardless of line count + val longText = "The quick brown fox jumps over the lazy dog. ".repeat(10) + rule.setContent { + IntUiTheme { + GotItTooltip( + body = buildGotItBody { append(longText) }, + visible = true, + onDismiss = {}, + maxWidth = 300.dp, + ) {} + } + } + rule.waitForIdle() + val bounds = rule.onNode(hasText(longText).and(hasAnyAncestor(isPopup()))).getBoundsInRoot() + val nodeWidth = bounds.right - bounds.left + + assertTrue("Expected text to not exceed maxWidth=300dp, was $nodeWidth", nodeWidth <= 300.dp) + assertTrue("Expected text to fill close to maxWidth=300dp, was $nodeWidth", nodeWidth > 250.dp) + } + + @Test + fun `popup text width matches image width when image is present`() { + // Long text fills the image-width column (300dp), confirming image drives content width + val longText = "The quick brown fox jumps over the lazy dog. ".repeat(10) + rule.setContent { + IntUiTheme { + GotItTooltip( + body = buildGotItBody { append(longText) }, + visible = true, + onDismiss = {}, + image = GotItImage("drawables/test_gotit.png", null), + ) {} + } + } + rule.waitForIdle() + val bounds = rule.onNode(hasText(longText).and(hasAnyAncestor(isPopup()))).getBoundsInRoot() + val nodeWidth = bounds.right - bounds.left + + assertTrue("Expected text to fill close to image width (300dp), was $nodeWidth", nodeWidth > 250.dp) + assertTrue("Expected text to not exceed image width (300dp), was $nodeWidth", nodeWidth <= 300.dp) + } + + @Test + fun `width does not extend beyond image width even with 5 or more lines`() { + // Long text produces 5+ lines but extension is blocked — image width (300dp) wins + val longText = "The quick brown fox jumps over the lazy dog. ".repeat(10) + rule.setContent { + IntUiTheme { + GotItTooltip( + body = buildGotItBody { append(longText) }, + visible = true, + onDismiss = {}, + image = GotItImage("drawables/test_gotit.png", null), + ) {} + } + } + rule.waitForIdle() + val bounds = rule.onNode(hasText(longText).and(hasAnyAncestor(isPopup()))).getBoundsInRoot() + val nodeWidth = bounds.right - bounds.left + + assertTrue("Expected no extension beyond image width (300dp), was $nodeWidth", nodeWidth <= 300.dp) + assertTrue("Expected text to fill close to image width (300dp), was $nodeWidth", nodeWidth > 250.dp) + } + + @Test + fun `image width overrides maxWidth for content width`() { + // Image is 300dp wide; maxWidth=50dp is ignored because image always takes priority + val longText = "The quick brown fox jumps over the lazy dog. ".repeat(10) + rule.setContent { + IntUiTheme { + GotItTooltip( + body = buildGotItBody { append(longText) }, + visible = true, + onDismiss = {}, + image = GotItImage("drawables/test_gotit.png", null), + maxWidth = 50.dp, + ) {} + } + } + rule.waitForIdle() + val bounds = rule.onNode(hasText(longText).and(hasAnyAncestor(isPopup()))).getBoundsInRoot() + val nodeWidth = bounds.right - bounds.left + + // Text width should be close to image width (300dp), well above the ignored maxWidth (50dp) + assertTrue("Expected text wider than ignored maxWidth=50dp, was $nodeWidth", nodeWidth > 50.dp) + assertTrue("Expected text within image width (300dp), was $nodeWidth", nodeWidth <= 300.dp) + } + + private fun inPopup(text: String) = rule.onNode(hasText(text).and(hasAnyAncestor(isPopup()))) +} diff --git a/platform/jewel/ui-tests/src/test/resources/drawables/test_gotit.png b/platform/jewel/ui-tests/src/test/resources/drawables/test_gotit.png new file mode 100644 index 0000000000000000000000000000000000000000..f5bfeef1733a097e792ab585128cf1803cf099ba GIT binary patch literal 945 zcmV;i15W&jP)Ca~2nY`k52K@_`}_NUe}AB$pbicWTU%RPTwH{N zgjQBof`Wp4e0)+;Qk9jJRaI4(n3#@^j+{fK$p8QX=Sf6CRCt{2-0M=?Fcbt}+p$xV zq$!sK0<@*?|CBXx0diMeNejLeX88H+E!!QiPFbu;m48t(YZKSSgh>b&= zU4b$7Re6_LK{1+LRh8wsq2bT*q54G1_MV0}yT=~ctZ8_1?9OhFbSF5|eV@aqRU^~c zY91cHknW=ahb;j$uoBtV_yxC44W6mI8-`PrfzjG#@9R)B=Jm8jAmDv zkff~+#W;636Oypip%~YFZa|W4UKFEN&#s1~+PpBvzH%W+zK$@)p>iV$HZP3vM>j@D z`4*|b7^8U@kn%lBnZ@`gG2+hF=$@OAsQZp$G&_S~LLyoniZSePP9(V1p%_|+b0dMR z4#m(roEr&ibufnMo9IFUzK$@4VV`@D$(t9(u+GDQOq>adVcxY~WYXqEF)X{*k4)IS zC`SJV8HC7REkTT<>+E_E>E@louyzKSAv#;O4ktuJFQc;Uc>P>iB({Xb&m&C8D^7@cw#kNjMS(JAX#WV;xnTeNV z4Rt2D=7D1JEo$oHuWlU5jXZwz~8k4%O%s8VtiQ48t%C!!QiPFlqB2URG3I TA!QGs00000NkvXXu0mjf)vd9b literal 0 HcmV?d00001 diff --git a/platform/jewel/ui/api-dump.txt b/platform/jewel/ui/api-dump.txt index 6c4902537cdf7..76037c10aedd7 100644 --- a/platform/jewel/ui/api-dump.txt +++ b/platform/jewel/ui/api-dump.txt @@ -15,7 +15,8 @@ f:org.jetbrains.jewel.ui.DefaultComponentStyling - b:(org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle):V - b:(org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle):V - b:(org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle):V -- (org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):V +- b:(org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):V +- (org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SpeedSearchStyle,org.jetbrains.jewel.ui.component.styling.SearchMatchStyle,org.jetbrains.jewel.ui.component.styling.PopupAdStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle):V - b:(org.jetbrains.jewel.ui.component.styling.CheckboxStyle,org.jetbrains.jewel.ui.component.styling.ChipStyle,org.jetbrains.jewel.ui.component.styling.CircularProgressStyle,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyles,org.jetbrains.jewel.ui.component.styling.ComboBoxStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.DividerStyle,org.jetbrains.jewel.ui.component.styling.TabStyle,org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle,org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle,org.jetbrains.jewel.ui.component.styling.IconButtonStyle,org.jetbrains.jewel.ui.component.styling.InlineBannerStyles,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,org.jetbrains.jewel.ui.component.styling.LinkStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,org.jetbrains.jewel.ui.component.styling.ButtonStyle,org.jetbrains.jewel.ui.component.styling.PopupContainerStyle,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,org.jetbrains.jewel.ui.component.styling.RadioButtonStyle,org.jetbrains.jewel.ui.component.styling.ScrollbarStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle,org.jetbrains.jewel.ui.component.styling.SegmentedControlStyle,org.jetbrains.jewel.ui.component.styling.SelectableLazyColumnStyle,org.jetbrains.jewel.ui.component.styling.SimpleListItemStyle,org.jetbrains.jewel.ui.component.styling.SliderStyle,org.jetbrains.jewel.ui.component.styling.TextAreaStyle,org.jetbrains.jewel.ui.component.styling.TextFieldStyle,org.jetbrains.jewel.ui.component.styling.TooltipStyle,org.jetbrains.jewel.ui.component.styling.DropdownStyle):V - equals(java.lang.Object):Z - f:getCheckboxStyle():org.jetbrains.jewel.ui.component.styling.CheckboxStyle @@ -30,6 +31,8 @@ f:org.jetbrains.jewel.ui.DefaultComponentStyling - f:getDefaultTabStyle():org.jetbrains.jewel.ui.component.styling.TabStyle - f:getDividerStyle():org.jetbrains.jewel.ui.component.styling.DividerStyle - f:getEditorTabStyle():org.jetbrains.jewel.ui.component.styling.TabStyle +- f:getGotItButtonStyle():org.jetbrains.jewel.ui.component.styling.ButtonStyle +- f:getGotItTooltipStyle():org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle - f:getGroupHeaderStyle():org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle - f:getHorizontalProgressBarStyle():org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle - f:getIconButtonStyle():org.jetbrains.jewel.ui.component.styling.IconButtonStyle @@ -977,6 +980,145 @@ org.jetbrains.jewel.ui.component.banner.BannerIconActionScope - bs:iconAction$default(org.jetbrains.jewel.ui.component.banner.BannerIconActionScope,org.jetbrains.jewel.ui.icon.IconKey,java.lang.String,java.lang.String,kotlin.jvm.functions.Function0,I,java.lang.Object):V org.jetbrains.jewel.ui.component.banner.BannerLinkActionScope - a:action(java.lang.String,kotlin.jvm.functions.Function0):V +e:org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition +- java.lang.Enum +- sf:ABOVE:org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition +- sf:BELOW:org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition +- sf:END:org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition +- sf:START:org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition +- s:getEntries():kotlin.enums.EnumEntries +- s:valueOf(java.lang.String):org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition +- s:values():org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition[] +f:org.jetbrains.jewel.ui.component.gotit.GotItBody +- sf:$stable:I +f:org.jetbrains.jewel.ui.component.gotit.GotItBodyBuilder +- sf:$stable:I +- ():V +- f:append(java.lang.String):org.jetbrains.jewel.ui.component.gotit.GotItBodyBuilder +- f:bold(java.lang.String):org.jetbrains.jewel.ui.component.gotit.GotItBodyBuilder +- f:browserLink(java.lang.String,java.lang.String):org.jetbrains.jewel.ui.component.gotit.GotItBodyBuilder +- f:build():org.jetbrains.jewel.ui.component.gotit.GotItBody +- f:code(java.lang.String):org.jetbrains.jewel.ui.component.gotit.GotItBodyBuilder +- f:icon(java.lang.String,kotlin.jvm.functions.Function2):org.jetbrains.jewel.ui.component.gotit.GotItBodyBuilder +- f:link(java.lang.String,kotlin.jvm.functions.Function0):org.jetbrains.jewel.ui.component.gotit.GotItBodyBuilder +f:org.jetbrains.jewel.ui.component.gotit.GotItButton +- sf:$stable:I +- sf:Companion:org.jetbrains.jewel.ui.component.gotit.GotItButton$Companion +- (java.lang.String,kotlin.jvm.functions.Function0):V +- b:(java.lang.String,kotlin.jvm.functions.Function0,I,kotlin.jvm.internal.DefaultConstructorMarker):V +- equals(java.lang.Object):Z +- f:getAction():kotlin.jvm.functions.Function0 +- f:getLabel():java.lang.String +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItButton$Companion +- f:getDefault():org.jetbrains.jewel.ui.component.gotit.GotItButton +f:org.jetbrains.jewel.ui.component.gotit.GotItButtons +- sf:$stable:I +- sf:Companion:org.jetbrains.jewel.ui.component.gotit.GotItButtons$Companion +- (org.jetbrains.jewel.ui.component.gotit.GotItButton,org.jetbrains.jewel.ui.component.gotit.GotItButton):V +- b:(org.jetbrains.jewel.ui.component.gotit.GotItButton,org.jetbrains.jewel.ui.component.gotit.GotItButton,I,kotlin.jvm.internal.DefaultConstructorMarker):V +- equals(java.lang.Object):Z +- f:getPrimary():org.jetbrains.jewel.ui.component.gotit.GotItButton +- f:getSecondary():org.jetbrains.jewel.ui.component.gotit.GotItButton +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItButtons$Companion +- f:getDefault():org.jetbrains.jewel.ui.component.gotit.GotItButtons +- f:getNone():org.jetbrains.jewel.ui.component.gotit.GotItButtons +f:org.jetbrains.jewel.ui.component.gotit.GotItColors +- sf:$stable:I +- sf:Companion:org.jetbrains.jewel.ui.component.gotit.GotItColors$Companion +- equals(java.lang.Object):Z +- f:getBackground-0d7_KjU():J +- f:getBalloonBorderColor-0d7_KjU():J +- f:getCodeBackground-0d7_KjU():J +- f:getCodeForeground-0d7_KjU():J +- f:getForeground-0d7_KjU():J +- f:getHeaderForeground-0d7_KjU():J +- f:getImageBorderColor-0d7_KjU():J +- f:getLink-0d7_KjU():J +- f:getSecondaryActionForeground-0d7_KjU():J +- f:getStepForeground-0d7_KjU():J +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItColors$Companion +org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep +f:org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep$Icon +- org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep +- sf:$stable:I +- (kotlin.jvm.functions.Function2):V +- equals(java.lang.Object):Z +- f:getContent():kotlin.jvm.functions.Function2 +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep$Step +- org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep +- sf:$stable:I +- (I):V +- equals(java.lang.Object):Z +- f:getFormattedText():java.lang.String +- f:getNumber():I +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItImage +- sf:$stable:I +- (java.lang.String,java.lang.String,Z):V +- b:(java.lang.String,java.lang.String,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V +- equals(java.lang.Object):Z +- f:getContentDescription():java.lang.String +- f:getPath():java.lang.String +- f:getShowBorder():Z +- hashCode():I +org.jetbrains.jewel.ui.component.gotit.GotItLink +- a:getAction():kotlin.jvm.functions.Function0 +- a:getLabel():java.lang.String +f:org.jetbrains.jewel.ui.component.gotit.GotItLink$Browser +- org.jetbrains.jewel.ui.component.gotit.GotItLink +- sf:$stable:I +- (java.lang.String,java.lang.String,kotlin.jvm.functions.Function0):V +- b:(java.lang.String,java.lang.String,kotlin.jvm.functions.Function0,I,kotlin.jvm.internal.DefaultConstructorMarker):V +- equals(java.lang.Object):Z +- getAction():kotlin.jvm.functions.Function0 +- getLabel():java.lang.String +- f:getUri():java.lang.String +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItLink$Regular +- org.jetbrains.jewel.ui.component.gotit.GotItLink +- sf:$stable:I +- (java.lang.String,kotlin.jvm.functions.Function0):V +- equals(java.lang.Object):Z +- getAction():kotlin.jvm.functions.Function0 +- getLabel():java.lang.String +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItMetrics +- sf:$stable:I +- sf:Companion:org.jetbrains.jewel.ui.component.gotit.GotItMetrics$Companion +- equals(java.lang.Object):Z +- f:getButtonPadding():androidx.compose.foundation.layout.PaddingValues +- f:getContentPadding():androidx.compose.foundation.layout.PaddingValues +- f:getCornerRadius-D9Ej5fM():F +- f:getIconPadding-D9Ej5fM():F +- f:getImagePadding():androidx.compose.foundation.layout.PaddingValues +- f:getTextPadding-D9Ej5fM():F +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItMetrics$Companion +f:org.jetbrains.jewel.ui.component.gotit.GotItTooltipBodyKt +- sf:buildBodyAnnotatedString(org.jetbrains.jewel.ui.component.gotit.GotItBody,org.jetbrains.jewel.ui.component.gotit.GotItColors):androidx.compose.ui.text.AnnotatedString +- sf:buildGotItBody(kotlin.jvm.functions.Function1):org.jetbrains.jewel.ui.component.gotit.GotItBody +- sf:buildInlineContent(org.jetbrains.jewel.ui.component.gotit.GotItBody,org.jetbrains.jewel.ui.component.gotit.GotItColors,androidx.compose.ui.text.TextStyle,org.jetbrains.jewel.ui.icon.IconKey):java.util.Map +- bs:buildInlineContent$default(org.jetbrains.jewel.ui.component.gotit.GotItBody,org.jetbrains.jewel.ui.component.gotit.GotItColors,androidx.compose.ui.text.TextStyle,org.jetbrains.jewel.ui.icon.IconKey,I,java.lang.Object):java.util.Map +f:org.jetbrains.jewel.ui.component.gotit.GotItTooltipKt +- sf:GotItTooltip-sf_bbGk(java.lang.String,Z,kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,java.lang.String,org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep,org.jetbrains.jewel.ui.component.gotit.GotItButtons,org.jetbrains.jewel.ui.component.gotit.GotItLink,org.jetbrains.jewel.ui.component.gotit.GotItImage,androidx.compose.ui.unit.Dp,J,org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition,androidx.compose.ui.Alignment,F,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function0,org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I,I):V +- sf:GotItTooltip-sf_bbGk(org.jetbrains.jewel.ui.component.gotit.GotItBody,Z,kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,java.lang.String,org.jetbrains.jewel.ui.component.gotit.GotItIconOrStep,org.jetbrains.jewel.ui.component.gotit.GotItButtons,org.jetbrains.jewel.ui.component.gotit.GotItLink,org.jetbrains.jewel.ui.component.gotit.GotItImage,androidx.compose.ui.unit.Dp,J,org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition,androidx.compose.ui.Alignment,F,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function0,org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I,I):V +- sf:rememberGotItTooltipBalloonPopupPositionProvider-UuyPYSY(org.jetbrains.jewel.ui.component.gotit.GotItBalloonPosition,androidx.compose.ui.Alignment,F,F,androidx.compose.runtime.Composer,I,I):androidx.compose.ui.window.PopupPositionProvider +f:org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +- sf:$stable:I +- sf:Companion:org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle$Companion +- (org.jetbrains.jewel.ui.component.gotit.GotItColors,org.jetbrains.jewel.ui.component.gotit.GotItMetrics):V +- equals(java.lang.Object):Z +- f:getColors():org.jetbrains.jewel.ui.component.gotit.GotItColors +- f:getMetrics():org.jetbrains.jewel.ui.component.gotit.GotItMetrics +- hashCode():I +f:org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle$Companion +f:org.jetbrains.jewel.ui.component.gotit.GotItTooltipStylingKt +- sf:getLocalGotItButtonStyle():androidx.compose.runtime.ProvidableCompositionLocal +- sf:getLocalGotItTooltipStyle():androidx.compose.runtime.ProvidableCompositionLocal f:org.jetbrains.jewel.ui.component.search.HighlightKt f:org.jetbrains.jewel.ui.component.search.SpeedSearchableComboBoxKt f:org.jetbrains.jewel.ui.component.search.SpeedSearchableLazyColumnKt @@ -4010,6 +4152,8 @@ f:org.jetbrains.jewel.ui.theme.JewelThemeKt - sf:getDividerStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.DividerStyle - sf:getDropdownStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.DropdownStyle - sf:getEditorTabStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.TabStyle +- sf:getGotItButtonStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.ButtonStyle +- sf:getGotItTooltipStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle - sf:getGroupHeaderStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.GroupHeaderStyle - sf:getHorizontalProgressBarStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.HorizontalProgressBarStyle - sf:getIconButtonStyle(org.jetbrains.jewel.foundation.theme.JewelTheme$Companion,androidx.compose.runtime.Composer,I):org.jetbrains.jewel.ui.component.styling.IconButtonStyle diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/DefaultComponentStyling.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/DefaultComponentStyling.kt index 86c231f277b19..543fc90539ccd 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/DefaultComponentStyling.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/DefaultComponentStyling.kt @@ -8,6 +8,11 @@ import androidx.compose.runtime.Stable import org.jetbrains.jewel.foundation.GenerateDataFunctions import org.jetbrains.jewel.ui.component.ContextMenuRepresentation import org.jetbrains.jewel.ui.component.TextContextMenu +import org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +import org.jetbrains.jewel.ui.component.gotit.LocalGotItButtonStyle +import org.jetbrains.jewel.ui.component.gotit.LocalGotItTooltipStyle +import org.jetbrains.jewel.ui.component.gotit.fallbackGotItTooltipButtonStyle +import org.jetbrains.jewel.ui.component.gotit.fallbackGotItTooltipStyle import org.jetbrains.jewel.ui.component.styling.ButtonStyle import org.jetbrains.jewel.ui.component.styling.CheckboxStyle import org.jetbrains.jewel.ui.component.styling.ChipStyle @@ -83,6 +88,7 @@ import org.jetbrains.jewel.ui.component.styling.fallbackPopupAdStyle import org.jetbrains.jewel.ui.component.styling.fallbackSearchMatchStyle import org.jetbrains.jewel.ui.component.styling.fallbackSpeedSearchStyle +@Suppress("LargeClass") @Stable @GenerateDataFunctions public class DefaultComponentStyling( @@ -124,7 +130,95 @@ public class DefaultComponentStyling( public val popupAdStyle: PopupAdStyle, public val defaultSlimButtonStyle: ButtonStyle, public val outlinedSlimButtonStyle: ButtonStyle, + public val gotItTooltipStyle: GotItTooltipStyle, + public val gotItButtonStyle: ButtonStyle, ) : ComponentStyling { + @Deprecated( + message = "Use the constructor with `gotItButtonStyle` and `gotItButtonStyle`.", + level = DeprecationLevel.HIDDEN, + ) + public constructor( + checkboxStyle: CheckboxStyle, + chipStyle: ChipStyle, + circularProgressStyle: CircularProgressStyle, + defaultBannerStyle: DefaultBannerStyles, + comboBoxStyle: ComboBoxStyle, + defaultButtonStyle: ButtonStyle, + defaultDropdownStyle: DropdownStyle, + defaultSplitButtonStyle: SplitButtonStyle, + defaultTabStyle: TabStyle, + dividerStyle: DividerStyle, + editorTabStyle: TabStyle, + groupHeaderStyle: GroupHeaderStyle, + horizontalProgressBarStyle: HorizontalProgressBarStyle, + iconButtonStyle: IconButtonStyle, + transparentIconButtonStyle: IconButtonStyle, + inlineBannerStyle: InlineBannerStyles, + lazyTreeStyle: LazyTreeStyle, + linkStyle: LinkStyle, + menuStyle: MenuStyle, + outlinedButtonStyle: ButtonStyle, + popupContainerStyle: PopupContainerStyle, + outlinedSplitButtonStyle: SplitButtonStyle, + radioButtonStyle: RadioButtonStyle, + scrollbarStyle: ScrollbarStyle, + segmentedControlButtonStyle: SegmentedControlButtonStyle, + segmentedControlStyle: SegmentedControlStyle, + selectableLazyColumnStyle: SelectableLazyColumnStyle, + simpleListItemStyle: SimpleListItemStyle, + sliderStyle: SliderStyle, + textAreaStyle: TextAreaStyle, + textFieldStyle: TextFieldStyle, + tooltipStyle: TooltipStyle, + undecoratedDropdownStyle: DropdownStyle, + speedSearchStyle: SpeedSearchStyle, + searchMatchStyle: SearchMatchStyle, + popupAdStyle: PopupAdStyle, + defaultSlimButtonStyle: ButtonStyle, + outlinedSlimButtonStyle: ButtonStyle, + ) : this( + checkboxStyle, + chipStyle, + circularProgressStyle, + defaultBannerStyle, + comboBoxStyle, + defaultButtonStyle, + defaultDropdownStyle, + defaultSplitButtonStyle, + defaultTabStyle, + dividerStyle, + editorTabStyle, + groupHeaderStyle, + horizontalProgressBarStyle, + iconButtonStyle, + transparentIconButtonStyle, + inlineBannerStyle, + lazyTreeStyle, + linkStyle, + menuStyle, + outlinedButtonStyle, + popupContainerStyle, + outlinedSplitButtonStyle, + radioButtonStyle, + scrollbarStyle, + segmentedControlButtonStyle, + segmentedControlStyle, + selectableLazyColumnStyle, + simpleListItemStyle, + sliderStyle, + textAreaStyle, + textFieldStyle, + tooltipStyle, + undecoratedDropdownStyle, + speedSearchStyle, + searchMatchStyle, + popupAdStyle, + defaultSlimButtonStyle, + outlinedSlimButtonStyle, + fallbackGotItTooltipStyle(), + fallbackGotItTooltipButtonStyle(), + ) + @Deprecated( message = "Use the variant with defaultSlimButtonStyle and outlinedSlimButtonStyle.", level = DeprecationLevel.HIDDEN, @@ -205,9 +299,11 @@ public class DefaultComponentStyling( popupAdStyle, fallbackDefaultSlimButtonStyle(defaultButtonStyle.colors), fallbackOutlinedSlimButtonStyle(outlinedButtonStyle.colors), + fallbackGotItTooltipStyle(), + fallbackGotItTooltipButtonStyle(), ) - @Deprecated("Use the variant with popupAdStyle.", level = DeprecationLevel.HIDDEN) + @Deprecated("Use the variant with popupAdStyle and gotItTooltipStyle.", level = DeprecationLevel.HIDDEN) public constructor( checkboxStyle: CheckboxStyle, chipStyle: ChipStyle, @@ -283,6 +379,8 @@ public class DefaultComponentStyling( fallbackPopupAdStyle(), fallbackDefaultSlimButtonStyle(defaultButtonStyle.colors), fallbackOutlinedSlimButtonStyle(outlinedButtonStyle.colors), + fallbackGotItTooltipStyle(), + fallbackGotItTooltipButtonStyle(), ) @Deprecated("Use the variant with speedSearchStyle.", level = DeprecationLevel.HIDDEN) @@ -359,6 +457,8 @@ public class DefaultComponentStyling( fallbackPopupAdStyle(), fallbackDefaultSlimButtonStyle(defaultButtonStyle.colors), fallbackOutlinedSlimButtonStyle(outlinedButtonStyle.colors), + fallbackGotItTooltipStyle(), + fallbackGotItTooltipButtonStyle(), ) @Deprecated("Use the variant with transparentIconButtonStyle.", level = DeprecationLevel.HIDDEN) @@ -434,6 +534,8 @@ public class DefaultComponentStyling( fallbackPopupAdStyle(), fallbackDefaultSlimButtonStyle(defaultButtonStyle.colors), fallbackOutlinedSlimButtonStyle(outlinedButtonStyle.colors), + fallbackGotItTooltipStyle(), + fallbackGotItTooltipButtonStyle(), ) @Composable @@ -479,6 +581,8 @@ public class DefaultComponentStyling( LocalPopupAdStyle provides popupAdStyle, LocalDefaultSlimButtonStyle provides defaultSlimButtonStyle, LocalOutlinedSlimButtonStyle provides outlinedSlimButtonStyle, + LocalGotItTooltipStyle provides gotItTooltipStyle, + LocalGotItButtonStyle provides gotItButtonStyle, ) override fun equals(other: Any?): Boolean { @@ -525,6 +629,8 @@ public class DefaultComponentStyling( if (popupAdStyle != other.popupAdStyle) return false if (defaultSlimButtonStyle != other.defaultSlimButtonStyle) return false if (outlinedSlimButtonStyle != other.outlinedSlimButtonStyle) return false + if (gotItTooltipStyle != other.gotItTooltipStyle) return false + if (gotItButtonStyle != other.gotItButtonStyle) return false return true } @@ -568,6 +674,8 @@ public class DefaultComponentStyling( result = 31 * result + popupAdStyle.hashCode() result = 31 * result + defaultSlimButtonStyle.hashCode() result = 31 * result + outlinedSlimButtonStyle.hashCode() + result = 31 * result + gotItTooltipStyle.hashCode() + result = 31 * result + gotItButtonStyle.hashCode() return result } @@ -607,9 +715,11 @@ public class DefaultComponentStyling( "tooltipStyle=$tooltipStyle, " + "undecoratedDropdownStyle=$undecoratedDropdownStyle, " + "speedSearchStyle=$speedSearchStyle, " + - "searchMatchStyle=$searchMatchStyle," + + "searchMatchStyle=$searchMatchStyle, " + "popupAdStyle=$popupAdStyle, " + "defaultSlimButtonStyle=$defaultSlimButtonStyle, " + - "outlinedSlimButtonStyle=$outlinedSlimButtonStyle" + + "outlinedSlimButtonStyle=$outlinedSlimButtonStyle, " + + "gotItTooltipStyle=$gotItTooltipStyle, " + + "gotItButtonStyle=$gotItButtonStyle" + ")" } diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltip.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltip.kt new file mode 100644 index 0000000000000..63ebed9d17180 --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltip.kt @@ -0,0 +1,971 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.isSpecified +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.PopupPositionProvider +import androidx.compose.ui.window.PopupProperties +import kotlin.math.roundToInt +import kotlin.time.Duration +import kotlinx.coroutines.delay +import org.jetbrains.annotations.Nls +import org.jetbrains.jewel.foundation.GenerateDataFunctions +import org.jetbrains.jewel.foundation.InternalJewelApi +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.foundation.theme.LocalContentColor +import org.jetbrains.jewel.foundation.theme.LocalTextStyle +import org.jetbrains.jewel.foundation.theme.OverrideDarkMode +import org.jetbrains.jewel.ui.component.DefaultButton +import org.jetbrains.jewel.ui.component.ExternalLink +import org.jetbrains.jewel.ui.component.Link +import org.jetbrains.jewel.ui.component.Popup +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.gotit.GotItButtons.Companion.hasNoButtons +import org.jetbrains.jewel.ui.component.styling.LinkColors +import org.jetbrains.jewel.ui.component.styling.LinkStyle +import org.jetbrains.jewel.ui.component.styling.LocalLinkStyle +import org.jetbrains.jewel.ui.painter.ResourcePainterProvider +import org.jetbrains.jewel.ui.popupShadowAndBorder +import org.jetbrains.jewel.ui.util.isDark + +private val DEFAULT_TOOLTIP_WIDTH = 280.dp +private val DEFAULT_TOOLTIP_EXTENDED_WIDTH = 328.dp + +/** + * A Got It tooltip component that provides contextual information about new or changed features. + * + * Got It tooltips are specialized popups (called "Balloons" in the Swing implementation) that: + * - Appear near UI elements to guide users + * - Can display icons or step numbers + * - Support rich content (headers, links, images, buttons) + * - Auto-hide after a timeout (optional, mutually exclusive with buttons) + * - Display an arrow pointing to the target element + * - Can limit how many times they appear (showCount) + * + * **Important:** When a timeout is set, buttons are automatically hidden. The tooltip will auto-dismiss after the + * timeout duration. This matches the Swing implementation behavior. + * + * **Dismissal:** This component never removes itself from the UI. When a button is clicked, or when the timeout + * elapses, [onDismiss] is called — but the tooltip stays visible until the caller reacts by setting [visible] to + * `false`. Always wire [onDismiss] to update your state, otherwise the tooltip cannot be closed. + * + * **Guidelines:** [on IJP SDK webhelp](https://plugins.jetbrains.com/docs/intellij/got-it-tooltip.html) + * + * **Swing equivalent:** + * [`GotItTooltip`](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-impl/src/com/intellij/ui/GotItTooltip.kt) + * + * **Usage example:** + * + * ```kotlin + * var showTooltip by remember { mutableStateOf(false) } + * + * GotItTooltip( + * text = "This is a new feature!", + * visible = showTooltip, + * onDismiss = { showTooltip = false }, + * ) { + * Button(onClick = { showTooltip = true }) { + * Text("Click me") + * } + * } + * ``` + * + * @param text The main content text of the component + * @param visible Controls the visibility of the component. + * @param onDismiss Called when a button is clicked or the timeout elapses. Must set [visible] to `false` to actually + * hide the tooltip; without this the popup remains on screen indefinitely. + * @param modifier Modifier to apply to the content wrapper + * @param header Optional header text displayed above the main content + * @param iconOrStep Optional icon or step number to display (mutually exclusive) + * @param buttons Button configuration (primary and optional secondary button). Each button's [GotItButton.action] runs + * as a side effect before [onDismiss] is called automatically. + * @param link Type of link to display below the text + * @param image Optional image to display at the top of the tooltip + * @param maxWidth Optional fixed text width. When set, auto-extension (280→328 dp) is disabled. **Ignored when an image + * is present** — the image width always takes priority. Matches `GotItComponentBuilder.withMaxWidth()`. + * @param timeout Auto-hide timeout. Buttons are hidden and [onDismiss] is called after this duration elapses. Use + * [Duration.INFINITE] (the default) to disable the timeout. + * @param padding Extra gap between the balloon arrow tip and the anchor point. Pushes the balloon away from the + * component it is anchored to. + * @param onShow Callback invoked when the tooltip is displayed + * @param onEscapePress Code to run when users press escape while the popup is focused. If set, it'll automatically call + * [onDismiss] + * @param style Visual styling configuration + * @param content The target component to which this tooltip is anchored + */ +@Composable +public fun GotItTooltip( + @Nls text: String, + visible: Boolean, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + @Nls header: String? = null, + iconOrStep: GotItIconOrStep? = null, + buttons: GotItButtons = GotItButtons.Default, + link: GotItLink? = null, + image: GotItImage? = null, + maxWidth: Dp? = null, + timeout: Duration = Duration.INFINITE, + gotItBalloonPosition: GotItBalloonPosition = GotItBalloonPosition.BELOW, + anchor: Alignment = Alignment.BottomCenter, + padding: Dp = 0.dp, + onShow: () -> Unit = {}, + onEscapePress: (() -> Unit)? = null, + style: GotItTooltipStyle = LocalGotItTooltipStyle.current, + content: @Composable () -> Unit, +) { + GotItTooltip( + body = buildGotItBody { append(text) }, + visible = visible, + onDismiss = onDismiss, + modifier = modifier, + header = header, + iconOrStep = iconOrStep, + buttons = buttons, + link = link, + image = image, + maxWidth = maxWidth, + timeout = timeout, + gotItBalloonPosition = gotItBalloonPosition, + anchor = anchor, + offset = padding, + onShow = onShow, + onEscapePress = onEscapePress, + style = style, + content = content, + ) +} + +/** + * A Got It tooltip component that provides contextual information about new or changed features, with support for rich + * body content (bold, code, inline links, browser links). + * + * Build the body with [buildGotItBody]: + * ```kotlin + * GotItTooltip( + * body = buildGotItBody { + * append("Press ") + * bold("Resume") + * append(", or ") + * link("open the docs") { openUrl("https://example.com") } + * append(".") + * }, + * visible = showTooltip, + * onDismiss = { showTooltip = false }, + * ) { ... } + * ``` + * + * @param body The rich-text body built with [buildGotItBody] + * @param visible Controls the visibility of the component. + * @param onDismiss Called when a button is clicked or the timeout elapses. Must set [visible] to `false` to actually + * hide the tooltip; without this the popup remains on screen indefinitely. + * @param modifier Modifier to apply to the content wrapper + * @param header Optional header text displayed above the main content + * @param iconOrStep Optional icon or step number to display (mutually exclusive) + * @param buttons Button configuration (primary and optional secondary button). Each button's [GotItButton.action] runs + * as a side effect before [onDismiss] is called automatically. + * @param link Type of link to display below the text + * @param image Optional image to display at the top of the tooltip + * @param maxWidth Optional fixed text width. When set, auto-extension (280→328 dp) is disabled. **Ignored when an image + * is present** — the image width always takes priority. Matches `GotItComponentBuilder.withMaxWidth()`. + * @param timeout Auto-hide timeout. Buttons are hidden and [onDismiss] is called after this duration elapses. Use + * [Duration.INFINITE] (the default) to disable the timeout. + * @param offset Extra gap between the balloon arrow tip and the anchor point. Pushes the balloon away from the + * component it is anchored to. + * @param onShow Callback invoked when the tooltip is displayed + * @param onEscapePress Code to run when users press escape while the popup is focused. + * @param style Visual styling configuration + * @param content The target component to which this tooltip is anchored + */ +@OptIn(InternalJewelApi::class) +@Composable +public fun GotItTooltip( + body: GotItBody, + visible: Boolean, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + @Nls header: String? = null, + iconOrStep: GotItIconOrStep? = null, + buttons: GotItButtons = GotItButtons.Default, + link: GotItLink? = null, + image: GotItImage? = null, + maxWidth: Dp? = null, + timeout: Duration = Duration.INFINITE, + gotItBalloonPosition: GotItBalloonPosition = GotItBalloonPosition.BELOW, + anchor: Alignment = Alignment.BottomCenter, + offset: Dp = 0.dp, + onShow: () -> Unit = {}, + onEscapePress: (() -> Unit)? = null, + style: GotItTooltipStyle = LocalGotItTooltipStyle.current, + content: @Composable () -> Unit, +) { + val layoutDirection = LocalLayoutDirection.current + val cornerRadiusPx = style.metrics.cornerRadius.value.roundToInt() + val currentOnDismiss by rememberUpdatedState(onDismiss) + val currentOnShow by rememberUpdatedState(onShow) + val currentOnEscapePress by rememberUpdatedState(onEscapePress) + val shouldListenForEscapeKeyPress by + rememberUpdatedState(visible && currentOnEscapePress != null || buttons.hasNoButtons()) + + Box( + modifier = + Modifier.onPreviewKeyEvent { event -> + if (shouldListenForEscapeKeyPress && event.key == Key.Escape && event.type == KeyEventType.KeyDown) { + currentOnEscapePress?.invoke() + currentOnDismiss() + true + } else { + false + } + } + ) { + content() + + if (visible) { + Popup( + popupPositionProvider = + rememberGotItTooltipBalloonPopupPositionProvider(gotItBalloonPosition, anchor, padding = offset), + cornerSize = CornerSize(style.metrics.cornerRadius), + properties = PopupProperties(focusable = false, dismissOnClickOutside = false), + onDismissRequest = {}, + windowShape = { logicalSize -> + createBalloonAwtShape( + size = logicalSize, + arrowWidthPx = 16, + arrowHeightPx = 8, + cornerRadiusPx = cornerRadiusPx, + arrowPosition = gotItBalloonPosition, + arrowOffsetPx = 24, + layoutDirection = layoutDirection, + ) + }, + ) { + GotItTooltipBalloonContainer(modifier = modifier, gotItBalloonPosition = gotItBalloonPosition) { + GotItTooltipImpl( + body = body, + header = header, + iconOrStep = iconOrStep, + // Buttons are not shown when a finite timeout is set + buttons = if (timeout.isFinite()) GotItButtons.None else buttons, + link = link, + image = image, + maxWidth = maxWidth, + onDismiss = onDismiss, + style = style, + ) + + LaunchedEffect(Unit) { currentOnShow() } + + if (timeout.isFinite()) { + LaunchedEffect(Unit) { + delay(timeout) + currentOnDismiss() + } + } + } + } + } + } +} + +/** + * Represents either an icon or a step number for the Got It tooltip. Icon and step number are mutually exclusive - only + * one can be shown at a time. + */ +@Stable +public sealed interface GotItIconOrStep { + /** + * Display a custom icon. + * + * @param content Composable icon content to display + */ + @Stable + @GenerateDataFunctions + public class Icon(public val content: @Composable () -> Unit) : GotItIconOrStep { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Icon + + return content == other.content + } + + override fun hashCode(): Int = content.hashCode() + + override fun toString(): String = "Icon(content=$content)" + } + + /** + * Display a step number (e.g., "01", "02", "10"). + * + * @param number Step number in the range [1, 99] + * @throws IllegalArgumentException if number is not in [1, 99] + */ + @Stable + @GenerateDataFunctions + public class Step(public val number: Int) : GotItIconOrStep { + init { + require(number in 1..99) { "Step number must be in range [1, 99], got: $number" } + } + + /** Formatted step text with leading zero for numbers < 10 (e.g., "01", "02", "10") */ + public val formattedText: String = number.toString().padStart(2, '0') + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Step + + if (number != other.number) return false + if (formattedText != other.formattedText) return false + + return true + } + + override fun hashCode(): Int { + var result = number + result = 31 * result + formattedText.hashCode() + return result + } + + override fun toString(): String = "Step(number=$number, formattedText='$formattedText')" + } +} + +/** + * Configuration for buttons displayed in the Got It tooltip. + * + * @param primary Primary button ("Got it" by default). Null means no buttons are shown. + * @param secondary Optional secondary button for additional actions + */ +@Stable +@GenerateDataFunctions +public class GotItButtons(public val primary: GotItButton?, public val secondary: GotItButton? = null) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GotItButtons + + if (primary != other.primary) return false + if (secondary != other.secondary) return false + + return true + } + + override fun hashCode(): Int { + var result = primary?.hashCode() ?: 0 + result = 31 * result + (secondary?.hashCode() ?: 0) + return result + } + + override fun toString(): String = "GotItButtons(primary=$primary, secondary=$secondary)" + + public companion object { + /** Default configuration with a "Got it" primary button */ + public val Default: GotItButtons = GotItButtons(primary = GotItButton.Default) + + /** Configuration with no buttons */ + public val None: GotItButtons = GotItButtons(primary = null) + + internal fun GotItButtons.hasNoButtons() = this.primary == null && this.secondary == null + } +} + +/** + * Configuration for a single button in the Got It tooltip. + * + * @param label Button label text + * @param action Optional side effect to run when the button is clicked, before the tooltip is dismissed. The tooltip is + * always dismissed automatically after this runs — there is no need to call [onDismiss][GotItTooltip] here. + */ +@Stable +@GenerateDataFunctions +public class GotItButton(@Nls public val label: String, public val action: () -> Unit = {}) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GotItButton + + if (label != other.label) return false + if (action != other.action) return false + + return true + } + + override fun hashCode(): Int { + var result = label.hashCode() + result = 31 * result + action.hashCode() + return result + } + + override fun toString(): String = "GotItButton(label='$label', action=$action)" + + public companion object { + /** Default "Got it" button with no extra action */ + public val Default: GotItButton = GotItButton(label = "Got it") + } +} + +/** + * Represents a link in the Got It tooltip. Links are displayed below the main text as separate interactive elements. + */ +@Stable +public sealed interface GotItLink { + public val label: String + public val action: () -> Unit + + /** + * A regular internal link (blue text with underline). + * + * @param label Link text to display + * @param action Action to perform when clicked + */ + @Stable + @GenerateDataFunctions + public class Regular(@Nls override val label: String, override val action: () -> Unit) : GotItLink { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Regular + + if (label != other.label) return false + if (action != other.action) return false + + return true + } + + override fun hashCode(): Int { + var result = label.hashCode() + result = 31 * result + action.hashCode() + return result + } + + override fun toString(): String = "Regular(label='$label', action=$action)" + } + + /** + * A browser link that opens an external URL (displayed with an arrow icon). + * + * @param label Link text to display + * @param uri URI to open in the browser + * @param action Optional side effect callback invoked when the link is clicked (e.g., for analytics). The URI is + * always opened in the browser regardless of this callback. + */ + @Stable + @GenerateDataFunctions + public class Browser( + @Nls override val label: String, + public val uri: String, + override val action: () -> Unit = {}, + ) : GotItLink { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Browser + + if (label != other.label) return false + if (uri != other.uri) return false + if (action != other.action) return false + + return true + } + + override fun hashCode(): Int { + var result = label.hashCode() + result = 31 * result + uri.hashCode() + result = 31 * result + action.hashCode() + return result + } + + override fun toString(): String = "Browser(label='$label', uri='$uri', action=$action)" + } +} + +/** + * Configuration for an image displayed in the Got It tooltip. + * + * The image is loaded from the classpath using the thread's context classloader, which in IntelliJ Platform resolves to + * the calling plugin's classloader automatically. + * + * @param path Classpath-relative path to the image resource (e.g. `"images/promo.png"`) + * @param contentDescription The content description for the image + * @param showBorder Whether to show a border around the image + */ +@Stable +@GenerateDataFunctions +public class GotItImage( + public val path: String, + public val contentDescription: String?, + public val showBorder: Boolean = false, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GotItImage + + if (showBorder != other.showBorder) return false + if (path != other.path) return false + if (contentDescription != other.contentDescription) return false + + return true + } + + override fun hashCode(): Int { + var result = showBorder.hashCode() + result = 31 * result + path.hashCode() + result = 31 * result + (contentDescription?.hashCode() ?: 0) + return result + } + + override fun toString(): String = + "GotItImage(" + "path='$path', " + "contentDescription=$contentDescription, " + "showBorder=$showBorder" + ")" +} + +/** + * Position of the Got It tooltip balloon relative to the target component. The arrow is drawn on the opposite side of + * the position (e.g., BELOW means tooltip below target with arrow pointing up). + * + * The arrow tip will always point to the center of the provided anchor. + */ +public enum class GotItBalloonPosition { + /** Tooltip appears below the target, arrow points up */ + BELOW, + + /** Tooltip appears above the target, arrow points down */ + ABOVE, + + /** Tooltip appears to the start of the target, arrow points end */ + START, + + /** Tooltip appears to the end of the target, arrow points start */ + END, +} + +@Composable +private fun GotItTooltipImpl( + body: GotItBody, + header: String?, + iconOrStep: GotItIconOrStep?, + buttons: GotItButtons, + link: GotItLink?, + image: GotItImage?, + maxWidth: Dp?, + onDismiss: () -> Unit, + style: GotItTooltipStyle, +) { + CompositionLocalProvider( + LocalContentColor provides style.colors.foreground, + LocalTextStyle provides LocalTextStyle.current.copy(color = style.colors.foreground), + ) { + Box { + OverrideDarkMode(isDark = style.colors.background.isDark()) { + GotItTooltipContent(body, header, iconOrStep, buttons, link, image, maxWidth, onDismiss, style) + } + } + } +} + +@Composable +private fun GotItTooltipContent( + body: GotItBody, + header: String?, + iconOrStep: GotItIconOrStep?, + buttons: GotItButtons, + link: GotItLink?, + image: GotItImage?, + maxWidth: Dp?, + onDismiss: () -> Unit, + style: GotItTooltipStyle, +) { + var imageWidth by remember { mutableStateOf(null) } + val density = LocalDensity.current + val editorTextStyle = JewelTheme.editorTextStyle + val externalLinkIconKey = LocalLinkStyle.current.icons.externalLink + + // Auto-extension (280 → 328 dp) is only allowed when there is no image and no custom maxWidth + val allowWidthExtension = image == null && maxWidth == null + var currentWidth by remember(maxWidth) { mutableStateOf(maxWidth ?: DEFAULT_TOOLTIP_WIDTH) } + + val annotatedBody = + remember(body, style.colors.link, style.colors.codeForeground, style.colors.codeBackground) { + buildBodyAnnotatedString(body, style.colors) + } + val inlineContent = + remember(body, style.colors.codeForeground, style.colors.codeBackground, externalLinkIconKey) { + buildInlineContent(body, style.colors, editorTextStyle, externalLinkIconKey) + } + + val contentWidthModifier = + when { + image != null -> Modifier.width(imageWidth ?: DEFAULT_TOOLTIP_WIDTH) + maxWidth != null -> Modifier.widthIn(max = maxWidth) + else -> Modifier.widthIn(max = currentWidth) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + image?.let { + Box( + modifier = + Modifier.onGloballyPositioned { coordinates -> + imageWidth = with(density) { coordinates.size.width.toDp() } + } + ) { + ImageContent(image = it, style = style) + } + } + + Column(modifier = contentWidthModifier) { + Row(verticalAlignment = Alignment.Top) { + iconOrStep?.let { iconOrStepValue -> + IconOrStepContent(iconOrStep = iconOrStepValue, style = style) + Spacer(modifier = Modifier.width(style.metrics.iconPadding)) + } + + Column { + if (!header.isNullOrEmpty()) { + Text(text = header, fontWeight = FontWeight.Bold, color = style.colors.headerForeground) + Spacer(modifier = Modifier.height(style.metrics.textPadding)) + } + + Text( + text = annotatedBody, + color = style.colors.foreground, + inlineContent = inlineContent, + onTextLayout = { result -> + if (allowWidthExtension && result.lineCount >= 5 && currentWidth == DEFAULT_TOOLTIP_WIDTH) { + currentWidth = DEFAULT_TOOLTIP_EXTENDED_WIDTH + } + }, + ) + + if (link != null) { + Spacer(Modifier.height(style.metrics.textPadding)) + LinkContent(link, style) + } + + if (buttons.primary != null) { + ButtonsContent(buttons, onDismiss, style) + } + } + } + } + } +} + +@Composable +private fun ImageContent(image: GotItImage, style: GotItTooltipStyle) { + Box( + modifier = + Modifier.padding(style.metrics.imagePadding) + .clip(RoundedCornerShape(style.metrics.cornerRadius)) + .then( + if (image.showBorder) { + Modifier.border( + width = 1.dp, + color = style.colors.imageBorderColor, + shape = RoundedCornerShape(style.metrics.cornerRadius), + ) + } else { + Modifier + } + ) + ) { + val classLoader = Thread.currentThread().contextClassLoader ?: GotItImage::class.java.classLoader + val painterProvider = remember(image.path, classLoader) { ResourcePainterProvider(image.path, classLoader) } + val painter by painterProvider.getPainter() + + // Render at pixel dimensions treated as dp, matching IJP's Swing behavior where iconWidth/iconHeight + // are used directly as logical pixels (not density-divided). Without this, BitmapPainter would render + // the image at intrinsicSize/density dp, making it appear 2x smaller on Retina displays. + val intrinsicSize = painter.intrinsicSize + val sizeModifier = + if (intrinsicSize.isSpecified) { + Modifier.size(intrinsicSize.width.dp, intrinsicSize.height.dp) + } else { + Modifier + } + + Image(painter, image.contentDescription, modifier = sizeModifier) + } +} + +@Composable +private fun IconOrStepContent(iconOrStep: GotItIconOrStep, style: GotItTooltipStyle) { + when (iconOrStep) { + is GotItIconOrStep.Icon -> { + iconOrStep.content() + } + + is GotItIconOrStep.Step -> { + Text( + text = iconOrStep.formattedText, + color = style.colors.stepForeground, + style = JewelTheme.editorTextStyle, + ) + } + } +} + +@Composable +private fun LinkContent(link: GotItLink, style: GotItTooltipStyle) { + val currentLinkStyle = LocalLinkStyle.current + val gotItLinkColor = style.colors.link + val overriddenLinkStyle = + LinkStyle( + colors = + LinkColors( + content = gotItLinkColor, + contentDisabled = currentLinkStyle.colors.contentDisabled, + contentFocused = gotItLinkColor, + contentPressed = gotItLinkColor, + contentHovered = gotItLinkColor, + contentVisited = gotItLinkColor, + ), + metrics = currentLinkStyle.metrics, + icons = currentLinkStyle.icons, + underlineBehavior = currentLinkStyle.underlineBehavior, + ) + + Column { + when (link) { + is GotItLink.Regular -> { + Link(text = link.label, onClick = link.action, style = overriddenLinkStyle) + } + + is GotItLink.Browser -> { + val uriHandler = LocalUriHandler.current + ExternalLink( + text = link.label, + onClick = { + uriHandler.openUri(link.uri) + link.action() + }, + style = overriddenLinkStyle, + ) + } + } + + Spacer(modifier = Modifier.height(style.metrics.textPadding)) + } +} + +@Composable +private fun ButtonsContent(buttons: GotItButtons, onDismiss: () -> Unit, style: GotItTooltipStyle) { + val gotItButtonStyle = LocalGotItButtonStyle.current + val currentLinkStyle = LocalLinkStyle.current + val gotItSecondaryButtonColor = style.colors.secondaryActionForeground + val overriddenLinkStyle = + LinkStyle( + colors = + LinkColors( + content = gotItSecondaryButtonColor, + contentDisabled = currentLinkStyle.colors.contentDisabled, + contentFocused = gotItSecondaryButtonColor, + contentPressed = gotItSecondaryButtonColor, + contentHovered = gotItSecondaryButtonColor, + contentVisited = gotItSecondaryButtonColor, + ), + metrics = currentLinkStyle.metrics, + icons = currentLinkStyle.icons, + underlineBehavior = currentLinkStyle.underlineBehavior, + ) + + Row(modifier = Modifier.padding(style.metrics.buttonPadding), horizontalArrangement = Arrangement.spacedBy(16.dp)) { + buttons.primary?.let { primaryButton -> + DefaultButton( + onClick = { + primaryButton.action() + onDismiss() + }, + style = gotItButtonStyle, + ) { + Text(text = primaryButton.label) + } + } + + buttons.secondary?.let { secondaryButton -> + Link( + modifier = Modifier.align(Alignment.CenterVertically), + text = secondaryButton.label, + onClick = { + secondaryButton.action() + onDismiss() + }, + style = overriddenLinkStyle, + ) + } + } +} + +private class BalloonShape( + private val arrowWidth: Dp = 16.dp, + private val arrowHeight: Dp = 16.dp, + private val cornerRadius: Dp = 8.dp, + private val arrowPosition: GotItBalloonPosition, + private val arrowOffset: Dp, +) : Shape { + @OptIn(InternalJewelApi::class) + override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline = + createBalloonOutline( + size = size, + layoutDirection = layoutDirection, + density = density, + arrowWidth = arrowWidth, + arrowHeight = arrowHeight, + cornerRadius = cornerRadius, + arrowPosition = arrowPosition, + arrowOffset = arrowOffset, + ) +} + +@Composable +private fun GotItTooltipBalloonContainer( + modifier: Modifier = Modifier, + gotItBalloonPosition: GotItBalloonPosition = GotItBalloonPosition.ABOVE, + content: @Composable () -> Unit, +) { + val style = LocalGotItTooltipStyle.current + val arrowWidth = 16.dp + val arrowHeight = 8.dp + val arrowOffset: Dp = 24.dp + + val balloonShape = + BalloonShape(arrowWidth, arrowHeight, style.metrics.cornerRadius, gotItBalloonPosition, arrowOffset) + + Box( + modifier = + modifier + .popupShadowAndBorder( + shape = balloonShape, + shadowSize = 1.dp, + shadowColor = style.colors.background, + borderWidth = 1.dp, + borderColor = style.colors.balloonBorderColor, + ) + .background(style.colors.background, balloonShape) + .padding( + start = if (gotItBalloonPosition == GotItBalloonPosition.END) arrowHeight else 0.dp, + top = if (gotItBalloonPosition == GotItBalloonPosition.BELOW) arrowHeight else 0.dp, + end = if (gotItBalloonPosition == GotItBalloonPosition.START) arrowHeight else 0.dp, + bottom = if (gotItBalloonPosition == GotItBalloonPosition.ABOVE) arrowHeight else 0.dp, + ) + .padding(style.metrics.contentPadding) + ) { + content() + } +} + +/** + * Creates and remembers a [PopupPositionProvider] that positions a balloon popup so that its arrow (drawn at + * [arrowOffset] from the leading edge of the balloon) aligns exactly with the [anchor] point on the target component. + * + * Use this together with [GotItTooltipBalloonContainer] inside a [Popup] to get correct hit-testing bounds — no + * `graphicsLayer` translation is applied, so the layout bounds and the visual bounds always match. + * + * @param gotItBalloonPosition Which side of the target the balloon appears on. + * @param anchor The alignment point on the target that the arrow points to. + * @param arrowOffset Distance of the arrow from the leading edge of the balloon. Must match the value passed to + * [GotItTooltipBalloonContainer]. + * @param padding Extra gap between the balloon arrow tip and the anchor point. Pushes the balloon away from the + * component it is anchored to. + * @return a remembered [GotItTooltipBalloonPopupPositionProvider] + */ +@Composable +public fun rememberGotItTooltipBalloonPopupPositionProvider( + gotItBalloonPosition: GotItBalloonPosition, + anchor: Alignment, + arrowOffset: Dp = 24.dp, + padding: Dp = 0.dp, +): PopupPositionProvider { + val density = LocalDensity.current + val arrowOffsetPx = with(density) { arrowOffset.roundToPx() } + val paddingPx = with(density) { padding.roundToPx() } + return remember(gotItBalloonPosition, anchor, arrowOffsetPx, paddingPx) { + GotItTooltipBalloonPopupPositionProvider(gotItBalloonPosition, anchor, arrowOffsetPx, paddingPx) + } +} + +private class GotItTooltipBalloonPopupPositionProvider( + private val gotItBalloonPosition: GotItBalloonPosition, + private val anchor: Alignment, + private val arrowOffsetPx: Int, + private val paddingPx: Int = 0, +) : PopupPositionProvider { + @OptIn(InternalJewelApi::class) + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + ): IntOffset { + val base = + calculateBalloonPosition( + gotItBalloonPosition = gotItBalloonPosition, + anchor = anchor, + arrowOffsetPx = arrowOffsetPx, + anchorBounds = anchorBounds, + layoutDirection = layoutDirection, + popupContentSize = popupContentSize, + ) + if (paddingPx == 0) return base + return when (gotItBalloonPosition) { + GotItBalloonPosition.BELOW -> base.copy(y = base.y + paddingPx) + GotItBalloonPosition.ABOVE -> base.copy(y = base.y - paddingPx) + GotItBalloonPosition.START -> base.copy(x = base.x - paddingPx) + GotItBalloonPosition.END -> base.copy(x = base.x + paddingPx) + } + } +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonInternals.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonInternals.kt new file mode 100644 index 0000000000000..081d7edf1091f --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBalloonInternals.kt @@ -0,0 +1,234 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.ui.Alignment +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.RoundRect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathOperation +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import java.awt.Shape +import java.awt.geom.Area +import java.awt.geom.Path2D +import java.awt.geom.RoundRectangle2D +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.jewel.foundation.InternalJewelApi + +/** + * Calculates the position of a balloon popup such that its arrow aligns with the given [anchor] point on the target. + * + * The popup is placed so that [arrowOffsetPx] pixels from the popup's leading edge land exactly on the resolved anchor + * point. + */ +@InternalJewelApi +@ApiStatus.Internal +public fun calculateBalloonPosition( + gotItBalloonPosition: GotItBalloonPosition, + anchor: Alignment, + arrowOffsetPx: Int, + anchorBounds: IntRect, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, +): IntOffset { + val anchorPoint = anchor.align(IntSize.Zero, IntSize(anchorBounds.width, anchorBounds.height), layoutDirection) + val anchorX = anchorBounds.left + anchorPoint.x + val anchorY = anchorBounds.top + anchorPoint.y + + return when (gotItBalloonPosition) { + GotItBalloonPosition.BELOW -> IntOffset(x = anchorX - arrowOffsetPx, y = anchorY) + + GotItBalloonPosition.ABOVE -> IntOffset(x = anchorX - arrowOffsetPx, y = anchorY - popupContentSize.height) + + GotItBalloonPosition.START -> IntOffset(x = anchorX - popupContentSize.width, y = anchorY - arrowOffsetPx) + + GotItBalloonPosition.END -> IntOffset(x = anchorX, y = anchorY - arrowOffsetPx) + } +} + +/** + * Creates the [Outline] for a balloon shape with a directional arrow. + * + * The arrow is drawn on the side facing the anchor — the opposite side from [arrowPosition]: + * - [GotItBalloonPosition.BELOW] → arrow at the top of the balloon (pointing up) + * - [GotItBalloonPosition.ABOVE] → arrow at the bottom of the balloon (pointing down) + * - [GotItBalloonPosition.START] → arrow at the end of the balloon (pointing right/end) + * - [GotItBalloonPosition.END] → arrow at the start of the balloon (pointing left/start) + */ +@InternalJewelApi +@ApiStatus.Internal +public fun createBalloonOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + arrowWidth: Dp, + arrowHeight: Dp, + cornerRadius: Dp, + arrowPosition: GotItBalloonPosition, + arrowOffset: Dp, +): Outline { + val arrowWidthPx = with(density) { arrowWidth.toPx() } + val arrowHeightPx = with(density) { arrowHeight.toPx() } + val cornerRadiusPx = with(density) { cornerRadius.toPx() } + val arrowOffsetPx = with(density) { arrowOffset.toPx() } + + val isRtl = layoutDirection == LayoutDirection.Rtl + + val rectLeft = if (arrowPosition == GotItBalloonPosition.END) arrowHeightPx else 0f + val rectTop = if (arrowPosition == GotItBalloonPosition.BELOW) arrowHeightPx else 0f + val rectRight = size.width - if (arrowPosition == GotItBalloonPosition.START) arrowHeightPx else 0f + val rectBottom = size.height - if (arrowPosition == GotItBalloonPosition.ABOVE) arrowHeightPx else 0f + + val roundRect = RoundRect(rectLeft, rectTop, rectRight, rectBottom, CornerRadius(cornerRadiusPx)) + val baseRectPath = Path().apply { addRoundRect(roundRect) } + val arrowPath = Path() + + when (arrowPosition) { + GotItBalloonPosition.ABOVE, + GotItBalloonPosition.BELOW -> { + val actualOffsetPx = if (isRtl) size.width - arrowOffsetPx else arrowOffsetPx + + val center = + actualOffsetPx.coerceIn( + rectLeft + cornerRadiusPx + arrowWidthPx / 2, + rectRight - cornerRadiusPx - arrowWidthPx / 2, + ) + + if (arrowPosition == GotItBalloonPosition.BELOW) { + // Arrow at top, pointing up + arrowPath.moveTo(center - arrowWidthPx / 2, rectTop) + arrowPath.lineTo(center, 0f) + arrowPath.lineTo(center + arrowWidthPx / 2, rectTop) + } else { + // ABOVE: arrow at bottom, pointing down + arrowPath.moveTo(center - arrowWidthPx / 2, rectBottom) + arrowPath.lineTo(center, size.height) + arrowPath.lineTo(center + arrowWidthPx / 2, rectBottom) + } + } + + GotItBalloonPosition.START, + GotItBalloonPosition.END -> { + val center = + arrowOffsetPx.coerceIn( + rectTop + cornerRadiusPx + arrowWidthPx / 2, + rectBottom - cornerRadiusPx - arrowWidthPx / 2, + ) + + if (arrowPosition == GotItBalloonPosition.END) { + // Arrow at start/left, pointing left + arrowPath.moveTo(rectLeft, center - arrowWidthPx / 2) + arrowPath.lineTo(0f, center) + arrowPath.lineTo(rectLeft, center + arrowWidthPx / 2) + } else { + // START: arrow at end/right, pointing right + arrowPath.moveTo(rectRight, center - arrowWidthPx / 2) + arrowPath.lineTo(size.width, center) + arrowPath.lineTo(rectRight, center + arrowWidthPx / 2) + } + } + } + arrowPath.close() + + val finalPath = Path().apply { op(baseRectPath, arrowPath, PathOperation.Union) } + + return Outline.Generic(finalPath) +} + +/** + * Creates a [java.awt.Shape] for a balloon with a directional arrow, mirroring the geometry of [createBalloonOutline] + * exactly but producing an AWT shape suitable for use with [java.awt.Window.setShape]. + * + * All coordinate parameters are in **logical units** (equivalent to dp values as ints), matching the AWT coordinate + * system where `1 logical unit == 1 dp`. + * + * The arrow is drawn on the side facing the anchor — the opposite side from [arrowPosition]: + * - [GotItBalloonPosition.BELOW] → arrow at the top (pointing up) + * - [GotItBalloonPosition.ABOVE] → arrow at the bottom (pointing down) + * - [GotItBalloonPosition.START] → arrow at the end (pointing right/end) + * - [GotItBalloonPosition.END] → arrow at the start (pointing left/start) + */ +@InternalJewelApi +@ApiStatus.Internal +public fun createBalloonAwtShape( + size: IntSize, + arrowWidthPx: Int, + arrowHeightPx: Int, + cornerRadiusPx: Int, + arrowPosition: GotItBalloonPosition, + arrowOffsetPx: Int, + layoutDirection: LayoutDirection, +): Shape { + val isRtl = layoutDirection == LayoutDirection.Rtl + + val rectLeft = if (arrowPosition == GotItBalloonPosition.END) arrowHeightPx.toDouble() else 0.0 + val rectTop = if (arrowPosition == GotItBalloonPosition.BELOW) arrowHeightPx.toDouble() else 0.0 + val rectRight = + size.width.toDouble() - if (arrowPosition == GotItBalloonPosition.START) arrowHeightPx.toDouble() else 0.0 + val rectBottom = + size.height.toDouble() - if (arrowPosition == GotItBalloonPosition.ABOVE) arrowHeightPx.toDouble() else 0.0 + + val arcSize = cornerRadiusPx.toDouble() * 2 + val roundRect = + RoundRectangle2D.Double(rectLeft, rectTop, rectRight - rectLeft, rectBottom - rectTop, arcSize, arcSize) + + val arrowPath = Path2D.Double() + + when (arrowPosition) { + GotItBalloonPosition.ABOVE, + GotItBalloonPosition.BELOW -> { + val actualOffsetPx = if (isRtl) size.width - arrowOffsetPx else arrowOffsetPx + val center = + actualOffsetPx + .toDouble() + .coerceIn( + rectLeft + cornerRadiusPx + arrowWidthPx / 2.0, + rectRight - cornerRadiusPx - arrowWidthPx / 2.0, + ) + + if (arrowPosition == GotItBalloonPosition.BELOW) { + // Arrow at top, pointing up + arrowPath.moveTo(center - arrowWidthPx / 2.0, rectTop) + arrowPath.lineTo(center, 0.0) + arrowPath.lineTo(center + arrowWidthPx / 2.0, rectTop) + } else { + // ABOVE: arrow at bottom, pointing down + arrowPath.moveTo(center - arrowWidthPx / 2.0, rectBottom) + arrowPath.lineTo(center, size.height.toDouble()) + arrowPath.lineTo(center + arrowWidthPx / 2.0, rectBottom) + } + } + + GotItBalloonPosition.START, + GotItBalloonPosition.END -> { + val center = + arrowOffsetPx + .toDouble() + .coerceIn( + rectTop + cornerRadiusPx + arrowWidthPx / 2.0, + rectBottom - cornerRadiusPx - arrowWidthPx / 2.0, + ) + + if (arrowPosition == GotItBalloonPosition.END) { + // Arrow at start/left, pointing left + arrowPath.moveTo(rectLeft, center - arrowWidthPx / 2.0) + arrowPath.lineTo(0.0, center) + arrowPath.lineTo(rectLeft, center + arrowWidthPx / 2.0) + } else { + // START: arrow at end/right, pointing right + arrowPath.moveTo(rectRight, center - arrowWidthPx / 2.0) + arrowPath.lineTo(size.width.toDouble(), center) + arrowPath.lineTo(rectRight, center + arrowWidthPx / 2.0) + } + } + } + arrowPath.closePath() + + return Area(roundRect).also { it.add(Area(arrowPath)) } +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBody.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBody.kt new file mode 100644 index 0000000000000..c434f92797d73 --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipBody.kt @@ -0,0 +1,252 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.text.InlineTextContent +import androidx.compose.foundation.text.appendInlineContent +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.Placeholder +import androidx.compose.ui.text.PlaceholderVerticalAlign +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.em +import org.jetbrains.annotations.Nls +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.icon.IconKey +import org.jetbrains.jewel.ui.icons.AllIconsKeys + +/** An immutable, structured body for a [GotItTooltip]. Build one with [buildGotItBody]. */ +@Stable public class GotItBody internal constructor(internal val segments: List) + +/** + * DSL builder for [GotItBody]. All methods return `this` to allow chaining. + * + * Use [buildGotItBody] to create a [GotItBody] from this builder. + */ +public class GotItBodyBuilder @PublishedApi internal constructor() { + private val segments = mutableListOf() + + /** Appends plain text. */ + public fun append(@Nls text: String): GotItBodyBuilder = apply { segments += GotItBodySegment.Plain(text) } + + /** Appends text rendered in **bold**. */ + public fun bold(@Nls text: String): GotItBodyBuilder = apply { segments += GotItBodySegment.Bold(text) } + + /** Appends text rendered in a `monospace` font with a distinct background color. */ + public fun code(@Nls text: String): GotItBodyBuilder = apply { + if (text.isNotEmpty()) segments += GotItBodySegment.Code(text) + } + + /** Appends a clickable inline link. */ + public fun link(@Nls text: String, action: () -> Unit): GotItBodyBuilder = apply { + segments += GotItBodySegment.InlineLink(text, action) + } + + /** Appends a link that opens [uri] in the browser when clicked. */ + public fun browserLink(@Nls text: String, uri: String): GotItBodyBuilder = apply { + segments += GotItBodySegment.BrowserLink(text, uri) + } + + /** Appends an inline icon rendered in a 1em square placeholder, vertically centred with the surrounding text. */ + public fun icon(contentDescription: String?, content: @Composable () -> Unit): GotItBodyBuilder = apply { + segments += GotItBodySegment.Icon(contentDescription, content) + } + + @PublishedApi internal fun build(): GotItBody = GotItBody(segments.toList()) +} + +internal sealed interface GotItBodySegment { + @JvmInline value class Plain(val text: String) : GotItBodySegment + + @JvmInline value class Bold(val text: String) : GotItBodySegment + + @JvmInline value class Code(val text: String) : GotItBodySegment + + data class InlineLink(val text: String, val action: () -> Unit) : GotItBodySegment + + data class BrowserLink(val text: String, val uri: String) : GotItBodySegment + + data class Icon(val contentDescription: String?, val content: @Composable () -> Unit) : GotItBodySegment +} + +@VisibleForTesting +public fun buildBodyAnnotatedString(body: GotItBody, styleColors: GotItColors): AnnotatedString = buildAnnotatedString { + val linkStyles = + TextLinkStyles( + style = SpanStyle(color = styleColors.link, textDecoration = TextDecoration.None), + hoveredStyle = SpanStyle(color = styleColors.link, textDecoration = TextDecoration.Underline), + focusedStyle = SpanStyle(color = styleColors.link, textDecoration = TextDecoration.Underline), + pressedStyle = SpanStyle(color = styleColors.link, textDecoration = TextDecoration.None), + ) + body.segments.forEachIndexed { index, segment -> + when (segment) { + is GotItBodySegment.Plain -> append(segment.text) + is GotItBodySegment.Bold -> withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append(segment.text) } + is GotItBodySegment.Code -> appendInlineContent(codeInlineId(segment.text), segment.text) + is GotItBodySegment.InlineLink -> + withLink( + LinkAnnotation.Clickable( + tag = segment.text, + styles = linkStyles, + linkInteractionListener = { segment.action() }, + ) + ) { + withStyle(SpanStyle(color = styleColors.link)) { append(segment.text) } + } + is GotItBodySegment.BrowserLink -> { + withLink(LinkAnnotation.Url(url = segment.uri, styles = linkStyles)) { + withStyle(SpanStyle(color = styleColors.link)) { append(segment.text) } + } + append(" ") + appendInlineContent(browserLinkArrowId(index), "↗") + } + is GotItBodySegment.Icon -> appendInlineContent(iconInlineId(index), segment.contentDescription ?: "\uFFFD") + } + } +} + +private fun codeInlineId(text: String) = "jewel:code:$text" + +private fun iconInlineId(index: Int) = "jewel:icon:$index" + +private fun browserLinkArrowId(index: Int) = "jewel:browser-link-arrow:$index" + +/** + * Builds a [Map] of [InlineTextContent] entries for code and icon segments in [body], keyed by the ID used in the + * [AnnotatedString] produced by [buildBodyAnnotatedString]. Pass the result to the `inlineContent` parameter of a + * `Text` composable. + */ +@VisibleForTesting +public fun buildInlineContent( + body: GotItBody, + styleColors: GotItColors, + editorFontStyle: TextStyle, + externalLinkIconKey: IconKey = AllIconsKeys.Ide.External_link_arrow, +): Map { + // InlineTextContent requires a Placeholder whose width is committed upfront, before the text is laid out. + // There is no callback to measure the actual rendered width and feed it back without a two-pass approach + // (e.g. SubcomposeLayout + state recomposition), which would add significant complexity and a one-frame + // flicker on first render. + // + // Instead, we estimate the placeholder width from style information alone: + // charAdvance = naturalCharWidth + letterSpacing + // + // naturalCharWidth is approximated as 0.6 em — a reasonable midpoint for common monospace fonts + // (JetBrains Mono ≈ 0.577, Consolas ≈ 0.55, Courier New ≈ 0.60). letterSpacing is the extra inter-character + // gap defined in the style; we normalize it to em so it can be summed directly with the ratio. + // The result will be slightly off for unusual fonts or font sizes, but will never clip the text + // (we err on the side of a marginally wider placeholder). + val letterSpacingEm: Float = + when { + editorFontStyle.letterSpacing.isEm -> editorFontStyle.letterSpacing.value + editorFontStyle.letterSpacing.isSp && editorFontStyle.fontSize.isSp && editorFontStyle.fontSize.value > 0 -> + editorFontStyle.letterSpacing.value / editorFontStyle.fontSize.value + else -> 0f + } + val charAdvanceEm = 0.6f + letterSpacingEm + + val result = mutableMapOf() + for ((index, segment) in body.segments.withIndex()) { + when (segment) { + is GotItBodySegment.Code -> { + val id = codeInlineId(segment.text) + if (id !in result) { + result[id] = + InlineTextContent( + Placeholder( + width = (segment.text.length * charAdvanceEm + 0.7f).em, + height = 1.3.em, + placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter, + ) + ) { codeText -> + Box( + contentAlignment = Alignment.Center, + modifier = + Modifier.fillMaxSize() + // The code text is already accessible via the alternate text in the outer + // AnnotatedString. Suppressing here avoids a duplicate semantics node + // that would cause ambiguous matches in UI tests. + .clearAndSetSemantics {} + .background(styleColors.codeBackground, RoundedCornerShape(3.dp)) + .border(0.5.dp, styleColors.codeForeground, RoundedCornerShape(3.dp)) + .padding(horizontal = 2.dp), + ) { + BasicText( + text = codeText, + style = editorFontStyle.copy(color = styleColors.codeForeground), + ) + } + } + } + } + is GotItBodySegment.Icon -> { + result[iconInlineId(index)] = + InlineTextContent( + Placeholder( + width = 1.em, + height = 1.em, + placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter, + ) + ) { _ -> + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { segment.content() } + } + } + is GotItBodySegment.BrowserLink -> { + result[browserLinkArrowId(index)] = + InlineTextContent( + Placeholder( + width = 1.em, + height = 1.em, + placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter, + ) + ) { _ -> + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon(key = externalLinkIconKey, contentDescription = null, modifier = Modifier.size(16.dp)) + } + } + } + else -> { + // do nothing + } + } + } + return result +} + +/** + * Builds a [GotItBody] using the [GotItBodyBuilder] DSL. + * + * Example: + * ```kotlin + * val body = buildGotItBody { + * append("Press ") + * bold("Resume") + * append(" to continue, or ") + * link("open the docs") { openUrl("https://example.com") } + * append(".") + * } + * ``` + */ +public inline fun buildGotItBody(builder: GotItBodyBuilder.() -> Unit): GotItBody = + GotItBodyBuilder().apply(builder).build() diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipStyling.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipStyling.kt new file mode 100644 index 0000000000000..a5eb44ac1d86a --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/gotit/GotItTooltipStyling.kt @@ -0,0 +1,225 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.component.gotit + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.Stable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.foundation.GenerateDataFunctions +import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.ui.component.styling.ButtonColors +import org.jetbrains.jewel.ui.component.styling.ButtonMetrics +import org.jetbrains.jewel.ui.component.styling.ButtonStyle + +@Stable +@GenerateDataFunctions +public class GotItTooltipStyle(public val colors: GotItColors, public val metrics: GotItMetrics) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GotItTooltipStyle + + if (colors != other.colors) return false + if (metrics != other.metrics) return false + + return true + } + + override fun hashCode(): Int { + var result = colors.hashCode() + result = 31 * result + metrics.hashCode() + return result + } + + override fun toString(): String = "GotItStyle(colors=$colors, metrics=$metrics)" + + public companion object +} + +@Stable +@GenerateDataFunctions +public class GotItColors( + public val foreground: Color, + public val background: Color, + public val stepForeground: Color, + public val secondaryActionForeground: Color, + public val headerForeground: Color, + public val balloonBorderColor: Color, + public val imageBorderColor: Color, + public val link: Color, + public val codeForeground: Color, + public val codeBackground: Color, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GotItColors + + if (foreground != other.foreground) return false + if (background != other.background) return false + if (stepForeground != other.stepForeground) return false + if (secondaryActionForeground != other.secondaryActionForeground) return false + if (headerForeground != other.headerForeground) return false + if (balloonBorderColor != other.balloonBorderColor) return false + if (imageBorderColor != other.imageBorderColor) return false + if (link != other.link) return false + if (codeForeground != other.codeForeground) return false + if (codeBackground != other.codeBackground) return false + + return true + } + + override fun hashCode(): Int { + var result = foreground.hashCode() + result = 31 * result + background.hashCode() + result = 31 * result + stepForeground.hashCode() + result = 31 * result + secondaryActionForeground.hashCode() + result = 31 * result + headerForeground.hashCode() + result = 31 * result + balloonBorderColor.hashCode() + result = 31 * result + imageBorderColor.hashCode() + result = 31 * result + link.hashCode() + result = 31 * result + codeForeground.hashCode() + result = 31 * result + codeBackground.hashCode() + return result + } + + override fun toString(): String = + "GotItColors(" + + "foreground=$foreground, " + + "background=$background, " + + "stepForeground=$stepForeground, " + + "secondaryActionForeground=$secondaryActionForeground, " + + "headerForeground=$headerForeground, " + + "borderColor=$balloonBorderColor, " + + "imageBorderColor=$imageBorderColor, " + + "link=$link, " + + "codeForeground=$codeForeground, " + + "codeBackground=$codeBackground" + + ")" + + public companion object +} + +@Stable +@GenerateDataFunctions +public class GotItMetrics( + public val contentPadding: PaddingValues, + public val textPadding: Dp, + public val buttonPadding: PaddingValues, + public val iconPadding: Dp, + public val imagePadding: PaddingValues, + public val cornerRadius: Dp, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GotItMetrics + + if (contentPadding != other.contentPadding) return false + if (textPadding != other.textPadding) return false + if (buttonPadding != other.buttonPadding) return false + if (iconPadding != other.iconPadding) return false + if (imagePadding != other.imagePadding) return false + if (cornerRadius != other.cornerRadius) return false + + return true + } + + override fun hashCode(): Int { + var result = contentPadding.hashCode() + result = 31 * result + textPadding.hashCode() + result = 31 * result + buttonPadding.hashCode() + result = 31 * result + iconPadding.hashCode() + result = 31 * result + imagePadding.hashCode() + result = 31 * result + cornerRadius.hashCode() + return result + } + + override fun toString(): String = + "GotItMetrics(" + + "contentPadding=$contentPadding, " + + "textPadding=$textPadding, " + + "buttonPadding=$buttonPadding, " + + "iconPadding=$iconPadding, " + + "imagePadding=$imagePadding, " + + "cornerRadius=$cornerRadius" + + ")" + + public companion object +} + +public val LocalGotItTooltipStyle: ProvidableCompositionLocal = staticCompositionLocalOf { + error("No GotItStyle provided. Have you forgotten the theme?") +} + +public val LocalGotItButtonStyle: ProvidableCompositionLocal = staticCompositionLocalOf { + error("No GotIt ButtonStyle provided. Have you forgotten the theme?") +} + +internal fun fallbackGotItTooltipStyle() = + GotItTooltipStyle( + colors = + GotItColors( + foreground = Color(0xFF000000), + background = Color(0xFFF7F7F7), + stepForeground = Color(0xFF000000), + secondaryActionForeground = Color(0xFF000000), + headerForeground = Color(0xFF000000), + balloonBorderColor = Color(0xFFABABAB), + imageBorderColor = Color(0xFFF7F7F7), + link = Color(0xFF88ADF7), + codeForeground = Color(0xFFC9CCD6), + codeBackground = Color(0xFF393B40), + ), + metrics = + GotItMetrics( + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + textPadding = 4.dp, + buttonPadding = PaddingValues(top = 12.dp, bottom = 6.dp), + iconPadding = 6.dp, + imagePadding = PaddingValues(top = 4.dp, bottom = 12.dp), + cornerRadius = 8.dp, + ), + ) + +internal fun fallbackGotItTooltipButtonStyle(): ButtonStyle { + val bg = SolidColor(Color(0xFF494B57)) + return ButtonStyle( + colors = + ButtonColors( + background = bg, + backgroundDisabled = SolidColor(Color.Unspecified), + backgroundFocused = bg, + backgroundPressed = SolidColor(Color(0xFF383A42)), + backgroundHovered = SolidColor(Color(0xFF5A5D6B)), + content = Color(0xFFFFFFFF), + contentDisabled = Color(0xFF818594), + contentFocused = Color(0xFFFFFFFF), + contentPressed = Color(0xFFFFFFFF), + contentHovered = Color(0xFFFFFFFF), + border = bg, + borderDisabled = SolidColor(Color.Unspecified), + borderFocused = bg, + borderPressed = SolidColor(Color(0xFF383A42)), + borderHovered = SolidColor(Color(0xFF5A5D6B)), + ), + metrics = + ButtonMetrics( + cornerSize = CornerSize(4.dp), + padding = PaddingValues(horizontal = 12.dp, vertical = 6.dp), + minSize = DpSize(72.dp, 28.dp), + borderWidth = 1.dp, + focusOutlineExpand = Dp.Unspecified, + ), + focusOutlineAlignment = Stroke.Alignment.Center, + ) +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/theme/JewelTheme.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/theme/JewelTheme.kt index ec472e2d8a52c..e536ed4c0bc2a 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/theme/JewelTheme.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/theme/JewelTheme.kt @@ -17,6 +17,9 @@ import org.jetbrains.jewel.foundation.theme.ThemeColorPalette import org.jetbrains.jewel.foundation.theme.ThemeDefinition import org.jetbrains.jewel.foundation.theme.ThemeIconData import org.jetbrains.jewel.ui.ComponentStyling +import org.jetbrains.jewel.ui.component.gotit.GotItTooltipStyle +import org.jetbrains.jewel.ui.component.gotit.LocalGotItButtonStyle +import org.jetbrains.jewel.ui.component.gotit.LocalGotItTooltipStyle import org.jetbrains.jewel.ui.component.styling.ButtonStyle import org.jetbrains.jewel.ui.component.styling.CheckboxStyle import org.jetbrains.jewel.ui.component.styling.ChipStyle @@ -209,6 +212,12 @@ public val JewelTheme.Companion.defaultSlimButtonStyle: ButtonStyle public val JewelTheme.Companion.outlinedSlimButtonStyle: ButtonStyle @Composable @ReadOnlyComposable get() = LocalOutlinedSlimButtonStyle.current +public val JewelTheme.Companion.gotItTooltipStyle: GotItTooltipStyle + @Composable @ReadOnlyComposable get() = LocalGotItTooltipStyle.current + +public val JewelTheme.Companion.gotItButtonStyle: ButtonStyle + @Composable @ReadOnlyComposable get() = LocalGotItButtonStyle.current + @Composable public fun BaseJewelTheme(theme: ThemeDefinition, styling: ComponentStyling, content: @Composable () -> Unit) { BaseJewelTheme(theme, styling, swingCompatMode = false, content) diff --git a/plugins/devkit/intellij.devkit.compose/resources/messages/DevkitComposeBundle.properties b/plugins/devkit/intellij.devkit.compose/resources/messages/DevkitComposeBundle.properties index 2577ef2291556..ad8b24c2f9a25 100644 --- a/plugins/devkit/intellij.devkit.compose/resources/messages/DevkitComposeBundle.properties +++ b/plugins/devkit/intellij.devkit.compose/resources/messages/DevkitComposeBundle.properties @@ -58,6 +58,7 @@ jewel.swing.editable.disabled=Editable + Disabled jewel.swing.text.areas=Text areas: jewel.swing.label=Swing jewel.compose.label=Compose +jewel.swing.gotit.label=Got It Tooltip compose.sandbox=Compose Sandbox compose.sandbox.show.automatically.on.project.open=Show automatically on project open diff --git a/plugins/devkit/intellij.devkit.compose/src/demo/SwingComparisonTabPanel.kt b/plugins/devkit/intellij.devkit.compose/src/demo/SwingComparisonTabPanel.kt index 58ca79a7a5a71..36b23ece1c5ac 100644 --- a/plugins/devkit/intellij.devkit.compose/src/demo/SwingComparisonTabPanel.kt +++ b/plugins/devkit/intellij.devkit.compose/src/demo/SwingComparisonTabPanel.kt @@ -30,16 +30,23 @@ import com.intellij.icons.AllIcons import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI import com.intellij.openapi.editor.colors.EditorFontType import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.popup.Balloon import com.intellij.openapi.util.IconLoader +import com.intellij.openapi.wm.ToolWindowId +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.ui.GotItTextBuilder +import com.intellij.ui.GotItTooltip import com.intellij.ui.JBColor import com.intellij.ui.components.BrowserLink import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBScrollPane import com.intellij.ui.dsl.builder.AlignY +import com.intellij.ui.dsl.builder.BottomGap import com.intellij.ui.dsl.builder.COLUMNS_SHORT import com.intellij.ui.dsl.builder.Panel import com.intellij.ui.dsl.builder.Row import com.intellij.ui.dsl.builder.RowLayout +import com.intellij.ui.dsl.builder.TopGap import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI @@ -60,6 +67,8 @@ import org.jetbrains.jewel.ui.disabledAppearance import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.theme.textAreaStyle import org.jetbrains.jewel.ui.typography +import java.awt.Dimension +import java.net.URL import javax.swing.BoxLayout import javax.swing.DefaultComboBoxModel import javax.swing.JLabel @@ -83,6 +92,8 @@ internal class SwingComparisonTabPanel : BorderLayoutPanel() { separator() comboBoxesRow() separator() + gotItTooltipRow() + separator() } .apply { border = JBUI.Borders.empty(0, 10) @@ -214,7 +225,8 @@ internal class SwingComparisonTabPanel : BorderLayoutPanel() { } row(DevkitComposeBundle.message("jewel.swing.titles.swing")) { - text(DevkitComposeBundle.message("jewel.swing.label.this.will.wrap.over.couple.rows"), maxLineLength = 30).component.font = JBFont.h1() + text(DevkitComposeBundle.message("jewel.swing.label.this.will.wrap.over.couple.rows"), maxLineLength = 30).component.font = + JBFont.h1() } row(DevkitComposeBundle.message("jewel.swing.titles.compose")) { compose { @@ -508,6 +520,83 @@ internal class SwingComparisonTabPanel : BorderLayoutPanel() { .layout(RowLayout.PARENT_GRID) } + private fun Panel.gotItTooltipRow() { + row(DevkitComposeBundle.message("jewel.swing.gotit.label")) { + var currentBalloonPosition = 0 + val balloonPosition = listOf(Balloon.Position.atLeft, Balloon.Position.atRight, Balloon.Position.above, Balloon.Position.below) + var currentGotItPosition = 0 + val gotItPosition = listOf(GotItTooltip.LEFT_MIDDLE, GotItTooltip.RIGHT_MIDDLE, GotItTooltip.TOP_MIDDLE, GotItTooltip.BOTTOM_MIDDLE, + GotItTooltip.BOTTOM_LEFT) + panel { + row { + panel { + row { + text("Current balloon position: ${balloonPosition[currentBalloonPosition % balloonPosition.size]}") + } + row { + button("change balloon position") { currentBalloonPosition++ } + } + } + } + row { + panel { + row { + text("Current got it position: ${gotItPosition[currentGotItPosition % gotItPosition.size]}") + } + row { + button("change got it position") { currentGotItPosition++ } + } + } + } + }.apply { + minimumSize = Dimension(minimumSize.width, 500) + } + button(DevkitComposeBundle.message("jewel.swing.button.swing.button")) {} + .align(AlignY.CENTER) + .applyToComponent { + + this.addActionListener { + val randomId = java.util.UUID.randomUUID().toString() + GotItTooltip(randomId, "This is a got it tooltip") + .withHeader("This is a header") + .withPosition(balloonPosition[currentBalloonPosition % balloonPosition.size]) + .withShowCount(999) + .withLink("This is a link", {}) + .withBrowserLink("Open browser link", URL("https://www.jetbrains.com/help/idea/getting-started.html")) + .show(this, gotItPosition[currentGotItPosition % gotItPosition.size]) + } + } + + + compose { + val metrics = remember(JBFont.label(), LocalDensity.current) { getFontMetrics(JBFont.label()) } + val charWidth = + remember(metrics.widths) { + // Same logic as in JTextArea + metrics.charWidth('m') + } + val lineHeight = metrics.height + + val width = remember(charWidth) { (COLUMNS_SHORT * charWidth) } + val height = remember(lineHeight) { (3 * lineHeight) } + + val contentPadding = JewelTheme.textAreaStyle.metrics.contentPadding + val state = rememberTextFieldState("Hello") + TextArea( + state = state, + modifier = + Modifier.size( + width = width.dp + contentPadding.horizontal(LocalLayoutDirection.current), + height = height.dp + contentPadding.vertical(), + ), + ) + } + } + .topGap(TopGap.MEDIUM) + .bottomGap(BottomGap.MEDIUM) + .layout(RowLayout.PARENT_GRID) + } + private fun PaddingValues.vertical(): Dp = calculateTopPadding() + calculateBottomPadding() private fun PaddingValues.horizontal(layoutDirection: LayoutDirection): Dp =