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..83eda27710324 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 @@ -68,6 +68,7 @@ internal object JBPopupRenderer : PopupRenderer { onPreviewKeyEvent: ((KeyEvent) -> Boolean)?, onKeyEvent: ((KeyEvent) -> Boolean)?, cornerSize: CornerSize, + windowShape: ((IntSize) -> java.awt.Shape)?, content: @Composable () -> Unit, ) { JBPopup( 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/popup/JDialogRenderer.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/popup/JDialogRenderer.kt index de746621d1612..fbcc66d9e52c8 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 @@ -35,6 +35,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.popup import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize @@ -102,6 +103,7 @@ internal object JDialogRenderer : PopupRenderer { onPreviewKeyEvent: ((KeyEvent) -> Boolean)?, onKeyEvent: ((KeyEvent) -> Boolean)?, cornerSize: CornerSize, + windowShape: ((IntSize) -> java.awt.Shape)?, content: @Composable () -> Unit, ) { val isJBREnvironment = remember { JBR.isAvailable() && JBR.isRoundedCornersManagerSupported() } @@ -148,6 +150,7 @@ internal object JDialogRenderer : PopupRenderer { onKeyEvent = onKeyEvent, cornerSize = cornerSize, blendingEnabled = supportBlending, + windowShape = windowShape, content = content, ) } @@ -164,6 +167,7 @@ private fun JPopupImpl( onKeyEvent: ((KeyEvent) -> Boolean)?, cornerSize: CornerSize, blendingEnabled: Boolean, + windowShape: ((IntSize) -> java.awt.Shape)?, content: @Composable () -> Unit, ) { val popupDensity = LocalDensity.current @@ -175,6 +179,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) @@ -242,19 +247,40 @@ 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 (via compose.interop.blending), 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 / popupDensity.density).toInt(), + floor(size.height / popupDensity.density).toInt(), + ) + try { + dialog.shape = currentWindowShape(logicalSize) + } catch (_: UnsupportedOperationException) { + applyRoundedCorners(dialog, cornerSize, size, popupDensity) + } + 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. return@JPopupMeasurePolicy } - if (cornerSize != ZeroCornerSize) { - JBR.getRoundedCornersManager() - .setRoundedCorners( - dialog, - cornerSize.toPx(size.toSize(), popupDensity) / dialog.density(), - ) - } + applyRoundedCorners(dialog, cornerSize, size, popupDensity) } }, ) @@ -390,6 +416,11 @@ private class JPopupMeasurePolicy( } } +private fun applyRoundedCorners(dialog: Window, cornerSize: CornerSize, size: IntSize, density: Density) { + if (cornerSize == ZeroCornerSize) return + JBR.getRoundedCornersManager().setRoundedCorners(dialog, cornerSize.toPx(size.toSize(), 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/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 8e390b5498221..353770fd61d3d 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 907bffeb202f9..7f62d2585d61e 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 02bd15b92f536..4caf956e6001b 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 36a4aeeb581cf..e9ec0b06e11db 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 @@ -105,6 +106,7 @@ public class ComponentsViewModel( content = { TypographyShowcase() }, ), ViewInfo(title = "Brushes", iconKey = ShowcaseIcons.Components.brush, content = { BrushesShowcase() }), + 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 0000000000000..1cadaf083c2de Binary files /dev/null and b/platform/jewel/samples/showcase/src/main/resources/drawables/cool_jewel.png differ diff --git a/platform/jewel/samples/showcase/src/main/resources/icons/components/balloon.svg b/platform/jewel/samples/showcase/src/main/resources/icons/components/balloon.svg new file mode 100644 index 0000000000000..7380cc1295124 --- /dev/null +++ b/platform/jewel/samples/showcase/src/main/resources/icons/components/balloon.svg @@ -0,0 +1,7 @@ + + + + + + + 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 0000000000000..f5bfeef1733a0 Binary files /dev/null and b/platform/jewel/ui-tests/src/test/resources/drawables/test_gotit.png differ diff --git a/platform/jewel/ui/api-dump.txt b/platform/jewel/ui/api-dump.txt index e7d09117cd855..d5423de216861 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 @@ -573,8 +576,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 +595,10 @@ 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 -- 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 +- 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.Function1,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 @@ -971,6 +978,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 @@ -4003,6 +4149,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/Popup.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Popup.kt index cbd63c0718ee3..08734037bec85 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,158 @@ 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`; 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) + if (JewelFlags.useCustomPopupRenderer) { + LocalPopupRenderer.current.Popup( + popupPositionProvider = popupPositionProvider, + properties = properties, + onDismissRequest = onDismissRequest, + onPreviewKeyEvent = onPreviewKeyEvent, + onKeyEvent = onKeyEvent, + cornerSize = cornerSize, + windowShape = null, + content = content, + ) + } else { + ComposePopup( + popupPositionProvider = popupPositionProvider, + onDismissRequest = onDismissRequest, + properties = properties, + onPreviewKeyEvent = onPreviewKeyEvent, + onKeyEvent = onKeyEvent, + content = 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`; 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 +206,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 +217,7 @@ public fun Popup( onPreviewKeyEvent = onPreviewKeyEvent, onKeyEvent = onKeyEvent, cornerSize = cornerSize, + windowShape = windowShape, content = content, ) } else { @@ -110,6 +248,7 @@ public interface PopupRenderer { onPreviewKeyEvent: ((KeyEvent) -> Boolean)?, onKeyEvent: ((KeyEvent) -> Boolean)?, cornerSize: CornerSize, + windowShape: ((IntSize) -> java.awt.Shape)? = null, content: @Composable () -> Unit, ) @@ -134,6 +273,7 @@ private object DefaultPopupRenderer : PopupRenderer { onPreviewKeyEvent: ((KeyEvent) -> Boolean)?, onKeyEvent: ((KeyEvent) -> Boolean)?, cornerSize: CornerSize, + windowShape: ((IntSize) -> java.awt.Shape)?, content: @Composable () -> Unit, ) { ComposePopup( 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 0172bbf74e83f..e525d535d03a4 100644 --- a/plugins/devkit/intellij.devkit.compose/resources/messages/DevkitComposeBundle.properties +++ b/plugins/devkit/intellij.devkit.compose/resources/messages/DevkitComposeBundle.properties @@ -57,6 +57,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..e7a8635f3f68f 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,7 @@ 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.net.URL import javax.swing.BoxLayout import javax.swing.DefaultComboBoxModel import javax.swing.JLabel @@ -83,6 +91,8 @@ internal class SwingComparisonTabPanel : BorderLayoutPanel() { separator() comboBoxesRow() separator() + gotItTooltipRow() + separator() } .apply { border = JBUI.Borders.empty(0, 10) @@ -214,7 +224,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 +519,102 @@ 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++ } + } + } + } + row { + text("what") + } + row { + text("what") + } + row { + text("what") + } + row { + text("what") + } + row { + text("what") + } + row { + text("what") + } + row { + text("what") + } + } + 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("Yeah", {}) + .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 =