diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeScrollbar.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeScrollbar.kt index bd724bd27bc04..26b8dc812f4b8 100644 --- a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeScrollbar.kt +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeScrollbar.kt @@ -33,6 +33,27 @@ internal fun readScrollbarStyle(isDark: Boolean): ScrollbarStyle = scrollbarVisibility = readScrollbarVisibility(), ) +internal fun readTabStripScrollbarStyle(isDark: Boolean): ScrollbarStyle = + ScrollbarStyle( + colors = readScrollbarColors(isDark), + metrics = readScrollbarMetrics(), + trackClickBehavior = TrackClickBehavior.JumpToSpot, + scrollbarVisibility = + ScrollbarVisibility.AlwaysVisible( + trackThickness = 5.dp, + trackThicknessExpanded = 5.dp, + trackPadding = PaddingValues(1.dp), + trackPaddingExpanded = PaddingValues(), + trackPaddingWithBorder = PaddingValues(1.dp), + trackColorAnimationDuration = 125.milliseconds, + expandAnimationDuration = 125.milliseconds, + thumbColorAnimationDuration = 125.milliseconds, + lingerDuration = 700.milliseconds, + scrollbarBackgroundColorLight = Color.Unspecified, + scrollbarBackgroundColorDark = Color.Unspecified, + ), + ) + private fun readScrollbarColors(isDark: Boolean) = if (hostOs.isMacOS) { readScrollbarMacColors(isDark) diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeTab.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeTab.kt index f6e82a8f0c978..710fe3a3a051d 100644 --- a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeTab.kt +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridgeTab.kt @@ -68,7 +68,7 @@ internal fun readDefaultTabStyle(): TabStyle { contentHovered = 1f, contentSelected = 1f, ), - scrollbarStyle = readScrollbarStyle(isDark), + scrollbarStyle = readTabStripScrollbarStyle(isDark), ) } @@ -122,6 +122,6 @@ internal fun readEditorTabStyle(): TabStyle { contentHovered = 1f, contentSelected = 1f, ), - scrollbarStyle = readScrollbarStyle(isDark), + scrollbarStyle = readTabStripScrollbarStyle(isDark), ) } 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 32f9b34fabf06..7ee937012b2f5 100644 --- a/platform/jewel/int-ui/int-ui-standalone/api-dump.txt +++ b/platform/jewel/int-ui/int-ui-standalone/api-dump.txt @@ -441,6 +441,8 @@ f:org.jetbrains.jewel.intui.standalone.styling.IntUiScrollbarStylingKt - bs:macOsLight$default(org.jetbrains.jewel.ui.component.styling.ScrollbarStyle$Companion,org.jetbrains.jewel.ui.component.styling.ScrollbarColors,org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics,org.jetbrains.jewel.ui.component.styling.TrackClickBehavior,org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ScrollbarStyle - sf:macOsLight-zwkVjRg(org.jetbrains.jewel.ui.component.styling.ScrollbarColors$Companion,J,J,J,J,J,J,J,J,J,J,J,J):org.jetbrains.jewel.ui.component.styling.ScrollbarColors - bs:macOsLight-zwkVjRg$default(org.jetbrains.jewel.ui.component.styling.ScrollbarColors$Companion,J,J,J,J,J,J,J,J,J,J,J,J,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ScrollbarColors +- sf:tabStrip-FbBwQsc(org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$Companion,F,F,androidx.compose.foundation.layout.PaddingValues,androidx.compose.foundation.layout.PaddingValues,androidx.compose.foundation.layout.PaddingValues,J,J,J,J):org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility +- bs:tabStrip-FbBwQsc$default(org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$Companion,F,F,androidx.compose.foundation.layout.PaddingValues,androidx.compose.foundation.layout.PaddingValues,androidx.compose.foundation.layout.PaddingValues,J,J,J,J,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility - sf:windowsAndLinux-TZvXluI(org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$WhenScrolling$Companion,F,F,androidx.compose.foundation.layout.PaddingValues,androidx.compose.foundation.layout.PaddingValues,J,J,J,J):org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$WhenScrolling - bs:windowsAndLinux-TZvXluI$default(org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$WhenScrolling$Companion,F,F,androidx.compose.foundation.layout.PaddingValues,androidx.compose.foundation.layout.PaddingValues,J,J,J,J,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$WhenScrolling - sf:windowsAndLinux-tYhzLtE(org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$AlwaysVisible$Companion,F,androidx.compose.foundation.layout.PaddingValues,J,J):org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$AlwaysVisible diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt index c1fb1b6ef7f67..7dc02adb84b6e 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt @@ -395,7 +395,7 @@ public fun AlwaysVisible.Companion.macOs( */ public fun AlwaysVisible.Companion.windowsAndLinux( trackThickness: Dp = 10.dp, - trackPadding: PaddingValues = PaddingValues(0.dp), + trackPadding: PaddingValues = PaddingValues(), thumbColorAnimationDuration: Duration = 330.milliseconds, trackColorAnimationDuration: Duration = thumbColorAnimationDuration, ): AlwaysVisible = @@ -508,8 +508,8 @@ public fun WhenScrolling.Companion.macOs( public fun WhenScrolling.Companion.windowsAndLinux( trackThickness: Dp = 10.dp, trackThicknessExpanded: Dp = 10.dp, - trackPadding: PaddingValues = PaddingValues(0.dp), - trackPaddingWithBorder: PaddingValues = PaddingValues(0.dp), + trackPadding: PaddingValues = PaddingValues(), + trackPaddingWithBorder: PaddingValues = PaddingValues(), trackColorAnimationDuration: Duration = 330.milliseconds, expandAnimationDuration: Duration = 0.milliseconds, thumbColorAnimationDuration: Duration = trackColorAnimationDuration, @@ -525,3 +525,28 @@ public fun WhenScrolling.Companion.windowsAndLinux( thumbColorAnimationDuration = thumbColorAnimationDuration, lingerDuration = lingerDuration, ) + +public fun ScrollbarVisibility.Companion.tabStrip( + trackThickness: Dp = 5.dp, + trackThicknessExpanded: Dp = 5.dp, + trackPadding: PaddingValues = PaddingValues(1.dp), + trackPaddingExpanded: PaddingValues = PaddingValues(), + trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), + trackColorAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = trackColorAnimationDuration, + thumbColorAnimationDuration: Duration = trackColorAnimationDuration, + lingerDuration: Duration = 700.milliseconds, +): ScrollbarVisibility = + AlwaysVisible( + trackThickness = trackThickness, + trackThicknessExpanded = trackThicknessExpanded, + trackPadding = trackPadding, + trackPaddingExpanded = trackPaddingExpanded, + trackPaddingWithBorder = trackPaddingWithBorder, + trackColorAnimationDuration = trackColorAnimationDuration, + expandAnimationDuration = expandAnimationDuration, + thumbColorAnimationDuration = thumbColorAnimationDuration, + lingerDuration = lingerDuration, + scrollbarBackgroundColorLight = Color.Unspecified, + scrollbarBackgroundColorDark = Color.Unspecified, + ) diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt index c1334d15702f5..ecd5d94a89114 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt @@ -31,7 +31,7 @@ public fun ScrollbarStyle.Companion.tabStripMacOsLight( colors: ScrollbarColors = ScrollbarColors.macOsLight(), metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.tabStrip(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -44,7 +44,7 @@ public fun ScrollbarStyle.Companion.tabStripMacOsDark( colors: ScrollbarColors = ScrollbarColors.macOsDark(), metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.tabStrip(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -57,7 +57,7 @@ public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxLight( colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxLight(), metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripWindowsAndLinux(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.tabStrip(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.tabStrip(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -70,7 +70,7 @@ public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxDark( colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxDark(), metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripWindowsAndLinux(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.tabStrip(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.tabStrip(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -89,6 +89,16 @@ public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( minThumbLength: Dp = 20.dp, ): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) +@Deprecated( + "Replace with 'ScrollbarVisibility.tabStrip()' version", + ReplaceWith( + "ScrollbarVisibility.tabStrip(" + + "trackThickness = trackThickness," + + "trackPadding = trackPadding," + + "trackPaddingWithBorder = trackPaddingWithBorder" + + ")" + ), +) public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( trackThickness: Dp = 4.dp, trackPadding: PaddingValues = PaddingValues(), diff --git a/platform/jewel/ui/api-dump.txt b/platform/jewel/ui/api-dump.txt index b961e0a27e8d4..6685f59f3b429 100644 --- a/platform/jewel/ui/api-dump.txt +++ b/platform/jewel/ui/api-dump.txt @@ -1760,11 +1760,13 @@ f:org.jetbrains.jewel.ui.component.styling.ScrollbarStyle$Companion f:org.jetbrains.jewel.ui.component.styling.ScrollbarStylingKt - sf:getLocalScrollbarStyle():androidx.compose.runtime.ProvidableCompositionLocal org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility +- sf:Companion:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$Companion - a:getExpandAnimationDuration-UwyO8pc():J - a:getLingerDuration-UwyO8pc():J - a:getThumbColorAnimationDuration-UwyO8pc():J - a:getTrackColorAnimationDuration-UwyO8pc():J - a:getTrackPadding():androidx.compose.foundation.layout.PaddingValues +- a:getTrackPaddingExpanded():androidx.compose.foundation.layout.PaddingValues - a:getTrackPaddingWithBorder():androidx.compose.foundation.layout.PaddingValues - a:getTrackThickness-D9Ej5fM():F - a:getTrackThicknessExpanded-D9Ej5fM():F @@ -1772,6 +1774,7 @@ f:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$AlwaysVisible - org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility - sf:$stable:I - sf:Companion:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$AlwaysVisible$Companion +- b:(F,androidx.compose.foundation.layout.PaddingValues,androidx.compose.foundation.layout.PaddingValues,J,J,J,J,F,androidx.compose.foundation.layout.PaddingValues,J,J,I,kotlin.jvm.internal.DefaultConstructorMarker):V - equals(java.lang.Object):Z - getExpandAnimationDuration-UwyO8pc():J - getLingerDuration-UwyO8pc():J @@ -1780,11 +1783,13 @@ f:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$AlwaysVisible - getThumbColorAnimationDuration-UwyO8pc():J - getTrackColorAnimationDuration-UwyO8pc():J - getTrackPadding():androidx.compose.foundation.layout.PaddingValues +- getTrackPaddingExpanded():androidx.compose.foundation.layout.PaddingValues - getTrackPaddingWithBorder():androidx.compose.foundation.layout.PaddingValues - getTrackThickness-D9Ej5fM():F - getTrackThicknessExpanded-D9Ej5fM():F - hashCode():I f:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$AlwaysVisible$Companion +f:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$Companion f:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$WhenScrolling - org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility - sf:$stable:I @@ -1795,6 +1800,7 @@ f:org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility$WhenScrolling - getThumbColorAnimationDuration-UwyO8pc():J - getTrackColorAnimationDuration-UwyO8pc():J - getTrackPadding():androidx.compose.foundation.layout.PaddingValues +- getTrackPaddingExpanded():androidx.compose.foundation.layout.PaddingValues - getTrackPaddingWithBorder():androidx.compose.foundation.layout.PaddingValues - getTrackThickness-D9Ej5fM():F - getTrackThicknessExpanded-D9Ej5fM():F diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt index 820cae2e9818f..59a9d74f43bef 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -49,6 +49,11 @@ private const val ID_CONTENT = "VerticallyScrollableContainer_content" private const val ID_VERTICAL_SCROLLBAR = "VerticallyScrollableContainer_verticalScrollbar" private const val ID_HORIZONTAL_SCROLLBAR = "VerticallyScrollableContainer_horizontalScrollbar" +internal enum class ScrollbarPosition { + Start, + End, +} + /** * A vertically scrollable container that follows the standard visual styling. * @@ -161,8 +166,10 @@ public fun VerticallyScrollableContainer( ) }, verticalScrollbarVisible = scrollState.canScroll, + verticalScrollbarPosition = ScrollbarPosition.End, horizontalScrollbar = null, horizontalScrollbarVisible = false, + horizontalScrollbarPosition = ScrollbarPosition.End, scrollbarStyle = style, modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, ) { @@ -196,8 +203,10 @@ internal fun TextAreaScrollableContainer( ) }, verticalScrollbarVisible = scrollState.canScroll, + verticalScrollbarPosition = ScrollbarPosition.End, horizontalScrollbar = null, horizontalScrollbarVisible = false, + horizontalScrollbarPosition = ScrollbarPosition.End, scrollbarStyle = style, modifier = Modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, ) { @@ -388,8 +397,10 @@ public fun VerticallyScrollableContainer( ) }, verticalScrollbarVisible = scrollState.canScroll, + verticalScrollbarPosition = ScrollbarPosition.End, horizontalScrollbar = null, horizontalScrollbarVisible = false, + horizontalScrollbarPosition = ScrollbarPosition.End, scrollbarStyle = style, modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, ) { @@ -492,6 +503,33 @@ public fun HorizontallyScrollableContainer( scrollbarEnabled: Boolean = userScrollEnabled, scrollbarInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable BoxScope.() -> Unit, +) { + HorizontallyScrollableContainer( + ScrollbarPosition.End, + modifier, + scrollbarModifier, + scrollState, + style, + reverseLayout, + userScrollEnabled, + scrollbarEnabled, + scrollbarInteractionSource, + content, + ) +} + +@Composable +internal fun HorizontallyScrollableContainer( + scrollbarPosition: ScrollbarPosition, + modifier: Modifier = Modifier, + scrollbarModifier: Modifier = Modifier, + scrollState: ScrollState = rememberScrollState(), + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + reverseLayout: Boolean = false, + userScrollEnabled: Boolean = true, + scrollbarEnabled: Boolean = userScrollEnabled, + scrollbarInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable BoxScope.() -> Unit, ) { var keepVisible by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() @@ -499,6 +537,7 @@ public fun HorizontallyScrollableContainer( ScrollableContainerImpl( verticalScrollbar = null, verticalScrollbarVisible = false, + verticalScrollbarPosition = ScrollbarPosition.End, horizontalScrollbar = { HorizontalScrollbar( scrollState = scrollState, @@ -511,6 +550,7 @@ public fun HorizontallyScrollableContainer( ) }, horizontalScrollbarVisible = scrollState.canScroll, + horizontalScrollbarPosition = scrollbarPosition, scrollbarStyle = style, modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, ) { @@ -707,7 +747,9 @@ public fun HorizontallyScrollableContainer( interactionSource = scrollbarInteractionSource, ) }, + verticalScrollbarPosition = ScrollbarPosition.End, horizontalScrollbarVisible = scrollState.canScroll, + horizontalScrollbarPosition = ScrollbarPosition.End, scrollbarStyle = style, modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, ) { @@ -742,8 +784,10 @@ private fun Modifier.withKeepVisible( private fun ScrollableContainerImpl( verticalScrollbar: (@Composable () -> Unit)?, verticalScrollbarVisible: Boolean, + verticalScrollbarPosition: ScrollbarPosition, horizontalScrollbar: (@Composable () -> Unit)?, horizontalScrollbarVisible: Boolean, + horizontalScrollbarPosition: ScrollbarPosition, scrollbarStyle: ScrollbarStyle, modifier: Modifier = Modifier, content: @Composable () -> Unit, @@ -771,21 +815,27 @@ private fun ScrollableContainerImpl( val sizeOffsetWhenBothVisible = if (accountForVerticalScrollbar && accountForHorizontalScrollbar) { scrollbarStyle.scrollbarVisibility.trackThicknessExpanded.roundToPx() - } else 0 + } else { + 0 + } val verticalScrollbarPlaceable = if (accountForVerticalScrollbar) { val verticalScrollbarConstraints = Constraints.fixedHeight(incomingConstraints.maxHeight - sizeOffsetWhenBothVisible) verticalScrollbarMeasurable.measure(verticalScrollbarConstraints) - } else null + } else { + null + } val horizontalScrollbarPlaceable = if (accountForHorizontalScrollbar) { val horizontalScrollbarConstraints = Constraints.fixedWidth(incomingConstraints.maxWidth - sizeOffsetWhenBothVisible) horizontalScrollbarMeasurable.measure(horizontalScrollbarConstraints) - } else null + } else { + null + } val isMacOs = hostOs == OS.MacOS val contentMeasurable = measurables.find { it.layoutId == ID_CONTENT } ?: error("Content not provided") @@ -818,10 +868,22 @@ private fun ScrollableContainerImpl( layout(width, height) { contentPlaceable.placeRelative(x = 0, y = 0, zIndex = 0f) - verticalScrollbarPlaceable?.placeRelative(x = width - verticalScrollbarPlaceable.width, y = 0, zIndex = 1f) + verticalScrollbarPlaceable?.placeRelative( + x = + when (verticalScrollbarPosition) { + ScrollbarPosition.Start -> 0 + ScrollbarPosition.End -> width - verticalScrollbarPlaceable.width + }, + y = 0, + zIndex = 1f, + ) horizontalScrollbarPlaceable?.placeRelative( x = 0, - y = height - horizontalScrollbarPlaceable.height, + y = + when (horizontalScrollbarPosition) { + ScrollbarPosition.Start -> 0 + ScrollbarPosition.End -> height - horizontalScrollbarPlaceable.height + }, zIndex = 1f, ) } @@ -864,7 +926,9 @@ private fun computeContentConstraints( visibility is WhenScrolling -> minWidth else -> error("Unsupported visibility style: $visibility") } - } else 0 + } else { + 0 + } fun maxHeight() = if (incomingConstraints.hasBoundedHeight) { @@ -886,7 +950,9 @@ private fun computeContentConstraints( visibility is WhenScrolling -> minHeight else -> error("Unsupported visibility style: $visibility") } - } else 0 + } else { + 0 + } return when { incomingConstraints.hasBoundedWidth && incomingConstraints.hasBoundedHeight -> { diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt index cf53b7b12908e..2cca5ad0d89d5 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable 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 @@ -158,7 +159,11 @@ private fun BaseScrollbar( keepVisible: Boolean, modifier: Modifier = Modifier, ) { + val isHovered by interactionSource.collectIsHoveredAsState() + val dragInteraction = remember { mutableStateOf(null) } + var showScrollbar by remember { mutableStateOf(false) } + DisposableEffect(interactionSource) { onDispose { dragInteraction.value?.let { interaction -> @@ -168,27 +173,21 @@ private fun BaseScrollbar( } } - val visibilityStyle = style.scrollbarVisibility - val isOpaque = visibilityStyle is AlwaysVisible - var isExpanded by remember { mutableStateOf(false) } - val isHovered by interactionSource.collectIsHoveredAsState() - var showScrollbar by remember { mutableStateOf(false) } - - val isDragging = dragInteraction.value != null - val isScrolling = scrollState.isScrollInProgress || isDragging - val isActive = isOpaque || isScrolling || (keepVisible && showScrollbar) + val visibilityStyle by rememberUpdatedState(style.scrollbarVisibility) + val isOpaque by remember { derivedStateOf { visibilityStyle is AlwaysVisible } } - if (isHovered && showScrollbar) isExpanded = true + val isDragging by remember { derivedStateOf { dragInteraction.value != null } } + val isExpanded by remember { derivedStateOf { showScrollbar && (isHovered || isDragging) } } + val isScrolling by remember { derivedStateOf { scrollState.isScrollInProgress || isDragging } } + val isActive by remember { derivedStateOf { isOpaque || isScrolling || (keepVisible && showScrollbar) } } - LaunchedEffect(isActive, isHovered, showScrollbar) { - val isVisibleAndHovered = showScrollbar && isHovered - if (isActive || isVisibleAndHovered) { + LaunchedEffect(isActive, isHovered, isDragging) { + if (isActive || isHovered) { showScrollbar = true - } else { + } else if (!isDragging) { launch { delay(visibilityStyle.lingerDuration) showScrollbar = false - isExpanded = false } } } @@ -222,8 +221,14 @@ private fun BaseScrollbar( val thumbBackgroundColor = getThumbBackgroundColor(isOpaque, isHovered, isScrolling, style, showScrollbar) val thumbBorderColor = getThumbBorderColor(isOpaque, isHovered, isScrolling, style, showScrollbar) val hasVisibleBorder = !areTheSameColor(thumbBackgroundColor, thumbBorderColor) - val trackPadding = - if (hasVisibleBorder) visibilityStyle.trackPaddingWithBorder else visibilityStyle.trackPadding + val trackPadding by + rememberUpdatedState( + when { + isExpanded -> visibilityStyle.trackPaddingExpanded + hasVisibleBorder -> visibilityStyle.trackPaddingWithBorder + else -> visibilityStyle.trackPadding + } + ) val thumbThicknessPx = if (isVertical) { @@ -433,9 +438,11 @@ private fun trackColorTween(visibility: ScrollbarVisibility) = private fun thumbColorTween(showScrollbar: Boolean, visibility: ScrollbarVisibility) = tween( durationMillis = - if (visibility is AlwaysVisible || !showScrollbar) { - visibility.thumbColorAnimationDuration.inWholeMilliseconds.toInt() - } else 0, + when { + visibility is AlwaysVisible || !showScrollbar -> + visibility.thumbColorAnimationDuration.inWholeMilliseconds.toInt() + else -> 0 + }, delayMillis = when { visibility is AlwaysVisible && !showScrollbar -> visibility.lingerDuration.inWholeMilliseconds.toInt() @@ -588,8 +595,10 @@ private class TrackPressScroller( if (direction == 0) return - if (clickBehavior == NextPage) startScrollingByPage() - else if (clickBehavior == JumpToSpot) scrollToOffset(offset) + when (clickBehavior) { + NextPage -> startScrollingByPage() + JumpToSpot -> scrollToOffset(offset) + } } /** Invoked when the pointer moves while pressed during the gesture. */ diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt index a7f269e04dc6e..1fb1cc516f175 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt @@ -1,30 +1,24 @@ package org.jetbrains.jewel.ui.component -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.focusable import androidx.compose.foundation.gestures.animateScrollBy -import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.hoverable import androidx.compose.foundation.interaction.FocusInteraction import androidx.compose.foundation.interaction.HoverInteraction import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectableGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key @@ -35,14 +29,16 @@ import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.semantics.CollectionInfo import androidx.compose.ui.semantics.collectionInfo -import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.semantics import androidx.compose.ui.util.fastRoundToInt +import kotlinx.coroutines.delay import org.jetbrains.annotations.ApiStatus import org.jetbrains.jewel.foundation.ExperimentalJewelApi import org.jetbrains.jewel.foundation.GenerateDataFunctions import org.jetbrains.jewel.foundation.state.CommonStateBitMask import org.jetbrains.jewel.foundation.state.FocusableComponentState +import org.jetbrains.jewel.ui.component.styling.ScrollbarColors +import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle import org.jetbrains.jewel.ui.component.styling.TabStyle /** @@ -94,51 +90,55 @@ public fun TabStrip( } } - Box( - modifier - .onPreviewKeyEvent { event -> - if (!enabled || tabs.isEmpty()) return@onPreviewKeyEvent false - - when (event.type) { - KeyEventType.KeyDown -> - when (event.key) { - Key.DirectionLeft -> { - tabs[if (selectedIndex > 0) (selectedIndex - 1) else tabs.lastIndex].onClick() - true + HorizontallyScrollableContainer( + modifier = + modifier + .onPreviewKeyEvent { event -> + if (!enabled || tabs.isEmpty()) return@onPreviewKeyEvent false + + when (event.type) { + KeyEventType.KeyDown -> + when (event.key) { + Key.DirectionLeft -> { + tabs[if (selectedIndex > 0) (selectedIndex - 1) else tabs.lastIndex].onClick() + true + } + + Key.DirectionRight -> { + tabs[(selectedIndex + 1) % tabCount].onClick() + true + } + + else -> false } - Key.DirectionRight -> { - tabs[(selectedIndex + 1) % tabCount].onClick() - true - } - - else -> false - } + KeyEventType.KeyUp -> + when (event.key) { + Key.MoveHome -> { + tabs.first().onClick() + true + } - KeyEventType.KeyUp -> - when (event.key) { - Key.MoveHome -> { - tabs.first().onClick() - true - } + Key.MoveEnd -> { + tabs.last().onClick() + true + } - Key.MoveEnd -> { - tabs.last().onClick() - true + else -> false } - else -> false - } - - else -> false + else -> false + } } - } - .focusable(enabled, interactionSource) - .hoverable(interactionSource, enabled) + .focusable(enabled, interactionSource) + .hoverable(interactionSource, enabled), + style = rememberTabStripScrollbarStyle(tabStripState, style), + scrollState = scrollState, + scrollbarPosition = ScrollbarPosition.Start, ) { Row( modifier = - Modifier.horizontalScroll(scrollState).selectableGroup().semantics { + Modifier.selectableGroup().semantics { collectionInfo = CollectionInfo(rowCount = 1, columnCount = tabCount) } ) { @@ -182,15 +182,6 @@ public fun TabStrip( } } } - - AnimatedVisibility( - visible = tabStripState.isHovered, - enter = fadeIn(tween(durationMillis = 125, delayMillis = 0, easing = LinearEasing)), - exit = fadeOut(tween(durationMillis = 125, delayMillis = 700, easing = LinearEasing)), - modifier = Modifier.semantics { hideFromAccessibility() }, - ) { - HorizontalScrollbar(scrollState, style = style.scrollbarStyle, modifier = Modifier.fillMaxWidth()) - } } } @@ -380,3 +371,46 @@ public value class TabStripState(public val state: ULong) : FocusableComponentSt ) } } + +@Composable +private fun rememberTabStripScrollbarStyle(container: TabStripState, style: TabStyle): ScrollbarStyle { + var scrollbarColors by remember { mutableStateOf(ScrollbarColors.transparent()) } + val scrollbarStyle by remember { + derivedStateOf { + ScrollbarStyle( + colors = scrollbarColors, + metrics = style.scrollbarStyle.metrics, + trackClickBehavior = style.scrollbarStyle.trackClickBehavior, + scrollbarVisibility = style.scrollbarStyle.scrollbarVisibility, + ) + } + } + + LaunchedEffect(container.isHovered, style) { + scrollbarColors = + if (container.isHovered) { + style.scrollbarStyle.colors + } else { + delay(style.scrollbarStyle.scrollbarVisibility.lingerDuration) + ScrollbarColors.transparent() + } + } + + return scrollbarStyle +} + +private fun ScrollbarColors.Companion.transparent() = + ScrollbarColors( + thumbBackground = Color.Transparent, + thumbBackgroundActive = Color.Transparent, + thumbOpaqueBackground = Color.Transparent, + thumbOpaqueBackgroundHovered = Color.Transparent, + thumbBorder = Color.Transparent, + thumbBorderActive = Color.Transparent, + thumbOpaqueBorder = Color.Transparent, + thumbOpaqueBorderHovered = Color.Transparent, + trackBackground = Color.Transparent, + trackBackgroundExpanded = Color.Transparent, + trackOpaqueBackground = Color.Transparent, + trackOpaqueBackgroundHovered = Color.Transparent, + ) diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt index c967ec147b02f..2ed7003f2c690 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt @@ -198,6 +198,9 @@ public sealed interface ScrollbarVisibility { /** The padding around the scrollbar track. */ public val trackPadding: PaddingValues + /** The padding around the scrollbar track when expanded (e.g., on hover). */ + public val trackPaddingExpanded: PaddingValues + /** The padding around the scrollbar track when a border is visible. */ public val trackPaddingWithBorder: PaddingValues @@ -218,6 +221,8 @@ public sealed interface ScrollbarVisibility { */ public val lingerDuration: Duration + public companion object + /** * A [ScrollbarVisibility] that keeps the scrollbar always visible. * @@ -249,10 +254,33 @@ public sealed interface ScrollbarVisibility { public override val trackColorAnimationDuration: Duration, public val scrollbarBackgroundColorLight: Color, public val scrollbarBackgroundColorDark: Color, + public override val trackThicknessExpanded: Dp = trackThickness, + public override val trackPaddingExpanded: PaddingValues = trackPadding, + public override val expandAnimationDuration: Duration = 0.milliseconds, + public override val lingerDuration: Duration = 0.milliseconds, ) : ScrollbarVisibility { - public override val trackThicknessExpanded: Dp = trackThickness - public override val expandAnimationDuration: Duration = 0.milliseconds - public override val lingerDuration: Duration = 0.milliseconds + @Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN) + public constructor( + trackThickness: Dp, + trackPadding: PaddingValues, + trackPaddingWithBorder: PaddingValues, + thumbColorAnimationDuration: Duration, + trackColorAnimationDuration: Duration, + scrollbarBackgroundColorLight: Color, + scrollbarBackgroundColorDark: Color, + ) : this( + trackThickness = trackThickness, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPaddingWithBorder, + thumbColorAnimationDuration = thumbColorAnimationDuration, + trackColorAnimationDuration = trackColorAnimationDuration, + scrollbarBackgroundColorLight = scrollbarBackgroundColorLight, + scrollbarBackgroundColorDark = scrollbarBackgroundColorDark, + trackThicknessExpanded = trackThickness, + trackPaddingExpanded = trackPadding, + expandAnimationDuration = 0.milliseconds, + lingerDuration = 0.milliseconds, + ) override fun equals(other: Any?): Boolean { if (this === other) return true @@ -270,6 +298,7 @@ public sealed interface ScrollbarVisibility { if (trackThicknessExpanded != other.trackThicknessExpanded) return false if (expandAnimationDuration != other.expandAnimationDuration) return false if (lingerDuration != other.lingerDuration) return false + if (trackPaddingExpanded != other.trackPaddingExpanded) return false return true } @@ -285,6 +314,7 @@ public sealed interface ScrollbarVisibility { result = 31 * result + trackThicknessExpanded.hashCode() result = 31 * result + expandAnimationDuration.hashCode() result = 31 * result + lingerDuration.hashCode() + result = 31 * result + trackPaddingExpanded.hashCode() return result } @@ -299,7 +329,8 @@ public sealed interface ScrollbarVisibility { "scrollbarBackgroundColorDark=$scrollbarBackgroundColorDark, " + "trackThicknessExpanded=$trackThicknessExpanded, " + "expandAnimationDuration=$expandAnimationDuration, " + - "lingerDuration=$lingerDuration" + + "lingerDuration=$lingerDuration, " + + "trackPaddingExpanded=$trackPaddingExpanded" + ")" } @@ -333,6 +364,8 @@ public sealed interface ScrollbarVisibility { public override val thumbColorAnimationDuration: Duration, public override val lingerDuration: Duration, ) : ScrollbarVisibility { + public override val trackPaddingExpanded: PaddingValues = trackPadding + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -347,6 +380,7 @@ public sealed interface ScrollbarVisibility { if (expandAnimationDuration != other.expandAnimationDuration) return false if (thumbColorAnimationDuration != other.thumbColorAnimationDuration) return false if (lingerDuration != other.lingerDuration) return false + if (trackPaddingExpanded != other.trackPaddingExpanded) return false return true } @@ -360,6 +394,7 @@ public sealed interface ScrollbarVisibility { result = 31 * result + expandAnimationDuration.hashCode() result = 31 * result + thumbColorAnimationDuration.hashCode() result = 31 * result + lingerDuration.hashCode() + result = 31 * result + trackPaddingExpanded.hashCode() return result } @@ -372,7 +407,8 @@ public sealed interface ScrollbarVisibility { "trackColorAnimationDuration=$trackColorAnimationDuration, " + "expandAnimationDuration=$expandAnimationDuration, " + "thumbColorAnimationDuration=$thumbColorAnimationDuration, " + - "lingerDuration=$lingerDuration" + + "lingerDuration=$lingerDuration, " + + "trackPaddingExpanded=$trackPaddingExpanded" + ")" }