New iteration on scrollbars (#515)

* iterate on scrollbars

* update API files

* add Scrollbars demo to Standalone

* fix popup border width regression

Reference https://github.com/JetBrains/jewel/pull/515#discussion_r1704184163

Signed-off-by: Ivan Morgillo <imorgillo@gmail.com>

* remove unnecessary platform check in ScrollbarBridge

Reference https://github.com/JetBrains/jewel/pull/515#discussion_r1704190394

Signed-off-by: Ivan Morgillo <imorgillo@gmail.com>

* fix wrong thumb background color

Reference https://github.com/JetBrains/jewel/pull/515#discussion_r1704209751

Signed-off-by: Ivan Morgillo <imorgillo@gmail.com>

---------

Signed-off-by: Ivan Morgillo <imorgillo@gmail.com>
GitOrigin-RevId: f4b1a2367e0b781311491bd41d006f4fe001b87f
This commit is contained in:
Ivan Morgillo
2024-08-05 17:07:21 +02:00
committed by intellij-monorepo-bot
parent 34791ce8da
commit 38fa7f8f62
15 changed files with 1511 additions and 175 deletions

View File

@@ -125,6 +125,11 @@ public final class org/jetbrains/jewel/bridge/theme/IntUiBridgeKt {
public static final fun retrieveEditorTextStyle ()Landroidx/compose/ui/text/TextStyle;
}
public final class org/jetbrains/jewel/bridge/theme/ScrollbarBridgeKt {
public static final fun defaults-6ksGUsA (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling;
public static synthetic fun defaults-6ksGUsA$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling;
}
public final class org/jetbrains/jewel/bridge/theme/SwingBridgeThemeKt {
public static final fun SwingBridgeTheme (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
}

View File

@@ -18,7 +18,6 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.takeOrElse
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil
import com.intellij.ide.ui.laf.intellij.IdeaPopupMenuUI
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.colors.ColorKey
import com.intellij.openapi.editor.colors.EditorFontType
import com.intellij.openapi.util.registry.Registry
@@ -49,6 +48,7 @@ import org.jetbrains.jewel.foundation.Stroke
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.foundation.util.JewelLogger
import org.jetbrains.jewel.ui.ComponentStyling
import org.jetbrains.jewel.ui.DefaultComponentStyling
import org.jetbrains.jewel.ui.component.styling.ButtonColors
@@ -96,9 +96,6 @@ import org.jetbrains.jewel.ui.component.styling.RadioButtonColors
import org.jetbrains.jewel.ui.component.styling.RadioButtonIcons
import org.jetbrains.jewel.ui.component.styling.RadioButtonMetrics
import org.jetbrains.jewel.ui.component.styling.RadioButtonStyle
import org.jetbrains.jewel.ui.component.styling.ScrollbarColors
import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonColors
import org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonMetrics
import org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle
@@ -128,7 +125,7 @@ import org.jetbrains.jewel.ui.icons.AllIconsKeys
import javax.swing.UIManager
import kotlin.time.Duration.Companion.milliseconds
private val logger = Logger.getInstance("JewelIntUiBridge")
private val logger = JewelLogger.getInstance("JewelIntUiBridge")
internal val uiDefaults
get() = UIManager.getDefaults()
@@ -178,13 +175,14 @@ public fun retrieveConsoleTextStyle(): TextStyle {
)
}
private val isDark: Boolean
get() = !JBColor.isBright()
internal fun createBridgeThemeDefinition(
textStyle: TextStyle,
editorTextStyle: TextStyle,
consoleTextStyle: TextStyle,
): ThemeDefinition {
val isDark = !JBColor.isBright()
logger.debug("Obtaining theme definition from Swing...")
return ThemeDefinition(
@@ -697,7 +695,7 @@ private fun readMenuStyle(): MenuStyle {
)
return MenuStyle(
isDark = !JBColor.isBright(),
isDark = isDark,
colors = colors,
metrics =
MenuMetrics(
@@ -810,31 +808,6 @@ private object NewUiRadioButtonMetrics : BridgeRadioButtonMetrics {
override val iconContentGap = 4.dp
}
private fun readScrollbarStyle(isDark: Boolean) =
ScrollbarStyle(
colors =
ScrollbarColors(
// See ScrollBarPainter.THUMB_OPAQUE_BACKGROUND
thumbBackground =
retrieveColorOrUnspecified("ScrollBar.Mac.Transparent.thumbColor")
.let { if (it.alpha == 0f) Color.Unspecified else it } // See https://github.com/JetBrains/jewel/issues/259
.takeOrElse { if (isDark) Color(0x59808080) else Color(0x33000000) },
// See ScrollBarPainter.THUMB_OPAQUE_HOVERED_BACKGROUND
thumbBackgroundHovered =
retrieveColorOrUnspecified("ScrollBar.Mac.Transparent.hoverThumbColor")
.let { if (it.alpha == 0f) Color.Unspecified else it } // See https://github.com/JetBrains/jewel/issues/259
.takeOrElse { if (isDark) Color(0x8C808080) else Color(0x80000000) },
),
metrics =
ScrollbarMetrics(
thumbCornerSize = CornerSize(100),
thumbThickness = 8.dp,
minThumbLength = 16.dp,
trackPadding = PaddingValues(start = 7.dp, end = 3.dp),
),
hoverDuration = 300.milliseconds,
)
private fun readSegmentedControlButtonStyle(): SegmentedControlButtonStyle {
val selectedBackground = SolidColor(JBUI.CurrentTheme.SegmentedButton.SELECTED_BUTTON_COLOR.toComposeColor())
@@ -1094,6 +1067,7 @@ private fun readDefaultTabStyle(): TabStyle {
contentHovered = 1f,
contentSelected = 1f,
),
scrollbarStyle = readScrollbarStyle(isDark),
)
}
@@ -1150,6 +1124,7 @@ private fun readEditorTabStyle(): TabStyle {
contentHovered = 1f,
contentSelected = 1f,
),
scrollbarStyle = readScrollbarStyle(isDark),
)
}

View File

@@ -0,0 +1,230 @@
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.takeOrElse
import androidx.compose.ui.unit.dp
import com.intellij.ui.mac.foundation.Foundation
import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified
import org.jetbrains.jewel.ui.component.styling.ScrollbarColors
import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior
import org.jetbrains.skiko.hostOs
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
internal fun readScrollbarStyle(isDark: Boolean): ScrollbarStyle =
ScrollbarStyle(
colors = readScrollbarColors(isDark),
metrics = readScrollbarMetrics(),
trackClickBehavior = readTrackClickBehavior(),
scrollbarVisibility = readScrollbarVisibility(),
)
private fun readScrollbarVisibility() =
if (hostOs.isMacOS) {
readMacScrollbarStyle()
} else {
ScrollbarVisibility.AlwaysVisible
}
private fun readScrollbarColors(isDark: Boolean) =
if (hostOs.isMacOS) {
readScrollbarMacColors(isDark)
} else {
readScrollbarWinColors(isDark)
}
private fun readTrackClickBehavior() =
if (hostOs.isMacOS) {
readMacScrollbarBehavior()
} else {
TrackClickBehavior.JumpToSpot
}
private fun readScrollbarWinColors(isDark: Boolean): ScrollbarColors =
ScrollbarColors(
thumbBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Transparent.thumbColor",
0x33737373,
0x47A6A6A6,
),
thumbBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbColor",
0x47737373,
0x59A6A6A6,
),
thumbBackgroundPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbColor",
0x47737373,
0x59A6A6A6,
),
thumbBorder =
readScrollBarColorForKey(
isDark,
"ScrollBar.thumbBorderColor",
0x33595959,
0x47383838,
),
thumbBorderHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbBorderColor",
0x47595959,
0x59383838,
),
thumbBorderPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbBorderColor",
0x47595959,
0x59383838,
),
trackBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Transparent.trackColor",
0x00808080,
0x00808080,
),
trackBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Transparent.hoverTrackColor",
0x1A808080,
0x1A808080,
),
)
private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors =
ScrollbarColors(
thumbBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.Transparent.thumbColor",
0x00000000,
0x00808080,
),
thumbBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbColor",
0x80000000,
0x8C808080,
),
thumbBackgroundPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbColor",
0x80000000,
0x8C808080,
),
thumbBorder =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.thumbBorderColor",
0x33000000,
0x59262626,
),
thumbBorderHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbBorderColor",
0x80000000,
0x8C262626,
),
thumbBorderPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbBorderColor",
0x80000000,
0x8C262626,
),
trackBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.Transparent.trackColor",
0x00808080,
0x00808080,
),
trackBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.Transparent.hoverTrackColor",
0x1A808080,
0x1A808080,
),
)
private fun readScrollBarColorForKey(
isDark: Boolean,
colorKey: String,
fallbackLight: Long,
fallbackDark: Long,
) = retrieveColorOrUnspecified(colorKey)
.takeOrElse { if (isDark) Color(fallbackDark) else Color(fallbackLight) }
private fun readScrollbarMetrics(): ScrollbarMetrics =
if (hostOs.isMacOS) {
ScrollbarMetrics(
thumbCornerSize = CornerSize(percent = 100),
thumbThickness = 8.dp,
thumbThicknessExpanded = 14.dp,
minThumbLength = 20.dp,
trackPadding = PaddingValues(2.dp),
trackPaddingExpanded = PaddingValues(2.dp),
)
} else {
ScrollbarMetrics(
thumbCornerSize = CornerSize(0),
thumbThickness = 8.dp,
thumbThicknessExpanded = 8.dp,
minThumbLength = 16.dp,
trackPadding = PaddingValues(),
trackPaddingExpanded = PaddingValues(),
)
}
private fun readMacScrollbarStyle(): ScrollbarVisibility {
val nsScroller =
Foundation
.invoke(Foundation.getObjcClass("NSScroller"), "preferredScrollerStyle")
val visibility: ScrollbarVisibility =
if (1 == nsScroller.toInt()) {
ScrollbarVisibility.WhenScrolling.Companion.defaults()
} else {
ScrollbarVisibility.AlwaysVisible
}
return visibility
}
private fun readMacScrollbarBehavior(): TrackClickBehavior {
val defaults = Foundation.invoke("NSUserDefaults", "standardUserDefaults")
Foundation.invoke(defaults, "synchronize")
return Foundation
.invoke(defaults, "boolForKey:", Foundation.nsString("AppleScrollerPagingBehavior"))
.run { if (toInt() == 1) TrackClickBehavior.JumpToSpot else TrackClickBehavior.NextPage }
}
public fun ScrollbarVisibility.WhenScrolling.Companion.defaults(
appearAnimationDuration: Duration = 125.milliseconds,
disappearAnimationDuration: Duration = 125.milliseconds,
expandAnimationDuration: Duration = 125.milliseconds,
lingerDuration: Duration = 700.milliseconds,
): ScrollbarVisibility.WhenScrolling =
ScrollbarVisibility.WhenScrolling(
appearAnimationDuration = appearAnimationDuration,
disappearAnimationDuration = disappearAnimationDuration,
expandAnimationDuration = expandAnimationDuration,
lingerDuration = lingerDuration,
)

View File

@@ -116,8 +116,8 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiDefaultTabC
public final class org/jetbrains/jewel/intui/standalone/styling/IntUiDefaultTabStyleFactory {
public static final field $stable I
public static final field INSTANCE Lorg/jetbrains/jewel/intui/standalone/styling/IntUiDefaultTabStyleFactory;
public final fun dark (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
public final fun light (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
public final fun dark (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
public final fun light (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
}
public final class org/jetbrains/jewel/intui/standalone/styling/IntUiDividerStyleKt {
@@ -150,8 +150,8 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiEditorTabCo
public final class org/jetbrains/jewel/intui/standalone/styling/IntUiEditorTabStyleFactory {
public static final field $stable I
public static final field INSTANCE Lorg/jetbrains/jewel/intui/standalone/styling/IntUiEditorTabStyleFactory;
public final fun dark (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
public final fun light (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
public final fun dark (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
public final fun light (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/TabStyle;
}
public final class org/jetbrains/jewel/intui/standalone/styling/IntUiGroupHeaderStylingKt {
@@ -252,12 +252,34 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiRadioButton
}
public final class org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStylingKt {
public static final fun dark-45ZY6uE (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;JLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun dark-RIQooxk (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static final fun defaults--JS8el8 (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static synthetic fun defaults--JS8el8$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static final fun light-45ZY6uE (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;JLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun light-RIQooxk (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static final fun dark (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun defaults-6ksGUsA (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling;
public static synthetic fun defaults-6ksGUsA$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling;
public static final fun defaults-VkLD3kw (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static synthetic fun defaults-VkLD3kw$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static final fun light (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun linux-VkLD3kw (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static synthetic fun linux-VkLD3kw$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static final fun macOs-VkLD3kw (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static synthetic fun macOs-VkLD3kw$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static final fun macOsDark (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static synthetic fun macOsDark$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun macOsDark-iLRpYWo (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static synthetic fun macOsDark-iLRpYWo$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static final fun macOsLight (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static synthetic fun macOsLight$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun macOsLight-iLRpYWo (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static synthetic fun macOsLight-iLRpYWo$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static final fun winOsDark (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static synthetic fun winOsDark$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun winOsDark-iLRpYWo (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static synthetic fun winOsDark-iLRpYWo$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static final fun winOsLight (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static synthetic fun winOsLight$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public static final fun winOsLight-iLRpYWo (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static synthetic fun winOsLight-iLRpYWo$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public static final fun windows-VkLD3kw (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public static synthetic fun windows-VkLD3kw$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;FLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
}
public final class org/jetbrains/jewel/intui/standalone/styling/IntUiSegmentedControlButtonStylingKt {

View File

@@ -2,45 +2,252 @@ package org.jetbrains.jewel.intui.standalone.styling
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.ui.component.styling.ScrollbarColors
import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior
import org.jetbrains.skiko.hostOs
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@Composable
public fun ScrollbarStyle.Companion.light(
colors: ScrollbarColors = ScrollbarColors.light(),
metrics: ScrollbarMetrics = ScrollbarMetrics.defaults(),
hoverDuration: Duration = 300.milliseconds,
): ScrollbarStyle = ScrollbarStyle(colors, metrics, hoverDuration)
public fun ScrollbarStyle.Companion.macOsLight(
colors: ScrollbarColors = ScrollbarColors.macOsLight(),
metrics: ScrollbarMetrics = provideScrollbarMetrics(),
trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage,
scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.defaults(),
): ScrollbarStyle =
ScrollbarStyle(
colors = colors,
metrics = metrics,
trackClickBehavior = trackClickBehavior,
scrollbarVisibility = scrollbarVisibility,
)
@Composable
public fun ScrollbarStyle.Companion.dark(
colors: ScrollbarColors = ScrollbarColors.dark(),
metrics: ScrollbarMetrics = ScrollbarMetrics.defaults(),
hoverDuration: Duration = 300.milliseconds,
): ScrollbarStyle = ScrollbarStyle(colors, metrics, hoverDuration)
public fun ScrollbarStyle.Companion.macOsDark(
colors: ScrollbarColors = ScrollbarColors.macOsDark(),
metrics: ScrollbarMetrics = provideScrollbarMetrics(),
trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage,
scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.defaults(),
): ScrollbarStyle =
ScrollbarStyle(
colors = colors,
metrics = metrics,
trackClickBehavior = trackClickBehavior,
scrollbarVisibility = scrollbarVisibility,
)
@Composable
public fun ScrollbarColors.Companion.light(
thumbBackground: Color = Color(0x33000000),
public fun ScrollbarStyle.Companion.winOsDark(
colors: ScrollbarColors = ScrollbarColors.winOsDark(),
metrics: ScrollbarMetrics = provideScrollbarMetrics(),
trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot,
scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible,
): ScrollbarStyle =
ScrollbarStyle(
colors = colors,
metrics = metrics,
trackClickBehavior = trackClickBehavior,
scrollbarVisibility = scrollbarVisibility,
)
public fun ScrollbarStyle.Companion.winOsLight(
colors: ScrollbarColors = ScrollbarColors.winOsLight(),
metrics: ScrollbarMetrics = provideScrollbarMetrics(),
trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot,
scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible,
): ScrollbarStyle =
ScrollbarStyle(
colors = colors,
metrics = metrics,
trackClickBehavior = trackClickBehavior,
scrollbarVisibility = scrollbarVisibility,
)
public fun ScrollbarStyle.Companion.dark(): ScrollbarStyle =
if (hostOs.isMacOS) {
ScrollbarStyle.macOsDark()
} else {
ScrollbarStyle.winOsDark()
}
public fun ScrollbarStyle.Companion.light(): ScrollbarStyle =
if (hostOs.isMacOS) {
ScrollbarStyle.macOsLight()
} else {
ScrollbarStyle.winOsLight()
}
public fun ScrollbarVisibility.WhenScrolling.Companion.defaults(
appearAnimationDuration: Duration = 125.milliseconds,
disappearAnimationDuration: Duration = 125.milliseconds,
expandAnimationDuration: Duration = 125.milliseconds,
lingerDuration: Duration = 700.milliseconds,
): ScrollbarVisibility.WhenScrolling =
ScrollbarVisibility.WhenScrolling(
appearAnimationDuration = appearAnimationDuration,
disappearAnimationDuration = disappearAnimationDuration,
expandAnimationDuration = expandAnimationDuration,
lingerDuration = lingerDuration,
)
public fun ScrollbarColors.Companion.macOsLight(
thumbBackground: Color = Color(0x00000000),
thumbBackgroundHovered: Color = Color(0x80000000),
): ScrollbarColors = ScrollbarColors(thumbBackground, thumbBackgroundHovered)
thumbBackgroundPressed: Color = thumbBackgroundHovered,
thumbBorder: Color = Color(0x33000000),
thumbBorderHovered: Color = Color(0x80000000),
thumbBorderPressed: Color = thumbBorderHovered,
trackBackground: Color = Color(0x00808080),
trackBackgroundHovered: Color = Color(0x00808080),
): ScrollbarColors =
ScrollbarColors(
thumbBackground,
thumbBackgroundHovered,
thumbBackgroundPressed,
thumbBorder,
thumbBorderHovered,
thumbBorderPressed,
trackBackground,
trackBackgroundHovered,
)
@Composable
public fun ScrollbarColors.Companion.dark(
public fun ScrollbarColors.Companion.winOsLight(
thumbBackground: Color = Color(0x33737373),
thumbBackgroundHovered: Color = Color(0x47737373),
thumbBackgroundPressed: Color = thumbBackgroundHovered,
thumbBorder: Color = Color(0x33595959),
thumbBorderHovered: Color = Color(0x47595959),
thumbBorderPressed: Color = thumbBorderHovered,
trackBackground: Color = Color(0x00808080),
trackBackgroundHovered: Color = Color(0x1A808080),
): ScrollbarColors =
ScrollbarColors(
thumbBackground,
thumbBackgroundHovered,
thumbBackgroundPressed,
thumbBorder,
thumbBorderHovered,
thumbBorderPressed,
trackBackground,
trackBackgroundHovered,
)
public fun ScrollbarColors.Companion.macOsDark(
thumbBackground: Color = Color(0x59808080),
thumbBackgroundHovered: Color = Color(0x8C808080),
): ScrollbarColors = ScrollbarColors(thumbBackground, thumbBackgroundHovered)
thumbBackgroundPressed: Color = Color(0x8C808080),
thumbBorder: Color = Color(0x59262626),
thumbBorderHovered: Color = Color(0x8C262626),
thumbBorderPressed: Color = Color(0x8C262626),
trackBackground: Color = Color(0x00808080),
trackBackgroundHovered: Color = Color(0x1A808080),
): ScrollbarColors =
ScrollbarColors(
thumbBackground,
thumbBackgroundHovered,
thumbBackgroundPressed,
thumbBorder,
thumbBorderHovered,
thumbBorderPressed,
trackBackground,
trackBackgroundHovered,
)
public fun ScrollbarColors.Companion.winOsDark(
thumbBackground: Color = Color(0x47A6A6A6),
thumbBackgroundHovered: Color = Color(0x59A6A6A6),
thumbBackgroundPressed: Color = Color(0x59A6A6A6),
thumbBorder: Color = Color(0x47383838),
thumbBorderHovered: Color = Color(0x59A6A6A6),
thumbBorderPressed: Color = Color(0x59A6A6A6),
trackBackground: Color = Color(0x00808080),
trackBackgroundHovered: Color = Color(0x1A808080),
): ScrollbarColors =
ScrollbarColors(
thumbBackground,
thumbBackgroundHovered,
thumbBackgroundPressed,
thumbBorder,
thumbBorderHovered,
thumbBorderPressed,
trackBackground,
trackBackgroundHovered,
)
public fun ScrollbarMetrics.Companion.defaults(
thumbCornerSize: CornerSize = CornerSize(100),
thumbThickness: Dp = 8.dp,
minThumbLength: Dp = 20.dp,
trackPadding: PaddingValues = PaddingValues(2.dp),
thumbThicknessExpanded: Dp = 14.dp,
trackPaddingExpanded: PaddingValues = PaddingValues(2.dp),
): ScrollbarMetrics =
ScrollbarMetrics(
thumbCornerSize,
thumbThickness,
thumbThicknessExpanded,
minThumbLength,
trackPadding,
trackPaddingExpanded,
)
private fun provideScrollbarMetrics(): ScrollbarMetrics =
when {
hostOs.isMacOS -> ScrollbarMetrics.macOs()
hostOs.isLinux -> ScrollbarMetrics.linux()
else -> ScrollbarMetrics.windows()
}
public fun ScrollbarMetrics.Companion.macOs(
thumbCornerSize: CornerSize = CornerSize(100),
thumbThickness: Dp = 8.dp,
minThumbLength: Dp = 20.dp,
trackPadding: PaddingValues = PaddingValues(2.dp),
thumbThicknessExpanded: Dp = 14.dp,
trackPaddingExpanded: PaddingValues = PaddingValues(2.dp),
): ScrollbarMetrics =
ScrollbarMetrics(
thumbCornerSize,
thumbThickness,
thumbThicknessExpanded,
minThumbLength,
trackPadding,
trackPaddingExpanded,
)
public fun ScrollbarMetrics.Companion.windows(
thumbCornerSize: CornerSize = CornerSize(0),
thumbThickness: Dp = 8.dp,
minThumbLength: Dp = 16.dp,
trackPadding: PaddingValues = PaddingValues(start = 7.dp, end = 3.dp),
): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, thumbThickness, minThumbLength, trackPadding)
trackPadding: PaddingValues = PaddingValues(horizontal = 0.dp),
thumbThicknessExpanded: Dp = 8.dp,
trackPaddingExpanded: PaddingValues = PaddingValues(horizontal = 0.dp),
): ScrollbarMetrics =
ScrollbarMetrics(
thumbCornerSize,
thumbThickness,
thumbThicknessExpanded,
minThumbLength,
trackPadding,
trackPaddingExpanded,
)
public fun ScrollbarMetrics.Companion.linux(
thumbCornerSize: CornerSize = CornerSize(0),
thumbThickness: Dp = 8.dp,
minThumbLength: Dp = 16.dp,
trackPadding: PaddingValues = PaddingValues(horizontal = 0.dp),
thumbThicknessExpanded: Dp = 8.dp,
trackPaddingExpanded: PaddingValues = PaddingValues(horizontal = 0.dp),
): ScrollbarMetrics =
ScrollbarMetrics(
thumbCornerSize,
thumbThickness,
thumbThicknessExpanded,
minThumbLength,
trackPadding,
trackPaddingExpanded,
)

View File

@@ -7,6 +7,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme
import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.TabColors
import org.jetbrains.jewel.ui.component.styling.TabContentAlpha
import org.jetbrains.jewel.ui.component.styling.TabIcons
@@ -25,7 +26,8 @@ public object IntUiDefaultTabStyleFactory {
metrics: TabMetrics = TabMetrics.defaults(),
icons: TabIcons = TabIcons.defaults(),
contentAlpha: TabContentAlpha = TabContentAlpha.default(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha)
scrollbarStyle: ScrollbarStyle = ScrollbarStyle.macOsLight(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha, scrollbarStyle)
@Composable
public fun dark(
@@ -33,7 +35,8 @@ public object IntUiDefaultTabStyleFactory {
metrics: TabMetrics = TabMetrics.defaults(),
icons: TabIcons = TabIcons.defaults(),
contentAlpha: TabContentAlpha = TabContentAlpha.default(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha)
scrollbarStyle: ScrollbarStyle = ScrollbarStyle.dark(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha, scrollbarStyle)
}
public val TabStyle.Companion.Editor: IntUiEditorTabStyleFactory
@@ -46,7 +49,8 @@ public object IntUiEditorTabStyleFactory {
metrics: TabMetrics = TabMetrics.defaults(),
icons: TabIcons = TabIcons.defaults(),
contentAlpha: TabContentAlpha = TabContentAlpha.editor(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha)
scrollbarStyle: ScrollbarStyle = ScrollbarStyle.light(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha, scrollbarStyle)
@Composable
public fun dark(
@@ -54,7 +58,8 @@ public object IntUiEditorTabStyleFactory {
metrics: TabMetrics = TabMetrics.defaults(),
icons: TabIcons = TabIcons.defaults(),
contentAlpha: TabContentAlpha = TabContentAlpha.editor(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha)
scrollbarStyle: ScrollbarStyle = ScrollbarStyle.dark(),
): TabStyle = TabStyle(colors, metrics, icons, contentAlpha, scrollbarStyle)
}
public val TabColors.Companion.Default: IntUiDefaultTabColorsFactory

View File

@@ -0,0 +1,190 @@
package org.jetbrains.jewel.samples.standalone.view.component
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.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.intui.standalone.styling.defaults
import org.jetbrains.jewel.intui.standalone.styling.macOsDark
import org.jetbrains.jewel.intui.standalone.styling.macOsLight
import org.jetbrains.jewel.intui.standalone.styling.winOsDark
import org.jetbrains.jewel.intui.standalone.styling.winOsLight
import org.jetbrains.jewel.samples.standalone.viewmodel.View
import org.jetbrains.jewel.ui.Orientation
import org.jetbrains.jewel.ui.component.CheckboxRow
import org.jetbrains.jewel.ui.component.Divider
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.component.VerticalScrollbar
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
import org.jetbrains.jewel.ui.theme.scrollbarStyle
import org.jetbrains.skiko.hostOs
import java.util.Locale
@Composable
@View(title = "Scrollbars", position = 14, icon = "icons/components/scrollbar.svg")
fun Scrollbars() {
Column {
val isDark = JewelTheme.isDark
var alwaysVisible by remember { mutableStateOf(false) }
val initialStyle by remember { mutableStateOf(readStyle(hostOs.isMacOS, isDark)) }
var style by remember { mutableStateOf(initialStyle) }
LaunchedEffect(alwaysVisible) {
style =
if (alwaysVisible) {
ScrollbarStyle(
colors = style.colors,
metrics = style.metrics,
trackClickBehavior = style.trackClickBehavior,
scrollbarVisibility = ScrollbarVisibility.AlwaysVisible,
)
} else {
ScrollbarStyle(
colors = style.colors,
metrics = style.metrics,
trackClickBehavior = style.trackClickBehavior,
scrollbarVisibility = ScrollbarVisibility.WhenScrolling.defaults(),
)
}
}
CheckboxRow(
checked = alwaysVisible,
onCheckedChange = { alwaysVisible = it },
text = "Always visible",
)
Spacer(modifier = Modifier.height(16.dp))
Row(
Modifier.padding(horizontal = 16.dp).height(200.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column {
Text("LazyColumn", fontSize = 18.sp)
Spacer(Modifier.height(8.dp))
Box(Modifier.border(1.dp, JewelTheme.globalColors.borders.normal)) {
val scrollState = rememberLazyListState()
LazyColumn(
Modifier
.width(200.dp)
.padding(end = JewelTheme.scrollbarStyle.metrics.thumbThicknessExpanded)
.align(Alignment.CenterStart),
verticalArrangement = Arrangement.spacedBy(4.dp),
state = scrollState,
) {
items(LIST_ITEMS) { item ->
Column {
Text(
modifier = Modifier.padding(horizontal = 8.dp),
text = item,
)
Divider(orientation = Orientation.Horizontal, color = Color.Gray)
}
}
}
VerticalScrollbar(
scrollState = scrollState,
modifier = Modifier.align(Alignment.CenterEnd),
style = style,
)
}
}
Column {
Text("Column", fontSize = 18.sp)
Spacer(Modifier.height(8.dp))
Box(Modifier.border(1.dp, JewelTheme.globalColors.borders.normal)) {
val scrollState = rememberScrollState()
Column(
modifier =
Modifier
.verticalScroll(scrollState)
.padding(end = JewelTheme.scrollbarStyle.metrics.thumbThicknessExpanded)
.align(Alignment.CenterStart),
) {
LIST_ITEMS.forEach {
Text(
modifier = Modifier.padding(horizontal = 8.dp),
text = it,
)
}
}
VerticalScrollbar(
scrollState = scrollState,
modifier = Modifier.align(Alignment.CenterEnd),
style = style,
)
}
}
}
}
}
fun readStyle(
isMac: Boolean,
isDark: Boolean,
): ScrollbarStyle =
if (isDark) {
if (isMac) {
ScrollbarStyle.macOsDark()
} else {
ScrollbarStyle.winOsDark()
}
} else {
if (isMac) {
ScrollbarStyle.macOsLight()
} else {
ScrollbarStyle.winOsLight()
}
}
@Suppress("SpellCheckingInspection")
private const val LOREM_IPSUM =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. \n" +
"Sed auctor, neque in accumsan vehicula, enim purus vestibulum odio, non tristique dolor quam vel ipsum. \n" +
"Proin egestas, orci id hendrerit bibendum, nisl neque imperdiet nisl, a euismod nibh diam nec lectus. \n" +
"Duis euismod, quam nec aliquam iaculis, dolor lorem bibendum turpis, vel malesuada augue sapien vel mi. \n" +
"Quisque ut facilisis nibh. Maecenas euismod hendrerit sem, ac scelerisque odio auctor nec. \n" +
"Sed sit amet consequat eros. Donec nisl tellus, accumsan nec ligula in, eleifend sodales sem. \n" +
"Sed malesuada, nulla ac eleifend fermentum, nibh mi consequat quam, quis convallis lacus nunc eu dui. \n" +
"Pellentesque eget enim quis orci porttitor consequat sed sed quam. \n" +
"Sed aliquam, nisl et lacinia lacinia, diam nunc laoreet nisi, sit amet consectetur dolor lorem et sem. \n" +
"Duis ultricies, mauris in aliquam interdum, orci nulla finibus massa, a tristique urna sapien vel quam. \n" +
"Sed nec sapien nec dui rhoncus bibendum. Sed blandit bibendum libero."
private val LIST_ITEMS =
LOREM_IPSUM
.split(",")
.map { lorem ->
lorem
.trim()
.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}
}

View File

@@ -3,6 +3,7 @@
package org.jetbrains.jewel.samples.standalone.view.component
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.fillMaxWidth
@@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -27,16 +29,19 @@ import org.jetbrains.jewel.ui.component.SimpleTabContent
import org.jetbrains.jewel.ui.component.TabData
import org.jetbrains.jewel.ui.component.TabStrip
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.component.styling.TabStyle
import org.jetbrains.jewel.ui.icons.AllIconsKeys
import org.jetbrains.jewel.ui.painter.hints.Stateful
import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider
import org.jetbrains.jewel.ui.theme.defaultTabStyle
import org.jetbrains.jewel.ui.theme.editorTabStyle
import org.jetbrains.jewel.ui.util.thenIf
import kotlin.math.max
@Composable
@View(title = "Tabs", position = 7, icon = "icons/components/tabs.svg")
fun Tabs() {
Column {
Text("Default tabs", Modifier.fillMaxWidth())
DefaultTabShowcase()
@@ -44,10 +49,11 @@ fun Tabs() {
Text("Editor tabs", Modifier.fillMaxWidth())
EditorTabShowcase()
}
}
@Composable
private fun DefaultTabShowcase() {
var selectedTabIndex by remember { mutableStateOf(0) }
var selectedTabIndex by remember { mutableIntStateOf(0) }
var tabIds by remember { mutableStateOf((1..12).toList()) }
val maxId = remember(tabIds) { tabIds.maxOrNull() ?: 0 }
@@ -80,12 +86,13 @@ private fun DefaultTabShowcase() {
}
}
TabStripWithAddButton(tabs) {
TabStripWithAddButton(tabs, JewelTheme.defaultTabStyle) {
val insertionIndex = (selectedTabIndex + 1).coerceIn(0..tabIds.size)
val nextTabId = maxId + 1
tabIds =
tabIds.toMutableList()
tabIds
.toMutableList()
.apply { add(insertionIndex, nextTabId) }
selectedTabIndex = insertionIndex
}
@@ -93,7 +100,7 @@ private fun DefaultTabShowcase() {
@Composable
private fun EditorTabShowcase() {
var selectedTabIndex by remember { mutableStateOf(0) }
var selectedTabIndex by remember { mutableIntStateOf(0) }
var tabIds by remember { mutableStateOf((1..12).toList()) }
val maxId = remember(tabIds) { tabIds.maxOrNull() ?: 0 }
@@ -145,12 +152,13 @@ private fun EditorTabShowcase() {
}
}
TabStripWithAddButton(tabs) {
TabStripWithAddButton(tabs, JewelTheme.editorTabStyle) {
val insertionIndex = (selectedTabIndex + 1).coerceIn(0..tabIds.size)
val nextTabId = maxId + 1
tabIds =
tabIds.toMutableList()
tabIds
.toMutableList()
.apply { add(insertionIndex, nextTabId) }
selectedTabIndex = insertionIndex
}
@@ -159,10 +167,11 @@ private fun EditorTabShowcase() {
@Composable
private fun TabStripWithAddButton(
tabs: List<TabData>,
style: TabStyle,
onAddClick: () -> Unit,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
TabStrip(tabs, modifier = Modifier.weight(1f))
TabStrip(tabs, style, modifier = Modifier.weight(1f))
IconButton(
onClick = onAddClick,

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg fill="none" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<polyline points="0 2 1 0.4 2 2 0 2" fill="#6C707E" transform="translate(14, 1)"/>
<rect x="14" y="4" width="2" height="7" rx="1" ry="1" fill="#6C707E"/>
<polyline points="0 2 1 0.4 2 2 0 2" fill="#6C707E" transform="translate(14, 14) rotate(180, 1, 1)"/>
<line x1="1" y1="3" x2="10" y2="3" stroke="#6C707E"/>
<line x1="1" y1="6" x2="8" y2="6" stroke="#6C707E"/>
<line x1="1" y1="9" x2="11" y2="9" stroke="#6C707E"/>
<line x1="1" y1="12" x2="11" y2="12" stroke="#6C707E"/>
<line x1="1" y1="15" x2="9" y2="15" stroke="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg fill="none" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<polyline points="0 2 1 0.4 2 2 0 2" fill="#CED0D6" transform="translate(14, 1)"/>
<rect x="14" y="4" width="2" height="7" rx="1" ry="1" fill="#CED0D6"/>
<polyline points="0 2 1 0.4 2 2 0 2" fill="#CED0D6" transform="translate(14, 14) rotate(180, 1, 1)"/>
<line x1="1" y1="3" x2="10" y2="3" stroke="#CED0D6"/>
<line x1="1" y1="6" x2="8" y2="6" stroke="#CED0D6"/>
<line x1="1" y1="9" x2="11" y2="9" stroke="#CED0D6"/>
<line x1="1" y1="12" x2="11" y2="12" stroke="#CED0D6"/>
<line x1="1" y1="15" x2="9" y2="15" stroke="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

View File

@@ -578,8 +578,10 @@ public final class org/jetbrains/jewel/ui/component/RadioButtonState$Companion {
}
public final class org/jetbrains/jewel/ui/component/ScrollbarsKt {
public static final fun HorizontalScrollbar (Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/ui/Modifier;ZLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun HorizontalScrollbar (Landroidx/compose/foundation/v2/ScrollbarAdapter;Landroidx/compose/ui/Modifier;ZLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun TabStripHorizontalScrollbar (Landroidx/compose/foundation/v2/ScrollbarAdapter;Landroidx/compose/ui/Modifier;ZLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun TabStripHorizontalScrollbar (Landroidx/compose/foundation/v2/ScrollbarAdapter;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/ui/Modifier;ZLandroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;II)V
public static final fun VerticalScrollbar (Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/ui/Modifier;ZLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun VerticalScrollbar (Landroidx/compose/foundation/v2/ScrollbarAdapter;Landroidx/compose/ui/Modifier;ZLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)V
}
@@ -824,7 +826,7 @@ public final class org/jetbrains/jewel/ui/component/TabState$Companion {
}
public final class org/jetbrains/jewel/ui/component/TabStripKt {
public static final fun TabStrip (Ljava/util/List;Landroidx/compose/ui/Modifier;ZLandroidx/compose/runtime/Composer;II)V
public static final fun TabStrip (Ljava/util/List;Lorg/jetbrains/jewel/ui/component/styling/TabStyle;Landroidx/compose/ui/Modifier;ZLandroidx/compose/runtime/Composer;II)V
}
public final class org/jetbrains/jewel/ui/component/TabStripState : org/jetbrains/jewel/foundation/state/FocusableComponentState {
@@ -1858,10 +1860,16 @@ public final class org/jetbrains/jewel/ui/component/styling/RadioButtonStylingKt
public final class org/jetbrains/jewel/ui/component/styling/ScrollbarColors {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;
public synthetic fun <init> (JJLkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (JJJJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getThumbBackground-0d7_KjU ()J
public final fun getThumbBackgroundHovered-0d7_KjU ()J
public final fun getThumbBackgroundPressed-0d7_KjU ()J
public final fun getThumbBorder-0d7_KjU ()J
public final fun getThumbBorderHovered-0d7_KjU ()J
public final fun getThumbBorderPressed-0d7_KjU ()J
public final fun getTrackBackground-0d7_KjU ()J
public final fun getTrackBackgroundHovered-0d7_KjU ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -1872,12 +1880,14 @@ public final class org/jetbrains/jewel/ui/component/styling/ScrollbarColors$Comp
public final class org/jetbrains/jewel/ui/component/styling/ScrollbarMetrics {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;
public synthetic fun <init> (Landroidx/compose/foundation/shape/CornerSize;FFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Landroidx/compose/foundation/shape/CornerSize;FFFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getMinThumbLength-D9Ej5fM ()F
public final fun getThumbCornerSize ()Landroidx/compose/foundation/shape/CornerSize;
public final fun getThumbThickness-D9Ej5fM ()F
public final fun getThumbThicknessExpanded-D9Ej5fM ()F
public final fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues;
public final fun getTrackPaddingExpanded ()Landroidx/compose/foundation/layout/PaddingValues;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -1888,11 +1898,12 @@ public final class org/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Com
public final class org/jetbrains/jewel/ui/component/styling/ScrollbarStyle {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;
public synthetic fun <init> (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;JLkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getColors ()Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors;
public final fun getHoverDuration-UwyO8pc ()J
public final fun getMetrics ()Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics;
public final fun getScrollbarVisibility ()Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility;
public final fun getTrackClickBehavior ()Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -1904,6 +1915,33 @@ public final class org/jetbrains/jewel/ui/component/styling/ScrollbarStylingKt {
public static final fun getLocalScrollbarStyle ()Landroidx/compose/runtime/ProvidableCompositionLocal;
}
public abstract interface class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility {
}
public final class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible : org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility {
public static final field $stable I
public static final field INSTANCE Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible;
public fun equals (Ljava/lang/Object;)Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling : org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;
public synthetic fun <init> (JJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getAppearAnimationDuration-UwyO8pc ()J
public final fun getDisappearAnimationDuration-UwyO8pc ()J
public final fun getExpandAnimationDuration-UwyO8pc ()J
public final fun getLingerDuration-UwyO8pc ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion {
}
public final class org/jetbrains/jewel/ui/component/styling/SegmentedControlButtonColors {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/SegmentedControlButtonColors$Companion;
@@ -2177,12 +2215,13 @@ public final class org/jetbrains/jewel/ui/component/styling/TabMetrics$Companion
public final class org/jetbrains/jewel/ui/component/styling/TabStyle {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/TabStyle$Companion;
public fun <init> (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;)V
public fun <init> (Lorg/jetbrains/jewel/ui/component/styling/TabColors;Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;Lorg/jetbrains/jewel/ui/component/styling/TabIcons;Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getColors ()Lorg/jetbrains/jewel/ui/component/styling/TabColors;
public final fun getContentAlpha ()Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha;
public final fun getIcons ()Lorg/jetbrains/jewel/ui/component/styling/TabIcons;
public final fun getMetrics ()Lorg/jetbrains/jewel/ui/component/styling/TabMetrics;
public final fun getScrollbarStyle ()Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -2395,6 +2434,14 @@ public final class org/jetbrains/jewel/ui/component/styling/TooltipStylingKt {
public static final fun getLocalTooltipStyle ()Landroidx/compose/runtime/ProvidableCompositionLocal;
}
public final class org/jetbrains/jewel/ui/component/styling/TrackClickBehavior : java/lang/Enum {
public static final field JumpToSpot Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;
public static final field NextPage Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;
public static fun values ()[Lorg/jetbrains/jewel/ui/component/styling/TrackClickBehavior;
}
public abstract interface class org/jetbrains/jewel/ui/icon/IconKey {
public abstract fun getIconClass ()Ljava/lang/Class;
public abstract fun path (Z)Ljava/lang/String;

View File

@@ -1,24 +1,229 @@
package org.jetbrains.jewel.ui.component
import androidx.compose.foundation.HorizontalScrollbar
import androidx.compose.foundation.LocalScrollbarStyle
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitHorizontalDragOrCancellation
import androidx.compose.foundation.gestures.awaitVerticalDragOrCancellation
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextFieldScrollState
import androidx.compose.foundation.v2.ScrollbarAdapter
import androidx.compose.foundation.v2.maxScrollOffset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility.AlwaysVisible
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility.WhenScrolling
import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior.JumpToSpot
import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior.NextPage
import org.jetbrains.jewel.ui.theme.scrollbarStyle
import kotlin.time.DurationUnit
import androidx.compose.foundation.ScrollbarStyle as ComposeScrollbarStyle
import org.jetbrains.jewel.ui.util.thenIf
import kotlin.math.roundToInt
@Composable
public fun VerticalScrollbar(
scrollState: ScrollableState,
modifier: Modifier = Modifier,
reverseLayout: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: ScrollbarStyle = JewelTheme.scrollbarStyle,
) {
MyScrollbar(
scrollState = scrollState,
modifier = modifier,
reverseLayout = reverseLayout,
interactionSource = interactionSource,
isVertical = true,
style = style,
)
}
@Composable
public fun HorizontalScrollbar(
scrollState: ScrollableState,
modifier: Modifier = Modifier,
reverseLayout: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: ScrollbarStyle = JewelTheme.scrollbarStyle,
) {
MyScrollbar(
scrollState = scrollState,
modifier = modifier,
reverseLayout = reverseLayout,
interactionSource = interactionSource,
isVertical = false,
style = style,
)
}
@Composable
private fun MyScrollbar(
scrollState: ScrollableState,
modifier: Modifier = Modifier,
reverseLayout: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
isVertical: Boolean,
style: ScrollbarStyle,
) {
// Click to scroll
var clickPosition by remember { mutableIntStateOf(0) }
val scrollbarWidth = remember { mutableIntStateOf(0) }
val scrollbarHeight = remember { mutableIntStateOf(0) }
LaunchedEffect(clickPosition) {
if (scrollState is ScrollState) {
if (scrollbarHeight.value == 0) return@LaunchedEffect
val jumpTo = when (style.trackClickBehavior) {
NextPage -> scrollbarHeight.value + scrollState.viewportSize
JumpToSpot -> (scrollState.maxValue * clickPosition) / scrollbarHeight.value
}
scrollState.scrollTo(jumpTo)
}
}
// Visibility, hover and fade out
var visible by remember { mutableStateOf(scrollState.canScrollBackward) }
val hovered = interactionSource.collectIsHoveredAsState().value
var trackIsVisible by remember { mutableStateOf(false) }
val animatedAlpha by animateFloatAsState(
targetValue = if (visible) 1.0f else 0f,
label = "alpha",
)
LaunchedEffect(scrollState.isScrollInProgress, hovered, style.scrollbarVisibility) {
when (style.scrollbarVisibility) {
AlwaysVisible -> {
visible = true
trackIsVisible = true
}
is WhenScrolling -> {
when {
scrollState.isScrollInProgress -> visible = true
hovered -> {
visible = true
trackIsVisible = true
}
!hovered -> {
delay(style.scrollbarVisibility.lingerDuration)
trackIsVisible = false
visible = false
}
!scrollState.isScrollInProgress && !hovered -> {
delay(style.scrollbarVisibility.lingerDuration)
visible = false
}
}
}
}
when {
scrollState.isScrollInProgress -> visible = true
hovered -> {
visible = true
trackIsVisible = true
}
}
}
val adapter =
when (scrollState) {
is LazyListState -> rememberScrollbarAdapter(scrollState)
is LazyGridState -> rememberScrollbarAdapter(scrollState)
is ScrollState -> rememberScrollbarAdapter(scrollState)
is TextFieldScrollState -> rememberScrollbarAdapter(scrollState)
else -> error("Unsupported scroll state type: ${scrollState::class}")
}
val thumbWidth = if (trackIsVisible) style.metrics.thumbThicknessExpanded else style.metrics.thumbThickness
val trackBackground = if (trackIsVisible) style.colors.trackBackground else Color.Transparent
val trackPadding = if (trackIsVisible) style.metrics.trackPaddingExpanded else style.metrics.trackPadding
ScrollbarImpl(
adapter = adapter,
modifier =
modifier
.alpha(animatedAlpha)
.animateContentSize()
.width(thumbWidth)
.background(trackBackground)
.padding(trackPadding)
.scrollable(
scrollState,
orientation = Orientation.Vertical,
reverseDirection = true,
).pointerInput(Unit) {
detectTapGestures { offset ->
clickPosition = offset.y.toInt()
}
}.onSizeChanged {
scrollbarWidth.value = it.width
scrollbarHeight.value = it.height
},
reverseLayout = reverseLayout,
style = style,
interactionSource = interactionSource,
isVertical = isVertical,
)
}
@Composable
public fun VerticalScrollbar(
@@ -28,29 +233,15 @@ public fun VerticalScrollbar(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: ScrollbarStyle = JewelTheme.scrollbarStyle,
) {
val shape by remember { mutableStateOf(RoundedCornerShape(style.metrics.thumbCornerSize)) }
val hoverDurationMillis by remember { mutableStateOf(style.hoverDuration.inWholeMilliseconds) }
val composeScrollbarStyle =
ComposeScrollbarStyle(
minimalHeight = style.metrics.minThumbLength,
thickness = style.metrics.thumbThickness,
shape = shape,
hoverDurationMillis = hoverDurationMillis.toInt(),
unhoverColor = style.colors.thumbBackground,
hoverColor = style.colors.thumbBackgroundHovered,
)
CompositionLocalProvider(LocalScrollbarStyle provides composeScrollbarStyle) {
VerticalScrollbar(
ScrollbarImpl(
adapter = adapter,
modifier = modifier.padding(style.metrics.trackPadding),
modifier = modifier,
reverseLayout = reverseLayout,
style = LocalScrollbarStyle.current,
style = style,
interactionSource = interactionSource,
isVertical = true,
)
}
}
@Composable
public fun HorizontalScrollbar(
@@ -60,60 +251,458 @@ public fun HorizontalScrollbar(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: ScrollbarStyle = JewelTheme.scrollbarStyle,
) {
val shape by remember { mutableStateOf(RoundedCornerShape(style.metrics.thumbCornerSize)) }
val hoverDurationMillis by remember {
mutableStateOf(style.hoverDuration.toInt(DurationUnit.MILLISECONDS))
}
val composeScrollbarStyle =
ComposeScrollbarStyle(
minimalHeight = style.metrics.minThumbLength,
thickness = style.metrics.thumbThickness,
shape = shape,
hoverDurationMillis = hoverDurationMillis,
unhoverColor = style.colors.thumbBackground,
hoverColor = style.colors.thumbBackgroundHovered,
)
CompositionLocalProvider(LocalScrollbarStyle provides composeScrollbarStyle) {
HorizontalScrollbar(
ScrollbarImpl(
adapter = adapter,
modifier = modifier.padding(style.metrics.trackPadding),
modifier = modifier,
reverseLayout = reverseLayout,
style = LocalScrollbarStyle.current,
style = style,
interactionSource = interactionSource,
isVertical = false,
)
}
}
@Deprecated("Use HorizontalScrollbar with an appropriate style.")
@Composable
public fun TabStripHorizontalScrollbar(
adapter: ScrollbarAdapter,
style: ScrollbarStyle,
modifier: Modifier = Modifier,
reverseLayout: Boolean = false,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: ScrollbarStyle = JewelTheme.scrollbarStyle,
) {
val shape by remember { mutableStateOf(RoundedCornerShape(style.metrics.thumbCornerSize)) }
val hoverDurationMillis by remember {
mutableStateOf(style.hoverDuration.inWholeMilliseconds.toInt())
}
val composeScrollbarStyle =
ComposeScrollbarStyle(
minimalHeight = style.metrics.minThumbLength,
thickness = 3.dp,
shape = shape,
hoverDurationMillis = hoverDurationMillis,
unhoverColor = style.colors.thumbBackground,
hoverColor = style.colors.thumbBackgroundHovered,
)
CompositionLocalProvider(LocalScrollbarStyle provides composeScrollbarStyle) {
HorizontalScrollbar(
adapter = adapter,
modifier = modifier.padding(1.dp),
reverseLayout = reverseLayout,
style = LocalScrollbarStyle.current,
style = style,
interactionSource = interactionSource,
)
}
// ===========================================================================
// Note: most of the code below is copied and adapted from the stock scrollbar
// ===========================================================================
@Composable
private fun ScrollbarImpl(
adapter: ScrollbarAdapter,
reverseLayout: Boolean,
style: ScrollbarStyle,
interactionSource: MutableInteractionSource,
isVertical: Boolean,
modifier: Modifier = Modifier,
) {
with(LocalDensity.current) {
val dragInteraction = remember { mutableStateOf<DragInteraction.Start?>(null) }
DisposableEffect(interactionSource) {
onDispose {
dragInteraction.value?.let { interaction ->
interactionSource.tryEmit(DragInteraction.Cancel(interaction))
dragInteraction.value = null
}
}
}
var containerSize by remember { mutableIntStateOf(0) }
val isHovered by interactionSource.collectIsHoveredAsState()
val isHighlighted by remember {
derivedStateOf { isHovered || dragInteraction.value is DragInteraction.Start }
}
val thumbMinHeight = style.metrics.minThumbLength.toPx()
val coroutineScope = rememberCoroutineScope()
val sliderAdapter =
remember(
adapter,
containerSize,
thumbMinHeight,
reverseLayout,
isVertical,
coroutineScope,
) {
SliderAdapter(adapter, containerSize, thumbMinHeight, reverseLayout, isVertical, coroutineScope)
}
val thumbThickness = style.metrics.thumbThickness.roundToPx()
val measurePolicy =
if (isVertical) {
remember(sliderAdapter, thumbThickness) {
verticalMeasurePolicy(sliderAdapter, { containerSize = it }, thumbThickness)
}
} else {
remember(sliderAdapter, thumbThickness) {
horizontalMeasurePolicy(sliderAdapter, { containerSize = it }, thumbThickness)
}
}
val thumbBackgroundColor = if (isHighlighted) {
style.colors.thumbBackground
} else {
style.colors.thumbBackgroundHovered
}
val thumbColor = if (style.scrollbarVisibility is WhenScrolling) {
val durationMillis = style.scrollbarVisibility.expandAnimationDuration.inWholeMilliseconds.toInt()
animateColorAsState(
targetValue = thumbBackgroundColor,
animationSpec = tween(durationMillis),
).value
} else {
thumbBackgroundColor
}
val isVisible = sliderAdapter.thumbSize < containerSize
Layout(
{
Box(
Modifier
.layoutId("thumb")
.thenIf(isVisible) {
background(
color = thumbColor,
shape = RoundedCornerShape(style.metrics.thumbCornerSize),
)
}.scrollbarDrag(
interactionSource = interactionSource,
draggedInteraction = dragInteraction,
sliderAdapter = sliderAdapter,
),
)
},
modifier
.hoverable(interactionSource = interactionSource)
.scrollOnPressTrack(isVertical, reverseLayout, sliderAdapter),
measurePolicy,
)
}
}
private val SliderAdapter.thumbPixelRange: IntRange
get() {
val start = position.roundToInt()
val endExclusive = start + thumbSize.roundToInt()
return (start until endExclusive)
}
private val IntRange.size get() = last + 1 - first
private fun verticalMeasurePolicy(
sliderAdapter: SliderAdapter,
setContainerSize: (Int) -> Unit,
scrollThickness: Int,
) = MeasurePolicy { measurables, constraints ->
setContainerSize(constraints.maxHeight)
val pixelRange = sliderAdapter.thumbPixelRange
val placeable =
measurables.first().measure(
Constraints.fixed(
constraints.constrainWidth(scrollThickness),
pixelRange.size,
),
)
layout(placeable.width, constraints.maxHeight) {
placeable.place(0, pixelRange.first)
}
}
private fun horizontalMeasurePolicy(
sliderAdapter: SliderAdapter,
setContainerSize: (Int) -> Unit,
scrollThickness: Int,
) = MeasurePolicy { measurables, constraints ->
setContainerSize(constraints.maxWidth)
val pixelRange = sliderAdapter.thumbPixelRange
val placeable =
measurables.first().measure(
Constraints.fixed(
pixelRange.size,
constraints.constrainHeight(scrollThickness),
),
)
layout(constraints.maxWidth, placeable.height) {
placeable.place(pixelRange.first, 0)
}
}
private fun Modifier.scrollbarDrag(
interactionSource: MutableInteractionSource,
draggedInteraction: MutableState<DragInteraction.Start?>,
sliderAdapter: SliderAdapter,
): Modifier =
composed {
val currentInteractionSource by rememberUpdatedState(interactionSource)
val currentDraggedInteraction by rememberUpdatedState(draggedInteraction)
val currentSliderAdapter by rememberUpdatedState(sliderAdapter)
pointerInput(Unit) {
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
val interaction = DragInteraction.Start()
currentInteractionSource.tryEmit(interaction)
currentDraggedInteraction.value = interaction
currentSliderAdapter.onDragStarted()
val isSuccess =
drag(down.id) { change ->
currentSliderAdapter.onDragDelta(change.positionChange())
change.consume()
}
val finishInteraction =
if (isSuccess) {
DragInteraction.Stop(interaction)
} else {
DragInteraction.Cancel(interaction)
}
currentInteractionSource.tryEmit(finishInteraction)
currentDraggedInteraction.value = null
}
}
}
private fun Modifier.scrollOnPressTrack(
isVertical: Boolean,
reverseLayout: Boolean,
sliderAdapter: SliderAdapter,
) = composed {
val coroutineScope = rememberCoroutineScope()
val scroller =
remember(sliderAdapter, coroutineScope, reverseLayout) {
TrackPressScroller(coroutineScope, sliderAdapter, reverseLayout)
}
Modifier.pointerInput(scroller) {
detectScrollViaTrackGestures(
isVertical = isVertical,
scroller = scroller,
)
}
}
/**
* Responsible for scrolling when the scrollbar track is pressed (outside
* the thumb).
*/
private class TrackPressScroller(
private val coroutineScope: CoroutineScope,
private val sliderAdapter: SliderAdapter,
private val reverseLayout: Boolean,
) {
/**
* The current direction of scroll (1: down/right, -1: up/left, 0: not
* scrolling)
*/
private var direction = 0
/** The currently pressed location (in pixels) on the scrollable axis. */
private var offset: Float? = null
/** The job that keeps scrolling while the track is pressed. */
private var job: Job? = null
/**
* Calculates the direction of scrolling towards the given offset (in
* pixels).
*/
private fun directionOfScrollTowards(offset: Float): Int {
val pixelRange = sliderAdapter.thumbPixelRange
return when {
offset < pixelRange.first -> if (reverseLayout) 1 else -1
offset > pixelRange.last -> if (reverseLayout) -1 else 1
else -> 0
}
}
/**
* Scrolls once towards the current offset, if it matches the direction of
* the current gesture.
*/
private suspend fun scrollTowardsCurrentOffset() {
offset?.let {
val currentDirection = directionOfScrollTowards(it)
if (currentDirection != direction) {
return
}
with(sliderAdapter.adapter) {
scrollTo(scrollOffset + currentDirection * viewportSize)
}
}
}
/** Starts the job that scrolls continuously towards the current offset. */
private fun startScrolling() {
job?.cancel()
job =
coroutineScope.launch {
scrollTowardsCurrentOffset()
delay(DELAY_BEFORE_SECOND_SCROLL_ON_TRACK_PRESS)
while (true) {
scrollTowardsCurrentOffset()
delay(DELAY_BETWEEN_SCROLLS_ON_TRACK_PRESS)
}
}
}
/** Invoked on the first press for a gesture. */
fun onPress(offset: Float) {
this.offset = offset
this.direction = directionOfScrollTowards(offset)
if (direction != 0) {
startScrolling()
}
}
/** Invoked when the pointer moves while pressed during the gesture. */
fun onMovePressed(offset: Float) {
this.offset = offset
}
/** Cleans up when the gesture finishes. */
private fun cleanupAfterGesture() {
job?.cancel()
direction = 0
offset = null
}
/** Invoked when the button is released. */
fun onRelease() {
cleanupAfterGesture()
}
/** Invoked when the gesture is cancelled. */
fun onGestureCancelled() {
cleanupAfterGesture()
// Maybe revert to the initial position?
}
}
/**
* Detects the pointer events relevant for the "scroll by pressing on the
* track outside the thumb" gesture and calls the corresponding methods in
* the [scroller].
*/
private suspend fun PointerInputScope.detectScrollViaTrackGestures(
isVertical: Boolean,
scroller: TrackPressScroller,
) {
fun Offset.onScrollAxis() = if (isVertical) y else x
awaitEachGesture {
val down = awaitFirstDown()
scroller.onPress(down.position.onScrollAxis())
while (true) {
val drag =
if (isVertical) {
awaitVerticalDragOrCancellation(down.id)
} else {
awaitHorizontalDragOrCancellation(down.id)
}
if (drag == null) {
scroller.onGestureCancelled()
break
} else if (!drag.pressed) {
scroller.onRelease()
break
} else {
scroller.onMovePressed(drag.position.onScrollAxis())
}
}
}
}
/**
* The delay between the 1st and 2nd scroll while the scrollbar track is
* pressed outside the thumb.
*/
internal const val DELAY_BEFORE_SECOND_SCROLL_ON_TRACK_PRESS: Long = 300L
/**
* The delay between each subsequent (after the 2nd) scroll while the
* scrollbar track is pressed outside the thumb.
*/
internal const val DELAY_BETWEEN_SCROLLS_ON_TRACK_PRESS: Long = 100L
internal class SliderAdapter(
val adapter: ScrollbarAdapter,
private val trackSize: Int,
private val minHeight: Float,
private val reverseLayout: Boolean,
private val isVertical: Boolean,
private val coroutineScope: CoroutineScope,
) {
private val contentSize get() = adapter.contentSize
private val visiblePart: Double
get() {
val contentSize = contentSize
return if (contentSize == 0.0) {
1.0
} else {
(adapter.viewportSize / contentSize).coerceAtMost(1.0)
}
}
val thumbSize
get() = (trackSize * visiblePart).coerceAtLeast(minHeight.toDouble())
private val scrollScale: Double
get() {
val extraScrollbarSpace = trackSize - thumbSize
val extraContentSpace = adapter.maxScrollOffset // == contentSize - viewportSize
return if (extraContentSpace == 0.0) 1.0 else extraScrollbarSpace / extraContentSpace
}
private val rawPosition: Double
get() = scrollScale * adapter.scrollOffset
val position: Double
get() = if (reverseLayout) trackSize - thumbSize - rawPosition else rawPosition
val bounds get() = position..position + thumbSize
// How much of the current drag was ignored because we've reached the end of the scrollbar area
private var unscrolledDragDistance = 0.0
/** Called when the thumb dragging starts */
fun onDragStarted() {
unscrolledDragDistance = 0.0
}
private suspend fun setPosition(value: Double) {
val rawPosition =
if (reverseLayout) {
trackSize - thumbSize - value
} else {
value
}
adapter.scrollTo(rawPosition / scrollScale)
}
private val dragMutex = Mutex()
/** Called on every movement while dragging the thumb */
fun onDragDelta(offset: Offset) {
coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
// Mutex is used to ensure that all earlier drag deltas were applied
// before calculating a new raw position
dragMutex.withLock {
val dragDelta = if (isVertical) offset.y else offset.x
val maxScrollPosition = adapter.maxScrollOffset * scrollScale
val currentPosition = position
val targetPosition =
(currentPosition + dragDelta + unscrolledDragDistance).coerceIn(
0.0,
maxScrollPosition,
)
val sliderDelta = targetPosition - currentPosition
// Have to add to position for smooth content scroll if the items are of different size
val newPos = position + sliderDelta
setPosition(newPos)
unscrolledDragDistance += dragDelta - sliderDelta
}
}
}
}

View File

@@ -29,10 +29,12 @@ import org.jetbrains.jewel.foundation.GenerateDataFunctions
import org.jetbrains.jewel.foundation.modifier.onHover
import org.jetbrains.jewel.foundation.state.CommonStateBitMask
import org.jetbrains.jewel.foundation.state.FocusableComponentState
import org.jetbrains.jewel.ui.component.styling.TabStyle
@Composable
public fun TabStrip(
tabs: List<TabData>,
style: TabStyle,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
@@ -42,12 +44,14 @@ public fun TabStrip(
val scrollState = rememberScrollState()
Box(
modifier.focusable(true, remember { MutableInteractionSource() })
modifier
.focusable(true, remember { MutableInteractionSource() })
.onHover { tabStripState = tabStripState.copy(hovered = it) },
) {
Row(
modifier =
Modifier.horizontalScroll(scrollState)
Modifier
.horizontalScroll(scrollState)
.scrollable(
orientation = Orientation.Vertical,
reverseDirection =
@@ -58,8 +62,7 @@ public fun TabStrip(
),
state = scrollState,
interactionSource = remember { MutableInteractionSource() },
)
.selectableGroup(),
).selectableGroup(),
) {
tabs.forEach { TabImpl(isActive = tabStripState.isActive, tabData = it) }
}
@@ -71,6 +74,7 @@ public fun TabStrip(
) {
TabStripHorizontalScrollbar(
adapter = rememberScrollbarAdapter(scrollState),
style = style.scrollbarStyle,
modifier = Modifier.fillMaxWidth(),
)
}

View File

@@ -16,7 +16,8 @@ import kotlin.time.Duration
public class ScrollbarStyle(
public val colors: ScrollbarColors,
public val metrics: ScrollbarMetrics,
public val hoverDuration: Duration,
public val trackClickBehavior: TrackClickBehavior,
public val scrollbarVisibility: ScrollbarVisibility,
) {
public companion object
}
@@ -26,6 +27,12 @@ public class ScrollbarStyle(
public class ScrollbarColors(
public val thumbBackground: Color,
public val thumbBackgroundHovered: Color,
public val thumbBackgroundPressed: Color,
public val thumbBorder: Color,
public val thumbBorderHovered: Color,
public val thumbBorderPressed: Color,
public val trackBackground: Color,
public val trackBackgroundHovered: Color,
) {
public companion object
}
@@ -35,12 +42,33 @@ public class ScrollbarColors(
public class ScrollbarMetrics(
public val thumbCornerSize: CornerSize,
public val thumbThickness: Dp,
public val thumbThicknessExpanded: Dp,
public val minThumbLength: Dp,
public val trackPadding: PaddingValues,
public val trackPaddingExpanded: PaddingValues,
) {
public companion object
}
public sealed interface ScrollbarVisibility {
public data object AlwaysVisible : ScrollbarVisibility
@GenerateDataFunctions
public class WhenScrolling(
public val appearAnimationDuration: Duration,
public val disappearAnimationDuration: Duration,
public val expandAnimationDuration: Duration,
public val lingerDuration: Duration,
) : ScrollbarVisibility {
public companion object
}
}
public enum class TrackClickBehavior {
NextPage,
JumpToSpot,
}
public val LocalScrollbarStyle: ProvidableCompositionLocal<ScrollbarStyle> =
staticCompositionLocalOf {
error("No ScrollbarStyle provided. Have you forgotten the theme?")

View File

@@ -21,6 +21,7 @@ public class TabStyle(
public val metrics: TabMetrics,
public val icons: TabIcons,
public val contentAlpha: TabContentAlpha,
public val scrollbarStyle: ScrollbarStyle,
) {
public companion object
}