From 3c5417fed0bc9f34669d8cdcb2be089b0c49305f Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Mon, 19 Aug 2024 15:49:08 +0200 Subject: [PATCH 01/14] Fix Scrollbars [part 1/n] * Implement TrackClickBehaviors correctly * Fix macOS scrollbar styling * Provide ScrollableContainers for a simple, turn-key scrollbar API * Make expanded scrollbar not hide while the mouse moves over the scrollable composable * Fix the expanding behaviour and animation on macOS * Fix the visibility logic on macOS * Resolve a number of inconsistencies with Swing implementations * Simplify and consolidate internal implementation * Separate tabStrip scrollbar styling --- .../jewel/bridge/theme/ScrollbarBridge.kt | 98 +++-- .../styling/IntUiScrollbarStyling.kt | 369 +++++++---------- .../styling/IntUiTabStripScrollbarStyling.kt | 97 +++++ .../standalone/view/component/Scrollbars.kt | 256 +++++++----- .../view/markdown/MarkdownPreview.kt | 3 +- .../jewel/ui/component/ScrollableContainer.kt | 349 ++++++++++++++++ .../component/{Scrollbars.kt => Scrollbar.kt} | 375 ++++++++---------- .../jetbrains/jewel/ui/component/TabStrip.kt | 3 +- .../ui/component/styling/ScrollbarStyling.kt | 48 ++- 9 files changed, 1005 insertions(+), 593 deletions(-) create mode 100644 int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt create mode 100644 ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt rename ui/src/main/kotlin/org/jetbrains/jewel/ui/component/{Scrollbars.kt => Scrollbar.kt} (70%) diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt index 1121348cf..99b3d6f1b 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt @@ -35,7 +35,7 @@ private fun readScrollbarColors(isDark: Boolean) = if (hostOs.isMacOS) { readScrollbarMacColors(isDark) } else { - readScrollbarWinColors(isDark) + readScrollbarWindowsAndLinuxColors(isDark) } private fun readTrackClickBehavior() = @@ -45,7 +45,7 @@ private fun readTrackClickBehavior() = TrackClickBehavior.JumpToSpot } -private fun readScrollbarWinColors(isDark: Boolean): ScrollbarColors = +private fun readScrollbarWindowsAndLinuxColors(isDark: Boolean): ScrollbarColors = ScrollbarColors( thumbBackground = readScrollBarColorForKey( @@ -54,14 +54,7 @@ private fun readScrollbarWinColors(isDark: Boolean): ScrollbarColors = 0x33737373, 0x47A6A6A6, ), - thumbBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.hoverThumbColor", - 0x47737373, - 0x59A6A6A6, - ), - thumbBackgroundPressed = + thumbBackgroundActive = readScrollBarColorForKey( isDark, "ScrollBar.hoverThumbColor", @@ -75,14 +68,7 @@ private fun readScrollbarWinColors(isDark: Boolean): ScrollbarColors = 0x33595959, 0x47383838, ), - thumbBorderHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.hoverThumbBorderColor", - 0x47595959, - 0x59383838, - ), - thumbBorderPressed = + thumbBorderActive = readScrollBarColorForKey( isDark, "ScrollBar.hoverThumbBorderColor", @@ -96,13 +82,27 @@ private fun readScrollbarWinColors(isDark: Boolean): ScrollbarColors = 0x00808080, 0x00808080, ), - trackBackgroundHovered = + trackBackgroundExpanded = readScrollBarColorForKey( isDark, "ScrollBar.Transparent.hoverTrackColor", 0x1A808080, 0x1A808080, ), + trackOpaqueBackground = + readScrollBarColorForKey( + isDark, + "ScrollBar.trackColor", + 0x00808080, + 0x00808080, + ), + trackOpaqueBackgroundHovered = + readScrollBarColorForKey( + isDark, + "ScrollBar.hoverTrackColor", + 0x00808080, + 0x00808080, + ), ) private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = @@ -114,14 +114,7 @@ private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = 0x33000000, 0x59808080, ), - thumbBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.hoverThumbColor", - 0x80000000, - 0x8C808080, - ), - thumbBackgroundPressed = + thumbBackgroundActive = readScrollBarColorForKey( isDark, "ScrollBar.Mac.hoverThumbColor", @@ -135,14 +128,7 @@ private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = 0x33000000, 0x59262626, ), - thumbBorderHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.hoverThumbBorderColor", - 0x80000000, - 0x8C262626, - ), - thumbBorderPressed = + thumbBorderActive = readScrollBarColorForKey( isDark, "ScrollBar.Mac.hoverThumbBorderColor", @@ -150,19 +136,33 @@ private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = 0x8C262626, ), trackBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.trackColor", - 0x00808080, - 0x00808080, - ), - trackBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.hoverTrackColor", - 0x00808080, - 0x00808080, - ), + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.trackColor", + 0x00808080, + 0x00808080, + ), + trackBackgroundExpanded = + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.hoverTrackColor", + 0x1A808080, + 0x1A808080, + ), + trackOpaqueBackground = + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.trackColor", + 0x00808080, + 0x00808080, + ), + trackOpaqueBackgroundHovered = + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.hoverTrackColor", + 0x00808080, + 0x00808080, + ), ) private fun readScrollBarColorForKey( @@ -181,7 +181,6 @@ private fun readScrollbarMetrics(): ScrollbarMetrics = thumbThicknessExpanded = 14.dp, minThumbLength = 20.dp, trackPadding = PaddingValues(2.dp), - trackPaddingExpanded = PaddingValues(2.dp), ) } else { ScrollbarMetrics( @@ -190,7 +189,6 @@ private fun readScrollbarMetrics(): ScrollbarMetrics = thumbThicknessExpanded = 8.dp, minThumbLength = 16.dp, trackPadding = PaddingValues(), - trackPaddingExpanded = PaddingValues(), ) } diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt index 078dcbaae..5e284da7f 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt @@ -14,25 +14,25 @@ import org.jetbrains.skiko.hostOs import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds -public fun ScrollbarStyle.Companion.dark(): ScrollbarStyle = +public fun ScrollbarStyle.Companion.light(): ScrollbarStyle = if (hostOs.isMacOS) { - ScrollbarStyle.macOsDark() + ScrollbarStyle.macOsLight() } else { - ScrollbarStyle.windowsAndLinuxDark() + ScrollbarStyle.windowsAndLinuxLight() } -public fun ScrollbarStyle.Companion.light(): ScrollbarStyle = +public fun ScrollbarStyle.Companion.dark(): ScrollbarStyle = if (hostOs.isMacOS) { - ScrollbarStyle.macOsLight() + ScrollbarStyle.macOsDark() } else { - ScrollbarStyle.windowsAndLinuxLight() + ScrollbarStyle.windowsAndLinuxDark() } public fun ScrollbarStyle.Companion.macOsLight( colors: ScrollbarColors = ScrollbarColors.macOsLight(), metrics: ScrollbarMetrics = ScrollbarMetrics.macOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.defaults(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -45,7 +45,7 @@ public fun ScrollbarStyle.Companion.macOsDark( colors: ScrollbarColors = ScrollbarColors.macOsDark(), metrics: ScrollbarMetrics = ScrollbarMetrics.macOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.defaults(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -54,11 +54,11 @@ public fun ScrollbarStyle.Companion.macOsDark( scrollbarVisibility = scrollbarVisibility, ) -public fun ScrollbarStyle.Companion.windowsAndLinuxDark( - colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxDark(), - metrics: ScrollbarMetrics = ScrollbarMetrics.windows(), +public fun ScrollbarStyle.Companion.windowsAndLinuxLight( + colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxLight(), + metrics: ScrollbarMetrics = ScrollbarMetrics.windowsAndLinux(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible, + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.macOs(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -67,11 +67,11 @@ public fun ScrollbarStyle.Companion.windowsAndLinuxDark( scrollbarVisibility = scrollbarVisibility, ) -public fun ScrollbarStyle.Companion.windowsAndLinuxLight( - colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxLight(), - metrics: ScrollbarMetrics = ScrollbarMetrics.windows(), +public fun ScrollbarStyle.Companion.windowsAndLinuxDark( + colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxDark(), + metrics: ScrollbarMetrics = ScrollbarMetrics.windowsAndLinux(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible, + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.macOs(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -80,13 +80,58 @@ public fun ScrollbarStyle.Companion.windowsAndLinuxLight( scrollbarVisibility = scrollbarVisibility, ) -public fun ScrollbarVisibility.WhenScrolling.Companion.defaults( +public fun ScrollbarVisibility.AlwaysVisible.Companion.default(): ScrollbarVisibility.AlwaysVisible = + if (hostOs.isMacOS) { + ScrollbarVisibility.AlwaysVisible.macOs() + } else { + ScrollbarVisibility.AlwaysVisible.windowsAndLinux() + } + +public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( + thumbThickness: Dp = 14.dp, +): ScrollbarVisibility.AlwaysVisible = + ScrollbarVisibility.AlwaysVisible(thumbThickness) + +public fun ScrollbarVisibility.AlwaysVisible.Companion.windowsAndLinux( + thumbThickness: Dp = 18.dp, +): ScrollbarVisibility.AlwaysVisible = + ScrollbarVisibility.AlwaysVisible(thumbThickness) + +public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisibility.WhenScrolling = + if (hostOs.isMacOS) { + ScrollbarVisibility.WhenScrolling.macOs() + } else { + ScrollbarVisibility.WhenScrolling.windowsAndLinux() + } + +public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( + thumbThickness: Dp = 7.dp, + thumbThicknessExpanded: Dp = 10.dp, + appearAnimationDuration: Duration = 125.milliseconds, + disappearAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = 125.milliseconds, + lingerDuration: Duration = 700.milliseconds, +): ScrollbarVisibility.WhenScrolling = + ScrollbarVisibility.WhenScrolling( + thumbThickness = thumbThickness, + thumbThicknessExpanded = thumbThicknessExpanded, + appearAnimationDuration = appearAnimationDuration, + disappearAnimationDuration = disappearAnimationDuration, + expandAnimationDuration = expandAnimationDuration, + lingerDuration = lingerDuration, + ) + +public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( + thumbThickness: Dp = 7.dp, + thumbThicknessExpanded: Dp = 10.dp, appearAnimationDuration: Duration = 125.milliseconds, disappearAnimationDuration: Duration = 125.milliseconds, expandAnimationDuration: Duration = 125.milliseconds, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( + thumbThickness = thumbThickness, + thumbThicknessExpanded = thumbThicknessExpanded, appearAnimationDuration = appearAnimationDuration, disappearAnimationDuration = disappearAnimationDuration, expandAnimationDuration = expandAnimationDuration, @@ -94,253 +139,131 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.defaults( ) public fun ScrollbarColors.Companion.macOsLight( - thumbBackground: Color = Color(0xFFBBBBBA), - thumbBackgroundHovered: Color = Color(0xFF7D7D7C), - thumbBackgroundPressed: Color = Color(0xFF7D7D7C), - thumbBorder: Color = Color(0x33000000), - thumbBorderHovered: Color = Color(0x80000000), - thumbBorderPressed: Color = thumbBorderHovered, + thumbBackground: Color = Color(0x00000000), + thumbBackgroundHovered: Color = Color(0x80000000), + thumbOpaqueBackground: Color = Color(0x33000000), + thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, + thumbBorder: Color = thumbBackground, + thumbBorderHovered: Color = thumbBackgroundHovered, + thumbOpaqueBorder: Color = thumbOpaqueBackground, + thumbOpaqueBorderHovered: Color = thumbOpaqueBackgroundHovered, trackBackground: Color = Color(0x00808080), - trackBackgroundHovered: Color = Color(0x00808080), + trackBackgroundHovered: Color = Color(0x1A808080), + trackOpaqueBackground: Color = trackBackground, + trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, ): ScrollbarColors = ScrollbarColors( - thumbBackground, - thumbBackgroundHovered, - thumbBackgroundPressed, - thumbBorder, - thumbBorderHovered, - thumbBorderPressed, - trackBackground, - trackBackgroundHovered, + thumbBackground = thumbBackground, + thumbBackgroundActive = thumbBackgroundHovered, + thumbOpaqueBackground = thumbOpaqueBackground, + thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, + thumbBorder = thumbBorder, + thumbBorderActive = thumbBorderHovered, + thumbOpaqueBorder = thumbOpaqueBorder, + thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, + trackBackground = trackBackground, + trackBackgroundExpanded = trackBackgroundHovered, + trackOpaqueBackground = trackOpaqueBackground, + trackOpaqueBackgroundHovered = trackOpaqueBackgroundHovered, ) public fun ScrollbarColors.Companion.windowsAndLinuxLight( thumbBackground: Color = Color(0x33737373), thumbBackgroundHovered: Color = Color(0x47737373), - thumbBackgroundPressed: Color = thumbBackgroundHovered, + thumbOpaqueBackground: Color = thumbBackground, + thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, thumbBorder: Color = Color(0x33595959), thumbBorderHovered: Color = Color(0x47595959), - thumbBorderPressed: Color = thumbBorderHovered, + thumbOpaqueBorder: Color = thumbBorder, + thumbOpaqueBorderHovered: Color = thumbBorderHovered, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), + trackOpaqueBackground: Color = trackBackground, + trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, ): ScrollbarColors = ScrollbarColors( - thumbBackground, - thumbBackgroundHovered, - thumbBackgroundPressed, - thumbBorder, - thumbBorderHovered, - thumbBorderPressed, - trackBackground, - trackBackgroundHovered, + thumbBackground = thumbBackground, + thumbBackgroundActive = thumbBackgroundHovered, + thumbOpaqueBackground = thumbOpaqueBackground, + thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, + thumbBorder = thumbBorder, + thumbBorderActive = thumbBorderHovered, + thumbOpaqueBorder = thumbOpaqueBorder, + thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, + trackBackground = trackBackground, + trackBackgroundExpanded = trackBackgroundHovered, + trackOpaqueBackground = trackOpaqueBackground, + trackOpaqueBackgroundHovered = trackOpaqueBackgroundHovered, ) public fun ScrollbarColors.Companion.macOsDark( - thumbBackground: Color = Color(0x59808080), + thumbBackground: Color = Color(0x00808080), thumbBackgroundHovered: Color = Color(0x8C808080), - thumbBackgroundPressed: Color = Color(0x8C808080), - thumbBorder: Color = Color(0x59262626), + thumbOpaqueBackground: Color = Color(0x59808080), + thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, + thumbBorder: Color = Color(0x00262626), thumbBorderHovered: Color = Color(0x8C262626), - thumbBorderPressed: Color = Color(0x8C262626), + thumbOpaqueBorder: Color = Color(0x59262626), + thumbOpaqueBorderHovered: Color = thumbBorderHovered, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), + trackOpaqueBackground: Color = trackBackground, + trackOpaqueBackgroundHovered: Color = trackBackgroundHovered, ): ScrollbarColors = ScrollbarColors( - thumbBackground, - thumbBackgroundHovered, - thumbBackgroundPressed, - thumbBorder, - thumbBorderHovered, - thumbBorderPressed, - trackBackground, - trackBackgroundHovered, + thumbBackground = thumbBackground, + thumbBackgroundActive = thumbBackgroundHovered, + thumbOpaqueBackground = thumbOpaqueBackground, + thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, + thumbBorder = thumbBorder, + thumbBorderActive = thumbBorderHovered, + thumbOpaqueBorder = thumbOpaqueBorder, + thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, + trackBackground = trackBackground, + trackBackgroundExpanded = trackBackgroundHovered, + trackOpaqueBackground = trackOpaqueBackground, + trackOpaqueBackgroundHovered = trackOpaqueBackgroundHovered, ) public fun ScrollbarColors.Companion.windowsAndLinuxDark( thumbBackground: Color = Color(0x47A6A6A6), thumbBackgroundHovered: Color = Color(0x59A6A6A6), - thumbBackgroundPressed: Color = Color(0x59A6A6A6), + thumbOpaqueBackground: Color = thumbBackground, + thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, thumbBorder: Color = Color(0x47383838), - thumbBorderHovered: Color = Color(0x59A6A6A6), - thumbBorderPressed: Color = Color(0x59A6A6A6), + thumbBorderHovered: Color = Color(0x59383838), + thumbOpaqueBorder: Color = thumbBorder, + thumbOpaqueBorderHovered: Color = thumbBorderHovered, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), + trackOpaqueBackground: Color = trackBackground, + trackOpaqueBackgroundHovered: Color = trackBackgroundHovered, ): 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, + thumbBackground = thumbBackground, + thumbBackgroundActive = thumbBackgroundHovered, + thumbOpaqueBackground = thumbOpaqueBackground, + thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, + thumbBorder = thumbBorder, + thumbBorderActive = thumbBorderHovered, + thumbOpaqueBorder = thumbOpaqueBorder, + thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, + trackBackground = trackBackground, + trackBackgroundExpanded = trackBackgroundHovered, + trackOpaqueBackground = trackOpaqueBackground, + trackOpaqueBackgroundHovered = trackOpaqueBackgroundHovered, ) 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(horizontal = 0.dp), - thumbThicknessExpanded: Dp = 8.dp, - trackPaddingExpanded: PaddingValues = PaddingValues(horizontal = 0.dp), + trackPadding: PaddingValues = PaddingValues(3.dp), ): ScrollbarMetrics = - ScrollbarMetrics( - thumbCornerSize, - thumbThickness, - thumbThicknessExpanded, - minThumbLength, - trackPadding, - trackPaddingExpanded, - ) + ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) -public fun ScrollbarMetrics.Companion.linux( +public fun ScrollbarMetrics.Companion.windowsAndLinux( 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, - ) - -public fun ScrollbarStyle.Companion.tabStripMacOsDark( - colors: ScrollbarColors = ScrollbarColors.macOsDark(), - metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), - trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.defaults(), -): ScrollbarStyle = - ScrollbarStyle( - colors = colors, - metrics = metrics, - trackClickBehavior = trackClickBehavior, - scrollbarVisibility = scrollbarVisibility, - ) - -public fun ScrollbarStyle.Companion.tabStripMacOsLight( - colors: ScrollbarColors = ScrollbarColors.macOsLight(), - metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), - trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.defaults(), -): ScrollbarStyle = - ScrollbarStyle( - colors = colors, - metrics = metrics, - trackClickBehavior = trackClickBehavior, - scrollbarVisibility = scrollbarVisibility, - ) - -public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxDark( - colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxDark(), - metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripWindowsAndLinux(), - trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible, -): ScrollbarStyle = - ScrollbarStyle( - colors = colors, - metrics = metrics, - trackClickBehavior = trackClickBehavior, - scrollbarVisibility = scrollbarVisibility, - ) - -public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxLight( - colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxLight(), - metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripWindowsAndLinux(), - trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible, -): ScrollbarStyle = - ScrollbarStyle( - colors = colors, - metrics = metrics, - trackClickBehavior = trackClickBehavior, - scrollbarVisibility = scrollbarVisibility, - ) - -public fun ScrollbarStyle.Companion.tabStripDark(): ScrollbarStyle = - if (hostOs.isMacOS) { - ScrollbarStyle.tabStripMacOsDark() - } else { - ScrollbarStyle.tabStripWindowsAndLinuxDark() - } - -public fun ScrollbarStyle.Companion.tabStripLight(): ScrollbarStyle = - if (hostOs.isMacOS) { - ScrollbarStyle.tabStripMacOsLight() - } else { - ScrollbarStyle.tabStripWindowsAndLinuxLight() - } - -public fun ScrollbarMetrics.Companion.tabStripMacOs( - thumbCornerSize: CornerSize = CornerSize(100), - thumbThickness: Dp = 3.dp, minThumbLength: Dp = 20.dp, trackPadding: PaddingValues = PaddingValues(), - thumbThicknessExpanded: Dp = 3.dp, - trackPaddingExpanded: PaddingValues = PaddingValues(), ): ScrollbarMetrics = - ScrollbarMetrics( - thumbCornerSize, - thumbThickness, - thumbThicknessExpanded, - minThumbLength, - trackPadding, - trackPaddingExpanded, - ) - -public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( - thumbCornerSize: CornerSize = CornerSize(0), - thumbThickness: Dp = 3.dp, - minThumbLength: Dp = 16.dp, - trackPadding: PaddingValues = PaddingValues(), - thumbThicknessExpanded: Dp = 3.dp, - trackPaddingExpanded: PaddingValues = PaddingValues(), -): ScrollbarMetrics = - ScrollbarMetrics( - thumbCornerSize, - thumbThickness, - thumbThicknessExpanded, - minThumbLength, - trackPadding, - trackPaddingExpanded, - ) + ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt new file mode 100644 index 000000000..8e1d01c3e --- /dev/null +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt @@ -0,0 +1,97 @@ +package org.jetbrains.jewel.intui.standalone.styling + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.CornerSize +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 + +public fun ScrollbarStyle.Companion.tabStripLight(): ScrollbarStyle = + if (hostOs.isMacOS) { + ScrollbarStyle.tabStripMacOsLight() + } else { + ScrollbarStyle.tabStripWindowsAndLinuxLight() + } + +public fun ScrollbarStyle.Companion.tabStripDark(): ScrollbarStyle = + if (hostOs.isMacOS) { + ScrollbarStyle.tabStripMacOsDark() + } else { + ScrollbarStyle.tabStripWindowsAndLinuxDark() + } + +public fun ScrollbarStyle.Companion.tabStripMacOsLight( + colors: ScrollbarColors = ScrollbarColors.macOsLight(), + metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), + trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), +): ScrollbarStyle = + ScrollbarStyle( + colors = colors, + metrics = metrics, + trackClickBehavior = trackClickBehavior, + scrollbarVisibility = scrollbarVisibility, + ) + +public fun ScrollbarStyle.Companion.tabStripMacOsDark( + colors: ScrollbarColors = ScrollbarColors.macOsDark(), + metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), + trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), +): ScrollbarStyle = + ScrollbarStyle( + colors = colors, + metrics = metrics, + trackClickBehavior = trackClickBehavior, + scrollbarVisibility = scrollbarVisibility, + ) + +public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxLight( + colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxLight(), + metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripWindowsAndLinux(), + trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.tabStrip(), +): ScrollbarStyle = + ScrollbarStyle( + colors = colors, + metrics = metrics, + trackClickBehavior = trackClickBehavior, + scrollbarVisibility = scrollbarVisibility, + ) + +public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxDark( + colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxDark(), + metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripWindowsAndLinux(), + trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.tabStrip(), +): ScrollbarStyle = + ScrollbarStyle( + colors = colors, + metrics = metrics, + trackClickBehavior = trackClickBehavior, + scrollbarVisibility = scrollbarVisibility, + ) + +public fun ScrollbarMetrics.Companion.tabStripMacOs( + thumbCornerSize: CornerSize = CornerSize(100), + minThumbLength: Dp = 20.dp, + trackPadding: PaddingValues = PaddingValues(), +): ScrollbarMetrics = + ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) + +public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( + thumbCornerSize: CornerSize = CornerSize(0), + minThumbLength: Dp = 16.dp, + trackPadding: PaddingValues = PaddingValues(), +): ScrollbarMetrics = + ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) + +public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( + thumbThickness: Dp = 3.dp, +): ScrollbarVisibility.AlwaysVisible = + ScrollbarVisibility.AlwaysVisible(thumbThickness) diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index be6663483..615e15077 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -1,166 +1,214 @@ package org.jetbrains.jewel.samples.standalone.view.component +import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth 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.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.selectableGroup 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 androidx.compose.ui.window.singleWindowApplication 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.windowsAndLinuxDark -import org.jetbrains.jewel.intui.standalone.styling.windowsAndLinuxLight +import org.jetbrains.jewel.intui.standalone.styling.dark +import org.jetbrains.jewel.intui.standalone.styling.default +import org.jetbrains.jewel.intui.standalone.styling.light +import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme 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.RadioButtonRow import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.Typography import org.jetbrains.jewel.ui.component.VerticalScrollbar +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer +import org.jetbrains.jewel.ui.component.scrollbarContentSafePadding 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 org.jetbrains.jewel.ui.component.styling.TrackClickBehavior +import org.jetbrains.jewel.ui.theme.textAreaStyle import java.util.Locale +// STOPSHIP testing only +fun main() = singleWindowApplication { + IntUiTheme { + Box(Modifier.fillMaxSize().background(JewelTheme.globalColors.panelBackground).padding(16.dp)) { + Scrollbars() + } + } +} + @Composable 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(), - ) - } + val baseStyle = remember(isDark) { + if (isDark) ScrollbarStyle.dark() else ScrollbarStyle.light() } - CheckboxRow( - checked = alwaysVisible, - onCheckedChange = { alwaysVisible = it }, - text = "Always visible", - ) + var alwaysVisible by remember { mutableStateOf(false) } + var clickBehavior by remember { mutableStateOf(baseStyle.trackClickBehavior) } + SettingsRow(alwaysVisible, clickBehavior, { alwaysVisible = it }, { clickBehavior = it }) Spacer(modifier = Modifier.height(16.dp)) Row( - Modifier.padding(horizontal = 16.dp).height(200.dp), + Modifier.fillMaxWidth(), 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) - } - } + val style by + remember(alwaysVisible, clickBehavior, baseStyle) { + mutableStateOf( + if (alwaysVisible) { + ScrollbarStyle( + colors = baseStyle.colors, + metrics = baseStyle.metrics, + trackClickBehavior = clickBehavior, + scrollbarVisibility = ScrollbarVisibility.AlwaysVisible.default(), + ) + } else { + ScrollbarStyle( + colors = baseStyle.colors, + metrics = baseStyle.metrics, + trackClickBehavior = clickBehavior, + scrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), + ) } - 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, + + LazyColumnWithScrollbar(style, Modifier.weight(1f).height(200.dp)) + ColumnWithScrollbar(style, Modifier.weight(1f).height(200.dp)) + } + } +} + +@Composable +private fun SettingsRow( + alwaysVisible: Boolean, + clickBehavior: TrackClickBehavior, + onAlwaysVisibleChange: (Boolean) -> Unit, + onClickBehaviorChange: (TrackClickBehavior) -> Unit, +) { + Row(verticalAlignment = Alignment.CenterVertically) { + CheckboxRow( + checked = alwaysVisible, + onCheckedChange = onAlwaysVisibleChange, + text = "Always visible", + ) + + Spacer(Modifier.weight(1f)) + + Text("Track click behavior:") + + Spacer(Modifier.width(8.dp)) + + Row(Modifier.selectableGroup(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RadioButtonRow( + text = "Jump to spot", + selected = clickBehavior == TrackClickBehavior.JumpToSpot, + onClick = { onClickBehaviorChange(TrackClickBehavior.JumpToSpot) }, + ) + RadioButtonRow( + text = "Move by one page", + selected = clickBehavior == TrackClickBehavior.NextPage, + onClick = { onClickBehaviorChange(TrackClickBehavior.NextPage) }, + ) + } + } +} + +@Composable +private fun LazyColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { + Column(modifier) { + Text("LazyColumn", style = Typography.h2TextStyle()) + + Spacer(Modifier.height(8.dp)) + + val scrollState = rememberLazyListState() + VerticallyScrollableContainer( + scrollState, + modifier = Modifier.weight(1f) + .fillMaxWidth() + .border(1.dp, JewelTheme.globalColors.borders.normal), + style = style, + ) { + LazyColumn( + state = scrollState, + modifier = Modifier.fillMaxSize().background(JewelTheme.textAreaStyle.colors.background) + ) { + itemsIndexed(LIST_ITEMS) { index, item -> + Column { + Text( + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + .padding(end = scrollbarContentSafePadding(style)), + text = item, + ) + + if (index != LIST_ITEMS.lastIndex) { + Divider( + orientation = Orientation.Horizontal, + color = JewelTheme.globalColors.borders.normal ) } } - 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.windowsAndLinuxDark() - } - } else { - if (isMac) { - ScrollbarStyle.macOsLight() - } else { - ScrollbarStyle.windowsAndLinuxLight() +@Composable +private fun ColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { + Column(modifier) { + 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 + .background(JewelTheme.textAreaStyle.colors.background) + .verticalScroll(scrollState) + .padding(end = scrollbarContentSafePadding(style)) + .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, + ) } } +} @Suppress("SpellCheckingInspection") private const val LOREM_IPSUM = @@ -185,4 +233,4 @@ private val LIST_ITEMS = .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } - } + }.let { it + it + it + it + it + it } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt index 4194a58d8..78c5c7d8d 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -99,7 +98,7 @@ internal fun MarkdownPreview( ) VerticalScrollbar( - rememberScrollbarAdapter(lazyListState), + lazyListState, Modifier.align(Alignment.TopEnd).fillMaxHeight().padding(2.dp), ) } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt new file mode 100644 index 000000000..323af2f58 --- /dev/null +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -0,0 +1,349 @@ +package org.jetbrains.jewel.ui.component + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.jetbrains.jewel.foundation.modifier.onHover +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.theme.scrollbarStyle +import kotlin.time.Duration.Companion.milliseconds + +private const val ID_CONTENT = "VerticallyScrollableContainer_content" +private const val ID_VERTICAL_SCROLLBAR = "VerticallyScrollableContainer_verticalScrollbar" +private const val ID_HORIZONTAL_SCROLLBAR = "VerticallyScrollableContainer_horizontalScrollbar" + +@Composable +public fun VerticallyScrollableContainer( + modifier: Modifier = Modifier, + scrollbarModifier: Modifier = Modifier, + scrollState: ScrollState = rememberScrollState(), + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + var keepVisible by remember { mutableStateOf(false) } + ScrollableContainerImpl( + verticalScrollbar = { + VerticalScrollbar( + scrollState, + scrollbarModifier, + style = style, + keepVisible = keepVisible, + ) + }, + horizontalScrollbar = null, + modifier = modifier.onHover { keepVisible = it }, + scrollbarStyle = style, + ) { + Box(modifier.layoutId(ID_CONTENT).verticalScroll(scrollState)) { content() } + } +} + +@Composable +public fun VerticallyScrollableContainer( + scrollState: LazyListState, + modifier: Modifier = Modifier, + scrollbarModifier: Modifier = Modifier, + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + var keepVisible by remember { mutableStateOf(false) } + var delayJob by remember { mutableStateOf(null) } + val scope = rememberCoroutineScope() + + ScrollableContainerImpl( + verticalScrollbar = { + VerticalScrollbar( + scrollState, + scrollbarModifier, + style = style, + keepVisible = keepVisible, + ) + }, + horizontalScrollbar = null, + modifier = modifier.pointerInput(scrollState) { + awaitEachGesture { + val event = awaitPointerEvent() + if (event.type == PointerEventType.Move) { + delayJob?.cancel() + keepVisible = true + delayJob = scope.launch { + delay(50.milliseconds) + keepVisible = false + } + } + } + }, + scrollbarStyle = style, + ) { + Box(Modifier.layoutId(ID_CONTENT)) { content() } + } +} + +@Composable +public fun VerticallyScrollableContainer( + scrollState: LazyGridState, + modifier: Modifier = Modifier, + scrollbarModifier: Modifier = Modifier, + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + ScrollableContainerImpl( + verticalScrollbar = { VerticalScrollbar(scrollState, scrollbarModifier, style = style) }, + horizontalScrollbar = null, + modifier = modifier, + scrollbarStyle = style + ) { + Box(Modifier.layoutId(ID_CONTENT)) { content() } + } +} + +@Composable +public fun HorizontallyScrollableContainer( + modifier: Modifier = Modifier, + scrollbarModifier: Modifier = Modifier, + scrollState: ScrollState = rememberScrollState(), + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + ScrollableContainerImpl( + verticalScrollbar = null, + horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, + modifier = modifier, + scrollbarStyle = style + ) { + Box(Modifier.layoutId(ID_CONTENT).verticalScroll(scrollState)) { content() } + } +} + +@Composable +public fun HorizontallyScrollableContainer( + scrollState: LazyListState, + modifier: Modifier = Modifier, + scrollbarModifier: Modifier = Modifier, + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + ScrollableContainerImpl( + verticalScrollbar = null, + horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, + modifier = modifier, + scrollbarStyle = style + ) { + Box(Modifier.layoutId(ID_CONTENT)) { content() } + } +} + +@Composable +public fun HorizontallyScrollableContainer( + scrollState: LazyGridState, + modifier: Modifier = Modifier, + scrollbarModifier: Modifier = Modifier, + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + ScrollableContainerImpl( + verticalScrollbar = null, + horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, + modifier = modifier, + scrollbarStyle = style + ) { + Box(Modifier.layoutId(ID_CONTENT)) { content() } + } +} + +@Composable +public fun ScrollableContainer( + modifier: Modifier = Modifier, + verticalScrollState: ScrollState = rememberScrollState(), + horizontalScrollState: ScrollState = rememberScrollState(), + verticalScrollbarModifier: Modifier = Modifier, + horizontalScrollbarModifier: Modifier = Modifier, + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + ScrollableContainerImpl( + verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, + horizontalScrollbar = { + HorizontalScrollbar( + horizontalScrollState, + horizontalScrollbarModifier, + style = style + ) + }, + modifier = modifier, + scrollbarStyle = style + ) { + Box( + Modifier.layoutId(ID_CONTENT) + .verticalScroll(verticalScrollState) + .horizontalScroll(horizontalScrollState) + ) { content() } + } +} + +@Composable +public fun ScrollableContainer( + verticalScrollState: LazyListState, + horizontalScrollState: LazyListState, + modifier: Modifier = Modifier, + verticalScrollbarModifier: Modifier = Modifier, + horizontalScrollbarModifier: Modifier = Modifier, + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + ScrollableContainerImpl( + verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, + horizontalScrollbar = { + HorizontalScrollbar( + horizontalScrollState, + horizontalScrollbarModifier, + style = style + ) + }, + modifier = modifier, + scrollbarStyle = style + ) { + Box(Modifier.layoutId(ID_CONTENT)) { content() } + } +} + +@Composable +public fun ScrollableContainer( + verticalScrollState: LazyGridState, + horizontalScrollState: LazyGridState, + modifier: Modifier = Modifier, + verticalScrollbarModifier: Modifier = Modifier, + horizontalScrollbarModifier: Modifier = Modifier, + style: ScrollbarStyle = JewelTheme.scrollbarStyle, + content: @Composable () -> Unit, +) { + ScrollableContainerImpl( + verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, + horizontalScrollbar = { + HorizontalScrollbar( + horizontalScrollState, + horizontalScrollbarModifier, + style = style + ) + }, + modifier = modifier, + scrollbarStyle = style + ) { + Box(Modifier.layoutId(ID_CONTENT)) { content() } + } +} + +@Composable +private fun ScrollableContainerImpl( + verticalScrollbar: (@Composable () -> Unit)?, + horizontalScrollbar: (@Composable () -> Unit)?, + modifier: Modifier, + scrollbarStyle: ScrollbarStyle, + content: @Composable () -> Unit, +) { + Layout( + content = { + content() + + if (verticalScrollbar != null) { + Box(Modifier.layoutId(ID_VERTICAL_SCROLLBAR)) { verticalScrollbar() } + } + + if (horizontalScrollbar != null) { + Box(Modifier.layoutId(ID_HORIZONTAL_SCROLLBAR)) { horizontalScrollbar() } + } + }, + modifier, + ) { measurables, incomingConstraints -> + val verticalScrollbarMeasurable = measurables.find { it.layoutId == ID_VERTICAL_SCROLLBAR } + val horizontalScrollbarMeasurable = measurables.find { it.layoutId == ID_HORIZONTAL_SCROLLBAR } + + // Leaving the bottom-end corner empty when both scrollbars visible at the same time + val sizeOffsetWhenBothVisible = + if (verticalScrollbarMeasurable != null && horizontalScrollbarMeasurable != null) { + scrollbarStyle.scrollbarVisibility.thumbThicknessExpanded.roundToPx() + } else 0 + + val verticalScrollbarPlaceable = if (verticalScrollbarMeasurable != null) { + val verticalScrollbarConstraints = + Constraints.fixedHeight(incomingConstraints.maxHeight - sizeOffsetWhenBothVisible) + verticalScrollbarMeasurable.measure(verticalScrollbarConstraints) + } else null + + val horizontalScrollbarPlaceable = if (horizontalScrollbarMeasurable != null) { + val horizontalScrollbarConstraints = + Constraints.fixedWidth(incomingConstraints.maxWidth - sizeOffsetWhenBothVisible) + horizontalScrollbarMeasurable.measure(horizontalScrollbarConstraints) + } else null + + val contentConstraints = Constraints.fixed( + width = when (scrollbarStyle.scrollbarVisibility) { + is AlwaysVisible -> incomingConstraints.maxWidth - (verticalScrollbarPlaceable?.width ?: 0) + is WhenScrolling -> incomingConstraints.maxWidth + }, + height = when (scrollbarStyle.scrollbarVisibility) { + is AlwaysVisible -> incomingConstraints.maxHeight - (horizontalScrollbarPlaceable?.height ?: 0) + is WhenScrolling -> incomingConstraints.maxHeight + }, + ) + val contentMeasurable = measurables.find { it.layoutId == ID_CONTENT } + ?: error("Content not provided") + val contentPlaceable = contentMeasurable.measure(contentConstraints) + + layout(incomingConstraints.maxWidth, incomingConstraints.maxHeight) { + contentPlaceable.placeRelative(x = 0, y = 0, zIndex = 0f) + verticalScrollbarPlaceable?.placeRelative( + x = incomingConstraints.maxWidth - verticalScrollbarPlaceable.width, + y = 0, + zIndex = 1f + ) + horizontalScrollbarPlaceable?.placeRelative( + x = 0, + y = incomingConstraints.maxHeight - horizontalScrollbarPlaceable.height, + zIndex = 1f + ) + } + } +} + +/** + * A content padding to apply when you want to ensure the content is not + * overlapped by scrollbars. This value can be used for both vertical and + * horizontal scrollbars. + * + * When the [style] is [AlwaysVisible], this value is zero, since the + * various `ScrollableContainer`s will prevent overlapping anyway. If it + * is [WhenScrolling], this value will be the maximum thickness of the + * scrollbar. + */ +@Composable +public fun scrollbarContentSafePadding(style: ScrollbarStyle = JewelTheme.scrollbarStyle): Dp = + when (style.scrollbarVisibility) { + is AlwaysVisible -> 0.dp + is WhenScrolling -> style.scrollbarVisibility.thumbThicknessExpanded + } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbars.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt similarity index 70% rename from ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbars.kt rename to ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt index 0bab1a1d8..6e9a24f47 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbars.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt @@ -1,8 +1,7 @@ package org.jetbrains.jewel.ui.component import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background @@ -12,7 +11,6 @@ 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 @@ -21,7 +19,6 @@ 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 @@ -33,7 +30,6 @@ 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 @@ -43,7 +39,6 @@ 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 @@ -52,7 +47,6 @@ 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 @@ -65,10 +59,13 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.foundation.modifier.border import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle +import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility 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 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 @@ -80,16 +77,20 @@ public fun VerticalScrollbar( scrollState: ScrollableState, modifier: Modifier = Modifier, reverseLayout: Boolean = false, + enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, style: ScrollbarStyle = JewelTheme.scrollbarStyle, + keepVisible: Boolean = false, ) { - MyScrollbar( + BaseScrollbar( scrollState = scrollState, modifier = modifier, reverseLayout = reverseLayout, + enabled = enabled, interactionSource = interactionSource, isVertical = true, style = style, + keepVisible = keepVisible, ) } @@ -98,206 +99,97 @@ public fun HorizontalScrollbar( scrollState: ScrollableState, modifier: Modifier = Modifier, reverseLayout: Boolean = false, + enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, style: ScrollbarStyle = JewelTheme.scrollbarStyle, + keepVisible: Boolean = false, ) { - MyScrollbar( + BaseScrollbar( scrollState = scrollState, modifier = modifier, reverseLayout = reverseLayout, + enabled = enabled, interactionSource = interactionSource, isVertical = false, style = style, + keepVisible = keepVisible, ) } @Composable -private fun MyScrollbar( +private fun BaseScrollbar( scrollState: ScrollableState, - modifier: Modifier = Modifier, - reverseLayout: Boolean = false, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + modifier: Modifier, + reverseLayout: Boolean, + enabled: Boolean, + interactionSource: MutableInteractionSource, isVertical: Boolean, style: ScrollbarStyle, + keepVisible: Boolean, ) { - // 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 + val dragInteraction = remember { mutableStateOf(null) } + DisposableEffect(interactionSource) { + onDispose { + dragInteraction.value?.let { interaction -> + interactionSource.tryEmit(DragInteraction.Cancel(interaction)) + dragInteraction.value = null } - - scrollState.scrollTo(jumpTo) } } - // Visibility, hover and fade out - var visible by remember { mutableStateOf(scrollState.canScrollBackward) } - val hovered = interactionSource.collectIsHoveredAsState().value + 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 animatedAlpha by animateFloatAsState( - targetValue = if (visible) 1.0f else 0f, - label = "alpha", - ) + val isActive = + isOpaque || + scrollState.isScrollInProgress || + dragInteraction.value != null || + (keepVisible && showScrollbar) - LaunchedEffect(scrollState.isScrollInProgress, hovered, style.scrollbarVisibility) { - if(style.scrollbarVisibility is AlwaysVisible || scrollState.isScrollInProgress || hovered) { - visible = true - } + if (isHovered && showScrollbar) isExpanded = true - if (style.scrollbarVisibility is WhenScrolling && !hovered) { - delay(style.scrollbarVisibility.lingerDuration) - visible = false + LaunchedEffect(isActive, isHovered, showScrollbar) { + val isVisibleAndHovered = showScrollbar && isHovered + if (isActive || isVisibleAndHovered) { + showScrollbar = true + } else { + launch { + delay(visibilityStyle.lingerDuration) + showScrollbar = false + isExpanded = false + } } } + val thumbWidth by animateDpAsState( + if (isExpanded) visibilityStyle.thumbThicknessExpanded else visibilityStyle.thumbThickness, + tween(visibilityStyle.expandAnimationDuration.inWholeMilliseconds.toInt()), + "scrollbar_thumbWidth", + ) + 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}") + else -> error("Unsupported scroll state type: ${scrollState::class.qualifiedName}") } - val thumbWidth = if (visible) style.metrics.thumbThicknessExpanded else style.metrics.thumbThickness - val trackBackground = if (visible) style.colors.trackBackground else Color.Transparent - val trackPadding = if (visible) 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( - adapter: ScrollbarAdapter, - modifier: Modifier = Modifier, - reverseLayout: Boolean = false, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - style: ScrollbarStyle = JewelTheme.scrollbarStyle, -) { - ScrollbarImpl( - adapter = adapter, - modifier = modifier, - reverseLayout = reverseLayout, - style = style, - interactionSource = interactionSource, - isVertical = true, - ) -} - -@Composable -public fun HorizontalScrollbar( - adapter: ScrollbarAdapter, - modifier: Modifier = Modifier, - reverseLayout: Boolean = false, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - style: ScrollbarStyle = JewelTheme.scrollbarStyle, -) { - ScrollbarImpl( - adapter = adapter, - modifier = modifier, - reverseLayout = reverseLayout, - 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() }, -) { - HorizontalScrollbar( - adapter = adapter, - modifier = modifier.padding(1.dp), - reverseLayout = reverseLayout, - 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(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, - ) { + remember(adapter, containerSize, thumbMinHeight, reverseLayout, isVertical, coroutineScope) { SliderAdapter(adapter, containerSize, thumbMinHeight, reverseLayout, isVertical, coroutineScope) } - val thumbThickness = style.metrics.thumbThickness.roundToPx() + val thumbThickness = thumbWidth.roundToPx() val measurePolicy = if (isVertical) { remember(sliderAdapter, thumbThickness) { @@ -309,47 +201,117 @@ private fun ScrollbarImpl( } } - val targetColor = if (isHighlighted) { - style.colors.thumbBackgroundHovered - } else { - style.colors.thumbBackground - } - val thumbColor = if (style.scrollbarVisibility is WhenScrolling) { - val durationMillis = style.scrollbarVisibility.expandAnimationDuration.inWholeMilliseconds.toInt() - animateColorAsState( - targetValue = targetColor, - animationSpec = tween(durationMillis), - ).value - } else { - targetColor - } - val isVisible = sliderAdapter.thumbSize < containerSize + val canScroll = sliderAdapter.thumbSize < containerSize + + val trackBackground by animateColorAsState( + if (isOpaque) { + if (isHovered) { + style.colors.trackOpaqueBackgroundHovered + } else { + style.colors.trackOpaqueBackground + } + } else { + if (isExpanded) { + style.colors.trackBackgroundExpanded + } else { + style.colors.trackBackground + } + }, + appearanceTween(showScrollbar, visibilityStyle), + "scrollbar_trackBackground", + ) Layout( - { + content = { + val animatedThumbBackground by animateColorAsState( + targetValue = + if (isOpaque) { + if (isHovered) { + style.colors.thumbOpaqueBackgroundHovered + } else { + style.colors.thumbOpaqueBackground + } + } else { + if (showScrollbar) { + style.colors.thumbBackgroundActive + } else { + style.colors.thumbBackground + } + }, + animationSpec = appearanceTween(showScrollbar, visibilityStyle), + "scrollbar_thumbBackground", + ) + val animatedThumbBorder by animateColorAsState( + targetValue = + if (isOpaque) { + if (isHovered) { + style.colors.thumbOpaqueBorderHovered + } else { + style.colors.thumbOpaqueBorder + } + } else { + if (showScrollbar) { + style.colors.thumbBorderActive + } else { + style.colors.thumbBorder + } + }, + animationSpec = appearanceTween(showScrollbar, visibilityStyle), + "scrollbar_thumbBorder", + ) + + val thumbShape = RoundedCornerShape(style.metrics.thumbCornerSize) Box( Modifier .layoutId("thumb") - .thenIf(isVisible) { - background( - color = thumbColor, - shape = RoundedCornerShape(style.metrics.thumbCornerSize), + .thenIf(canScroll) { + border( + Stroke.Alignment.Inside, + 1.dp, + color = animatedThumbBorder, + shape = thumbShape ) - }.scrollbarDrag( - interactionSource = interactionSource, - draggedInteraction = dragInteraction, - sliderAdapter = sliderAdapter, - ), + .padding(1.dp) + .background(color = animatedThumbBackground, shape = thumbShape) + } + .thenIf(enabled) { + scrollbarDrag(interactionSource, dragInteraction, sliderAdapter) + }, ) }, - modifier + modifier = modifier + .thenIf(showScrollbar && canScroll && isExpanded) { background(trackBackground) } + .scrollable( + state = scrollState, + orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal, + enabled = enabled, + reverseDirection = true, // Not sure why it's needed, but it is — TODO revisit this + ) + .padding(style.metrics.trackPadding) .hoverable(interactionSource = interactionSource) - .scrollOnPressTrack(isVertical, reverseLayout, sliderAdapter), - measurePolicy, + .thenIf(enabled && showScrollbar) { + scrollOnPressTrack(style.trackClickBehavior, isVertical, reverseLayout, sliderAdapter) + }, + measurePolicy = measurePolicy, ) } } +private fun appearanceTween( + showScrollbar: Boolean, + visibility: ScrollbarVisibility, +) = tween( + if (showScrollbar) { + visibility.appearAnimationDuration.inWholeMilliseconds.toInt() + } else { + visibility.disappearAnimationDuration.inWholeMilliseconds.toInt() + } +) + +// =========================================================================== +// Note: most of the code below is copied and adapted from the stock scrollbar +// =========================================================================== + private val SliderAdapter.thumbPixelRange: IntRange get() { val start = position.roundToInt() @@ -433,15 +395,17 @@ private fun Modifier.scrollbarDrag( } private fun Modifier.scrollOnPressTrack( + clickBehavior: TrackClickBehavior, isVertical: Boolean, reverseLayout: Boolean, sliderAdapter: SliderAdapter, ) = composed { val coroutineScope = rememberCoroutineScope() val scroller = - remember(sliderAdapter, coroutineScope, reverseLayout) { - TrackPressScroller(coroutineScope, sliderAdapter, reverseLayout) + remember(sliderAdapter, coroutineScope, reverseLayout, clickBehavior) { + TrackPressScroller(coroutineScope, sliderAdapter, reverseLayout, clickBehavior) } + Modifier.pointerInput(scroller) { detectScrollViaTrackGestures( isVertical = isVertical, @@ -458,6 +422,7 @@ private class TrackPressScroller( private val coroutineScope: CoroutineScope, private val sliderAdapter: SliderAdapter, private val reverseLayout: Boolean, + private val clickBehavior: TrackClickBehavior, ) { /** * The current direction of scroll (1: down/right, -1: up/left, 0: not @@ -501,7 +466,7 @@ private class TrackPressScroller( } /** Starts the job that scrolls continuously towards the current offset. */ - private fun startScrolling() { + private fun startScrollingByPage() { job?.cancel() job = coroutineScope.launch { @@ -519,14 +484,17 @@ private class TrackPressScroller( this.offset = offset this.direction = directionOfScrollTowards(offset) - if (direction != 0) { - startScrolling() - } + if (direction == 0) return + + if (clickBehavior == NextPage) startScrollingByPage() + else if (clickBehavior == JumpToSpot) scrollToOffset(offset) } + /** Invoked when the pointer moves while pressed during the gesture. */ fun onMovePressed(offset: Float) { this.offset = offset + if (clickBehavior == JumpToSpot) scrollToOffset(offset) } /** Cleans up when the gesture finishes. */ @@ -541,6 +509,15 @@ private class TrackPressScroller( cleanupAfterGesture() } + private fun scrollToOffset(offset: Float) { + job?.cancel() + job = coroutineScope.launch { + val contentSize = sliderAdapter.adapter.contentSize + val scrollOffset = offset / sliderAdapter.adapter.viewportSize * contentSize + sliderAdapter.adapter.scrollTo(scrollOffset) + } + } + /** Invoked when the gesture is cancelled. */ fun onGestureCancelled() { cleanupAfterGesture() diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt index ec4038ae7..cd83d43a2 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TabStrip.kt @@ -15,7 +15,6 @@ 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.rememberScrollbarAdapter import androidx.compose.foundation.selection.selectableGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable @@ -73,7 +72,7 @@ public fun TabStrip( exit = fadeOut(tween(durationMillis = 125, delayMillis = 700, easing = LinearEasing)), ) { HorizontalScrollbar( - adapter = rememberScrollbarAdapter(scrollState), + scrollState, style = style.scrollbarStyle, modifier = Modifier.fillMaxWidth(), ) diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt index d447f95f9..56df3049f 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import org.jetbrains.jewel.foundation.GenerateDataFunctions import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds @Stable @GenerateDataFunctions @@ -26,13 +27,17 @@ public class ScrollbarStyle( @GenerateDataFunctions public class ScrollbarColors( public val thumbBackground: Color, - public val thumbBackgroundHovered: Color, - public val thumbBackgroundPressed: Color, + public val thumbBackgroundActive: Color, + public val thumbOpaqueBackground: Color, + public val thumbOpaqueBackgroundHovered: Color, public val thumbBorder: Color, - public val thumbBorderHovered: Color, - public val thumbBorderPressed: Color, + public val thumbBorderActive: Color, + public val thumbOpaqueBorder: Color, + public val thumbOpaqueBorderHovered: Color, public val trackBackground: Color, - public val trackBackgroundHovered: Color, + public val trackBackgroundExpanded: Color, + public val trackOpaqueBackground: Color, + public val trackOpaqueBackgroundHovered: Color, ) { public companion object } @@ -41,24 +46,41 @@ public class ScrollbarColors( @GenerateDataFunctions 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 + public val thumbThickness: Dp + public val thumbThicknessExpanded: Dp + public val appearAnimationDuration: Duration + public val disappearAnimationDuration: Duration + public val expandAnimationDuration: Duration + public val lingerDuration: Duration + + @GenerateDataFunctions + public class AlwaysVisible( + public override val thumbThickness: Dp, + ) : ScrollbarVisibility { + override val thumbThicknessExpanded: Dp = thumbThickness + public override val appearAnimationDuration: Duration = 0.milliseconds + public override val disappearAnimationDuration: Duration = 0.milliseconds + public override val expandAnimationDuration: Duration = 0.milliseconds + public override val lingerDuration: Duration = 0.milliseconds + + public companion object + } @GenerateDataFunctions public class WhenScrolling( - public val appearAnimationDuration: Duration, - public val disappearAnimationDuration: Duration, - public val expandAnimationDuration: Duration, - public val lingerDuration: Duration, + public override val thumbThickness: Dp, + public override val thumbThicknessExpanded: Dp, + public override val appearAnimationDuration: Duration, + public override val disappearAnimationDuration: Duration, + public override val expandAnimationDuration: Duration, + public override val lingerDuration: Duration, ) : ScrollbarVisibility { public companion object } From 95c07270199e36d3191daf509bc1a43df70d8f74 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Mon, 19 Aug 2024 16:39:43 +0200 Subject: [PATCH 02/14] Fix Scrollbars [part 2/n] * Fix thumb colour fade in AlwaysVisible mode * Move sample border to Outside so it doesn't overlap the scrollbar --- .../styling/IntUiScrollbarStyling.kt | 20 ++++++++++++------- .../styling/IntUiTabStripScrollbarStyling.kt | 10 +++++----- .../standalone/view/component/Scrollbars.kt | 9 +++++---- .../jetbrains/jewel/ui/component/Scrollbar.kt | 19 ++++++++++++++---- .../ui/component/styling/ScrollbarStyling.kt | 10 ++++++++-- 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt index 5e284da7f..d00e545fa 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt @@ -88,14 +88,18 @@ public fun ScrollbarVisibility.AlwaysVisible.Companion.default(): ScrollbarVisib } public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( - thumbThickness: Dp = 14.dp, + thumbThickness: Dp = 8.dp, + trackPadding: PaddingValues = PaddingValues(3.dp), + thumbColorAnimationDuration: Duration = 330.milliseconds, ): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(thumbThickness) + ScrollbarVisibility.AlwaysVisible(thumbThickness, trackPadding, thumbColorAnimationDuration) public fun ScrollbarVisibility.AlwaysVisible.Companion.windowsAndLinux( thumbThickness: Dp = 18.dp, + trackPadding: PaddingValues = PaddingValues(), + thumbColorAnimationDuration: Duration = 330.milliseconds, ): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(thumbThickness) + ScrollbarVisibility.AlwaysVisible(thumbThickness, trackPadding, thumbColorAnimationDuration) public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisibility.WhenScrolling = if (hostOs.isMacOS) { @@ -107,6 +111,7 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisib public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( thumbThickness: Dp = 7.dp, thumbThicknessExpanded: Dp = 10.dp, + trackPadding: PaddingValues = PaddingValues(3.dp), appearAnimationDuration: Duration = 125.milliseconds, disappearAnimationDuration: Duration = 125.milliseconds, expandAnimationDuration: Duration = 125.milliseconds, @@ -115,6 +120,7 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( ScrollbarVisibility.WhenScrolling( thumbThickness = thumbThickness, thumbThicknessExpanded = thumbThicknessExpanded, + trackPadding = trackPadding, appearAnimationDuration = appearAnimationDuration, disappearAnimationDuration = disappearAnimationDuration, expandAnimationDuration = expandAnimationDuration, @@ -124,6 +130,7 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( thumbThickness: Dp = 7.dp, thumbThicknessExpanded: Dp = 10.dp, + trackPadding: PaddingValues = PaddingValues(), appearAnimationDuration: Duration = 125.milliseconds, disappearAnimationDuration: Duration = 125.milliseconds, expandAnimationDuration: Duration = 125.milliseconds, @@ -132,6 +139,7 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( ScrollbarVisibility.WhenScrolling( thumbThickness = thumbThickness, thumbThicknessExpanded = thumbThicknessExpanded, + trackPadding = trackPadding, appearAnimationDuration = appearAnimationDuration, disappearAnimationDuration = disappearAnimationDuration, expandAnimationDuration = expandAnimationDuration, @@ -257,13 +265,11 @@ public fun ScrollbarColors.Companion.windowsAndLinuxDark( public fun ScrollbarMetrics.Companion.macOs( thumbCornerSize: CornerSize = CornerSize(100), minThumbLength: Dp = 20.dp, - trackPadding: PaddingValues = PaddingValues(3.dp), ): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) + ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarMetrics.Companion.windowsAndLinux( thumbCornerSize: CornerSize = CornerSize(0), minThumbLength: Dp = 20.dp, - trackPadding: PaddingValues = PaddingValues(), ): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) + ScrollbarMetrics(thumbCornerSize, minThumbLength) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt index 8e1d01c3e..5278404ae 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt @@ -10,6 +10,7 @@ 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.Companion.milliseconds public fun ScrollbarStyle.Companion.tabStripLight(): ScrollbarStyle = if (hostOs.isMacOS) { @@ -80,18 +81,17 @@ public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxDark( public fun ScrollbarMetrics.Companion.tabStripMacOs( thumbCornerSize: CornerSize = CornerSize(100), minThumbLength: Dp = 20.dp, - trackPadding: PaddingValues = PaddingValues(), ): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) + ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( thumbCornerSize: CornerSize = CornerSize(0), minThumbLength: Dp = 16.dp, - trackPadding: PaddingValues = PaddingValues(), ): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength, trackPadding) + ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( thumbThickness: Dp = 3.dp, + trackPadding: PaddingValues = PaddingValues(), ): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(thumbThickness) + ScrollbarVisibility.AlwaysVisible(thumbThickness, trackPadding, thumbColorAnimationDuration = 0.milliseconds) diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index 615e15077..68c1798f7 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -1,7 +1,6 @@ package org.jetbrains.jewel.samples.standalone.view.component import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -28,6 +27,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.singleWindowApplication +import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.foundation.modifier.border import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.intui.standalone.styling.dark import org.jetbrains.jewel.intui.standalone.styling.default @@ -49,7 +50,7 @@ import org.jetbrains.jewel.ui.theme.textAreaStyle import java.util.Locale // STOPSHIP testing only -fun main() = singleWindowApplication { +fun main() = singleWindowApplication(title = "Scrollbars") { IntUiTheme { Box(Modifier.fillMaxSize().background(JewelTheme.globalColors.panelBackground).padding(16.dp)) { Scrollbars() @@ -150,7 +151,7 @@ private fun LazyColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { scrollState, modifier = Modifier.weight(1f) .fillMaxWidth() - .border(1.dp, JewelTheme.globalColors.borders.normal), + .border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal), style = style, ) { LazyColumn( @@ -184,7 +185,7 @@ private fun ColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { Text("Column", fontSize = 18.sp) Spacer(Modifier.height(8.dp)) - Box(Modifier.border(1.dp, JewelTheme.globalColors.borders.normal)) { + Box(Modifier.border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal)) { val scrollState = rememberScrollState() Column( modifier = diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt index 6e9a24f47..d2bd0f18a 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt @@ -238,7 +238,7 @@ private fun BaseScrollbar( style.colors.thumbBackground } }, - animationSpec = appearanceTween(showScrollbar, visibilityStyle), + animationSpec = thumbColorTween(showScrollbar, visibilityStyle), "scrollbar_thumbBackground", ) val animatedThumbBorder by animateColorAsState( @@ -256,7 +256,7 @@ private fun BaseScrollbar( style.colors.thumbBorder } }, - animationSpec = appearanceTween(showScrollbar, visibilityStyle), + animationSpec = thumbColorTween(showScrollbar, visibilityStyle), "scrollbar_thumbBorder", ) @@ -269,7 +269,7 @@ private fun BaseScrollbar( Stroke.Alignment.Inside, 1.dp, color = animatedThumbBorder, - shape = thumbShape + shape = thumbShape, ) .padding(1.dp) .background(color = animatedThumbBackground, shape = thumbShape) @@ -287,7 +287,7 @@ private fun BaseScrollbar( enabled = enabled, reverseDirection = true, // Not sure why it's needed, but it is — TODO revisit this ) - .padding(style.metrics.trackPadding) + .padding(visibilityStyle.trackPadding) .hoverable(interactionSource = interactionSource) .thenIf(enabled && showScrollbar) { scrollOnPressTrack(style.trackClickBehavior, isVertical, reverseLayout, sliderAdapter) @@ -308,6 +308,17 @@ private fun appearanceTween( } ) +private fun thumbColorTween( + showScrollbar: Boolean, + visibility: ScrollbarVisibility, +) = tween( + durationMillis = visibility.thumbColorAnimationDuration.inWholeMilliseconds.toInt(), + delayMillis = when { + visibility is AlwaysVisible && !showScrollbar -> visibility.lingerDuration.inWholeMilliseconds.toInt() + else -> 0 + } +) + // =========================================================================== // Note: most of the code below is copied and adapted from the stock scrollbar // =========================================================================== diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt index 56df3049f..5cea0ceba 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt @@ -47,7 +47,6 @@ public class ScrollbarColors( public class ScrollbarMetrics( public val thumbCornerSize: CornerSize, public val minThumbLength: Dp, - public val trackPadding: PaddingValues, ) { public companion object } @@ -55,16 +54,20 @@ public class ScrollbarMetrics( public sealed interface ScrollbarVisibility { public val thumbThickness: Dp public val thumbThicknessExpanded: Dp + public val trackPadding: PaddingValues public val appearAnimationDuration: Duration public val disappearAnimationDuration: Duration public val expandAnimationDuration: Duration public val lingerDuration: Duration + public val thumbColorAnimationDuration: Duration @GenerateDataFunctions public class AlwaysVisible( public override val thumbThickness: Dp, + public override val trackPadding: PaddingValues, + public override val thumbColorAnimationDuration: Duration, ) : ScrollbarVisibility { - override val thumbThicknessExpanded: Dp = thumbThickness + public override val thumbThicknessExpanded: Dp = thumbThickness public override val appearAnimationDuration: Duration = 0.milliseconds public override val disappearAnimationDuration: Duration = 0.milliseconds public override val expandAnimationDuration: Duration = 0.milliseconds @@ -77,11 +80,14 @@ public sealed interface ScrollbarVisibility { public class WhenScrolling( public override val thumbThickness: Dp, public override val thumbThicknessExpanded: Dp, + public override val trackPadding: PaddingValues, public override val appearAnimationDuration: Duration, public override val disappearAnimationDuration: Duration, public override val expandAnimationDuration: Duration, public override val lingerDuration: Duration, ) : ScrollbarVisibility { + public override val thumbColorAnimationDuration: Duration = 0.milliseconds + public companion object } } From ad7ba0b59624e1d2fe9b62ae40c50dcbb3d902b2 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Mon, 19 Aug 2024 20:00:42 +0200 Subject: [PATCH 03/14] Fix Scrollbars [part 4/n] * Rework layout/draw logic to finally align with Swing, in dark mode too * Switch to custom drawing for the thumb for more control --- .../styling/IntUiScrollbarStyling.kt | 84 +++--- .../styling/IntUiTabStripScrollbarStyling.kt | 8 +- .../jewel/ui/component/ScrollableContainer.kt | 4 +- .../jetbrains/jewel/ui/component/Scrollbar.kt | 247 ++++++++++++------ .../ui/component/styling/ScrollbarStyling.kt | 15 +- 5 files changed, 236 insertions(+), 122 deletions(-) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt index d00e545fa..f25bdafd8 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt @@ -88,18 +88,20 @@ public fun ScrollbarVisibility.AlwaysVisible.Companion.default(): ScrollbarVisib } public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( - thumbThickness: Dp = 8.dp, - trackPadding: PaddingValues = PaddingValues(3.dp), + trackThickness: Dp = 14.dp, + trackPadding: PaddingValues = PaddingValues(2.dp), + trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), thumbColorAnimationDuration: Duration = 330.milliseconds, ): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(thumbThickness, trackPadding, thumbColorAnimationDuration) + ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPaddingWithBorder, thumbColorAnimationDuration) public fun ScrollbarVisibility.AlwaysVisible.Companion.windowsAndLinux( - thumbThickness: Dp = 18.dp, + trackThickness: Dp = 18.dp, trackPadding: PaddingValues = PaddingValues(), + trackPaddingWithBorder: PaddingValues = trackPadding, thumbColorAnimationDuration: Duration = 330.milliseconds, ): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(thumbThickness, trackPadding, thumbColorAnimationDuration) + ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPaddingWithBorder, thumbColorAnimationDuration) public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisibility.WhenScrolling = if (hostOs.isMacOS) { @@ -109,18 +111,20 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisib } public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( - thumbThickness: Dp = 7.dp, - thumbThicknessExpanded: Dp = 10.dp, - trackPadding: PaddingValues = PaddingValues(3.dp), + trackThickness: Dp = 11.dp, + trackThicknessExpanded: Dp = 14.dp, + trackPadding: PaddingValues = PaddingValues(2.dp), + trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), appearAnimationDuration: Duration = 125.milliseconds, disappearAnimationDuration: Duration = 125.milliseconds, expandAnimationDuration: Duration = 125.milliseconds, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( - thumbThickness = thumbThickness, - thumbThicknessExpanded = thumbThicknessExpanded, + trackThickness = trackThickness, + trackThicknessExpanded = trackThicknessExpanded, trackPadding = trackPadding, + trackPaddingWithBorder = trackPaddingWithBorder, appearAnimationDuration = appearAnimationDuration, disappearAnimationDuration = disappearAnimationDuration, expandAnimationDuration = expandAnimationDuration, @@ -128,18 +132,20 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( ) public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( - thumbThickness: Dp = 7.dp, - thumbThicknessExpanded: Dp = 10.dp, + trackThickness: Dp = 11.dp, + trackThicknessExpanded: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(), + trackPaddingWithBorder: PaddingValues = trackPadding, appearAnimationDuration: Duration = 125.milliseconds, disappearAnimationDuration: Duration = 125.milliseconds, expandAnimationDuration: Duration = 125.milliseconds, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( - thumbThickness = thumbThickness, - thumbThicknessExpanded = thumbThicknessExpanded, + trackThickness = trackThickness, + trackThicknessExpanded = trackThicknessExpanded, trackPadding = trackPadding, + trackPaddingWithBorder = trackPaddingWithBorder, appearAnimationDuration = appearAnimationDuration, disappearAnimationDuration = disappearAnimationDuration, expandAnimationDuration = expandAnimationDuration, @@ -148,11 +154,11 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( public fun ScrollbarColors.Companion.macOsLight( thumbBackground: Color = Color(0x00000000), - thumbBackgroundHovered: Color = Color(0x80000000), + thumbBackgroundActive: Color = Color(0x80000000), thumbOpaqueBackground: Color = Color(0x33000000), - thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, + thumbOpaqueBackgroundHovered: Color = thumbBackgroundActive, thumbBorder: Color = thumbBackground, - thumbBorderHovered: Color = thumbBackgroundHovered, + thumbBorderActive: Color = thumbBackgroundActive, thumbOpaqueBorder: Color = thumbOpaqueBackground, thumbOpaqueBorderHovered: Color = thumbOpaqueBackgroundHovered, trackBackground: Color = Color(0x00808080), @@ -162,11 +168,11 @@ public fun ScrollbarColors.Companion.macOsLight( ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, - thumbBackgroundActive = thumbBackgroundHovered, + thumbBackgroundActive = thumbBackgroundActive, thumbOpaqueBackground = thumbOpaqueBackground, thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, thumbBorder = thumbBorder, - thumbBorderActive = thumbBorderHovered, + thumbBorderActive = thumbBorderActive, thumbOpaqueBorder = thumbOpaqueBorder, thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, trackBackground = trackBackground, @@ -177,13 +183,13 @@ public fun ScrollbarColors.Companion.macOsLight( public fun ScrollbarColors.Companion.windowsAndLinuxLight( thumbBackground: Color = Color(0x33737373), - thumbBackgroundHovered: Color = Color(0x47737373), + thumbBackgroundActive: Color = Color(0x47737373), thumbOpaqueBackground: Color = thumbBackground, - thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, + thumbOpaqueBackgroundHovered: Color = thumbBackgroundActive, thumbBorder: Color = Color(0x33595959), - thumbBorderHovered: Color = Color(0x47595959), + thumbBorderActive: Color = Color(0x47595959), thumbOpaqueBorder: Color = thumbBorder, - thumbOpaqueBorderHovered: Color = thumbBorderHovered, + thumbOpaqueBorderHovered: Color = thumbBorderActive, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, @@ -191,11 +197,11 @@ public fun ScrollbarColors.Companion.windowsAndLinuxLight( ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, - thumbBackgroundActive = thumbBackgroundHovered, + thumbBackgroundActive = thumbBackgroundActive, thumbOpaqueBackground = thumbOpaqueBackground, thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, thumbBorder = thumbBorder, - thumbBorderActive = thumbBorderHovered, + thumbBorderActive = thumbBorderActive, thumbOpaqueBorder = thumbOpaqueBorder, thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, trackBackground = trackBackground, @@ -206,25 +212,25 @@ public fun ScrollbarColors.Companion.windowsAndLinuxLight( public fun ScrollbarColors.Companion.macOsDark( thumbBackground: Color = Color(0x00808080), - thumbBackgroundHovered: Color = Color(0x8C808080), + thumbBackgroundActive: Color = Color(0x8C808080), thumbOpaqueBackground: Color = Color(0x59808080), - thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, + thumbOpaqueBackgroundHovered: Color = thumbBackgroundActive, thumbBorder: Color = Color(0x00262626), - thumbBorderHovered: Color = Color(0x8C262626), + thumbBorderActive: Color = Color(0x8C262626), thumbOpaqueBorder: Color = Color(0x59262626), - thumbOpaqueBorderHovered: Color = thumbBorderHovered, + thumbOpaqueBorderHovered: Color = thumbBorderActive, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackBackgroundHovered, + trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, - thumbBackgroundActive = thumbBackgroundHovered, + thumbBackgroundActive = thumbBackgroundActive, thumbOpaqueBackground = thumbOpaqueBackground, thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, thumbBorder = thumbBorder, - thumbBorderActive = thumbBorderHovered, + thumbBorderActive = thumbBorderActive, thumbOpaqueBorder = thumbOpaqueBorder, thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, trackBackground = trackBackground, @@ -235,25 +241,25 @@ public fun ScrollbarColors.Companion.macOsDark( public fun ScrollbarColors.Companion.windowsAndLinuxDark( thumbBackground: Color = Color(0x47A6A6A6), - thumbBackgroundHovered: Color = Color(0x59A6A6A6), + thumbBackgroundActive: Color = Color(0x59A6A6A6), thumbOpaqueBackground: Color = thumbBackground, - thumbOpaqueBackgroundHovered: Color = thumbBackgroundHovered, + thumbOpaqueBackgroundHovered: Color = thumbBackgroundActive, thumbBorder: Color = Color(0x47383838), - thumbBorderHovered: Color = Color(0x59383838), + thumbBorderActive: Color = Color(0x59383838), thumbOpaqueBorder: Color = thumbBorder, - thumbOpaqueBorderHovered: Color = thumbBorderHovered, + thumbOpaqueBorderHovered: Color = thumbBorderActive, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackBackgroundHovered, + trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, - thumbBackgroundActive = thumbBackgroundHovered, + thumbBackgroundActive = thumbBackgroundActive, thumbOpaqueBackground = thumbOpaqueBackground, thumbOpaqueBackgroundHovered = thumbOpaqueBackgroundHovered, thumbBorder = thumbBorder, - thumbBorderActive = thumbBorderHovered, + thumbBorderActive = thumbBorderActive, thumbOpaqueBorder = thumbOpaqueBorder, thumbOpaqueBorderHovered = thumbOpaqueBorderHovered, trackBackground = trackBackground, diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt index 5278404ae..9ee65d9fa 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt @@ -93,5 +93,11 @@ public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( thumbThickness: Dp = 3.dp, trackPadding: PaddingValues = PaddingValues(), + trackPaddingWithBorder: PaddingValues = trackPadding, ): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(thumbThickness, trackPadding, thumbColorAnimationDuration = 0.milliseconds) + ScrollbarVisibility.AlwaysVisible( + thumbThickness, + trackPadding, + trackPaddingWithBorder, + thumbColorAnimationDuration = 0.milliseconds + ) diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt index 323af2f58..c89d2ac9c 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -286,7 +286,7 @@ private fun ScrollableContainerImpl( // Leaving the bottom-end corner empty when both scrollbars visible at the same time val sizeOffsetWhenBothVisible = if (verticalScrollbarMeasurable != null && horizontalScrollbarMeasurable != null) { - scrollbarStyle.scrollbarVisibility.thumbThicknessExpanded.roundToPx() + scrollbarStyle.scrollbarVisibility.trackThicknessExpanded.roundToPx() } else 0 val verticalScrollbarPlaceable = if (verticalScrollbarMeasurable != null) { @@ -345,5 +345,5 @@ private fun ScrollableContainerImpl( public fun scrollbarContentSafePadding(style: ScrollbarStyle = JewelTheme.scrollbarStyle): Dp = when (style.scrollbarVisibility) { is AlwaysVisible -> 0.dp - is WhenScrolling -> style.scrollbarVisibility.thumbThicknessExpanded + is WhenScrolling -> style.scrollbarVisibility.trackThicknessExpanded } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt index d2bd0f18a..12b6a98b3 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt @@ -1,6 +1,7 @@ package org.jetbrains.jewel.ui.component import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState @@ -22,7 +23,7 @@ import androidx.compose.foundation.layout.padding 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.shape.CornerSize import androidx.compose.foundation.text.TextFieldScrollState import androidx.compose.foundation.v2.ScrollbarAdapter import androidx.compose.foundation.v2.maxScrollOffset @@ -39,8 +40,13 @@ 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.drawBehind +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange @@ -48,7 +54,10 @@ import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp @@ -59,8 +68,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import org.jetbrains.jewel.foundation.Stroke -import org.jetbrains.jewel.foundation.modifier.border import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility @@ -164,10 +171,10 @@ private fun BaseScrollbar( } } - val thumbWidth by animateDpAsState( - if (isExpanded) visibilityStyle.thumbThicknessExpanded else visibilityStyle.thumbThickness, - tween(visibilityStyle.expandAnimationDuration.inWholeMilliseconds.toInt()), - "scrollbar_thumbWidth", + val animatedThickness by animateDpAsState( + if (isExpanded) visibilityStyle.trackThicknessExpanded else visibilityStyle.trackThickness, + tween(visibilityStyle.expandAnimationDuration.inWholeMilliseconds.toInt(), easing = LinearEasing), + "scrollbar_thickness", ) val adapter = @@ -189,15 +196,31 @@ private fun BaseScrollbar( SliderAdapter(adapter, containerSize, thumbMinHeight, reverseLayout, isVertical, coroutineScope) } - val thumbThickness = thumbWidth.roundToPx() + val thumbBackgroundColor = getThumbBackgroundColor(isOpaque, isHovered, style, showScrollbar) + val thumbBorderColor = getThumbBorderColor(isOpaque, isHovered, style, showScrollbar) + val hasVisibleBorder = !areTheSameColor(thumbBackgroundColor, thumbBorderColor) + val trackPadding = + if (hasVisibleBorder) visibilityStyle.trackPaddingWithBorder else visibilityStyle.trackPadding + + val thumbThicknessPx = if (isVertical) { + val layoutDirection = LocalLayoutDirection.current + animatedThickness - + trackPadding.calculateLeftPadding(layoutDirection) - + trackPadding.calculateRightPadding(layoutDirection) + } else { + animatedThickness - + trackPadding.calculateTopPadding() - + trackPadding.calculateBottomPadding() + }.roundToPx() + val measurePolicy = if (isVertical) { - remember(sliderAdapter, thumbThickness) { - verticalMeasurePolicy(sliderAdapter, { containerSize = it }, thumbThickness) + remember(sliderAdapter, thumbThicknessPx) { + verticalMeasurePolicy(sliderAdapter, { containerSize = it }, thumbThicknessPx) } } else { - remember(sliderAdapter, thumbThickness) { - horizontalMeasurePolicy(sliderAdapter, { containerSize = it }, thumbThickness) + remember(sliderAdapter, thumbThicknessPx) { + horizontalMeasurePolicy(sliderAdapter, { containerSize = it }, thumbThicknessPx) } } @@ -223,71 +246,31 @@ private fun BaseScrollbar( Layout( content = { - val animatedThumbBackground by animateColorAsState( - targetValue = - if (isOpaque) { - if (isHovered) { - style.colors.thumbOpaqueBackgroundHovered - } else { - style.colors.thumbOpaqueBackground - } - } else { - if (showScrollbar) { - style.colors.thumbBackgroundActive - } else { - style.colors.thumbBackground - } - }, - animationSpec = thumbColorTween(showScrollbar, visibilityStyle), - "scrollbar_thumbBackground", - ) - val animatedThumbBorder by animateColorAsState( - targetValue = - if (isOpaque) { - if (isHovered) { - style.colors.thumbOpaqueBorderHovered - } else { - style.colors.thumbOpaqueBorder - } - } else { - if (showScrollbar) { - style.colors.thumbBorderActive - } else { - style.colors.thumbBorder - } - }, - animationSpec = thumbColorTween(showScrollbar, visibilityStyle), - "scrollbar_thumbBorder", - ) - - val thumbShape = RoundedCornerShape(style.metrics.thumbCornerSize) - Box( - Modifier - .layoutId("thumb") - .thenIf(canScroll) { - border( - Stroke.Alignment.Inside, - 1.dp, - color = animatedThumbBorder, - shape = thumbShape, - ) - .padding(1.dp) - .background(color = animatedThumbBackground, shape = thumbShape) - } - .thenIf(enabled) { - scrollbarDrag(interactionSource, dragInteraction, sliderAdapter) - }, + Thumb( + showScrollbar, + visibilityStyle, + canScroll, + enabled, + interactionSource, + dragInteraction, + sliderAdapter, + thumbBackgroundColor, + thumbBorderColor, + hasVisibleBorder, + style.metrics.thumbCornerSize ) }, modifier = modifier - .thenIf(showScrollbar && canScroll && isExpanded) { background(trackBackground) } + .thenIf(showScrollbar && canScroll && isExpanded) { + background(trackBackground) + } .scrollable( state = scrollState, orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal, enabled = enabled, reverseDirection = true, // Not sure why it's needed, but it is — TODO revisit this ) - .padding(visibilityStyle.trackPadding) + .padding(trackPadding) .hoverable(interactionSource = interactionSource) .thenIf(enabled && showScrollbar) { scrollOnPressTrack(style.trackClickBehavior, isVertical, reverseLayout, sliderAdapter) @@ -297,6 +280,120 @@ private fun BaseScrollbar( } } +private fun getThumbBackgroundColor( + isOpaque: Boolean, + isHovered: Boolean, + style: ScrollbarStyle, + showScrollbar: Boolean, +) = if (isOpaque) { + if (isHovered) { + style.colors.thumbOpaqueBackgroundHovered + } else { + style.colors.thumbOpaqueBackground + } +} else { + if (showScrollbar) { + style.colors.thumbBackgroundActive + } else { + style.colors.thumbBackground + } +} + +private fun getThumbBorderColor( + isOpaque: Boolean, + isHovered: Boolean, + style: ScrollbarStyle, + showScrollbar: Boolean, +) = if (isOpaque) { + if (isHovered) { + style.colors.thumbOpaqueBorderHovered + } else { + style.colors.thumbOpaqueBorder + } +} else { + if (showScrollbar) { + style.colors.thumbBorderActive + } else { + style.colors.thumbBorder + } +} + +private fun areTheSameColor(first: Color, second: Color) = + first.toArgb() == second.toArgb() + +@Composable +private fun Thumb( + showScrollbar: Boolean, + visibilityStyle: ScrollbarVisibility, + canScroll: Boolean, + enabled: Boolean, + interactionSource: MutableInteractionSource, + dragInteraction: MutableState, + sliderAdapter: SliderAdapter, + thumbBackgroundColor: Color, + thumbBorderColor: Color, + hasVisibleBorder: Boolean, + cornerSize: CornerSize, +) { + val background by animateColorAsState( + targetValue = thumbBackgroundColor, + animationSpec = thumbColorTween(showScrollbar, visibilityStyle), + "scrollbar_thumbBackground", + ) + + val border by animateColorAsState( + targetValue = thumbBorderColor, + animationSpec = thumbColorTween(showScrollbar, visibilityStyle), + "scrollbar_thumbBorder", + ) + + val borderWidth = 1.dp + val density = LocalDensity.current + Box( + Modifier + .layoutId("thumb") + .thenIf(canScroll) { + drawThumb(background, borderWidth, border, hasVisibleBorder, cornerSize, density) + } + .thenIf(enabled) { + scrollbarDrag(interactionSource, dragInteraction, sliderAdapter) + }, + ) +} + +private fun Modifier.drawThumb( + backgroundColor: Color, + borderWidth: Dp, + borderColor: Color, + hasVisibleBorder: Boolean, + cornerSize: CornerSize, + density: Density, +) = drawBehind { + val borderWidthPx = if (hasVisibleBorder) borderWidth.toPx() else 0f + + // First, draw the background, leaving room for the border around it + val bgCornerRadius = + CornerRadius((cornerSize.toPx(size, density) - borderWidthPx * 2).coerceAtLeast(0f)) + drawRoundRect( + color = backgroundColor, + topLeft = Offset(borderWidthPx, borderWidthPx), + size = Size(size.width - borderWidthPx * 2, size.height - borderWidthPx * 2f), + cornerRadius = bgCornerRadius, + ) + + // Then, draw the border itself + if (hasVisibleBorder) { + val strokeCornerRadius = CornerRadius(cornerSize.toPx(size, density)) + drawRoundRect( + color = borderColor, + topLeft = Offset(borderWidthPx / 2, borderWidthPx / 2), + size = Size(size.width - borderWidthPx, size.height - borderWidthPx), + cornerRadius = strokeCornerRadius, + style = Stroke(borderWidthPx) + ) + } +} + private fun appearanceTween( showScrollbar: Boolean, visibility: ScrollbarVisibility, @@ -305,7 +402,8 @@ private fun appearanceTween( visibility.appearAnimationDuration.inWholeMilliseconds.toInt() } else { visibility.disappearAnimationDuration.inWholeMilliseconds.toInt() - } + }, + easing = LinearEasing ) private fun thumbColorTween( @@ -316,7 +414,8 @@ private fun thumbColorTween( delayMillis = when { visibility is AlwaysVisible && !showScrollbar -> visibility.lingerDuration.inWholeMilliseconds.toInt() else -> 0 - } + }, + easing = LinearEasing ) // =========================================================================== @@ -336,14 +435,14 @@ private val IntRange.size get() = last + 1 - first private fun verticalMeasurePolicy( sliderAdapter: SliderAdapter, setContainerSize: (Int) -> Unit, - scrollThickness: Int, + thumbThickness: Int, ) = MeasurePolicy { measurables, constraints -> setContainerSize(constraints.maxHeight) val pixelRange = sliderAdapter.thumbPixelRange val placeable = measurables.first().measure( Constraints.fixed( - constraints.constrainWidth(scrollThickness), + constraints.constrainWidth(thumbThickness), pixelRange.size, ), ) @@ -355,7 +454,7 @@ private fun verticalMeasurePolicy( private fun horizontalMeasurePolicy( sliderAdapter: SliderAdapter, setContainerSize: (Int) -> Unit, - scrollThickness: Int, + thumbThickness: Int, ) = MeasurePolicy { measurables, constraints -> setContainerSize(constraints.maxWidth) val pixelRange = sliderAdapter.thumbPixelRange @@ -363,7 +462,7 @@ private fun horizontalMeasurePolicy( measurables.first().measure( Constraints.fixed( pixelRange.size, - constraints.constrainHeight(scrollThickness), + constraints.constrainHeight(thumbThickness), ), ) layout(constraints.maxWidth, placeable.height) { diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt index 5cea0ceba..dd5e89d03 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt @@ -52,9 +52,10 @@ public class ScrollbarMetrics( } public sealed interface ScrollbarVisibility { - public val thumbThickness: Dp - public val thumbThicknessExpanded: Dp + public val trackThickness: Dp + public val trackThicknessExpanded: Dp public val trackPadding: PaddingValues + public val trackPaddingWithBorder: PaddingValues public val appearAnimationDuration: Duration public val disappearAnimationDuration: Duration public val expandAnimationDuration: Duration @@ -63,11 +64,12 @@ public sealed interface ScrollbarVisibility { @GenerateDataFunctions public class AlwaysVisible( - public override val thumbThickness: Dp, + public override val trackThickness: Dp, public override val trackPadding: PaddingValues, + public override val trackPaddingWithBorder: PaddingValues, public override val thumbColorAnimationDuration: Duration, ) : ScrollbarVisibility { - public override val thumbThicknessExpanded: Dp = thumbThickness + public override val trackThicknessExpanded: Dp = trackThickness public override val appearAnimationDuration: Duration = 0.milliseconds public override val disappearAnimationDuration: Duration = 0.milliseconds public override val expandAnimationDuration: Duration = 0.milliseconds @@ -78,9 +80,10 @@ public sealed interface ScrollbarVisibility { @GenerateDataFunctions public class WhenScrolling( - public override val thumbThickness: Dp, - public override val thumbThicknessExpanded: Dp, + public override val trackThickness: Dp, + public override val trackThicknessExpanded: Dp, public override val trackPadding: PaddingValues, + public override val trackPaddingWithBorder: PaddingValues, public override val appearAnimationDuration: Duration, public override val disappearAnimationDuration: Duration, public override val expandAnimationDuration: Duration, From 0a6f50bf6df4d72ef7353084ef7c7dbb83d4cd17 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Mon, 19 Aug 2024 20:05:46 +0200 Subject: [PATCH 04/14] Fix Scrollbars [part 5/n] * Cleanup code * Update API definitions --- .../api/int-ui-standalone.api | 73 ++++++++------- .../styling/IntUiScrollbarStyling.kt | 14 +-- .../styling/IntUiTabStripScrollbarStyling.kt | 10 +-- .../standalone/view/component/Scrollbars.kt | 90 +++++++++---------- ui/api/ui.api | 83 ++++++++++++----- 5 files changed, 154 insertions(+), 116 deletions(-) diff --git a/int-ui/int-ui-standalone/api/int-ui-standalone.api b/int-ui/int-ui-standalone/api/int-ui-standalone.api index 96ada1ff4..20fb3c639 100644 --- a/int-ui/int-ui-standalone/api/int-ui-standalone.api +++ b/int-ui/int-ui-standalone/api/int-ui-standalone.api @@ -254,47 +254,37 @@ 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 (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 default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; 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 macOs-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static synthetic fun macOs-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static final fun macOs-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun macOs-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun macOs-wH6b6FI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;F)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; + public static synthetic fun macOs-wH6b6FI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FILjava/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 macOsDark-zwkVjRg (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; + public static synthetic fun macOsDark-zwkVjRg$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJILjava/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 tabStripDark (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; - public static final fun tabStripLight (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; - public static final fun tabStripMacOs-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 tabStripMacOs-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 tabStripMacOsDark (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 tabStripMacOsDark$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 tabStripMacOsLight (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 tabStripMacOsLight$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 tabStripWindowsAndLinux-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 tabStripWindowsAndLinux-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 tabStripWindowsAndLinuxDark (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 tabStripWindowsAndLinuxDark$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 tabStripWindowsAndLinuxLight (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 tabStripWindowsAndLinuxLight$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 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 static final fun macOsLight-zwkVjRg (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; + public static synthetic fun macOsLight-zwkVjRg$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; + public static final fun windowsAndLinux-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static synthetic fun windowsAndLinux-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static final fun windowsAndLinux-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun windowsAndLinux-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun windowsAndLinux-wH6b6FI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;F)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; + public static synthetic fun windowsAndLinux-wH6b6FI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; public static final fun windowsAndLinuxDark (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 windowsAndLinuxDark$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 windowsAndLinuxDark-iLRpYWo (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; - public static synthetic fun windowsAndLinuxDark-iLRpYWo$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; + public static final fun windowsAndLinuxDark-zwkVjRg (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; + public static synthetic fun windowsAndLinuxDark-zwkVjRg$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; public static final fun windowsAndLinuxLight (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 windowsAndLinuxLight$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 windowsAndLinuxLight-iLRpYWo (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; - public static synthetic fun windowsAndLinuxLight-iLRpYWo$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; + public static final fun windowsAndLinuxLight-zwkVjRg (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; + public static synthetic fun windowsAndLinuxLight-zwkVjRg$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; } public final class org/jetbrains/jewel/intui/standalone/styling/IntUiSegmentedControlButtonStylingKt { @@ -323,6 +313,25 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiSliderStyli public static final fun light-8v1krLo (Lorg/jetbrains/jewel/ui/component/styling/SliderColors$Companion;JJJJJJJJJJJJJJJLandroidx/compose/runtime/Composer;III)Lorg/jetbrains/jewel/ui/component/styling/SliderColors; } +public final class org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStylingKt { + public static final fun tabStrip-ziNgDLE (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun tabStrip-ziNgDLE$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun tabStripDark (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; + public static final fun tabStripLight (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; + public static final fun tabStripMacOs-wH6b6FI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;F)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; + public static synthetic fun tabStripMacOs-wH6b6FI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; + public static final fun tabStripMacOsDark (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 tabStripMacOsDark$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 tabStripMacOsLight (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 tabStripMacOsLight$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 tabStripWindowsAndLinux-wH6b6FI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;F)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; + public static synthetic fun tabStripWindowsAndLinux-wH6b6FI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; + public static final fun tabStripWindowsAndLinuxDark (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 tabStripWindowsAndLinuxDark$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 tabStripWindowsAndLinuxLight (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 tabStripWindowsAndLinuxLight$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 final class org/jetbrains/jewel/intui/standalone/styling/IntUiTabStylingKt { public static final fun default (Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha$Companion;FFFFFFFFFF)Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha; public static synthetic fun default$default (Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha$Companion;FFFFFFFFFFILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/TabContentAlpha; diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt index f25bdafd8..00bf4f668 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt @@ -90,18 +90,14 @@ public fun ScrollbarVisibility.AlwaysVisible.Companion.default(): ScrollbarVisib public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( trackThickness: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(2.dp), - trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), thumbColorAnimationDuration: Duration = 330.milliseconds, -): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPaddingWithBorder, thumbColorAnimationDuration) +): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) public fun ScrollbarVisibility.AlwaysVisible.Companion.windowsAndLinux( trackThickness: Dp = 18.dp, trackPadding: PaddingValues = PaddingValues(), - trackPaddingWithBorder: PaddingValues = trackPadding, thumbColorAnimationDuration: Duration = 330.milliseconds, -): ScrollbarVisibility.AlwaysVisible = - ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPaddingWithBorder, thumbColorAnimationDuration) +): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisibility.WhenScrolling = if (hostOs.isMacOS) { @@ -271,11 +267,9 @@ public fun ScrollbarColors.Companion.windowsAndLinuxDark( public fun ScrollbarMetrics.Companion.macOs( thumbCornerSize: CornerSize = CornerSize(100), minThumbLength: Dp = 20.dp, -): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength) +): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarMetrics.Companion.windowsAndLinux( thumbCornerSize: CornerSize = CornerSize(0), minThumbLength: Dp = 20.dp, -): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength) +): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt index 9ee65d9fa..a96bce94e 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt @@ -81,17 +81,15 @@ public fun ScrollbarStyle.Companion.tabStripWindowsAndLinuxDark( public fun ScrollbarMetrics.Companion.tabStripMacOs( thumbCornerSize: CornerSize = CornerSize(100), minThumbLength: Dp = 20.dp, -): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength) +): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( thumbCornerSize: CornerSize = CornerSize(0), minThumbLength: Dp = 16.dp, -): ScrollbarMetrics = - ScrollbarMetrics(thumbCornerSize, minThumbLength) +): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( - thumbThickness: Dp = 3.dp, + thumbThickness: Dp = 4.dp, trackPadding: PaddingValues = PaddingValues(), trackPaddingWithBorder: PaddingValues = trackPadding, ): ScrollbarVisibility.AlwaysVisible = @@ -99,5 +97,5 @@ public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( thumbThickness, trackPadding, trackPaddingWithBorder, - thumbColorAnimationDuration = 0.milliseconds + thumbColorAnimationDuration = 0.milliseconds, ) diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index 68c1798f7..f58ee8d19 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -49,22 +49,14 @@ import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior import org.jetbrains.jewel.ui.theme.textAreaStyle import java.util.Locale -// STOPSHIP testing only -fun main() = singleWindowApplication(title = "Scrollbars") { - IntUiTheme { - Box(Modifier.fillMaxSize().background(JewelTheme.globalColors.panelBackground).padding(16.dp)) { - Scrollbars() - } - } -} - @Composable fun Scrollbars() { Column { val isDark = JewelTheme.isDark - val baseStyle = remember(isDark) { - if (isDark) ScrollbarStyle.dark() else ScrollbarStyle.light() - } + val baseStyle = + remember(isDark) { + if (isDark) ScrollbarStyle.dark() else ScrollbarStyle.light() + } var alwaysVisible by remember { mutableStateOf(false) } var clickBehavior by remember { mutableStateOf(baseStyle.trackClickBehavior) } @@ -78,25 +70,25 @@ fun Scrollbars() { verticalAlignment = Alignment.CenterVertically, ) { val style by - remember(alwaysVisible, clickBehavior, baseStyle) { - mutableStateOf( - if (alwaysVisible) { - ScrollbarStyle( - colors = baseStyle.colors, - metrics = baseStyle.metrics, - trackClickBehavior = clickBehavior, - scrollbarVisibility = ScrollbarVisibility.AlwaysVisible.default(), - ) - } else { - ScrollbarStyle( - colors = baseStyle.colors, - metrics = baseStyle.metrics, - trackClickBehavior = clickBehavior, - scrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), - ) - } - ) - } + remember(alwaysVisible, clickBehavior, baseStyle) { + mutableStateOf( + if (alwaysVisible) { + ScrollbarStyle( + colors = baseStyle.colors, + metrics = baseStyle.metrics, + trackClickBehavior = clickBehavior, + scrollbarVisibility = ScrollbarVisibility.AlwaysVisible.default(), + ) + } else { + ScrollbarStyle( + colors = baseStyle.colors, + metrics = baseStyle.metrics, + trackClickBehavior = clickBehavior, + scrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), + ) + }, + ) + } LazyColumnWithScrollbar(style, Modifier.weight(1f).height(200.dp)) ColumnWithScrollbar(style, Modifier.weight(1f).height(200.dp)) @@ -140,7 +132,10 @@ private fun SettingsRow( } @Composable -private fun LazyColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { +private fun LazyColumnWithScrollbar( + style: ScrollbarStyle, + modifier: Modifier, +) { Column(modifier) { Text("LazyColumn", style = Typography.h2TextStyle()) @@ -149,27 +144,29 @@ private fun LazyColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { val scrollState = rememberLazyListState() VerticallyScrollableContainer( scrollState, - modifier = Modifier.weight(1f) - .fillMaxWidth() - .border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal), + modifier = + Modifier.weight(1f) + .fillMaxWidth() + .border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal), style = style, ) { LazyColumn( state = scrollState, - modifier = Modifier.fillMaxSize().background(JewelTheme.textAreaStyle.colors.background) + modifier = Modifier.fillMaxSize().background(JewelTheme.textAreaStyle.colors.background), ) { itemsIndexed(LIST_ITEMS) { index, item -> Column { Text( - modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) - .padding(end = scrollbarContentSafePadding(style)), + modifier = + Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + .padding(end = scrollbarContentSafePadding(style)), text = item, ) if (index != LIST_ITEMS.lastIndex) { Divider( orientation = Orientation.Horizontal, - color = JewelTheme.globalColors.borders.normal + color = JewelTheme.globalColors.borders.normal, ) } } @@ -180,7 +177,10 @@ private fun LazyColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { } @Composable -private fun ColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { +private fun ColumnWithScrollbar( + style: ScrollbarStyle, + modifier: Modifier, +) { Column(modifier) { Text("Column", fontSize = 18.sp) Spacer(Modifier.height(8.dp)) @@ -189,11 +189,11 @@ private fun ColumnWithScrollbar(style: ScrollbarStyle, modifier: Modifier) { val scrollState = rememberScrollState() Column( modifier = - Modifier - .background(JewelTheme.textAreaStyle.colors.background) - .verticalScroll(scrollState) - .padding(end = scrollbarContentSafePadding(style)) - .align(Alignment.CenterStart), + Modifier + .background(JewelTheme.textAreaStyle.colors.background) + .verticalScroll(scrollState) + .padding(end = scrollbarContentSafePadding(style)) + .align(Alignment.CenterStart), ) { LIST_ITEMS.forEach { Text( diff --git a/ui/api/ui.api b/ui/api/ui.api index 6b1c1116c..fd705abd9 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -577,12 +577,22 @@ public final class org/jetbrains/jewel/ui/component/RadioButtonState$Companion { public static synthetic fun of-fp8g3n8$default (Lorg/jetbrains/jewel/ui/component/RadioButtonState$Companion;ZZZZZZILjava/lang/Object;)J } -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;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 +public final class org/jetbrains/jewel/ui/component/ScrollableContainerKt { + public static final fun HorizontallyScrollableContainer (Landroidx/compose/foundation/lazy/LazyListState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun HorizontallyScrollableContainer (Landroidx/compose/foundation/lazy/grid/LazyGridState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun HorizontallyScrollableContainer (Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/ScrollState;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun ScrollableContainer (Landroidx/compose/foundation/lazy/LazyListState;Landroidx/compose/foundation/lazy/LazyListState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun ScrollableContainer (Landroidx/compose/foundation/lazy/grid/LazyGridState;Landroidx/compose/foundation/lazy/grid/LazyGridState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun ScrollableContainer (Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/ScrollState;Landroidx/compose/foundation/ScrollState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun VerticallyScrollableContainer (Landroidx/compose/foundation/lazy/LazyListState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun VerticallyScrollableContainer (Landroidx/compose/foundation/lazy/grid/LazyGridState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun VerticallyScrollableContainer (Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/ScrollState;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun scrollbarContentSafePadding (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;II)F +} + +public final class org/jetbrains/jewel/ui/component/ScrollbarKt { + public static final fun HorizontalScrollbar (Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/ui/Modifier;ZZLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;ZLandroidx/compose/runtime/Composer;II)V + public static final fun VerticalScrollbar (Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/ui/Modifier;ZZLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;ZLandroidx/compose/runtime/Composer;II)V } public final class org/jetbrains/jewel/ui/component/SegmentedControlButtonData { @@ -1862,16 +1872,20 @@ 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 (JJJJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (JJJJJJJJJJJJLkotlin/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 getThumbBackgroundActive-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 getThumbBorderActive-0d7_KjU ()J + public final fun getThumbOpaqueBackground-0d7_KjU ()J + public final fun getThumbOpaqueBackgroundHovered-0d7_KjU ()J + public final fun getThumbOpaqueBorder-0d7_KjU ()J + public final fun getThumbOpaqueBorderHovered-0d7_KjU ()J public final fun getTrackBackground-0d7_KjU ()J - public final fun getTrackBackgroundHovered-0d7_KjU ()J + public final fun getTrackBackgroundExpanded-0d7_KjU ()J + public final fun getTrackOpaqueBackground-0d7_KjU ()J + public final fun getTrackOpaqueBackgroundHovered-0d7_KjU ()J public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1882,14 +1896,10 @@ 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 (Landroidx/compose/foundation/shape/CornerSize;FFFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Landroidx/compose/foundation/shape/CornerSize;FLkotlin/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; } @@ -1918,25 +1928,52 @@ public final class org/jetbrains/jewel/ui/component/styling/ScrollbarStylingKt { } public abstract interface class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility { + public abstract fun getAppearAnimationDuration-UwyO8pc ()J + public abstract fun getDisappearAnimationDuration-UwyO8pc ()J + public abstract fun getExpandAnimationDuration-UwyO8pc ()J + public abstract fun getLingerDuration-UwyO8pc ()J + public abstract fun getThumbColorAnimationDuration-UwyO8pc ()J + public abstract fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public abstract fun getTrackPaddingWithBorder ()Landroidx/compose/foundation/layout/PaddingValues; + public abstract fun getTrackThickness-D9Ej5fM ()F + public abstract fun getTrackThicknessExpanded-D9Ej5fM ()F } 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 static final field Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion; + public synthetic fun (FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z + public fun getAppearAnimationDuration-UwyO8pc ()J + public fun getDisappearAnimationDuration-UwyO8pc ()J + public fun getExpandAnimationDuration-UwyO8pc ()J + public fun getLingerDuration-UwyO8pc ()J + public fun getThumbColorAnimationDuration-UwyO8pc ()J + public fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getTrackPaddingWithBorder ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getTrackThickness-D9Ej5fM ()F + public fun getTrackThicknessExpanded-D9Ej5fM ()F public fun hashCode ()I public fun toString ()Ljava/lang/String; } +public final class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion { +} + 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 (JJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;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 getAppearAnimationDuration-UwyO8pc ()J + public fun getDisappearAnimationDuration-UwyO8pc ()J + public fun getExpandAnimationDuration-UwyO8pc ()J + public fun getLingerDuration-UwyO8pc ()J + public fun getThumbColorAnimationDuration-UwyO8pc ()J + public fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getTrackPaddingWithBorder ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getTrackThickness-D9Ej5fM ()F + public fun getTrackThicknessExpanded-D9Ej5fM ()F public fun hashCode ()I public fun toString ()Ljava/lang/String; } From 1cac4a1028ed215c34ce2cf45db780f0459230da Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Tue, 20 Aug 2024 12:39:58 +0200 Subject: [PATCH 05/14] Fix Scrollbars [part 6/n] * Make bridge compile * Fix styling in bridge * Update bridge samples * Fix TextArea scrollbars --- ide-laf-bridge/api/ide-laf-bridge.api | 11 +- .../jewel/bridge/MacScrollbarHelper.kt | 21 +- .../jewel/bridge/theme/ScrollbarBridge.kt | 193 +++++++++++++----- .../samples/ideplugin/ComponentShowcaseTab.kt | 29 ++- .../ideplugin/ScrollbarsShowcaseTab.kt | 39 +--- .../releasessample/ReleasesSampleCompose.kt | 40 +--- .../standalone/view/component/Scrollbars.kt | 2 - ui/api/ui.api | 2 +- .../jewel/ui/component/InputField.kt | 2 + .../jetbrains/jewel/ui/component/TextArea.kt | 5 + .../jetbrains/jewel/ui/component/TextField.kt | 1 + 11 files changed, 205 insertions(+), 140 deletions(-) diff --git a/ide-laf-bridge/api/ide-laf-bridge.api b/ide-laf-bridge/api/ide-laf-bridge.api index b2bb465e5..5320bb937 100644 --- a/ide-laf-bridge/api/ide-laf-bridge.api +++ b/ide-laf-bridge/api/ide-laf-bridge.api @@ -139,8 +139,15 @@ public final class org/jetbrains/jewel/bridge/theme/IntUiBridgeKt { } 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 static final fun default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static final fun macOs-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static synthetic fun macOs-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static final fun macOs-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun macOs-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun windowsAndLinux-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static synthetic fun windowsAndLinux-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static final fun windowsAndLinux-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun windowsAndLinux-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; } public final class org/jetbrains/jewel/bridge/theme/SwingBridgeThemeKt { diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt index f3d3296f0..6e234613e 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt @@ -8,12 +8,15 @@ import com.sun.jna.Callback import com.sun.jna.Pointer import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.jetbrains.jewel.bridge.theme.defaults +import org.jetbrains.jewel.bridge.theme.macOs +import org.jetbrains.jewel.bridge.theme.windowsAndLinux import org.jetbrains.jewel.foundation.util.myLogger import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior internal object MacScrollbarHelper { + private val logger = myLogger() + private val _scrollbarVisibilityStyleFlow = MutableStateFlow(scrollbarVisibility) val scrollbarVisibilityStyleFlow: StateFlow = _scrollbarVisibilityStyleFlow @@ -43,7 +46,7 @@ internal object MacScrollbarHelper { val scrollbarVisibility: ScrollbarVisibility get() { if (!SystemInfoRt.isMac) { - return ScrollbarVisibility.AlwaysVisible + return ScrollbarVisibility.AlwaysVisible.windowsAndLinux() } val pool = NSAutoreleasePool() @@ -53,7 +56,7 @@ internal object MacScrollbarHelper { } finally { pool.drain() } - return ScrollbarVisibility.AlwaysVisible + return readMacScrollbarStyle() } private fun initNotificationObserver() { @@ -61,13 +64,13 @@ internal object MacScrollbarHelper { val delegateClass = Foundation.allocateObjcClassPair(Foundation.getObjcClass("NSObject"), "NSScrollerChangesObserver") - if (ID.NIL != delegateClass) { + if (delegateClass != ID.NIL) { if (!addScrollbarVisibilityChangeListener(delegateClass)) { - myLogger().error("Cannot add observer method") + logger.error("Cannot add scrollbar visibility observer method") } if (!addTrackClickBehaviorChangeListener(delegateClass)) { - myLogger().error("Cannot add observer method") + logger.error("Cannot add scrollbar track click behavior observer method") } Foundation.registerObjcClassPair(delegateClass) } @@ -126,6 +129,7 @@ internal object MacScrollbarHelper { } private fun readMacScrollbarBehavior(): TrackClickBehavior { + logger.info("Reading scrollbar track click behavior...") val defaults = Foundation.invoke("NSUserDefaults", "standardUserDefaults") Foundation.invoke(defaults, "synchronize") return Foundation @@ -134,13 +138,14 @@ internal object MacScrollbarHelper { } private fun readMacScrollbarStyle(): ScrollbarVisibility { + logger.info("Reading scrollbar visibility...") val nsScroller = Foundation.invoke(Foundation.getObjcClass("NSScroller"), "preferredScrollerStyle") val visibility: ScrollbarVisibility = if (1 == nsScroller.toInt()) { - ScrollbarVisibility.WhenScrolling.Companion.defaults() + ScrollbarVisibility.WhenScrolling.macOs() } else { - ScrollbarVisibility.AlwaysVisible + ScrollbarVisibility.AlwaysVisible.macOs() } return visibility } diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt index 99b3d6f1b..e3ff96dce 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt @@ -4,6 +4,7 @@ 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 androidx.compose.ui.unit.dp import org.jetbrains.jewel.bridge.MacScrollbarHelper import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified @@ -28,7 +29,7 @@ private fun readScrollbarVisibility() = if (hostOs.isMacOS) { MacScrollbarHelper.scrollbarVisibility } else { - ScrollbarVisibility.AlwaysVisible + ScrollbarVisibility.AlwaysVisible.windowsAndLinux() } private fun readScrollbarColors(isDark: Boolean) = @@ -50,11 +51,25 @@ private fun readScrollbarWindowsAndLinuxColors(isDark: Boolean): ScrollbarColors thumbBackground = readScrollBarColorForKey( isDark, - "ScrollBar.thumbColor", + "ScrollBar.Transparent.thumbColor", 0x33737373, 0x47A6A6A6, ), thumbBackgroundActive = + readScrollBarColorForKey( + isDark, + "ScrollBar.Transparent.hoverThumbColor", + 0x47737373, + 0x59A6A6A6, + ), + thumbOpaqueBackground = + readScrollBarColorForKey( + isDark, + "ScrollBar.thumbColor", + 0x33737373, + 0x47A6A6A6, + ), + thumbOpaqueBackgroundHovered = readScrollBarColorForKey( isDark, "ScrollBar.hoverThumbColor", @@ -64,11 +79,25 @@ private fun readScrollbarWindowsAndLinuxColors(isDark: Boolean): ScrollbarColors thumbBorder = readScrollBarColorForKey( isDark, - "ScrollBar.thumbBorderColor", + "ScrollBar.Transparent.thumbBorderColor", 0x33595959, 0x47383838, ), thumbBorderActive = + readScrollBarColorForKey( + isDark, + "ScrollBar.Transparent.hoverThumbBorderColor", + 0x47595959, + 0x59383838, + ), + thumbOpaqueBorder = + readScrollBarColorForKey( + isDark, + "ScrollBar.thumbBorderColor", + 0x33595959, + 0x47383838, + ), + thumbOpaqueBorderHovered = readScrollBarColorForKey( isDark, "ScrollBar.hoverThumbBorderColor", @@ -90,31 +119,45 @@ private fun readScrollbarWindowsAndLinuxColors(isDark: Boolean): ScrollbarColors 0x1A808080, ), trackOpaqueBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.trackColor", - 0x00808080, - 0x00808080, + readScrollBarColorForKey( + isDark, + "ScrollBar.trackColor", + 0x00808080, + 0x00808080, ), trackOpaqueBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.hoverTrackColor", - 0x00808080, - 0x00808080, - ), + readScrollBarColorForKey( + isDark, + "ScrollBar.hoverTrackColor", + 0x1A808080, + 0x1A808080, + ), ) private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = ScrollbarColors( thumbBackground = + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.thumbColor", + 0x00000000, + 0x59808080, + ), + thumbBackgroundActive = + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.hoverThumbColor", + 0x80000000, + 0x8C808080, + ), + thumbOpaqueBackground = readScrollBarColorForKey( isDark, "ScrollBar.Mac.thumbColor", 0x33000000, 0x59808080, ), - thumbBackgroundActive = + thumbOpaqueBackgroundHovered = readScrollBarColorForKey( isDark, "ScrollBar.Mac.hoverThumbColor", @@ -122,13 +165,27 @@ private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = 0x8C808080, ), thumbBorder = + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.thumbBorderColor", + 0x00000000, + 0x59262626, + ), + thumbBorderActive = + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.hoverThumbBorderColor", + 0x80000000, + 0x8C262626, + ), + thumbOpaqueBorder = readScrollBarColorForKey( isDark, "ScrollBar.Mac.thumbBorderColor", 0x33000000, 0x59262626, ), - thumbBorderActive = + thumbOpaqueBorderHovered = readScrollBarColorForKey( isDark, "ScrollBar.Mac.hoverThumbBorderColor", @@ -136,33 +193,33 @@ private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = 0x8C262626, ), trackBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.trackColor", - 0x00808080, - 0x00808080, - ), + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.trackColor", + 0x00808080, + 0x00808080, + ), trackBackgroundExpanded = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.hoverTrackColor", - 0x1A808080, - 0x1A808080, - ), + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.Transparent.hoverTrackColor", + 0x1A808080, + 0x1A808080, + ), trackOpaqueBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.trackColor", - 0x00808080, - 0x00808080, - ), + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.trackColor", + 0x00808080, + 0x00808080, + ), trackOpaqueBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.hoverTrackColor", - 0x00808080, - 0x00808080, - ), + readScrollBarColorForKey( + isDark, + "ScrollBar.Mac.hoverTrackColor", + 0x1A808080, + 0x1A808080, + ), ) private fun readScrollBarColorForKey( @@ -177,30 +234,72 @@ 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), ) } else { ScrollbarMetrics( thumbCornerSize = CornerSize(0), - thumbThickness = 8.dp, - thumbThicknessExpanded = 8.dp, minThumbLength = 16.dp, - trackPadding = PaddingValues(), ) } -public fun ScrollbarVisibility.WhenScrolling.Companion.defaults( +public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisibility.WhenScrolling = + if (hostOs.isMacOS) { + ScrollbarVisibility.WhenScrolling.macOs() + } else { + ScrollbarVisibility.WhenScrolling.windowsAndLinux() + } + +public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( + trackThickness: Dp = 11.dp, + trackThicknessExpanded: Dp = 14.dp, + trackPadding: PaddingValues = PaddingValues(2.dp), + trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), appearAnimationDuration: Duration = 125.milliseconds, disappearAnimationDuration: Duration = 125.milliseconds, expandAnimationDuration: Duration = 125.milliseconds, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( + trackThickness = trackThickness, + trackThicknessExpanded = trackThicknessExpanded, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPaddingWithBorder, appearAnimationDuration = appearAnimationDuration, disappearAnimationDuration = disappearAnimationDuration, expandAnimationDuration = expandAnimationDuration, lingerDuration = lingerDuration, ) + +public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( + trackThickness: Dp = 11.dp, + trackThicknessExpanded: Dp = 14.dp, + trackPadding: PaddingValues = PaddingValues(), + trackPaddingWithBorder: PaddingValues = trackPadding, + appearAnimationDuration: Duration = 125.milliseconds, + disappearAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = 125.milliseconds, + lingerDuration: Duration = 700.milliseconds, +): ScrollbarVisibility.WhenScrolling = + ScrollbarVisibility.WhenScrolling( + trackThickness = trackThickness, + trackThicknessExpanded = trackThicknessExpanded, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPaddingWithBorder, + appearAnimationDuration = appearAnimationDuration, + disappearAnimationDuration = disappearAnimationDuration, + expandAnimationDuration = expandAnimationDuration, + lingerDuration = lingerDuration, + ) + +public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( + trackThickness: Dp = 14.dp, + trackPadding: PaddingValues = PaddingValues(2.dp), + thumbColorAnimationDuration: Duration = 330.milliseconds, +): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) + +public fun ScrollbarVisibility.AlwaysVisible.Companion.windowsAndLinux( + trackThickness: Dp = 18.dp, + trackPadding: PaddingValues = PaddingValues(), + thumbColorAnimationDuration: Duration = 330.milliseconds, +): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt index a330cdcae..c23cc9bfc 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt @@ -13,10 +13,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -64,6 +62,7 @@ import org.jetbrains.jewel.ui.component.Text import org.jetbrains.jewel.ui.component.TextField import org.jetbrains.jewel.ui.component.Tooltip import org.jetbrains.jewel.ui.component.Typography +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer import org.jetbrains.jewel.ui.component.separator import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.painter.badge.DotBadgeShape @@ -76,19 +75,19 @@ import org.jetbrains.jewel.ui.theme.colorPalette internal fun ComponentShowcaseTab() { val bgColor by remember(JBColor.PanelBackground.rgb) { mutableStateOf(JBColor.PanelBackground.toComposeColor()) } - val scrollState = rememberScrollState() - Row( - modifier = - Modifier - .trackComponentActivation(LocalComponent.current) - .fillMaxSize() - .background(bgColor) - .verticalScroll(scrollState) - .padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - ColumnOne() - ColumnTwo() + VerticallyScrollableContainer { + Row( + modifier = + Modifier + .trackComponentActivation(LocalComponent.current) + .fillMaxSize() + .background(bgColor) + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + ColumnOne() + ColumnTwo() + } } } diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt index ed12972c4..6444bf6e2 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt @@ -1,11 +1,10 @@ package org.jetbrains.jewel.samples.ideplugin -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -17,16 +16,10 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import com.intellij.ui.JBColor import org.jetbrains.jewel.bridge.LocalComponent -import org.jetbrains.jewel.bridge.toComposeColor import org.jetbrains.jewel.foundation.modifier.trackActivation import org.jetbrains.jewel.foundation.modifier.trackComponentActivation import org.jetbrains.jewel.foundation.theme.JewelTheme @@ -34,44 +27,36 @@ import org.jetbrains.jewel.ui.Orientation import org.jetbrains.jewel.ui.component.Divider import org.jetbrains.jewel.ui.component.Text import org.jetbrains.jewel.ui.component.TextArea -import org.jetbrains.jewel.ui.component.VerticalScrollbar -import org.jetbrains.jewel.ui.theme.scrollbarStyle +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer import java.util.Locale @Composable internal fun ScrollbarsShowcaseTab() { - val bgColor by remember(JBColor.PanelBackground.rgb) { mutableStateOf(JBColor.PanelBackground.toComposeColor()) } - Column( Modifier .trackComponentActivation(LocalComponent.current) .fillMaxSize() - .background(bgColor) .padding(16.dp) .trackActivation(), verticalArrangement = Arrangement.spacedBy(16.dp), ) { Row(modifier = Modifier.fillMaxWidth().height(200.dp)) { val textFieldState = rememberTextFieldState(ANDROID_IPSUM) - TextArea( - state = textFieldState, - modifier = Modifier.size(300.dp), - ) + TextArea(state = textFieldState, modifier = Modifier.size(300.dp)) - Divider(Orientation.Vertical, modifier = Modifier.width(10.dp)) + Spacer(Modifier.width(10.dp)) - Box(Modifier.border(1.dp, JewelTheme.globalColors.borders.normal)) { - val scrollState = rememberLazyListState() + val scrollState = rememberLazyListState() + VerticallyScrollableContainer( + scrollState, + Modifier.width(200.dp).border(1.dp, JewelTheme.globalColors.borders.normal), + ) { 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(modifier = Modifier.height(48.dp)) { + Column { Text( modifier = Modifier.padding(horizontal = 8.dp), text = item, @@ -80,10 +65,6 @@ internal fun ScrollbarsShowcaseTab() { } } } - VerticalScrollbar( - scrollState = scrollState, - modifier = Modifier.align(Alignment.CenterEnd), - ) } } } diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt index f47087c57..b106da6f4 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.Image -import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -30,11 +29,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.onClick -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -59,7 +55,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.intellij.icons.AllIcons import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.util.ui.JBUI @@ -84,7 +79,7 @@ import org.jetbrains.jewel.ui.component.PopupMenu import org.jetbrains.jewel.ui.component.Text import org.jetbrains.jewel.ui.component.TextField import org.jetbrains.jewel.ui.component.Typography -import org.jetbrains.jewel.ui.component.VerticalScrollbar +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer import org.jetbrains.jewel.ui.component.items import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider @@ -149,7 +144,7 @@ private fun LeftColumn( } val listState = rememberSelectableLazyListState() - Box(modifier) { + VerticallyScrollableContainer(listState.lazyListState, modifier) { SelectableLazyColumn( modifier = Modifier.fillMaxSize(), state = listState, @@ -180,11 +175,6 @@ private fun LeftColumn( } } } - - VerticalScrollbar( - adapter = rememberScrollbarAdapter(listState.lazyListState), - modifier = Modifier.fillMaxHeight().align(Alignment.CenterEnd), - ) } } } @@ -364,11 +354,7 @@ private fun OverflowMenu( }, onClick = { menuVisible = !menuVisible }, ) { - Icon( - resource = "actions/more.svg", - iconClass = AllIcons::class.java, - contentDescription = "Select data source", - ) + Icon(key = AllIconsKeys.Ide.Notification.Gear, contentDescription = "Select data source") } val contentSources = @@ -421,10 +407,8 @@ private fun RightColumn( if (selectedItem == null) { Text("Nothing to see here", color = JBUI.CurrentTheme.Label.disabledForeground().toComposeColor()) } else { - val scrollState = rememberScrollState() - VerticalScrollbarContainer(scrollState, modifier = modifier) { + VerticallyScrollableContainer(modifier = modifier) { Column( - modifier = Modifier.verticalScroll(scrollState), verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.Start, ) { @@ -479,22 +463,6 @@ private fun ReleaseImage(imagePath: String) { ) } -@Composable -private fun VerticalScrollbarContainer( - scrollState: ScrollState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - Box(modifier) { - content() - - VerticalScrollbar( - adapter = rememberScrollbarAdapter(scrollState), - modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), - ) - } -} - @Composable private fun ItemDetailsText(selectedItem: ContentItem) { Column( diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index f58ee8d19..01cf27467 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -26,14 +26,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.singleWindowApplication import org.jetbrains.jewel.foundation.Stroke import org.jetbrains.jewel.foundation.modifier.border import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.intui.standalone.styling.dark import org.jetbrains.jewel.intui.standalone.styling.default import org.jetbrains.jewel.intui.standalone.styling.light -import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme import org.jetbrains.jewel.ui.Orientation import org.jetbrains.jewel.ui.component.CheckboxRow import org.jetbrains.jewel.ui.component.Divider diff --git a/ui/api/ui.api b/ui/api/ui.api index fd705abd9..1092b3a04 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -880,7 +880,7 @@ public final class org/jetbrains/jewel/ui/component/TabsKt { } public final class org/jetbrains/jewel/ui/component/TextAreaKt { - public static final fun TextArea (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/foundation/text/KeyboardOptions;ILorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;ZLandroidx/compose/runtime/Composer;III)V + public static final fun TextArea (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/foundation/text/KeyboardOptions;ILorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;ZLorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;III)V public static final fun TextArea (Landroidx/compose/ui/text/input/TextFieldValue;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLkotlin/jvm/functions/Function2;ZLorg/jetbrains/jewel/ui/Outline;Landroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;III)V public static final fun TextArea (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;III)V } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt index afb7a873c..4f686bd1d 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt @@ -41,6 +41,7 @@ import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Pressed import org.jetbrains.jewel.foundation.state.FocusableComponentState import org.jetbrains.jewel.ui.Outline import org.jetbrains.jewel.ui.component.styling.InputFieldStyle +import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle import org.jetbrains.jewel.ui.focusOutline import org.jetbrains.jewel.ui.outline import org.jetbrains.jewel.ui.util.thenIf @@ -58,6 +59,7 @@ internal fun InputField( interactionSource: MutableInteractionSource, style: InputFieldStyle, textStyle: TextStyle, + scrollbarStyle: ScrollbarStyle?, showScrollbar: Boolean, modifier: Modifier, decorationBox: @Composable (innerTextField: @Composable () -> Unit, state: InputFieldState) -> Unit, diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt index cbdcb4cab..c99d97ebc 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt @@ -1,5 +1,6 @@ package org.jetbrains.jewel.ui.component +import androidx.compose.foundation.defaultScrollbarStyle import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -30,7 +31,9 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.LocalContentColor import org.jetbrains.jewel.foundation.theme.LocalTextStyle import org.jetbrains.jewel.ui.Outline +import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle import org.jetbrains.jewel.ui.component.styling.TextAreaStyle +import org.jetbrains.jewel.ui.theme.scrollbarStyle import org.jetbrains.jewel.ui.theme.textAreaStyle /** @@ -52,6 +55,7 @@ public fun TextArea( textStyle: TextStyle = JewelTheme.defaultTextStyle, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, showScrollbar: Boolean = true, + scrollbarStyle: ScrollbarStyle = JewelTheme.scrollbarStyle ) { val minSize = style.metrics.minSize InputField( @@ -66,6 +70,7 @@ public fun TextArea( interactionSource = interactionSource, style = style, textStyle = textStyle, + scrollbarStyle = scrollbarStyle, showScrollbar = showScrollbar, modifier = modifier.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height), decorationBox = { innerTextField, _ -> diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt index abd4722a2..01d400135 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt @@ -66,6 +66,7 @@ public fun TextField( style = style, textStyle = textStyle, showScrollbar = false, + scrollbarStyle = null, modifier = modifier, decorationBox = { innerTextField, _ -> val minSize = style.metrics.minSize From 71ba3e139793c56b07fbc09756bbf6ce4626957c Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Tue, 20 Aug 2024 13:26:18 +0200 Subject: [PATCH 06/14] Fix Scrollbars [part 7/n] * Fix bug in ScrollableContainer * Improve ReleasesSampleCompose --- .../releasessample/ReleasesSampleCompose.kt | 28 +++++++++---------- .../jewel/ui/component/ScrollableContainer.kt | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt index b106da6f4..fd14b36fe 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt @@ -403,22 +403,22 @@ private fun RightColumn( selectedItem: ContentItem?, modifier: Modifier, ) { - Box(modifier, contentAlignment = Alignment.Center) { - if (selectedItem == null) { + if (selectedItem == null) { + Box(modifier, contentAlignment = Alignment.Center) { Text("Nothing to see here", color = JBUI.CurrentTheme.Label.disabledForeground().toComposeColor()) - } else { - VerticallyScrollableContainer(modifier = modifier) { - Column( - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.Start, - ) { - val imagePath = selectedItem.imagePath - if (imagePath != null) { - ReleaseImage(imagePath) - } - - ItemDetailsText(selectedItem) + } + } else { + VerticallyScrollableContainer(modifier = modifier) { + Column( + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + val imagePath = selectedItem.imagePath + if (imagePath != null) { + ReleaseImage(imagePath) } + + ItemDetailsText(selectedItem) } } } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt index c89d2ac9c..8ad29fe0a 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -59,7 +59,7 @@ public fun VerticallyScrollableContainer( modifier = modifier.onHover { keepVisible = it }, scrollbarStyle = style, ) { - Box(modifier.layoutId(ID_CONTENT).verticalScroll(scrollState)) { content() } + Box(Modifier.layoutId(ID_CONTENT).verticalScroll(scrollState)) { content() } } } From b8e49ff84152f53ce705def960b12e2db1f88781 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Tue, 20 Aug 2024 13:40:00 +0200 Subject: [PATCH 07/14] Fix Scrollbars [part 8/n] * Rewrite MacScrollbarHelper to avoid native crashes --- .../jewel/bridge/MacScrollbarHelper.kt | 261 ++++++++++-------- .../jewel/bridge/theme/ScrollbarBridge.kt | 7 + 2 files changed, 155 insertions(+), 113 deletions(-) diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt index 6e234613e..31dc81d0c 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt @@ -1,168 +1,203 @@ package org.jetbrains.jewel.bridge import com.intellij.openapi.util.SystemInfoRt +import com.intellij.openapi.util.registry.Registry import com.intellij.ui.mac.foundation.Foundation import com.intellij.ui.mac.foundation.Foundation.NSAutoreleasePool import com.intellij.ui.mac.foundation.ID +import com.intellij.util.ui.EdtInvocationManager import com.sun.jna.Callback import com.sun.jna.Pointer import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.jetbrains.jewel.bridge.theme.default import org.jetbrains.jewel.bridge.theme.macOs import org.jetbrains.jewel.bridge.theme.windowsAndLinux import org.jetbrains.jewel.foundation.util.myLogger import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior +// Most of this code is copied from MacScrollBarUI — we don't have another way of +// knowing when the behaviour or style changes by reusing the existing IJP code internal object MacScrollbarHelper { private val logger = myLogger() - private val _scrollbarVisibilityStyleFlow = MutableStateFlow(scrollbarVisibility) + private val _scrollbarVisibilityStyleFlow = + MutableStateFlow(ScrollbarVisibility.AlwaysVisible.default()) val scrollbarVisibilityStyleFlow: StateFlow = _scrollbarVisibilityStyleFlow - private val _trackClickBehaviorFlow = MutableStateFlow(trackClickBehavior) + private val _trackClickBehaviorFlow = MutableStateFlow(TrackClickBehavior.JumpToSpot) val trackClickBehaviorFlow: StateFlow = _trackClickBehaviorFlow - init { - if (SystemInfoRt.isMac) { - initNotificationObserver() - } + private val copiedCode = CopiedCode { + _scrollbarVisibilityStyleFlow.value = it.asScrollbarVisibility() } val trackClickBehavior: TrackClickBehavior - get() { - if (!SystemInfoRt.isMac) { - return TrackClickBehavior.JumpToSpot - } + get() = CopiedCode.callMac { CopiedCode.Behavior.CURRENT_BEHAVIOR() } + ?.asTrackClickBehavior() + ?: TrackClickBehavior.JumpToSpot - val pool = NSAutoreleasePool() - try { - return readMacScrollbarBehavior() - } finally { - pool.drain() - } + val scrollbarVisibility: ScrollbarVisibility + get() = CopiedCode.callMac { copiedCode.CURRENT_STYLE() } + ?.asScrollbarVisibility() + ?: ScrollbarVisibility.AlwaysVisible.windowsAndLinux() + + init { + CopiedCode.callMac { + _scrollbarVisibilityStyleFlow.value = scrollbarVisibility + _trackClickBehaviorFlow.value = trackClickBehavior } + } - val scrollbarVisibility: ScrollbarVisibility - get() { - if (!SystemInfoRt.isMac) { - return ScrollbarVisibility.AlwaysVisible.windowsAndLinux() - } + private fun CopiedCode.Behavior.asTrackClickBehavior() = + when (this) { + CopiedCode.Behavior.NextPage -> TrackClickBehavior.NextPage + CopiedCode.Behavior.JumpToSpot -> TrackClickBehavior.JumpToSpot + } - val pool = NSAutoreleasePool() - try { - return readMacScrollbarStyle() - } catch (ignore: Throwable) { - } finally { - pool.drain() - } - return readMacScrollbarStyle() + private fun CopiedCode.MacScrollbarStyle.asScrollbarVisibility() = + when (this) { + CopiedCode.MacScrollbarStyle.Legacy -> ScrollbarVisibility.AlwaysVisible.macOs() + CopiedCode.MacScrollbarStyle.Overlay -> ScrollbarVisibility.WhenScrolling.macOs() } - private fun initNotificationObserver() { - val pool = NSAutoreleasePool() + // Code below this point has not been modified from the IJP, except where [MARKED] //////////////////////////////// - val delegateClass = - Foundation.allocateObjcClassPair(Foundation.getObjcClass("NSObject"), "NSScrollerChangesObserver") - if (delegateClass != ID.NIL) { - if (!addScrollbarVisibilityChangeListener(delegateClass)) { - logger.error("Cannot add scrollbar visibility observer method") + private class CopiedCode(private val onNewStyle: (MacScrollbarStyle) -> Unit) { + + val CURRENT_STYLE = object : MacScrollbarNative() { + override fun run() { + // [REMOVED] not checking the old style + if (SystemInfoRt.isMac && !Registry.`is`("ide.mac.disableMacScrollbars", false)) { + super.run() } - if (!addTrackClickBehaviorChangeListener(delegateClass)) { - logger.error("Cannot add scrollbar track click behavior observer method") + // [CHANGED] Emit new value + onNewStyle(invoke()) } - Foundation.registerObjcClassPair(delegateClass) + + override fun invoke(): MacScrollbarStyle { + val style = Foundation.invoke(Foundation.getObjcClass("NSScroller"), "preferredScrollerStyle") + val value = if (1 == style.toInt()) MacScrollbarStyle.Overlay else MacScrollbarStyle.Legacy + logger.debug("scroll bar style $value from $style") + return value } - val delegate = Foundation.invoke("NSScrollerChangesObserver", "new") - try { - var center = Foundation.invoke("NSNotificationCenter", "defaultCenter") - Foundation.invoke( - center, + override fun toString(): String = "scroll bar style" + + override fun initialize(): ID { + return Foundation.invoke( + Foundation.invoke("NSNotificationCenter", "defaultCenter"), "addObserver:selector:name:object:", - delegate, + createDelegate( + "JBScrollBarStyleObserver", + Foundation.createSelector("handleScrollerStyleChanged:"), + this + ), Foundation.createSelector("handleScrollerStyleChanged:"), Foundation.nsString("NSPreferredScrollerStyleDidChangeNotification"), - ID.NIL, + ID.NIL ) + } + } - center = Foundation.invoke("NSDistributedNotificationCenter", "defaultCenter") - Foundation.invoke( - center, - "addObserver:selector:name:object:", - delegate, - Foundation.createSelector("handleBehaviorChanged:"), - Foundation.nsString("AppleNoRedisplayAppearancePreferenceChanged"), - ID.NIL, - 2, // NSNotificationSuspensionBehaviorCoalesce - ) - } finally { - pool.drain() + enum class MacScrollbarStyle { + Legacy, Overlay; } - } - private val APPEARANCE_CALLBACK: Callback = - object : Callback { - @Suppress("UNUSED_PARAMETER", "unused") - @SuppressWarnings("UnusedDeclaration") - fun callback( - self: ID?, - selector: Pointer?, - event: ID?, - ) { - _scrollbarVisibilityStyleFlow.tryEmit(scrollbarVisibility) + enum class Behavior { + NextPage, JumpToSpot; + + companion object { + val CURRENT_BEHAVIOR = object : MacScrollbarNative() { + override fun invoke(): Behavior { + val defaults = Foundation.invoke("NSUserDefaults", "standardUserDefaults") + Foundation.invoke(defaults, "synchronize") + val behavior = + Foundation.invoke( + defaults, + "boolForKey:", + Foundation.nsString("AppleScrollerPagingBehavior") + ) + val value = if (behavior.toInt() == 1) JumpToSpot else NextPage + logger.debug("scroll bar behavior $value from $behavior") + return value + } + + override fun toString(): String = "scroll bar behavior" + + override fun initialize(): ID { + return Foundation.invoke( + Foundation.invoke("NSDistributedNotificationCenter", "defaultCenter"), + "addObserver:selector:name:object:", + createDelegate( + "JBScrollBarBehaviorObserver", + Foundation.createSelector("handleBehaviorChanged:"), + this + ), + Foundation.createSelector("handleBehaviorChanged:"), + Foundation.nsString("AppleNoRedisplayAppearancePreferenceChanged"), + ID.NIL, + 2 // NSNotificationSuspensionBehaviorCoalesce + ) + } + } } } - private val BEHAVIOR_CALLBACK: Callback = - object : Callback { - @Suppress("UNUSED_PARAMETER", "unused") - @SuppressWarnings("UnusedDeclaration") - fun callback( - self: ID?, - selector: Pointer?, - event: ID?, - ) { - _trackClickBehaviorFlow.tryEmit(trackClickBehavior) + abstract class MacScrollbarNative : Callback, Runnable, () -> T? { + private var value: T? = null + + init { + callMac { initialize() } + @Suppress("LeakingThis") + EdtInvocationManager.invokeLaterIfNeeded(this) } - } - private fun readMacScrollbarBehavior(): TrackClickBehavior { - logger.info("Reading scrollbar track click behavior...") - 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 } - } + abstract fun initialize(): ID - private fun readMacScrollbarStyle(): ScrollbarVisibility { - logger.info("Reading scrollbar visibility...") - val nsScroller = Foundation.invoke(Foundation.getObjcClass("NSScroller"), "preferredScrollerStyle") + override fun invoke() = value - val visibility: ScrollbarVisibility = - if (1 == nsScroller.toInt()) { - ScrollbarVisibility.WhenScrolling.macOs() - } else { - ScrollbarVisibility.AlwaysVisible.macOs() + @Suppress("UNUSED_PARAMETER") + fun callback(self: ID?, selector: Pointer?, event: ID?) { + EdtInvocationManager.invokeLaterIfNeeded(this) } - return visibility - } - private fun addScrollbarVisibilityChangeListener(delegateClass: ID?) = - Foundation.addMethod( - delegateClass, - Foundation.createSelector("handleScrollerStyleChanged:"), - APPEARANCE_CALLBACK, - "v@", - ) - - private fun addTrackClickBehaviorChangeListener(delegateClass: ID?) = - Foundation.addMethod( - delegateClass, - Foundation.createSelector("handleBehaviorChanged:"), - BEHAVIOR_CALLBACK, - "v@", - ) + override fun run() { + value = callMac(this) + } + } + + companion object { + private fun createDelegate(name: String, pointer: Pointer, callback: Callback): ID { + val delegateClass = Foundation.allocateObjcClassPair(Foundation.getObjcClass("NSObject"), name) + if (ID.NIL != delegateClass) { + if (!Foundation.addMethod(delegateClass, pointer, callback, "v@")) { + @Suppress("detekt:TooGenericExceptionThrown") // From IJP code + throw RuntimeException("Cannot add observer method") + } + Foundation.registerObjcClassPair(delegateClass) + } + return Foundation.invoke(name, "new") + } + + fun callMac(producer: () -> T?): T? { + if (!SystemInfoRt.isMac) { + return null + } + + val pool = NSAutoreleasePool() + @Suppress("detekt:TooGenericExceptionCaught") // From IJP code + try { + return producer() + } catch (e: Throwable) { + logger.warn(e) + } finally { + pool.drain() + } + return null + } + } + } } diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt index e3ff96dce..9ee74e94b 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt @@ -292,6 +292,13 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( lingerDuration = lingerDuration, ) +public fun ScrollbarVisibility.AlwaysVisible.Companion.default(): ScrollbarVisibility.AlwaysVisible = + if (hostOs.isMacOS) { + ScrollbarVisibility.AlwaysVisible.macOs() + } else { + ScrollbarVisibility.AlwaysVisible.windowsAndLinux() + } + public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( trackThickness: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(2.dp), From b2b56e6f13a0f7d9bd3ac5163257b53fb8ca78b3 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Tue, 20 Aug 2024 15:50:49 +0200 Subject: [PATCH 08/14] Fix Scrollbars [part 10/n] * Add decorationModifier to TextArea API (useful e.g., for padding when there are scrollbars) * Fix ScrollableContainerImpl layout logic and bugs * Fix one HorizontallyScrollableContainer using vScroll by mistake * Use HorizontallyScrollableContainer in Markdown block renderer * Update standalone sample Markdown with new APIs --- ide-laf-bridge/api/ide-laf-bridge.api | 1 + .../jewel/bridge/MacScrollbarHelper.kt | 203 ------------------ .../jetbrains/jewel/bridge/ScrollbarHelper.kt | 191 ++++++++++++++++ .../jewel/bridge/SwingBridgeService.kt | 24 +-- .../jewel/bridge/theme/ScrollbarBridge.kt | 6 +- .../rendering/DefaultMarkdownBlockRenderer.kt | 161 +++----------- .../releasessample/ReleasesSampleCompose.kt | 4 +- .../view/markdown/MarkdownEditor.kt | 3 +- .../view/markdown/MarkdownPreview.kt | 19 +- ui/api/ui.api | 6 +- .../jewel/ui/component/ScrollableContainer.kt | 185 ++++++++-------- .../jetbrains/jewel/ui/component/TextArea.kt | 9 +- 12 files changed, 362 insertions(+), 450 deletions(-) delete mode 100644 ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt create mode 100644 ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ScrollbarHelper.kt diff --git a/ide-laf-bridge/api/ide-laf-bridge.api b/ide-laf-bridge/api/ide-laf-bridge.api index 5320bb937..f9eb1c60e 100644 --- a/ide-laf-bridge/api/ide-laf-bridge.api +++ b/ide-laf-bridge/api/ide-laf-bridge.api @@ -139,6 +139,7 @@ public final class org/jetbrains/jewel/bridge/theme/IntUiBridgeKt { } public final class org/jetbrains/jewel/bridge/theme/ScrollbarBridgeKt { + public static final fun default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; public static final fun default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; public static final fun macOs-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; public static synthetic fun macOs-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt deleted file mode 100644 index 31dc81d0c..000000000 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/MacScrollbarHelper.kt +++ /dev/null @@ -1,203 +0,0 @@ -package org.jetbrains.jewel.bridge - -import com.intellij.openapi.util.SystemInfoRt -import com.intellij.openapi.util.registry.Registry -import com.intellij.ui.mac.foundation.Foundation -import com.intellij.ui.mac.foundation.Foundation.NSAutoreleasePool -import com.intellij.ui.mac.foundation.ID -import com.intellij.util.ui.EdtInvocationManager -import com.sun.jna.Callback -import com.sun.jna.Pointer -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import org.jetbrains.jewel.bridge.theme.default -import org.jetbrains.jewel.bridge.theme.macOs -import org.jetbrains.jewel.bridge.theme.windowsAndLinux -import org.jetbrains.jewel.foundation.util.myLogger -import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility -import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior - -// Most of this code is copied from MacScrollBarUI — we don't have another way of -// knowing when the behaviour or style changes by reusing the existing IJP code -internal object MacScrollbarHelper { - private val logger = myLogger() - - private val _scrollbarVisibilityStyleFlow = - MutableStateFlow(ScrollbarVisibility.AlwaysVisible.default()) - val scrollbarVisibilityStyleFlow: StateFlow = _scrollbarVisibilityStyleFlow - - private val _trackClickBehaviorFlow = MutableStateFlow(TrackClickBehavior.JumpToSpot) - val trackClickBehaviorFlow: StateFlow = _trackClickBehaviorFlow - - private val copiedCode = CopiedCode { - _scrollbarVisibilityStyleFlow.value = it.asScrollbarVisibility() - } - - val trackClickBehavior: TrackClickBehavior - get() = CopiedCode.callMac { CopiedCode.Behavior.CURRENT_BEHAVIOR() } - ?.asTrackClickBehavior() - ?: TrackClickBehavior.JumpToSpot - - val scrollbarVisibility: ScrollbarVisibility - get() = CopiedCode.callMac { copiedCode.CURRENT_STYLE() } - ?.asScrollbarVisibility() - ?: ScrollbarVisibility.AlwaysVisible.windowsAndLinux() - - init { - CopiedCode.callMac { - _scrollbarVisibilityStyleFlow.value = scrollbarVisibility - _trackClickBehaviorFlow.value = trackClickBehavior - } - } - - private fun CopiedCode.Behavior.asTrackClickBehavior() = - when (this) { - CopiedCode.Behavior.NextPage -> TrackClickBehavior.NextPage - CopiedCode.Behavior.JumpToSpot -> TrackClickBehavior.JumpToSpot - } - - private fun CopiedCode.MacScrollbarStyle.asScrollbarVisibility() = - when (this) { - CopiedCode.MacScrollbarStyle.Legacy -> ScrollbarVisibility.AlwaysVisible.macOs() - CopiedCode.MacScrollbarStyle.Overlay -> ScrollbarVisibility.WhenScrolling.macOs() - } - - // Code below this point has not been modified from the IJP, except where [MARKED] //////////////////////////////// - - private class CopiedCode(private val onNewStyle: (MacScrollbarStyle) -> Unit) { - - val CURRENT_STYLE = object : MacScrollbarNative() { - override fun run() { - // [REMOVED] not checking the old style - if (SystemInfoRt.isMac && !Registry.`is`("ide.mac.disableMacScrollbars", false)) { - super.run() - } - - // [CHANGED] Emit new value - onNewStyle(invoke()) - } - - override fun invoke(): MacScrollbarStyle { - val style = Foundation.invoke(Foundation.getObjcClass("NSScroller"), "preferredScrollerStyle") - val value = if (1 == style.toInt()) MacScrollbarStyle.Overlay else MacScrollbarStyle.Legacy - logger.debug("scroll bar style $value from $style") - return value - } - - override fun toString(): String = "scroll bar style" - - override fun initialize(): ID { - return Foundation.invoke( - Foundation.invoke("NSNotificationCenter", "defaultCenter"), - "addObserver:selector:name:object:", - createDelegate( - "JBScrollBarStyleObserver", - Foundation.createSelector("handleScrollerStyleChanged:"), - this - ), - Foundation.createSelector("handleScrollerStyleChanged:"), - Foundation.nsString("NSPreferredScrollerStyleDidChangeNotification"), - ID.NIL - ) - } - } - - enum class MacScrollbarStyle { - Legacy, Overlay; - } - - enum class Behavior { - NextPage, JumpToSpot; - - companion object { - val CURRENT_BEHAVIOR = object : MacScrollbarNative() { - override fun invoke(): Behavior { - val defaults = Foundation.invoke("NSUserDefaults", "standardUserDefaults") - Foundation.invoke(defaults, "synchronize") - val behavior = - Foundation.invoke( - defaults, - "boolForKey:", - Foundation.nsString("AppleScrollerPagingBehavior") - ) - val value = if (behavior.toInt() == 1) JumpToSpot else NextPage - logger.debug("scroll bar behavior $value from $behavior") - return value - } - - override fun toString(): String = "scroll bar behavior" - - override fun initialize(): ID { - return Foundation.invoke( - Foundation.invoke("NSDistributedNotificationCenter", "defaultCenter"), - "addObserver:selector:name:object:", - createDelegate( - "JBScrollBarBehaviorObserver", - Foundation.createSelector("handleBehaviorChanged:"), - this - ), - Foundation.createSelector("handleBehaviorChanged:"), - Foundation.nsString("AppleNoRedisplayAppearancePreferenceChanged"), - ID.NIL, - 2 // NSNotificationSuspensionBehaviorCoalesce - ) - } - } - } - } - - abstract class MacScrollbarNative : Callback, Runnable, () -> T? { - private var value: T? = null - - init { - callMac { initialize() } - @Suppress("LeakingThis") - EdtInvocationManager.invokeLaterIfNeeded(this) - } - - abstract fun initialize(): ID - - override fun invoke() = value - - @Suppress("UNUSED_PARAMETER") - fun callback(self: ID?, selector: Pointer?, event: ID?) { - EdtInvocationManager.invokeLaterIfNeeded(this) - } - - override fun run() { - value = callMac(this) - } - } - - companion object { - private fun createDelegate(name: String, pointer: Pointer, callback: Callback): ID { - val delegateClass = Foundation.allocateObjcClassPair(Foundation.getObjcClass("NSObject"), name) - if (ID.NIL != delegateClass) { - if (!Foundation.addMethod(delegateClass, pointer, callback, "v@")) { - @Suppress("detekt:TooGenericExceptionThrown") // From IJP code - throw RuntimeException("Cannot add observer method") - } - Foundation.registerObjcClassPair(delegateClass) - } - return Foundation.invoke(name, "new") - } - - fun callMac(producer: () -> T?): T? { - if (!SystemInfoRt.isMac) { - return null - } - - val pool = NSAutoreleasePool() - @Suppress("detekt:TooGenericExceptionCaught") // From IJP code - try { - return producer() - } catch (e: Throwable) { - logger.warn(e) - } finally { - pool.drain() - } - return null - } - } - } -} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ScrollbarHelper.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ScrollbarHelper.kt new file mode 100644 index 000000000..cd588c8d4 --- /dev/null +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ScrollbarHelper.kt @@ -0,0 +1,191 @@ +package org.jetbrains.jewel.bridge + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.util.SystemInfoRt +import com.intellij.ui.mac.foundation.Foundation +import com.intellij.ui.mac.foundation.ID +import com.sun.jna.Callback +import com.sun.jna.Pointer +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.jetbrains.jewel.bridge.theme.default +import org.jetbrains.jewel.bridge.theme.macOs +import org.jetbrains.jewel.foundation.util.myLogger +import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility +import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior +import org.jetbrains.skiko.OS +import org.jetbrains.skiko.hostOs + +internal interface ScrollbarHelper { + val scrollbarVisibilityStyleFlow: StateFlow + val trackClickBehaviorFlow: StateFlow + + companion object { + @JvmStatic + fun getInstance(): ScrollbarHelper = if (hostOs == OS.MacOS) service() else DummyScrollbarHelper + } +} + +@Service(Service.Level.APP) +private class MacScrollbarHelperImpl : Callback, ScrollbarHelper { + private val logger = myLogger() + + private val _scrollbarVisibilityStyleFlow = + MutableStateFlow(ScrollbarVisibility.AlwaysVisible.default()) + override val scrollbarVisibilityStyleFlow: StateFlow = _scrollbarVisibilityStyleFlow + + private val _trackClickBehaviorFlow = MutableStateFlow(TrackClickBehavior.JumpToSpot) + override val trackClickBehaviorFlow: StateFlow = _trackClickBehaviorFlow + + init { + if (hostOs != OS.MacOS) { + logger.error("${javaClass.simpleName} should only be initialized on macOS.") + } else { + callback(null, null, null) + + listenToTrackClickBehaviorChange() + listenToScrollbarVisibilityChange() + } + } + + private fun listenToTrackClickBehaviorChange() { + callMac { + // Copied from MacScrollBarUI + Foundation.invoke( + Foundation.invoke("NSDistributedNotificationCenter", "defaultCenter"), + "addObserver:selector:name:object:", + createDelegate( + "JewelScrollbarTrackClickBehaviorObserver", + Foundation.createSelector("handleBehaviorChanged:"), + this, + ), + Foundation.createSelector("handleBehaviorChanged:"), + Foundation.nsString("AppleNoRedisplayAppearancePreferenceChanged"), + ID.NIL, + 2, // NSNotificationSuspensionBehaviorCoalesce + ) + } + } + + private fun listenToScrollbarVisibilityChange() { + callMac { + // Copied from MacScrollBarUI + Foundation.invoke( + Foundation.invoke("NSNotificationCenter", "defaultCenter"), + "addObserver:selector:name:object:", + createDelegate( + "JewelScrollbarVisibilityObserver", + Foundation.createSelector("handleScrollerStyleChanged:"), + this, + ), + Foundation.createSelector("handleScrollerStyleChanged:"), + Foundation.nsString("NSPreferredScrollerStyleDidChangeNotification"), + ID.NIL, + ) + } + } + + @Suppress("unused", "UNUSED_PARAMETER") + fun callback( + self: ID?, + selector: Pointer?, + event: ID?, + ) { + readTrackClickBehavior() + readScrollbarVisibility() + } + + private fun readTrackClickBehavior() { + callMac { + // Inspired from MacScrollBarUI + val userDefaults = Foundation.invoke("NSUserDefaults", "standardUserDefaults") + Foundation.invoke(userDefaults, "synchronize") + val isJumpToPage = + Foundation.invoke( + // id = + userDefaults, + // selector = + "boolForKey:", + // ...args = + Foundation.nsString("AppleScrollerPagingBehavior"), + ) + .booleanValue() + + val behavior = + if (isJumpToPage) { + TrackClickBehavior.JumpToSpot + } else { + TrackClickBehavior.NextPage + } + + logger.debug("Scrollbar track click behavior: $behavior") + _trackClickBehaviorFlow.value = behavior + } + } + + private fun readScrollbarVisibility() { + callMac { + // Inspired from MacScrollBarUI + val isOverlayStyle = + Foundation.invoke( + // id= + Foundation.getObjcClass("NSScroller"), + // selector= + "preferredScrollerStyle", + ) + .booleanValue() + + val visibility = + if (isOverlayStyle) { + ScrollbarVisibility.WhenScrolling.macOs() + } else { + ScrollbarVisibility.AlwaysVisible.macOs() + } + + logger.debug("Scrollbar visibility style: $visibility") + _scrollbarVisibilityStyleFlow.value = visibility + } + } + + // Copied from MacScrollBarUI + @Suppress("detekt:TooGenericExceptionCaught") // Copied from IJP + private fun callMac(producer: () -> T?): T? { + if (!SystemInfoRt.isMac) { + return null + } + + val pool = Foundation.NSAutoreleasePool() + try { + return producer() + } catch (e: Throwable) { + logger.warn(e) + } finally { + pool.drain() + } + return null + } + + // Copied from MacScrollBarUI + private fun createDelegate( + name: String, + pointer: Pointer, + callback: Callback, + ): ID { + val delegateClass = Foundation.allocateObjcClassPair(Foundation.getObjcClass("NSObject"), name) + if (ID.NIL != delegateClass) { + if (!Foundation.addMethod(delegateClass, pointer, callback, "v@")) { + @Suppress("detekt:TooGenericExceptionThrown") // Copied from IJP + throw RuntimeException("Cannot add observer method") + } + Foundation.registerObjcClassPair(delegateClass) + } + return Foundation.invoke(name, "new") + } +} + +private object DummyScrollbarHelper : ScrollbarHelper { + override val scrollbarVisibilityStyleFlow: StateFlow = + MutableStateFlow(ScrollbarVisibility.AlwaysVisible.default()) + override val trackClickBehaviorFlow: StateFlow = MutableStateFlow(TrackClickBehavior.JumpToSpot) +} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt index b27c3455c..53e99aacc 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/SwingBridgeService.kt @@ -19,14 +19,17 @@ import kotlin.time.Duration.Companion.milliseconds @Service(Level.APP) internal class SwingBridgeService(scope: CoroutineScope) { + private val scrollbarHelper = ScrollbarHelper.getInstance() + internal val currentBridgeThemeData: StateFlow = combine( IntelliJApplication.lookAndFeelChangedFlow(scope), - MacScrollbarHelper.scrollbarVisibilityStyleFlow, - MacScrollbarHelper.trackClickBehaviorFlow, + scrollbarHelper.scrollbarVisibilityStyleFlow, + scrollbarHelper.trackClickBehaviorFlow, ) { _, _, _ -> tryGettingThemeData() - }.stateIn(scope, SharingStarted.Eagerly, BridgeThemeData.DEFAULT) + } + .stateIn(scope, SharingStarted.Eagerly, BridgeThemeData.DEFAULT) private suspend fun tryGettingThemeData(): BridgeThemeData { var counter = 0 @@ -43,16 +46,10 @@ internal class SwingBridgeService(scope: CoroutineScope) { private fun readThemeData(): BridgeThemeData { val themeDefinition = createBridgeThemeDefinition() - return BridgeThemeData( - themeDefinition = createBridgeThemeDefinition(), - componentStyling = createBridgeComponentStyling(themeDefinition), - ) + return BridgeThemeData(themeDefinition = createBridgeThemeDefinition(), componentStyling = createBridgeComponentStyling(themeDefinition)) } - internal data class BridgeThemeData( - val themeDefinition: ThemeDefinition, - val componentStyling: ComponentStyling, - ) { + internal data class BridgeThemeData(val themeDefinition: ThemeDefinition, val componentStyling: ComponentStyling) { companion object { val DEFAULT = run { @@ -65,10 +62,7 @@ internal class SwingBridgeService(scope: CoroutineScope) { consoleTextStyle = monospaceTextStyle, ) - BridgeThemeData( - themeDefinition = themeDefinition, - componentStyling = createBridgeComponentStyling(themeDefinition), - ) + BridgeThemeData(themeDefinition = themeDefinition, componentStyling = createBridgeComponentStyling(themeDefinition)) } } } diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt index 9ee74e94b..c3c669ebb 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.bridge.MacScrollbarHelper +import org.jetbrains.jewel.bridge.ScrollbarHelper import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified import org.jetbrains.jewel.ui.component.styling.ScrollbarColors import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics @@ -27,7 +27,7 @@ internal fun readScrollbarStyle(isDark: Boolean): ScrollbarStyle = private fun readScrollbarVisibility() = if (hostOs.isMacOS) { - MacScrollbarHelper.scrollbarVisibility + ScrollbarHelper.getInstance().scrollbarVisibilityStyleFlow.value } else { ScrollbarVisibility.AlwaysVisible.windowsAndLinux() } @@ -41,7 +41,7 @@ private fun readScrollbarColors(isDark: Boolean) = private fun readTrackClickBehavior() = if (hostOs.isMacOS) { - MacScrollbarHelper.trackClickBehavior + ScrollbarHelper.getInstance().trackClickBehaviorFlow.value } else { TrackClickBehavior.JumpToSpot } diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt index 530d0952a..ebce59c67 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt @@ -1,13 +1,8 @@ package org.jetbrains.jewel.markdown.rendering -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.HorizontalScrollbar import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -20,19 +15,12 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -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.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.geometry.Offset @@ -41,15 +29,12 @@ import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.layout.layoutId import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection.Ltr import androidx.compose.ui.unit.dp import org.jetbrains.jewel.foundation.ExperimentalJewelApi -import org.jetbrains.jewel.foundation.modifier.onHover import org.jetbrains.jewel.foundation.theme.LocalContentColor import org.jetbrains.jewel.markdown.MarkdownBlock import org.jetbrains.jewel.markdown.MarkdownBlock.BlockQuote @@ -69,6 +54,7 @@ import org.jetbrains.jewel.markdown.WithInlineMarkdown import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension import org.jetbrains.jewel.ui.Orientation.Horizontal import org.jetbrains.jewel.ui.component.Divider +import org.jetbrains.jewel.ui.component.HorizontallyScrollableContainer import org.jetbrains.jewel.ui.component.Text @ExperimentalJewelApi @@ -126,13 +112,7 @@ public open class DefaultMarkdownBlockRenderer( onUrlClick: (String) -> Unit, onTextClick: () -> Unit, ) { - val renderedContent = - rememberRenderedContent( - block, - styling.inlinesStyling, - enabled, - onUrlClick, - ) + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling, enabled, onUrlClick) val textColor = styling.inlinesStyling.textStyle.color .takeOrElse { LocalContentColor.current } @@ -142,13 +122,8 @@ public open class DefaultMarkdownBlockRenderer( Text( modifier = - Modifier - .focusProperties { canFocus = false } - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = onTextClick, - ), + Modifier.focusProperties { canFocus = false } + .clickable(interactionSource = interactionSource, indication = null, onClick = onTextClick), text = renderedContent, style = mergedStyle, ) @@ -181,13 +156,7 @@ public open class DefaultMarkdownBlockRenderer( onUrlClick: (String) -> Unit, onTextClick: () -> Unit, ) { - val renderedContent = - rememberRenderedContent( - block, - styling.inlinesStyling, - enabled, - onUrlClick, - ) + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling, enabled, onUrlClick) Heading( renderedContent, styling.inlinesStyling.textStyle, @@ -208,15 +177,9 @@ public open class DefaultMarkdownBlockRenderer( underlineGap: Dp, ) { Column(modifier = Modifier.padding(paddingValues)) { - val textColor = - textStyle.color - .takeOrElse { LocalContentColor.current.takeOrElse { textStyle.color } } + val textColor = textStyle.color.takeOrElse { LocalContentColor.current.takeOrElse { textStyle.color } } val mergedStyle = textStyle.merge(TextStyle(color = textColor)) - Text( - text = renderedContent, - style = mergedStyle, - modifier = Modifier.focusProperties { canFocus = false }, - ) + Text(text = renderedContent, style = mergedStyle, modifier = Modifier.focusProperties { canFocus = false }) if (underlineWidth > 0.dp && underlineColor.isSpecified) { Spacer(Modifier.height(underlineGap)) @@ -234,21 +197,21 @@ public open class DefaultMarkdownBlockRenderer( onTextClick: () -> Unit, ) { Column( - Modifier - .drawBehind { - val isLtr = layoutDirection == Ltr - val lineWidthPx = styling.lineWidth.toPx() - val x = if (isLtr) lineWidthPx / 2 else size.width - lineWidthPx / 2 - - drawLine( - styling.lineColor, - Offset(x, 0f), - Offset(x, size.height), - lineWidthPx, - styling.strokeCap, - styling.pathEffect, - ) - }.padding(styling.padding), + Modifier.drawBehind { + val isLtr = layoutDirection == Ltr + val lineWidthPx = styling.lineWidth.toPx() + val x = if (isLtr) lineWidthPx / 2 else size.width - lineWidthPx / 2 + + drawLine( + styling.lineColor, + Offset(x, 0f), + Offset(x, size.height), + lineWidthPx, + styling.strokeCap, + styling.pathEffect, + ) + } + .padding(styling.padding), verticalArrangement = Arrangement.spacedBy(rootStyling.blockVerticalSpacing), ) { CompositionLocalProvider(LocalContentColor provides styling.textColor) { @@ -286,10 +249,7 @@ public open class DefaultMarkdownBlockRenderer( styling.itemVerticalSpacing } - Column( - modifier = Modifier.padding(styling.padding), - verticalArrangement = Arrangement.spacedBy(itemSpacing), - ) { + Column(modifier = Modifier.padding(styling.padding), verticalArrangement = Arrangement.spacedBy(itemSpacing)) { for ((index, item) in block.children.withIndex()) { Row { val number = block.startFrom + index @@ -298,8 +258,7 @@ public open class DefaultMarkdownBlockRenderer( style = styling.numberStyle, color = styling.numberStyle.color.takeOrElse { LocalContentColor.current }, modifier = - Modifier - .focusProperties { canFocus = false } + Modifier.focusProperties { canFocus = false } .widthIn(min = styling.numberMinWidth) .pointerHoverIcon(PointerIcon.Default, overrideDescendants = true), textAlign = styling.numberTextAlign, @@ -328,10 +287,7 @@ public open class DefaultMarkdownBlockRenderer( styling.itemVerticalSpacing } - Column( - modifier = Modifier.padding(styling.padding), - verticalArrangement = Arrangement.spacedBy(itemSpacing), - ) { + Column(modifier = Modifier.padding(styling.padding), verticalArrangement = Arrangement.spacedBy(itemSpacing)) { for (item in block.children) { Row { Text( @@ -379,10 +335,9 @@ public open class DefaultMarkdownBlockRenderer( block: IndentedCodeBlock, styling: MarkdownStyling.Code.Indented, ) { - HorizontallyScrollingContainer( + MaybeScrollingContainer( isScrollable = styling.scrollsHorizontally, - Modifier - .background(styling.background, styling.shape) + Modifier.background(styling.background, styling.shape) .border(styling.borderWidth, styling.borderColor, styling.shape) .then(if (styling.fillWidth) Modifier.fillMaxWidth() else Modifier), ) { @@ -403,10 +358,9 @@ public open class DefaultMarkdownBlockRenderer( block: FencedCodeBlock, styling: MarkdownStyling.Code.Fenced, ) { - HorizontallyScrollingContainer( + MaybeScrollingContainer( isScrollable = styling.scrollsHorizontally, - Modifier - .background(styling.background, styling.shape) + Modifier.background(styling.background, styling.shape) .border(styling.borderWidth, styling.borderColor, styling.shape) .then(if (styling.fillWidth) Modifier.fillMaxWidth() else Modifier), ) { @@ -486,60 +440,15 @@ public open class DefaultMarkdownBlockRenderer( } @Composable - private fun HorizontallyScrollingContainer( + private fun MaybeScrollingContainer( isScrollable: Boolean, modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - var isHovered by remember { mutableStateOf(false) } - - Layout( - content = { - val scrollState = rememberScrollState() - Box( - Modifier - .layoutId("mainContent") - .then(if (isScrollable) Modifier.horizontalScroll(scrollState) else Modifier), - ) { - content() - } - - val canScroll by derivedStateOf { - scrollState.canScrollBackward || scrollState.canScrollForward - } - - if (isScrollable && canScroll) { - val alpha by animateFloatAsState( - if (isHovered) 1f else 0f, - tween(durationMillis = 150, easing = LinearEasing), - ) - - HorizontalScrollbar( - rememberScrollbarAdapter(scrollState), - Modifier - .layoutId("containerHScrollbar") - .padding(start = 2.dp, end = 2.dp, bottom = 2.dp) - .alpha(alpha), - ) - } - }, - modifier.onHover { isHovered = it }, - { measurables, incomingConstraints -> - val contentMeasurable = - measurables.singleOrNull { it.layoutId == "mainContent" } - ?: error("There must be one and only one child with ID 'mainContent'") - - val contentPlaceable = contentMeasurable.measure(incomingConstraints) - - val scrollbarMeasurable = measurables.find { it.layoutId == "containerHScrollbar" } - val scrollbarPlaceable = scrollbarMeasurable?.measure(incomingConstraints) - val scrollbarHeight = scrollbarPlaceable?.measuredHeight ?: 0 - - layout(contentPlaceable.width, contentPlaceable.height + scrollbarHeight) { - contentPlaceable.place(0, 0) - scrollbarPlaceable?.place(0, contentPlaceable.height) - } - }, - ) + if (isScrollable) { + HorizontallyScrollableContainer(modifier) { content() } + } else { + content() + } } } diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt index fd14b36fe..27f9c6ef4 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt @@ -81,6 +81,7 @@ import org.jetbrains.jewel.ui.component.TextField import org.jetbrains.jewel.ui.component.Typography import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer import org.jetbrains.jewel.ui.component.items +import org.jetbrains.jewel.ui.component.scrollbarContentSafePadding import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider import org.jetbrains.jewel.ui.theme.iconButtonStyle @@ -196,7 +197,8 @@ private fun ContentItemRow( modifier = Modifier.height(JewelTheme.globalMetrics.rowHeight) .background(color) - .padding(start = 4.dp, end = 12.dp), + .padding(horizontal = 4.dp) + .padding(end = scrollbarContentSafePadding()), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp), ) { diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt index 51b83790d..51daef3c7 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt @@ -124,12 +124,13 @@ private fun Editor( state: TextFieldState, modifier: Modifier = Modifier, ) { - Box(modifier.padding(16.dp)) { + Box(modifier) { TextArea( state = state, modifier = Modifier.align(Alignment.TopStart).fillMaxWidth(), undecorated = true, textStyle = JewelTheme.editorTextStyle, + decorationBoxModifier = Modifier.padding(horizontal = 8.dp), ) } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt index 78c5c7d8d..9eaaacb0c 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt @@ -1,10 +1,7 @@ package org.jetbrains.jewel.samples.standalone.view.markdown import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -12,7 +9,6 @@ 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 @@ -35,7 +31,8 @@ import org.jetbrains.jewel.markdown.extensions.github.alerts.GitHubAlertRenderer import org.jetbrains.jewel.markdown.processing.MarkdownProcessor import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer import org.jetbrains.jewel.markdown.rendering.MarkdownStyling -import org.jetbrains.jewel.ui.component.VerticalScrollbar +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer +import org.jetbrains.jewel.ui.component.scrollbarContentSafePadding import java.awt.Desktop import java.net.URI @@ -86,21 +83,17 @@ internal fun MarkdownPreview( val background = remember(isDark) { if (isDark) Color(0xff0d1117) else Color.White } ProvideMarkdownStyling(markdownStyling, blockRenderer) { - Box(modifier.background(background)) { - val lazyListState = rememberLazyListState() + val lazyListState = rememberLazyListState() + VerticallyScrollableContainer(lazyListState, modifier.background(background)) { LazyMarkdown( markdownBlocks = markdownBlocks, modifier = Modifier.background(background), - contentPadding = PaddingValues(16.dp), + contentPadding = + PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp + scrollbarContentSafePadding(), bottom = 8.dp), state = lazyListState, selectable = true, onUrlClick = { url -> Desktop.getDesktop().browse(URI.create(url)) }, ) - - VerticalScrollbar( - lazyListState, - Modifier.align(Alignment.TopEnd).fillMaxHeight().padding(2.dp), - ) } } } diff --git a/ui/api/ui.api b/ui/api/ui.api index 1092b3a04..fe297ddb7 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -880,9 +880,9 @@ public final class org/jetbrains/jewel/ui/component/TabsKt { } public final class org/jetbrains/jewel/ui/component/TextAreaKt { - public static final fun TextArea (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/foundation/text/KeyboardOptions;ILorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;ZLorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;III)V - public static final fun TextArea (Landroidx/compose/ui/text/input/TextFieldValue;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLkotlin/jvm/functions/Function2;ZLorg/jetbrains/jewel/ui/Outline;Landroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;III)V - public static final fun TextArea (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;III)V + public static final fun TextArea (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/foundation/text/KeyboardOptions;ILorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/ui/Modifier;ZLorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;III)V + public static final fun TextArea (Landroidx/compose/ui/text/input/TextFieldValue;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLkotlin/jvm/functions/Function2;ZLorg/jetbrains/jewel/ui/Outline;Landroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;III)V + public static final fun TextArea (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;III)V } public final class org/jetbrains/jewel/ui/component/TextFieldKt { diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt index 8ad29fe0a..b52fe050d 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -18,10 +18,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -31,7 +33,6 @@ 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.theme.scrollbarStyle -import kotlin.time.Duration.Companion.milliseconds private const val ID_CONTENT = "VerticallyScrollableContainer_content" private const val ID_VERTICAL_SCROLLBAR = "VerticallyScrollableContainer_verticalScrollbar" @@ -48,12 +49,7 @@ public fun VerticallyScrollableContainer( var keepVisible by remember { mutableStateOf(false) } ScrollableContainerImpl( verticalScrollbar = { - VerticalScrollbar( - scrollState, - scrollbarModifier, - style = style, - keepVisible = keepVisible, - ) + VerticalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) }, horizontalScrollbar = null, modifier = modifier.onHover { keepVisible = it }, @@ -77,27 +73,24 @@ public fun VerticallyScrollableContainer( ScrollableContainerImpl( verticalScrollbar = { - VerticalScrollbar( - scrollState, - scrollbarModifier, - style = style, - keepVisible = keepVisible, - ) + VerticalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) }, horizontalScrollbar = null, - modifier = modifier.pointerInput(scrollState) { - awaitEachGesture { - val event = awaitPointerEvent() - if (event.type == PointerEventType.Move) { - delayJob?.cancel() - keepVisible = true - delayJob = scope.launch { - delay(50.milliseconds) - keepVisible = false + modifier = + modifier.pointerInput(scrollState) { + awaitEachGesture { + val event = awaitPointerEvent() + if (event.type == PointerEventType.Move) { + delayJob?.cancel() + keepVisible = true + delayJob = + scope.launch { + delay(50.milliseconds) + keepVisible = false + } } } - } - }, + }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } @@ -116,7 +109,7 @@ public fun VerticallyScrollableContainer( verticalScrollbar = { VerticalScrollbar(scrollState, scrollbarModifier, style = style) }, horizontalScrollbar = null, modifier = modifier, - scrollbarStyle = style + scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } @@ -134,9 +127,9 @@ public fun HorizontallyScrollableContainer( verticalScrollbar = null, horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, modifier = modifier, - scrollbarStyle = style + scrollbarStyle = style, ) { - Box(Modifier.layoutId(ID_CONTENT).verticalScroll(scrollState)) { content() } + Box(Modifier.layoutId(ID_CONTENT).horizontalScroll(scrollState)) { content() } } } @@ -152,7 +145,7 @@ public fun HorizontallyScrollableContainer( verticalScrollbar = null, horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, modifier = modifier, - scrollbarStyle = style + scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } @@ -170,7 +163,7 @@ public fun HorizontallyScrollableContainer( verticalScrollbar = null, horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, modifier = modifier, - scrollbarStyle = style + scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } @@ -189,20 +182,14 @@ public fun ScrollableContainer( ScrollableContainerImpl( verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, horizontalScrollbar = { - HorizontalScrollbar( - horizontalScrollState, - horizontalScrollbarModifier, - style = style - ) + HorizontalScrollbar(horizontalScrollState, horizontalScrollbarModifier, style = style) }, modifier = modifier, - scrollbarStyle = style + scrollbarStyle = style, ) { - Box( - Modifier.layoutId(ID_CONTENT) - .verticalScroll(verticalScrollState) - .horizontalScroll(horizontalScrollState) - ) { content() } + Box(Modifier.layoutId(ID_CONTENT).verticalScroll(verticalScrollState).horizontalScroll(horizontalScrollState)) { + content() + } } } @@ -219,14 +206,10 @@ public fun ScrollableContainer( ScrollableContainerImpl( verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, horizontalScrollbar = { - HorizontalScrollbar( - horizontalScrollState, - horizontalScrollbarModifier, - style = style - ) + HorizontalScrollbar(horizontalScrollState, horizontalScrollbarModifier, style = style) }, modifier = modifier, - scrollbarStyle = style + scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } @@ -245,14 +228,10 @@ public fun ScrollableContainer( ScrollableContainerImpl( verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, horizontalScrollbar = { - HorizontalScrollbar( - horizontalScrollState, - horizontalScrollbarModifier, - style = style - ) + HorizontalScrollbar(horizontalScrollState, horizontalScrollbarModifier, style = style) }, modifier = modifier, - scrollbarStyle = style + scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } @@ -289,57 +268,95 @@ private fun ScrollableContainerImpl( scrollbarStyle.scrollbarVisibility.trackThicknessExpanded.roundToPx() } else 0 - val verticalScrollbarPlaceable = if (verticalScrollbarMeasurable != null) { - val verticalScrollbarConstraints = - Constraints.fixedHeight(incomingConstraints.maxHeight - sizeOffsetWhenBothVisible) - verticalScrollbarMeasurable.measure(verticalScrollbarConstraints) - } else null + val verticalScrollbarPlaceable = + if (verticalScrollbarMeasurable != null) { + val verticalScrollbarConstraints = + Constraints.fixedHeight(incomingConstraints.maxHeight - sizeOffsetWhenBothVisible) + verticalScrollbarMeasurable.measure(verticalScrollbarConstraints) + } else null - val horizontalScrollbarPlaceable = if (horizontalScrollbarMeasurable != null) { - val horizontalScrollbarConstraints = - Constraints.fixedWidth(incomingConstraints.maxWidth - sizeOffsetWhenBothVisible) - horizontalScrollbarMeasurable.measure(horizontalScrollbarConstraints) - } else null + val horizontalScrollbarPlaceable = + if (horizontalScrollbarMeasurable != null) { + val horizontalScrollbarConstraints = + Constraints.fixedWidth(incomingConstraints.maxWidth - sizeOffsetWhenBothVisible) + horizontalScrollbarMeasurable.measure(horizontalScrollbarConstraints) + } else null - val contentConstraints = Constraints.fixed( - width = when (scrollbarStyle.scrollbarVisibility) { - is AlwaysVisible -> incomingConstraints.maxWidth - (verticalScrollbarPlaceable?.width ?: 0) - is WhenScrolling -> incomingConstraints.maxWidth - }, - height = when (scrollbarStyle.scrollbarVisibility) { - is AlwaysVisible -> incomingConstraints.maxHeight - (horizontalScrollbarPlaceable?.height ?: 0) - is WhenScrolling -> incomingConstraints.maxHeight - }, - ) - val contentMeasurable = measurables.find { it.layoutId == ID_CONTENT } - ?: error("Content not provided") + val contentConstraints = + computeContentConstraints( + scrollbarStyle, + incomingConstraints, + verticalScrollbarPlaceable, + horizontalScrollbarPlaceable, + ) + val contentMeasurable = measurables.find { it.layoutId == ID_CONTENT } ?: error("Content not provided") val contentPlaceable = contentMeasurable.measure(contentConstraints) - layout(incomingConstraints.maxWidth, incomingConstraints.maxHeight) { + layout( + width = contentPlaceable.width + (verticalScrollbarPlaceable?.width ?: 0), + height = contentPlaceable.height + (horizontalScrollbarPlaceable?.height ?: 0), + ) { contentPlaceable.placeRelative(x = 0, y = 0, zIndex = 0f) verticalScrollbarPlaceable?.placeRelative( x = incomingConstraints.maxWidth - verticalScrollbarPlaceable.width, y = 0, - zIndex = 1f + zIndex = 1f, ) horizontalScrollbarPlaceable?.placeRelative( x = 0, y = incomingConstraints.maxHeight - horizontalScrollbarPlaceable.height, - zIndex = 1f + zIndex = 1f, ) } } } +private fun computeContentConstraints( + scrollbarStyle: ScrollbarStyle, + incomingConstraints: Constraints, + verticalScrollbarPlaceable: Placeable?, + horizontalScrollbarPlaceable: Placeable?, +): Constraints { + fun width() = + if (incomingConstraints.hasBoundedWidth) { + when (scrollbarStyle.scrollbarVisibility) { + is AlwaysVisible -> incomingConstraints.maxWidth - (verticalScrollbarPlaceable?.width ?: 0) + is WhenScrolling -> incomingConstraints.maxWidth + } + } else { + error("Incoming constraints have infinite width, should not use fixed width") + } + + fun height() = + if (incomingConstraints.hasBoundedHeight) { + when (scrollbarStyle.scrollbarVisibility) { + is AlwaysVisible -> incomingConstraints.maxHeight - (horizontalScrollbarPlaceable?.height ?: 0) + is WhenScrolling -> incomingConstraints.maxHeight + } + } else { + error("Incoming constraints have infinite height, should not use fixed height") + } + + return when { + incomingConstraints.hasBoundedWidth && incomingConstraints.hasBoundedHeight -> { + Constraints.fixed(width(), height()) + } + !incomingConstraints.hasBoundedWidth && incomingConstraints.hasBoundedHeight -> { + Constraints.fixedHeight(height()) + } + incomingConstraints.hasBoundedWidth && !incomingConstraints.hasBoundedHeight -> { + Constraints.fixedWidth(width()) + } + else -> Constraints() + } +} + /** - * A content padding to apply when you want to ensure the content is not - * overlapped by scrollbars. This value can be used for both vertical and - * horizontal scrollbars. + * A content padding to apply when you want to ensure the content is not overlapped by scrollbars. This value can be + * used for both vertical and horizontal scrollbars. * - * When the [style] is [AlwaysVisible], this value is zero, since the - * various `ScrollableContainer`s will prevent overlapping anyway. If it - * is [WhenScrolling], this value will be the maximum thickness of the - * scrollbar. + * When the [style] is [AlwaysVisible], this value is zero, since the various `ScrollableContainer`s will prevent + * overlapping anyway. If it is [WhenScrolling], this value will be the maximum thickness of the scrollbar. */ @Composable public fun scrollbarContentSafePadding(style: ScrollbarStyle = JewelTheme.scrollbarStyle): Dp = diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt index c99d97ebc..4553de590 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt @@ -1,6 +1,5 @@ package org.jetbrains.jewel.ui.component -import androidx.compose.foundation.defaultScrollbarStyle import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -54,6 +53,7 @@ public fun TextArea( style: TextAreaStyle = JewelTheme.textAreaStyle, textStyle: TextStyle = JewelTheme.defaultTextStyle, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + decorationBoxModifier: Modifier = Modifier, showScrollbar: Boolean = true, scrollbarStyle: ScrollbarStyle = JewelTheme.scrollbarStyle ) { @@ -80,6 +80,7 @@ public fun TextArea( placeholderTextColor = style.colors.placeholder, placeholder = if (state.text.isEmpty()) placeholder else null, textStyle = textStyle, + modifier = decorationBoxModifier, ) }, ) @@ -108,6 +109,7 @@ public fun TextArea( style: TextAreaStyle = JewelTheme.textAreaStyle, textStyle: TextStyle = JewelTheme.defaultTextStyle, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + decorationBoxModifier: Modifier = Modifier, ) { var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) } val textFieldValue = textFieldValueState.copy(text = value) @@ -139,6 +141,7 @@ public fun TextArea( style = style, textStyle = textStyle, interactionSource = interactionSource, + decorationBoxModifier = decorationBoxModifier, ) } @@ -165,6 +168,7 @@ public fun TextArea( style: TextAreaStyle = JewelTheme.textAreaStyle, textStyle: TextStyle = JewelTheme.defaultTextStyle, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + decorationBoxModifier: Modifier = Modifier, ) { val minSize = style.metrics.minSize InputField( @@ -191,6 +195,7 @@ public fun TextArea( placeholderTextColor = style.colors.placeholder, placeholder = if (value.text.isEmpty()) placeholder else null, textStyle = textStyle, + modifier = decorationBoxModifier, ) } } @@ -200,6 +205,7 @@ private fun TextAreaDecorationBox( innerTextField: @Composable () -> Unit, contentPadding: PaddingValues, textStyle: TextStyle, + modifier: Modifier, placeholderTextColor: Color, placeholder: @Composable (() -> Unit)?, ) { @@ -226,6 +232,7 @@ private fun TextAreaDecorationBox( innerTextField() } }, + modifier, ) { measurables, incomingConstraints -> val leftPadding = contentPadding.calculateLeftPadding(layoutDirection) val rightPadding = contentPadding.calculateRightPadding(layoutDirection) From 9241db42195623c32e5d034094bb8e8f67f4d38c Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Wed, 21 Aug 2024 17:16:15 +0200 Subject: [PATCH 09/14] Fix Scrollbars [part 11/n] * Add mouse move linger behaviour to all ScrollableContainers * Add horizontal scrolling example to standalone sample * Fix ScrollableContainerImpl layout bug with WhenScrolling --- .../standalone/view/component/Scrollbars.kt | 121 +++++++------ .../jewel/ui/component/ScrollableContainer.kt | 168 ++++++++++++------ 2 files changed, 185 insertions(+), 104 deletions(-) diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index 01cf27467..078bc8f5f 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -6,6 +6,7 @@ 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -24,6 +25,7 @@ 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.Stroke @@ -35,6 +37,7 @@ import org.jetbrains.jewel.intui.standalone.styling.light 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.HorizontallyScrollableContainer import org.jetbrains.jewel.ui.component.RadioButtonRow import org.jetbrains.jewel.ui.component.Text import org.jetbrains.jewel.ui.component.Typography @@ -49,48 +52,45 @@ import java.util.Locale @Composable fun Scrollbars() { - Column { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { val isDark = JewelTheme.isDark - val baseStyle = - remember(isDark) { - if (isDark) ScrollbarStyle.dark() else ScrollbarStyle.light() - } + val baseStyle = remember(isDark) { if (isDark) ScrollbarStyle.dark() else ScrollbarStyle.light() } var alwaysVisible by remember { mutableStateOf(false) } var clickBehavior by remember { mutableStateOf(baseStyle.trackClickBehavior) } SettingsRow(alwaysVisible, clickBehavior, { alwaysVisible = it }, { clickBehavior = it }) - Spacer(modifier = Modifier.height(16.dp)) + val style by + remember(alwaysVisible, clickBehavior, baseStyle) { + mutableStateOf( + if (alwaysVisible) { + ScrollbarStyle( + colors = baseStyle.colors, + metrics = baseStyle.metrics, + trackClickBehavior = clickBehavior, + scrollbarVisibility = ScrollbarVisibility.AlwaysVisible.default(), + ) + } else { + ScrollbarStyle( + colors = baseStyle.colors, + metrics = baseStyle.metrics, + trackClickBehavior = clickBehavior, + scrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), + ) + }, + ) + } Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, ) { - val style by - remember(alwaysVisible, clickBehavior, baseStyle) { - mutableStateOf( - if (alwaysVisible) { - ScrollbarStyle( - colors = baseStyle.colors, - metrics = baseStyle.metrics, - trackClickBehavior = clickBehavior, - scrollbarVisibility = ScrollbarVisibility.AlwaysVisible.default(), - ) - } else { - ScrollbarStyle( - colors = baseStyle.colors, - metrics = baseStyle.metrics, - trackClickBehavior = clickBehavior, - scrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), - ) - }, - ) - } - - LazyColumnWithScrollbar(style, Modifier.weight(1f).height(200.dp)) - ColumnWithScrollbar(style, Modifier.weight(1f).height(200.dp)) + LazyColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) + ColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) } + + HorizontalScrollbarContent(style, Modifier.fillMaxWidth()) } } @@ -102,11 +102,7 @@ private fun SettingsRow( onClickBehaviorChange: (TrackClickBehavior) -> Unit, ) { Row(verticalAlignment = Alignment.CenterVertically) { - CheckboxRow( - checked = alwaysVisible, - onCheckedChange = onAlwaysVisibleChange, - text = "Always visible", - ) + CheckboxRow(checked = alwaysVisible, onCheckedChange = onAlwaysVisibleChange, text = "Always visible") Spacer(Modifier.weight(1f)) @@ -187,24 +183,47 @@ private fun ColumnWithScrollbar( val scrollState = rememberScrollState() Column( modifier = - Modifier - .background(JewelTheme.textAreaStyle.colors.background) + Modifier.background(JewelTheme.textAreaStyle.colors.background) .verticalScroll(scrollState) - .padding(end = scrollbarContentSafePadding(style)) .align(Alignment.CenterStart), ) { - LIST_ITEMS.forEach { + LIST_ITEMS.forEachIndexed { index, line -> Text( - modifier = Modifier.padding(horizontal = 8.dp), - text = it, + modifier = + Modifier.padding(horizontal = 8.dp).padding(end = scrollbarContentSafePadding(style)), + text = line, ) + if (index < LIST_ITEMS.lastIndex) { + Box(Modifier.height(8.dp), contentAlignment = Alignment.CenterStart) { + Divider(Orientation.Horizontal, Modifier.fillMaxWidth()) + } + } } } - VerticalScrollbar( - scrollState = scrollState, - modifier = Modifier.align(Alignment.CenterEnd), - style = style, - ) + VerticalScrollbar(scrollState = scrollState, modifier = Modifier.align(Alignment.CenterEnd), style = style) + } + } +} + +@Composable +private fun HorizontalScrollbarContent( + scrollbarStyle: ScrollbarStyle, + modifier: Modifier, +) { + HorizontallyScrollableContainer( + modifier = modifier.border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal).background(Color.Red), + style = scrollbarStyle, + ) { + Column( + modifier = + Modifier.fillMaxHeight() + .background(JewelTheme.textAreaStyle.colors.background) + .padding(bottom = scrollbarContentSafePadding(scrollbarStyle)) + .padding(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + val oneLineIpsum = LOREM_IPSUM.replace('\n', ' ') + repeat(4) { Text(oneLineIpsum) } } } } @@ -224,12 +243,8 @@ private const val LOREM_IPSUM = "Sed nec sapien nec dui rhoncus bibendum. Sed blandit bibendum libero." private val LIST_ITEMS = - LOREM_IPSUM - .split(",") + LOREM_IPSUM.split(",") .map { lorem -> - lorem - .trim() - .replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() - } - }.let { it + it + it + it + it + it } + lorem.trim().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + } + .let { it + it + it + it + it + it } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt index b52fe050d..df2b3fdb7 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -1,3 +1,5 @@ +@file:Suppress("DuplicatedCode") // Lots of identical-looking but not deduplicable code + package org.jetbrains.jewel.ui.component import androidx.compose.foundation.ScrollState @@ -23,11 +25,12 @@ import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.jetbrains.jewel.foundation.modifier.onHover +import org.jetbrains.jewel.foundation.ExperimentalJewelApi import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility.AlwaysVisible @@ -47,12 +50,14 @@ public fun VerticallyScrollableContainer( content: @Composable () -> Unit, ) { var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( verticalScrollbar = { VerticalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) }, horizontalScrollbar = null, - modifier = modifier.onHover { keepVisible = it }, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT).verticalScroll(scrollState)) { content() } @@ -68,7 +73,6 @@ public fun VerticallyScrollableContainer( content: @Composable () -> Unit, ) { var keepVisible by remember { mutableStateOf(false) } - var delayJob by remember { mutableStateOf(null) } val scope = rememberCoroutineScope() ScrollableContainerImpl( @@ -76,21 +80,7 @@ public fun VerticallyScrollableContainer( VerticalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) }, horizontalScrollbar = null, - modifier = - modifier.pointerInput(scrollState) { - awaitEachGesture { - val event = awaitPointerEvent() - if (event.type == PointerEventType.Move) { - delayJob?.cancel() - keepVisible = true - delayJob = - scope.launch { - delay(50.milliseconds) - keepVisible = false - } - } - } - }, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } @@ -105,10 +95,15 @@ public fun VerticallyScrollableContainer( style: ScrollbarStyle = JewelTheme.scrollbarStyle, content: @Composable () -> Unit, ) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( - verticalScrollbar = { VerticalScrollbar(scrollState, scrollbarModifier, style = style) }, + verticalScrollbar = { + VerticalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) + }, horizontalScrollbar = null, - modifier = modifier, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } @@ -123,10 +118,15 @@ public fun HorizontallyScrollableContainer( style: ScrollbarStyle = JewelTheme.scrollbarStyle, content: @Composable () -> Unit, ) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( verticalScrollbar = null, - horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, - modifier = modifier, + horizontalScrollbar = { + HorizontalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) + }, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT).horizontalScroll(scrollState)) { content() } @@ -141,10 +141,15 @@ public fun HorizontallyScrollableContainer( style: ScrollbarStyle = JewelTheme.scrollbarStyle, content: @Composable () -> Unit, ) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( verticalScrollbar = null, - horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, - modifier = modifier, + horizontalScrollbar = { + HorizontalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) + }, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } @@ -159,16 +164,22 @@ public fun HorizontallyScrollableContainer( style: ScrollbarStyle = JewelTheme.scrollbarStyle, content: @Composable () -> Unit, ) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( verticalScrollbar = null, - horizontalScrollbar = { HorizontalScrollbar(scrollState, scrollbarModifier, style = style) }, - modifier = modifier, + horizontalScrollbar = { + HorizontalScrollbar(scrollState, scrollbarModifier, style = style, keepVisible = keepVisible) + }, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } } +@ExperimentalJewelApi @Composable public fun ScrollableContainer( modifier: Modifier = Modifier, @@ -179,12 +190,22 @@ public fun ScrollableContainer( style: ScrollbarStyle = JewelTheme.scrollbarStyle, content: @Composable () -> Unit, ) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( - verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, + verticalScrollbar = { + VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style, keepVisible = keepVisible) + }, horizontalScrollbar = { - HorizontalScrollbar(horizontalScrollState, horizontalScrollbarModifier, style = style) + HorizontalScrollbar( + horizontalScrollState, + horizontalScrollbarModifier, + style = style, + keepVisible = keepVisible, + ) }, - modifier = modifier, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT).verticalScroll(verticalScrollState).horizontalScroll(horizontalScrollState)) { @@ -193,6 +214,7 @@ public fun ScrollableContainer( } } +@ExperimentalJewelApi @Composable public fun ScrollableContainer( verticalScrollState: LazyListState, @@ -203,18 +225,29 @@ public fun ScrollableContainer( style: ScrollbarStyle = JewelTheme.scrollbarStyle, content: @Composable () -> Unit, ) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( - verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, + verticalScrollbar = { + VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style, keepVisible = keepVisible) + }, horizontalScrollbar = { - HorizontalScrollbar(horizontalScrollState, horizontalScrollbarModifier, style = style) + HorizontalScrollbar( + horizontalScrollState, + horizontalScrollbarModifier, + style = style, + keepVisible = keepVisible, + ) }, - modifier = modifier, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } } +@ExperimentalJewelApi @Composable public fun ScrollableContainer( verticalScrollState: LazyGridState, @@ -225,18 +258,49 @@ public fun ScrollableContainer( style: ScrollbarStyle = JewelTheme.scrollbarStyle, content: @Composable () -> Unit, ) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + ScrollableContainerImpl( - verticalScrollbar = { VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style) }, + verticalScrollbar = { + VerticalScrollbar(verticalScrollState, verticalScrollbarModifier, style = style, keepVisible = keepVisible) + }, horizontalScrollbar = { - HorizontalScrollbar(horizontalScrollState, horizontalScrollbarModifier, style = style) + HorizontalScrollbar( + horizontalScrollState, + horizontalScrollbarModifier, + style = style, + keepVisible = keepVisible, + ) }, - modifier = modifier, + modifier = modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, scrollbarStyle = style, ) { Box(Modifier.layoutId(ID_CONTENT)) { content() } } } +private fun Modifier.withKeepVisible( + lingerDuration: Duration, + scope: CoroutineScope, + onKeepVisibleChange: (Boolean) -> Unit, +) = + pointerInput(scope) { + var delayJob: Job? = null + awaitEachGesture { + val event = awaitPointerEvent() + if (event.type == PointerEventType.Move) { + delayJob?.cancel() + onKeepVisibleChange(true) + delayJob = + scope.launch { + delay(lingerDuration) + onKeepVisibleChange(false) + } + } + } + } + @Composable private fun ScrollableContainerImpl( verticalScrollbar: (@Composable () -> Unit)?, @@ -282,6 +346,7 @@ private fun ScrollableContainerImpl( horizontalScrollbarMeasurable.measure(horizontalScrollbarConstraints) } else null + val contentMeasurable = measurables.find { it.layoutId == ID_CONTENT } ?: error("Content not provided") val contentConstraints = computeContentConstraints( scrollbarStyle, @@ -289,22 +354,21 @@ private fun ScrollableContainerImpl( verticalScrollbarPlaceable, horizontalScrollbarPlaceable, ) - val contentMeasurable = measurables.find { it.layoutId == ID_CONTENT } ?: error("Content not provided") val contentPlaceable = contentMeasurable.measure(contentConstraints) - layout( - width = contentPlaceable.width + (verticalScrollbarPlaceable?.width ?: 0), - height = contentPlaceable.height + (horizontalScrollbarPlaceable?.height ?: 0), - ) { + val isAlwaysVisible = scrollbarStyle.scrollbarVisibility is AlwaysVisible + val vScrollbarWidth = if (isAlwaysVisible) verticalScrollbarPlaceable?.width ?: 0 else 0 + val width = contentPlaceable.width + vScrollbarWidth + + val hScrollbarHeight = if (isAlwaysVisible) horizontalScrollbarPlaceable?.height ?: 0 else 0 + val height = contentPlaceable.height + hScrollbarHeight + + layout(width, height) { contentPlaceable.placeRelative(x = 0, y = 0, zIndex = 0f) - verticalScrollbarPlaceable?.placeRelative( - x = incomingConstraints.maxWidth - verticalScrollbarPlaceable.width, - y = 0, - zIndex = 1f, - ) + verticalScrollbarPlaceable?.placeRelative(x = width - verticalScrollbarPlaceable.width, y = 0, zIndex = 1f) horizontalScrollbarPlaceable?.placeRelative( x = 0, - y = incomingConstraints.maxHeight - horizontalScrollbarPlaceable.height, + y = height - horizontalScrollbarPlaceable.height, zIndex = 1f, ) } @@ -319,9 +383,10 @@ private fun computeContentConstraints( ): Constraints { fun width() = if (incomingConstraints.hasBoundedWidth) { + val maxWidth = incomingConstraints.maxWidth when (scrollbarStyle.scrollbarVisibility) { - is AlwaysVisible -> incomingConstraints.maxWidth - (verticalScrollbarPlaceable?.width ?: 0) - is WhenScrolling -> incomingConstraints.maxWidth + is AlwaysVisible -> maxWidth - (verticalScrollbarPlaceable?.width ?: 0) + is WhenScrolling -> maxWidth } } else { error("Incoming constraints have infinite width, should not use fixed width") @@ -329,9 +394,10 @@ private fun computeContentConstraints( fun height() = if (incomingConstraints.hasBoundedHeight) { + val maxHeight = incomingConstraints.maxHeight when (scrollbarStyle.scrollbarVisibility) { - is AlwaysVisible -> incomingConstraints.maxHeight - (horizontalScrollbarPlaceable?.height ?: 0) - is WhenScrolling -> incomingConstraints.maxHeight + is AlwaysVisible -> maxHeight - (horizontalScrollbarPlaceable?.height ?: 0) + is WhenScrolling -> maxHeight } } else { error("Incoming constraints have infinite height, should not use fixed height") From d2fbfc1de965203849db6ec654536ea1e792d7b4 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Wed, 21 Aug 2024 21:15:38 +0200 Subject: [PATCH 10/14] Fix Scrollbars [part 12/n] * Remove debug bg from Scrollbars sample * Fix thumb appearing/disappearing animation * Fix colour animations * Keep AlwaysVisible thumb "hovered" while dragging * Fix styling in the bridge --- .../org/jetbrains/jewel/bridge/BridgeUtils.kt | 33 +- .../jewel/bridge/theme/ScrollbarBridge.kt | 313 +++++++------ .../styling/IntUiScrollbarStyling.kt | 30 +- .../ideplugin/ScrollbarsShowcaseTab.kt | 44 +- .../standalone/view/component/Scrollbars.kt | 3 +- ui/api/ui.api | 9 +- .../jetbrains/jewel/ui/component/Scrollbar.kt | 415 ++++++++---------- .../ui/component/styling/ScrollbarStyling.kt | 13 +- 8 files changed, 397 insertions(+), 463 deletions(-) diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt index 6f2655a06..5682ffdb0 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt @@ -48,12 +48,19 @@ public fun retrieveColor( default: Color, ): Color = retrieveColorOrNull(key) ?: default +public fun retrieveColor( + key: String, + isDark: Boolean, + default: Color, + defaultDark: Color, +): Color = retrieveColorOrNull(key) ?: if (isDark) defaultDark else default + public fun retrieveColorOrNull(key: String): Color? = JBColor.namedColorOrNull(key)?.toComposeColor() public fun retrieveColorOrUnspecified(key: String): Color { val color = retrieveColorOrNull(key) if (color == null) { - logger.warn("Color with key \"$key\" not found, fallback to 'Color.Unspecified'") + logger.debug("Color with key \"$key\" not found, fallback to 'Color.Unspecified'") } return color ?: Color.Unspecified } @@ -98,9 +105,8 @@ public fun retrieveInsetsAsPaddingValues( ): PaddingValues = UIManager.getInsets(key)?.toPaddingValues() ?: default ?: keyNotFound(key, "Insets") /** - * Converts a [Insets] to [PaddingValues]. If the receiver is a [JBInsets] - * instance, this function delegates to the specific [toPaddingValues] for - * it, which is scaling-aware. + * Converts a [Insets] to [PaddingValues]. If the receiver is a [JBInsets] instance, this function delegates to the + * specific [toPaddingValues] for it, which is scaling-aware. */ public fun Insets.toPaddingValues(): PaddingValues = if (this is JBInsets) { @@ -110,26 +116,22 @@ public fun Insets.toPaddingValues(): PaddingValues = } /** - * Converts a [JBInsets] to [PaddingValues], in a scaling-aware way. This - * means that the resulting [PaddingValues] will be constructed from the - * [JBInsets.getUnscaled] values, treated as [Dp]. This avoids double - * scaling. + * Converts a [JBInsets] to [PaddingValues], in a scaling-aware way. This means that the resulting [PaddingValues] will + * be constructed from the [JBInsets.getUnscaled] values, treated as [Dp]. This avoids double scaling. */ @Suppress("ktlint:standard:function-signature") // False positive public fun JBInsets.toPaddingValues(): PaddingValues = PaddingValues(unscaled.left.dp, unscaled.top.dp, unscaled.right.dp, unscaled.bottom.dp) /** - * Converts a [Dimension] to [DpSize]. If the receiver is a [JBDimension] - * instance, this function delegates to the specific [toDpSize] for it, - * which is scaling-aware. + * Converts a [Dimension] to [DpSize]. If the receiver is a [JBDimension] instance, this function delegates to the + * specific [toDpSize] for it, which is scaling-aware. */ public fun Dimension.toDpSize(): DpSize = if (this is JBDimension) toDpSize() else DpSize(width.dp, height.dp) /** - * Converts a [JBDimension] to [DpSize], in a scaling-aware way. This means - * that the resulting [DpSize] will be constructed by first obtaining the - * unscaled values. This avoids double scaling. + * Converts a [JBDimension] to [DpSize], in a scaling-aware way. This means that the resulting [DpSize] will be + * constructed by first obtaining the unscaled values. This avoids double scaling. */ public fun JBDimension.toDpSize(): DpSize { val scaleFactor = scale(1f) @@ -182,7 +184,8 @@ public fun retrieveTextStyle( val jbFont = JBFont.create(lafFont, false) val derivedFont = - jbFont.let { if (bold) it.asBold() else it.asPlain() } + jbFont + .let { if (bold) it.asBold() else it.asPlain() } .let { if (fontStyle == FontStyle.Italic) it.asItalic() else it } return TextStyle( diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt index c3c669ebb..8f8bb3065 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt @@ -3,11 +3,10 @@ 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 androidx.compose.ui.unit.dp import org.jetbrains.jewel.bridge.ScrollbarHelper -import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified +import org.jetbrains.jewel.bridge.retrieveColor import org.jetbrains.jewel.ui.component.styling.ScrollbarColors import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle @@ -25,13 +24,6 @@ internal fun readScrollbarStyle(isDark: Boolean): ScrollbarStyle = scrollbarVisibility = readScrollbarVisibility(), ) -private fun readScrollbarVisibility() = - if (hostOs.isMacOS) { - ScrollbarHelper.getInstance().scrollbarVisibilityStyleFlow.value - } else { - ScrollbarVisibility.AlwaysVisible.windowsAndLinux() - } - private fun readScrollbarColors(isDark: Boolean) = if (hostOs.isMacOS) { readScrollbarMacColors(isDark) @@ -39,208 +31,201 @@ private fun readScrollbarColors(isDark: Boolean) = readScrollbarWindowsAndLinuxColors(isDark) } -private fun readTrackClickBehavior() = - if (hostOs.isMacOS) { - ScrollbarHelper.getInstance().trackClickBehaviorFlow.value - } else { - TrackClickBehavior.JumpToSpot - } - -private fun readScrollbarWindowsAndLinuxColors(isDark: Boolean): ScrollbarColors = +private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = ScrollbarColors( thumbBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Transparent.thumbColor", - 0x33737373, - 0x47A6A6A6, + retrieveColor( + key = "ScrollBar.Mac.Transparent.thumbColor", + isDark = isDark, + default = Color(0x00000000), + defaultDark = Color(0x00808080), ), thumbBackgroundActive = - readScrollBarColorForKey( - isDark, - "ScrollBar.Transparent.hoverThumbColor", - 0x47737373, - 0x59A6A6A6, + retrieveColor( + key = "ScrollBar.Mac.Transparent.hoverThumbColor", + isDark = isDark, + default = Color(0x80000000), + defaultDark = Color(0x8C808080), ), thumbOpaqueBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.thumbColor", - 0x33737373, - 0x47A6A6A6, + retrieveColor( + key = "ScrollBar.Mac.thumbColor", + isDark = isDark, + default = Color(0x33000000), + defaultDark = Color(0x59808080), ), thumbOpaqueBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.hoverThumbColor", - 0x47737373, - 0x59A6A6A6, + retrieveColor( + key = "ScrollBar.Mac.hoverThumbColor", + isDark = isDark, + default = Color(0x80000000), + defaultDark = Color(0x8C808080), ), thumbBorder = - readScrollBarColorForKey( - isDark, - "ScrollBar.Transparent.thumbBorderColor", - 0x33595959, - 0x47383838, + retrieveColor( + key = "ScrollBar.Mac.Transparent.thumbBorderColor", + isDark = isDark, + default = Color(0x00000000), + defaultDark = Color(0x00262626), ), thumbBorderActive = - readScrollBarColorForKey( - isDark, - "ScrollBar.Transparent.hoverThumbBorderColor", - 0x47595959, - 0x59383838, + retrieveColor( + key = "ScrollBar.Mac.Transparent.hoverThumbBorderColor", + isDark = isDark, + default = Color(0x80000000), + defaultDark = Color(0x8C262626), ), thumbOpaqueBorder = - readScrollBarColorForKey( - isDark, - "ScrollBar.thumbBorderColor", - 0x33595959, - 0x47383838, + retrieveColor( + key = "ScrollBar.Mac.thumbBorderColor", + isDark = isDark, + default = Color(0x33000000), + defaultDark = Color(0x59262626), ), thumbOpaqueBorderHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.hoverThumbBorderColor", - 0x47595959, - 0x59383838, + retrieveColor( + key = "ScrollBar.Mac.hoverThumbBorderColor", + isDark = isDark, + default = Color(0x80000000), + defaultDark = Color(0x8C262626), ), trackBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Transparent.trackColor", - 0x00808080, - 0x00808080, + retrieveColor( + key = "ScrollBar.Mac.Transparent.trackColor", + isDark = isDark, + default = Color(0x00808080), + defaultDark = Color(0x00808080), ), trackBackgroundExpanded = - readScrollBarColorForKey( - isDark, - "ScrollBar.Transparent.hoverTrackColor", - 0x1A808080, - 0x1A808080, + retrieveColor( + key = "ScrollBar.Mac.Transparent.hoverTrackColor", + isDark = isDark, + default = Color(0x1A808080), + defaultDark = Color(0x1A808080), ), trackOpaqueBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.trackColor", - 0x00808080, - 0x00808080, + retrieveColor( + key = "ScrollBar.Mac.trackColor", + isDark = isDark, + default = Color(0x00808080), + defaultDark = Color(0x00808080), ), trackOpaqueBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.hoverTrackColor", - 0x1A808080, - 0x1A808080, + retrieveColor( + key = "ScrollBar.Mac.hoverTrackColor", + isDark = isDark, + default = Color(0x00808080), + defaultDark = Color(0x00808080), ), ) -private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors = +private fun readScrollbarWindowsAndLinuxColors(isDark: Boolean): ScrollbarColors = ScrollbarColors( thumbBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.thumbColor", - 0x00000000, - 0x59808080, + retrieveColor( + key = "ScrollBar.Transparent.thumbColor", + isDark = isDark, + default = Color(0x33737373), + defaultDark = Color(0x47A6A6A6), ), thumbBackgroundActive = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.hoverThumbColor", - 0x80000000, - 0x8C808080, + retrieveColor( + key = "ScrollBar.Transparent.hoverThumbColor", + isDark = isDark, + default = Color(0x47737373), + defaultDark = Color(0x59A6A6A6), ), thumbOpaqueBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.thumbColor", - 0x33000000, - 0x59808080, + retrieveColor( + key = "ScrollBar.thumbColor", + isDark = isDark, + default = Color(0x33737373), + defaultDark = Color(0x47A6A6A6), ), thumbOpaqueBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.hoverThumbColor", - 0x80000000, - 0x8C808080, + retrieveColor( + key = "ScrollBar.hoverThumbColor", + isDark = isDark, + default = Color(0x47737373), + defaultDark = Color(0x59A6A6A6), ), thumbBorder = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.thumbBorderColor", - 0x00000000, - 0x59262626, + retrieveColor( + key = "ScrollBar.Transparent.thumbBorderColor", + isDark = isDark, + default = Color(0x33595959), + defaultDark = Color(0x47383838), ), thumbBorderActive = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.hoverThumbBorderColor", - 0x80000000, - 0x8C262626, + retrieveColor( + key = "ScrollBar.Transparent.hoverThumbBorderColor", + isDark = isDark, + default = Color(0x47595959), + defaultDark = Color(0x59383838), ), thumbOpaqueBorder = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.thumbBorderColor", - 0x33000000, - 0x59262626, + retrieveColor( + key = "ScrollBar.thumbBorderColor", + isDark = isDark, + default = Color(0x33595959), + defaultDark = Color(0x47383838), ), thumbOpaqueBorderHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.hoverThumbBorderColor", - 0x80000000, - 0x8C262626, + retrieveColor( + key = "ScrollBar.hoverThumbBorderColor", + isDark = isDark, + default = Color(0x47595959), + defaultDark = Color(0x59383838), ), trackBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.trackColor", - 0x00808080, - 0x00808080, + retrieveColor( + key = "ScrollBar.Transparent.trackColor", + isDark = isDark, + default = Color(0x00808080), + defaultDark = Color(0x00808080), ), trackBackgroundExpanded = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.Transparent.hoverTrackColor", - 0x1A808080, - 0x1A808080, + retrieveColor( + key = "ScrollBar.Transparent.hoverTrackColor", + isDark = isDark, + default = Color(0x1A808080), + defaultDark = Color(0x1A808080), ), trackOpaqueBackground = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.trackColor", - 0x00808080, - 0x00808080, + retrieveColor( + key = "ScrollBar.trackColor", + isDark = isDark, + default = Color(0x00808080), + defaultDark = Color(0x00808080), ), trackOpaqueBackgroundHovered = - readScrollBarColorForKey( - isDark, - "ScrollBar.Mac.hoverTrackColor", - 0x1A808080, - 0x1A808080, + retrieveColor( + key = "ScrollBar.hoverTrackColor", + isDark = isDark, + default = Color(0x00808080), + defaultDark = Color(0x00808080), ), ) -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), - minThumbLength = 20.dp, - ) + ScrollbarMetrics(thumbCornerSize = CornerSize(percent = 100), minThumbLength = 20.dp) + } else { + ScrollbarMetrics(thumbCornerSize = CornerSize(0), minThumbLength = 16.dp) + } + +private fun readTrackClickBehavior() = + if (hostOs.isMacOS) { + ScrollbarHelper.getInstance().trackClickBehaviorFlow.value + } else { + TrackClickBehavior.JumpToSpot + } + +private fun readScrollbarVisibility() = + if (hostOs.isMacOS) { + ScrollbarHelper.getInstance().scrollbarVisibilityStyleFlow.value } else { - ScrollbarMetrics( - thumbCornerSize = CornerSize(0), - minThumbLength = 16.dp, - ) + ScrollbarVisibility.AlwaysVisible.windowsAndLinux() } public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisibility.WhenScrolling = @@ -255,9 +240,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( trackThicknessExpanded: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(2.dp), trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), - appearAnimationDuration: Duration = 125.milliseconds, - disappearAnimationDuration: Duration = 125.milliseconds, - expandAnimationDuration: Duration = 125.milliseconds, + trackColorAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = trackColorAnimationDuration, + thumbColorAnimationDuration: Duration = trackColorAnimationDuration, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( @@ -265,9 +250,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( trackThicknessExpanded = trackThicknessExpanded, trackPadding = trackPadding, trackPaddingWithBorder = trackPaddingWithBorder, - appearAnimationDuration = appearAnimationDuration, - disappearAnimationDuration = disappearAnimationDuration, + trackColorAnimationDuration = trackColorAnimationDuration, expandAnimationDuration = expandAnimationDuration, + thumbColorAnimationDuration = thumbColorAnimationDuration, lingerDuration = lingerDuration, ) @@ -276,9 +261,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( trackThicknessExpanded: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(), trackPaddingWithBorder: PaddingValues = trackPadding, - appearAnimationDuration: Duration = 125.milliseconds, - disappearAnimationDuration: Duration = 125.milliseconds, - expandAnimationDuration: Duration = 125.milliseconds, + trackColorAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = trackColorAnimationDuration, + thumbColorAnimationDuration: Duration = trackColorAnimationDuration, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( @@ -286,9 +271,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( trackThicknessExpanded = trackThicknessExpanded, trackPadding = trackPadding, trackPaddingWithBorder = trackPaddingWithBorder, - appearAnimationDuration = appearAnimationDuration, - disappearAnimationDuration = disappearAnimationDuration, + trackColorAnimationDuration = trackColorAnimationDuration, expandAnimationDuration = expandAnimationDuration, + thumbColorAnimationDuration = thumbColorAnimationDuration, lingerDuration = lingerDuration, ) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt index 00bf4f668..26ced606c 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt @@ -111,9 +111,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( trackThicknessExpanded: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(2.dp), trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), - appearAnimationDuration: Duration = 125.milliseconds, - disappearAnimationDuration: Duration = 125.milliseconds, - expandAnimationDuration: Duration = 125.milliseconds, + trackColorAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = trackColorAnimationDuration, + thumbColorAnimationDuration: Duration = trackColorAnimationDuration, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( @@ -121,9 +121,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( trackThicknessExpanded = trackThicknessExpanded, trackPadding = trackPadding, trackPaddingWithBorder = trackPaddingWithBorder, - appearAnimationDuration = appearAnimationDuration, - disappearAnimationDuration = disappearAnimationDuration, + trackColorAnimationDuration = trackColorAnimationDuration, expandAnimationDuration = expandAnimationDuration, + thumbColorAnimationDuration = thumbColorAnimationDuration, lingerDuration = lingerDuration, ) @@ -132,9 +132,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( trackThicknessExpanded: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(), trackPaddingWithBorder: PaddingValues = trackPadding, - appearAnimationDuration: Duration = 125.milliseconds, - disappearAnimationDuration: Duration = 125.milliseconds, - expandAnimationDuration: Duration = 125.milliseconds, + trackColorAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = trackColorAnimationDuration, + thumbColorAnimationDuration: Duration = trackColorAnimationDuration, lingerDuration: Duration = 700.milliseconds, ): ScrollbarVisibility.WhenScrolling = ScrollbarVisibility.WhenScrolling( @@ -142,9 +142,9 @@ public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( trackThicknessExpanded = trackThicknessExpanded, trackPadding = trackPadding, trackPaddingWithBorder = trackPaddingWithBorder, - appearAnimationDuration = appearAnimationDuration, - disappearAnimationDuration = disappearAnimationDuration, + trackColorAnimationDuration = trackColorAnimationDuration, expandAnimationDuration = expandAnimationDuration, + thumbColorAnimationDuration = thumbColorAnimationDuration, lingerDuration = lingerDuration, ) @@ -156,11 +156,11 @@ public fun ScrollbarColors.Companion.macOsLight( thumbBorder: Color = thumbBackground, thumbBorderActive: Color = thumbBackgroundActive, thumbOpaqueBorder: Color = thumbOpaqueBackground, - thumbOpaqueBorderHovered: Color = thumbOpaqueBackgroundHovered, + thumbOpaqueBorderHovered: Color = thumbBackgroundActive, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, + trackOpaqueBackgroundHovered: Color = trackBackground, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, @@ -189,7 +189,7 @@ public fun ScrollbarColors.Companion.windowsAndLinuxLight( trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, + trackOpaqueBackgroundHovered: Color = trackBackground, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, @@ -218,7 +218,7 @@ public fun ScrollbarColors.Companion.macOsDark( trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, + trackOpaqueBackgroundHovered: Color = trackBackground, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, @@ -247,7 +247,7 @@ public fun ScrollbarColors.Companion.windowsAndLinuxDark( trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackOpaqueBackground, + trackOpaqueBackgroundHovered: Color = trackBackground, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt index 6444bf6e2..06c9dd123 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ScrollbarsShowcaseTab.kt @@ -2,7 +2,9 @@ package org.jetbrains.jewel.samples.ideplugin 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.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -12,12 +14,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import org.jetbrains.jewel.bridge.LocalComponent import org.jetbrains.jewel.foundation.modifier.trackActivation @@ -28,16 +30,13 @@ import org.jetbrains.jewel.ui.component.Divider import org.jetbrains.jewel.ui.component.Text import org.jetbrains.jewel.ui.component.TextArea import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer +import org.jetbrains.jewel.ui.component.scrollbarContentSafePadding import java.util.Locale @Composable internal fun ScrollbarsShowcaseTab() { Column( - Modifier - .trackComponentActivation(LocalComponent.current) - .fillMaxSize() - .padding(16.dp) - .trackActivation(), + Modifier.trackComponentActivation(LocalComponent.current).fillMaxSize().padding(16.dp).trackActivation(), verticalArrangement = Arrangement.spacedBy(16.dp), ) { Row(modifier = Modifier.fillMaxWidth().height(200.dp)) { @@ -51,17 +50,22 @@ internal fun ScrollbarsShowcaseTab() { scrollState, Modifier.width(200.dp).border(1.dp, JewelTheme.globalColors.borders.normal), ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(4.dp), - state = scrollState, - ) { - items(LIST_ITEMS) { item -> + LazyColumn(state = scrollState, contentPadding = PaddingValues(vertical = 8.dp)) { + itemsIndexed(LIST_ITEMS) { index, item -> Column { Text( - modifier = Modifier.padding(horizontal = 8.dp), text = item, + modifier = Modifier.padding(start = 8.dp, end = 8.dp + scrollbarContentSafePadding()), ) - Divider(orientation = Orientation.Horizontal, color = Color.Gray) + + if (index < LIST_ITEMS.lastIndex) { + Box(Modifier.height(8.dp)) { + Divider( + orientation = Orientation.Horizontal, + modifier = Modifier.fillMaxWidth().align(Alignment.CenterStart), + ) + } + } } } } @@ -87,12 +91,6 @@ private const val ANDROID_IPSUM = " sunt in culpa qui officia material design deserunt mollit anim id est laborum." private val LIST_ITEMS = - ANDROID_IPSUM - .split(",") - .map { lorem -> - lorem - .trim() - .replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() - } - } + ANDROID_IPSUM.split(",").map { lorem -> + lorem.trim().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index 078bc8f5f..fb0070c0d 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -25,7 +25,6 @@ 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.Stroke @@ -211,7 +210,7 @@ private fun HorizontalScrollbarContent( modifier: Modifier, ) { HorizontallyScrollableContainer( - modifier = modifier.border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal).background(Color.Red), + modifier = modifier.border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal), style = scrollbarStyle, ) { Column( diff --git a/ui/api/ui.api b/ui/api/ui.api index fe297ddb7..e2fa6a3f9 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -1928,11 +1928,10 @@ public final class org/jetbrains/jewel/ui/component/styling/ScrollbarStylingKt { } public abstract interface class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility { - public abstract fun getAppearAnimationDuration-UwyO8pc ()J - public abstract fun getDisappearAnimationDuration-UwyO8pc ()J public abstract fun getExpandAnimationDuration-UwyO8pc ()J public abstract fun getLingerDuration-UwyO8pc ()J public abstract fun getThumbColorAnimationDuration-UwyO8pc ()J + public abstract fun getTrackColorAnimationDuration-UwyO8pc ()J public abstract fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues; public abstract fun getTrackPaddingWithBorder ()Landroidx/compose/foundation/layout/PaddingValues; public abstract fun getTrackThickness-D9Ej5fM ()F @@ -1944,11 +1943,10 @@ public final class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$ public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion; public synthetic fun (FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z - public fun getAppearAnimationDuration-UwyO8pc ()J - public fun getDisappearAnimationDuration-UwyO8pc ()J public fun getExpandAnimationDuration-UwyO8pc ()J public fun getLingerDuration-UwyO8pc ()J public fun getThumbColorAnimationDuration-UwyO8pc ()J + public fun getTrackColorAnimationDuration-UwyO8pc ()J public fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues; public fun getTrackPaddingWithBorder ()Landroidx/compose/foundation/layout/PaddingValues; public fun getTrackThickness-D9Ej5fM ()F @@ -1965,11 +1963,10 @@ public final class org/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$ public static final field Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion; public synthetic fun (FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z - public fun getAppearAnimationDuration-UwyO8pc ()J - public fun getDisappearAnimationDuration-UwyO8pc ()J public fun getExpandAnimationDuration-UwyO8pc ()J public fun getLingerDuration-UwyO8pc ()J public fun getThumbColorAnimationDuration-UwyO8pc ()J + public fun getTrackColorAnimationDuration-UwyO8pc ()J public fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues; public fun getTrackPaddingWithBorder ()Landroidx/compose/foundation/layout/PaddingValues; public fun getTrackThickness-D9Ej5fM ()F diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt index 12b6a98b3..5b8efab5d 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job @@ -77,7 +78,6 @@ 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 org.jetbrains.jewel.ui.util.thenIf -import kotlin.math.roundToInt @Composable public fun VerticalScrollbar( @@ -150,11 +150,8 @@ private fun BaseScrollbar( val isHovered by interactionSource.collectIsHoveredAsState() var showScrollbar by remember { mutableStateOf(false) } - val isActive = - isOpaque || - scrollState.isScrollInProgress || - dragInteraction.value != null || - (keepVisible && showScrollbar) + val isScrolling = scrollState.isScrollInProgress || dragInteraction.value != null + val isActive = isOpaque || isScrolling || (keepVisible && showScrollbar) if (isHovered && showScrollbar) isExpanded = true @@ -171,11 +168,12 @@ private fun BaseScrollbar( } } - val animatedThickness by animateDpAsState( - if (isExpanded) visibilityStyle.trackThicknessExpanded else visibilityStyle.trackThickness, - tween(visibilityStyle.expandAnimationDuration.inWholeMilliseconds.toInt(), easing = LinearEasing), - "scrollbar_thickness", - ) + val animatedThickness by + animateDpAsState( + if (isExpanded) visibilityStyle.trackThicknessExpanded else visibilityStyle.trackThickness, + tween(visibilityStyle.expandAnimationDuration.inWholeMilliseconds.toInt(), easing = LinearEasing), + "scrollbar_thickness", + ) val adapter = when (scrollState) { @@ -196,22 +194,22 @@ private fun BaseScrollbar( SliderAdapter(adapter, containerSize, thumbMinHeight, reverseLayout, isVertical, coroutineScope) } - val thumbBackgroundColor = getThumbBackgroundColor(isOpaque, isHovered, style, showScrollbar) - val thumbBorderColor = getThumbBorderColor(isOpaque, isHovered, style, showScrollbar) + 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 thumbThicknessPx = if (isVertical) { - val layoutDirection = LocalLayoutDirection.current - animatedThickness - - trackPadding.calculateLeftPadding(layoutDirection) - - trackPadding.calculateRightPadding(layoutDirection) - } else { - animatedThickness - - trackPadding.calculateTopPadding() - - trackPadding.calculateBottomPadding() - }.roundToPx() + val thumbThicknessPx = + if (isVertical) { + val layoutDirection = LocalLayoutDirection.current + animatedThickness - + trackPadding.calculateLeftPadding(layoutDirection) - + trackPadding.calculateRightPadding(layoutDirection) + } else { + animatedThickness - trackPadding.calculateTopPadding() - trackPadding.calculateBottomPadding() + } + .roundToPx() val measurePolicy = if (isVertical) { @@ -226,23 +224,12 @@ private fun BaseScrollbar( val canScroll = sliderAdapter.thumbSize < containerSize - val trackBackground by animateColorAsState( - if (isOpaque) { - if (isHovered) { - style.colors.trackOpaqueBackgroundHovered - } else { - style.colors.trackOpaqueBackground - } - } else { - if (isExpanded) { - style.colors.trackBackgroundExpanded - } else { - style.colors.trackBackground - } - }, - appearanceTween(showScrollbar, visibilityStyle), - "scrollbar_trackBackground", - ) + val trackBackground by + animateColorAsState( + targetValue = getTrackColor(isOpaque, isHovered, style, isExpanded), + animationSpec = trackColorTween(showScrollbar, visibilityStyle), + label = "scrollbar_trackBackground", + ) Layout( content = { @@ -257,69 +244,86 @@ private fun BaseScrollbar( thumbBackgroundColor, thumbBorderColor, hasVisibleBorder, - style.metrics.thumbCornerSize + style.metrics.thumbCornerSize, ) }, - modifier = modifier - .thenIf(showScrollbar && canScroll && isExpanded) { - background(trackBackground) - } - .scrollable( - state = scrollState, - orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal, - enabled = enabled, - reverseDirection = true, // Not sure why it's needed, but it is — TODO revisit this - ) - .padding(trackPadding) - .hoverable(interactionSource = interactionSource) - .thenIf(enabled && showScrollbar) { - scrollOnPressTrack(style.trackClickBehavior, isVertical, reverseLayout, sliderAdapter) - }, + modifier = + modifier + .thenIf(showScrollbar && canScroll && isExpanded) { background(trackBackground) } + .scrollable( + state = scrollState, + orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal, + enabled = enabled, + reverseDirection = true, // Not sure why it's needed, but it is — TODO revisit this + ) + .padding(trackPadding) + .hoverable(interactionSource = interactionSource) + .thenIf(enabled && showScrollbar) { + scrollOnPressTrack(style.trackClickBehavior, isVertical, reverseLayout, sliderAdapter) + }, measurePolicy = measurePolicy, ) } } +private fun getTrackColor(isOpaque: Boolean, isHovered: Boolean, style: ScrollbarStyle, isExpanded: Boolean) = + if (isOpaque) { + if (isHovered) { + style.colors.trackOpaqueBackgroundHovered + } else { + style.colors.trackOpaqueBackground + } + } else { + if (isExpanded) { + style.colors.trackBackgroundExpanded + } else { + style.colors.trackBackground + } + } + private fun getThumbBackgroundColor( isOpaque: Boolean, isHovered: Boolean, + isScrolling: Boolean, style: ScrollbarStyle, showScrollbar: Boolean, -) = if (isOpaque) { - if (isHovered) { - style.colors.thumbOpaqueBackgroundHovered - } else { - style.colors.thumbOpaqueBackground - } -} else { - if (showScrollbar) { - style.colors.thumbBackgroundActive +) = + if (isOpaque) { + if (isHovered || isScrolling) { + style.colors.thumbOpaqueBackgroundHovered + } else { + style.colors.thumbOpaqueBackground + } } else { - style.colors.thumbBackground + if (showScrollbar) { + style.colors.thumbBackgroundActive + } else { + style.colors.thumbBackground + } } -} private fun getThumbBorderColor( isOpaque: Boolean, isHovered: Boolean, + isScrolling: Boolean, style: ScrollbarStyle, showScrollbar: Boolean, -) = if (isOpaque) { - if (isHovered) { - style.colors.thumbOpaqueBorderHovered - } else { - style.colors.thumbOpaqueBorder - } -} else { - if (showScrollbar) { - style.colors.thumbBorderActive +) = + if (isOpaque) { + if (isHovered || isScrolling) { + style.colors.thumbOpaqueBorderHovered + } else { + style.colors.thumbOpaqueBorder + } } else { - style.colors.thumbBorder + if (showScrollbar) { + style.colors.thumbBorderActive + } else { + style.colors.thumbBorder + } } -} -private fun areTheSameColor(first: Color, second: Color) = - first.toArgb() == second.toArgb() +private fun areTheSameColor(first: Color, second: Color) = first.toArgb() == second.toArgb() @Composable private fun Thumb( @@ -335,29 +339,26 @@ private fun Thumb( hasVisibleBorder: Boolean, cornerSize: CornerSize, ) { - val background by animateColorAsState( - targetValue = thumbBackgroundColor, - animationSpec = thumbColorTween(showScrollbar, visibilityStyle), - "scrollbar_thumbBackground", - ) + val background by + animateColorAsState( + targetValue = thumbBackgroundColor, + animationSpec = thumbColorTween(showScrollbar, visibilityStyle), + label = "scrollbar_thumbBackground", + ) - val border by animateColorAsState( - targetValue = thumbBorderColor, - animationSpec = thumbColorTween(showScrollbar, visibilityStyle), - "scrollbar_thumbBorder", - ) + val border by + animateColorAsState( + targetValue = thumbBorderColor, + animationSpec = thumbColorTween(showScrollbar, visibilityStyle), + label = "scrollbar_thumbBorder", + ) val borderWidth = 1.dp val density = LocalDensity.current Box( - Modifier - .layoutId("thumb") - .thenIf(canScroll) { - drawThumb(background, borderWidth, border, hasVisibleBorder, cornerSize, density) - } - .thenIf(enabled) { - scrollbarDrag(interactionSource, dragInteraction, sliderAdapter) - }, + Modifier.layoutId("thumb") + .thenIf(canScroll) { drawThumb(background, borderWidth, border, hasVisibleBorder, cornerSize, density) } + .thenIf(enabled) { scrollbarDrag(interactionSource, dragInteraction, sliderAdapter) } ) } @@ -372,8 +373,7 @@ private fun Modifier.drawThumb( val borderWidthPx = if (hasVisibleBorder) borderWidth.toPx() else 0f // First, draw the background, leaving room for the border around it - val bgCornerRadius = - CornerRadius((cornerSize.toPx(size, density) - borderWidthPx * 2).coerceAtLeast(0f)) + val bgCornerRadius = CornerRadius((cornerSize.toPx(size, density) - borderWidthPx * 2).coerceAtLeast(0f)) drawRoundRect( color = backgroundColor, topLeft = Offset(borderWidthPx, borderWidthPx), @@ -389,34 +389,34 @@ private fun Modifier.drawThumb( topLeft = Offset(borderWidthPx / 2, borderWidthPx / 2), size = Size(size.width - borderWidthPx, size.height - borderWidthPx), cornerRadius = strokeCornerRadius, - style = Stroke(borderWidthPx) + style = Stroke(borderWidthPx), ) } } -private fun appearanceTween( - showScrollbar: Boolean, - visibility: ScrollbarVisibility, -) = tween( - if (showScrollbar) { - visibility.appearAnimationDuration.inWholeMilliseconds.toInt() - } else { - visibility.disappearAnimationDuration.inWholeMilliseconds.toInt() - }, - easing = LinearEasing -) +private fun trackColorTween(showScrollbar: Boolean, visibility: ScrollbarVisibility) = + tween( + if (showScrollbar) { + 0 + } else { + visibility.trackColorAnimationDuration.inWholeMilliseconds.toInt() + }, + easing = LinearEasing, + ) -private fun thumbColorTween( - showScrollbar: Boolean, - visibility: ScrollbarVisibility, -) = tween( - durationMillis = visibility.thumbColorAnimationDuration.inWholeMilliseconds.toInt(), - delayMillis = when { - visibility is AlwaysVisible && !showScrollbar -> visibility.lingerDuration.inWholeMilliseconds.toInt() - else -> 0 - }, - easing = LinearEasing -) +private fun thumbColorTween(showScrollbar: Boolean, visibility: ScrollbarVisibility) = + tween( + durationMillis = + if (visibility is AlwaysVisible || !showScrollbar) { + visibility.thumbColorAnimationDuration.inWholeMilliseconds.toInt() + } else 0, + delayMillis = + when { + visibility is AlwaysVisible && !showScrollbar -> visibility.lingerDuration.inWholeMilliseconds.toInt() + else -> 0 + }, + easing = LinearEasing, + ) // =========================================================================== // Note: most of the code below is copied and adapted from the stock scrollbar @@ -430,26 +430,17 @@ private val SliderAdapter.thumbPixelRange: IntRange return (start until endExclusive) } -private val IntRange.size get() = last + 1 - first +private val IntRange.size + get() = last + 1 - first -private fun verticalMeasurePolicy( - sliderAdapter: SliderAdapter, - setContainerSize: (Int) -> Unit, - thumbThickness: Int, -) = MeasurePolicy { measurables, constraints -> - setContainerSize(constraints.maxHeight) - val pixelRange = sliderAdapter.thumbPixelRange - val placeable = - measurables.first().measure( - Constraints.fixed( - constraints.constrainWidth(thumbThickness), - pixelRange.size, - ), - ) - layout(placeable.width, constraints.maxHeight) { - placeable.place(0, pixelRange.first) +private fun verticalMeasurePolicy(sliderAdapter: SliderAdapter, setContainerSize: (Int) -> Unit, thumbThickness: Int) = + MeasurePolicy { measurables, constraints -> + setContainerSize(constraints.maxHeight) + val pixelRange = sliderAdapter.thumbPixelRange + val placeable = + measurables.first().measure(Constraints.fixed(constraints.constrainWidth(thumbThickness), pixelRange.size)) + layout(placeable.width, constraints.maxHeight) { placeable.place(0, pixelRange.first) } } -} private fun horizontalMeasurePolicy( sliderAdapter: SliderAdapter, @@ -459,50 +450,42 @@ private fun horizontalMeasurePolicy( setContainerSize(constraints.maxWidth) val pixelRange = sliderAdapter.thumbPixelRange val placeable = - measurables.first().measure( - Constraints.fixed( - pixelRange.size, - constraints.constrainHeight(thumbThickness), - ), - ) - layout(constraints.maxWidth, placeable.height) { - placeable.place(pixelRange.first, 0) - } + measurables.first().measure(Constraints.fixed(pixelRange.size, constraints.constrainHeight(thumbThickness))) + layout(constraints.maxWidth, placeable.height) { placeable.place(pixelRange.first, 0) } } private fun Modifier.scrollbarDrag( interactionSource: MutableInteractionSource, draggedInteraction: MutableState, 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 - } +): 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( clickBehavior: TrackClickBehavior, @@ -516,28 +499,17 @@ private fun Modifier.scrollOnPressTrack( TrackPressScroller(coroutineScope, sliderAdapter, reverseLayout, clickBehavior) } - Modifier.pointerInput(scroller) { - detectScrollViaTrackGestures( - isVertical = isVertical, - scroller = scroller, - ) - } + Modifier.pointerInput(scroller) { detectScrollViaTrackGestures(isVertical = isVertical, scroller = scroller) } } -/** - * Responsible for scrolling when the scrollbar track is pressed (outside - * the thumb). - */ +/** 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, private val clickBehavior: TrackClickBehavior, ) { - /** - * The current direction of scroll (1: down/right, -1: up/left, 0: not - * scrolling) - */ + /** 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. */ @@ -546,10 +518,7 @@ private class TrackPressScroller( /** 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). - */ + /** Calculates the direction of scrolling towards the given offset (in pixels). */ private fun directionOfScrollTowards(offset: Float): Int { val pixelRange = sliderAdapter.thumbPixelRange return when { @@ -559,19 +528,14 @@ private class TrackPressScroller( } } - /** - * Scrolls once towards the current offset, if it matches the direction of - * the current gesture. - */ + /** 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) - } + with(sliderAdapter.adapter) { scrollTo(scrollOffset + currentDirection * viewportSize) } } } @@ -600,7 +564,6 @@ private class TrackPressScroller( else if (clickBehavior == JumpToSpot) scrollToOffset(offset) } - /** Invoked when the pointer moves while pressed during the gesture. */ fun onMovePressed(offset: Float) { this.offset = offset @@ -621,11 +584,12 @@ private class TrackPressScroller( private fun scrollToOffset(offset: Float) { job?.cancel() - job = coroutineScope.launch { - val contentSize = sliderAdapter.adapter.contentSize - val scrollOffset = offset / sliderAdapter.adapter.viewportSize * contentSize - sliderAdapter.adapter.scrollTo(scrollOffset) - } + job = + coroutineScope.launch { + val contentSize = sliderAdapter.adapter.contentSize + val scrollOffset = offset / sliderAdapter.adapter.viewportSize * contentSize + sliderAdapter.adapter.scrollTo(scrollOffset) + } } /** Invoked when the gesture is cancelled. */ @@ -636,14 +600,10 @@ private class TrackPressScroller( } /** - * 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]. + * 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, -) { +private suspend fun PointerInputScope.detectScrollViaTrackGestures(isVertical: Boolean, scroller: TrackPressScroller) { fun Offset.onScrollAxis() = if (isVertical) y else x awaitEachGesture { @@ -671,16 +631,10 @@ private suspend fun PointerInputScope.detectScrollViaTrackGestures( } } -/** - * The delay between the 1st and 2nd scroll while the scrollbar track is - * pressed outside the thumb. - */ +/** 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. - */ +/** 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( @@ -691,7 +645,9 @@ internal class SliderAdapter( private val isVertical: Boolean, private val coroutineScope: CoroutineScope, ) { - private val contentSize get() = adapter.contentSize + private val contentSize + get() = adapter.contentSize + private val visiblePart: Double get() { val contentSize = contentSize @@ -718,7 +674,8 @@ internal class SliderAdapter( val position: Double get() = if (reverseLayout) trackSize - thumbSize - rawPosition else rawPosition - val bounds get() = position..position + thumbSize + 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 @@ -750,13 +707,11 @@ internal class SliderAdapter( val maxScrollPosition = adapter.maxScrollOffset * scrollScale val currentPosition = position val targetPosition = - (currentPosition + dragDelta + unscrolledDragDistance).coerceIn( - 0.0, - maxScrollPosition, - ) + (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 + // 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 diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt index dd5e89d03..850e64d22 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt @@ -56,11 +56,10 @@ public sealed interface ScrollbarVisibility { public val trackThicknessExpanded: Dp public val trackPadding: PaddingValues public val trackPaddingWithBorder: PaddingValues - public val appearAnimationDuration: Duration - public val disappearAnimationDuration: Duration + public val trackColorAnimationDuration: Duration public val expandAnimationDuration: Duration - public val lingerDuration: Duration public val thumbColorAnimationDuration: Duration + public val lingerDuration: Duration @GenerateDataFunctions public class AlwaysVisible( @@ -70,8 +69,7 @@ public sealed interface ScrollbarVisibility { public override val thumbColorAnimationDuration: Duration, ) : ScrollbarVisibility { public override val trackThicknessExpanded: Dp = trackThickness - public override val appearAnimationDuration: Duration = 0.milliseconds - public override val disappearAnimationDuration: Duration = 0.milliseconds + public override val trackColorAnimationDuration: Duration = 0.milliseconds public override val expandAnimationDuration: Duration = 0.milliseconds public override val lingerDuration: Duration = 0.milliseconds @@ -84,12 +82,11 @@ public sealed interface ScrollbarVisibility { public override val trackThicknessExpanded: Dp, public override val trackPadding: PaddingValues, public override val trackPaddingWithBorder: PaddingValues, - public override val appearAnimationDuration: Duration, - public override val disappearAnimationDuration: Duration, + public override val trackColorAnimationDuration: Duration, public override val expandAnimationDuration: Duration, + public override val thumbColorAnimationDuration: Duration, public override val lingerDuration: Duration, ) : ScrollbarVisibility { - public override val thumbColorAnimationDuration: Duration = 0.milliseconds public companion object } From beb279749c67e1ddda2d892881efed9fa4e51b0d Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Thu, 22 Aug 2024 10:01:28 +0200 Subject: [PATCH 11/14] Fix Scrollbars [part 13/n] * Fix InputField ignoring provided scrollbar style * Remove useless KDocs from TextArea/TextField * Clean up code * Mark non-state-based TextArea/TextField as scheduled for removal --- ide-laf-bridge/api/ide-laf-bridge.api | 1 + .../jewel/ui/component/InputField.kt | 7 ++- .../jetbrains/jewel/ui/component/TextArea.kt | 47 +++++-------------- .../jetbrains/jewel/ui/component/TextField.kt | 25 ++++------ 4 files changed, 30 insertions(+), 50 deletions(-) diff --git a/ide-laf-bridge/api/ide-laf-bridge.api b/ide-laf-bridge/api/ide-laf-bridge.api index f9eb1c60e..8786180b3 100644 --- a/ide-laf-bridge/api/ide-laf-bridge.api +++ b/ide-laf-bridge/api/ide-laf-bridge.api @@ -25,6 +25,7 @@ public final class org/jetbrains/jewel/bridge/BridgeUtilsKt { public static final fun retrieveArcAsCornerSize (Ljava/lang/String;)Landroidx/compose/foundation/shape/CornerSize; public static final fun retrieveArcAsCornerSizeOrDefault (Ljava/lang/String;Landroidx/compose/foundation/shape/CornerSize;)Landroidx/compose/foundation/shape/CornerSize; public static final fun retrieveArcAsCornerSizeWithFallbacks ([Ljava/lang/String;)Landroidx/compose/foundation/shape/CornerSize; + public static final fun retrieveColor-0YGnOg8 (Ljava/lang/String;ZJJ)J public static final fun retrieveColor-4WTKRHQ (Ljava/lang/String;J)J public static final fun retrieveColorOrNull (Ljava/lang/String;)Landroidx/compose/ui/graphics/Color; public static final fun retrieveColorOrUnspecified (Ljava/lang/String;)J diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt index 4f686bd1d..98355021e 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import org.jetbrains.jewel.foundation.Stroke import org.jetbrains.jewel.foundation.modifier.border import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Active @@ -46,6 +47,7 @@ import org.jetbrains.jewel.ui.focusOutline import org.jetbrains.jewel.ui.outline import org.jetbrains.jewel.ui.util.thenIf +@Suppress("DuplicatedCode") // The dupe is deprecated and is scheduled for removal @Composable internal fun InputField( state: TextFieldState, @@ -140,16 +142,19 @@ internal fun InputField( scrollState = scrollState, ) - if (showScrollbar) { + if (showScrollbar && scrollbarStyle != null) { VerticalScrollbar( scrollState = scrollState, modifier = Modifier.align(Alignment.CenterEnd), interactionSource = interactionSource, + style = scrollbarStyle ) } } } +@ScheduledForRemoval(inVersion = "Before 1.0") +@Suppress("DuplicatedCode") // This is deprecated and will be removed before 1.0 @Deprecated("Please use InputField(state) instead. If you want to observe text changes, use snapshotFlow { state.text }") @Composable internal fun InputField( diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt index 4553de590..3acb28fc9 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.offset +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.LocalContentColor import org.jetbrains.jewel.foundation.theme.LocalTextStyle @@ -35,10 +36,6 @@ import org.jetbrains.jewel.ui.component.styling.TextAreaStyle import org.jetbrains.jewel.ui.theme.scrollbarStyle import org.jetbrains.jewel.ui.theme.textAreaStyle -/** - * @param placeholder the optional placeholder to be displayed over the - * component when the [value] is empty. - */ @Composable public fun TextArea( state: TextFieldState, @@ -46,7 +43,7 @@ public fun TextArea( enabled: Boolean = true, readOnly: Boolean = false, outline: Outline = Outline.None, - placeholder: @Composable() (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, undecorated: Boolean = false, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, maxLines: Int = Int.MAX_VALUE, @@ -55,7 +52,7 @@ public fun TextArea( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, decorationBoxModifier: Modifier = Modifier, showScrollbar: Boolean = true, - scrollbarStyle: ScrollbarStyle = JewelTheme.scrollbarStyle + scrollbarStyle: ScrollbarStyle = JewelTheme.scrollbarStyle, ) { val minSize = style.metrics.minSize InputField( @@ -86,10 +83,7 @@ public fun TextArea( ) } -/** - * @param placeholder the optional placeholder to be displayed over the - * component when the [value] is empty. - */ +@ScheduledForRemoval(inVersion = "Before 1.0") @Deprecated("Please use TextArea(state) instead. If you want to observe text changes, use snapshotFlow { state.text }") @Composable public fun TextArea( @@ -115,6 +109,7 @@ public fun TextArea( val textFieldValue = textFieldValueState.copy(text = value) var lastTextValue by remember(value) { mutableStateOf(value) } + @Suppress("DEPRECATION") TextArea( value = textFieldValue, onValueChange = { newTextFieldValueState -> @@ -145,10 +140,7 @@ public fun TextArea( ) } -/** - * @param placeholder the optional placeholder to be displayed over the - * component when the [value] is empty. - */ +@ScheduledForRemoval(inVersion = "Before 1.0") @Deprecated("Please use TextArea(state) instead. If you want to observe text changes, use snapshotFlow { state.text }") @Composable public fun TextArea( @@ -171,6 +163,7 @@ public fun TextArea( decorationBoxModifier: Modifier = Modifier, ) { val minSize = style.metrics.minSize + @Suppress("DEPRECATION") InputField( value = value, onValueChange = onValueChange, @@ -212,10 +205,7 @@ private fun TextAreaDecorationBox( Layout( content = { if (placeholder != null) { - Box( - modifier = Modifier.layoutId(PLACEHOLDER_ID), - contentAlignment = Alignment.TopStart, - ) { + Box(modifier = Modifier.layoutId(PLACEHOLDER_ID), contentAlignment = Alignment.TopStart) { CompositionLocalProvider( LocalTextStyle provides textStyle.copy(color = placeholderTextColor), LocalContentColor provides placeholderTextColor, @@ -238,25 +228,16 @@ private fun TextAreaDecorationBox( val rightPadding = contentPadding.calculateRightPadding(layoutDirection) val horizontalPadding = (leftPadding + rightPadding).roundToPx() val verticalPadding = - (contentPadding.calculateTopPadding() + contentPadding.calculateBottomPadding()) - .roundToPx() + (contentPadding.calculateTopPadding() + contentPadding.calculateBottomPadding()).roundToPx() val textAreaConstraints = - incomingConstraints - .offset(horizontal = -horizontalPadding, vertical = -verticalPadding) - .copy(minHeight = 0) + incomingConstraints.offset(horizontal = -horizontalPadding, vertical = -verticalPadding).copy(minHeight = 0) - val textAreaPlaceable = - measurables - .single { it.layoutId == TEXT_AREA_ID } - .measure(textAreaConstraints) + val textAreaPlaceable = measurables.single { it.layoutId == TEXT_AREA_ID }.measure(textAreaConstraints) // Measure placeholder val placeholderConstraints = textAreaConstraints.copy(minWidth = 0, minHeight = 0) - val placeholderPlaceable = - measurables - .find { it.layoutId == PLACEHOLDER_ID } - ?.measure(placeholderConstraints) + val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints) val width = calculateWidth(textAreaPlaceable, placeholderPlaceable, incomingConstraints) val height = calculateHeight(textAreaPlaceable, placeholderPlaceable, verticalPadding, incomingConstraints) @@ -278,9 +259,7 @@ private fun calculateWidth( textFieldPlaceable: Placeable, placeholderPlaceable: Placeable?, incomingConstraints: Constraints, -): Int = - maxOf(textFieldPlaceable.width, placeholderPlaceable?.width ?: 0) - .coerceAtLeast(incomingConstraints.minWidth) +): Int = maxOf(textFieldPlaceable.width, placeholderPlaceable?.width ?: 0).coerceAtLeast(incomingConstraints.minWidth) private fun calculateHeight( textFieldPlaceable: Placeable, diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt index 01d400135..5cb5ee061 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.offset +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.LocalContentColor import org.jetbrains.jewel.foundation.theme.LocalTextStyle @@ -33,10 +34,7 @@ import org.jetbrains.jewel.ui.component.styling.TextFieldStyle import org.jetbrains.jewel.ui.theme.textFieldStyle import kotlin.math.max -/** - * @param placeholder the optional placeholder to be displayed over the - * component when the [value] is empty. - */ +@Suppress("DuplicatedCode") // The dupe is scheduled for removal @Composable public fun TextField( state: TextFieldState, @@ -44,9 +42,9 @@ public fun TextField( enabled: Boolean = true, readOnly: Boolean = false, outline: Outline = Outline.None, - placeholder: @Composable() (() -> Unit)? = null, - leadingIcon: @Composable() (() -> Unit)? = null, - trailingIcon: @Composable() (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, undecorated: Boolean = false, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, style: TextFieldStyle = JewelTheme.textFieldStyle, @@ -86,10 +84,7 @@ public fun TextField( ) } -/** - * @param placeholder the optional placeholder to be displayed over the - * component when the [value] is empty. - */ +@ScheduledForRemoval(inVersion = "Before 1.0") @Deprecated("Please use TextField(state) instead. If you want to observe text changes, use snapshotFlow { state.text }") @Composable public fun TextField( @@ -114,6 +109,7 @@ public fun TextField( val textFieldValue = textFieldValueState.copy(text = value) var lastTextValue by remember(value) { mutableStateOf(value) } + @Suppress("DEPRECATION") TextField( value = textFieldValue, onValueChange = { newTextFieldValueState -> @@ -143,10 +139,8 @@ public fun TextField( ) } -/** - * @param placeholder the optional placeholder to be displayed over the - * component when the [value] is empty. - */ +@Suppress("DuplicatedCode") // This is scheduled for removal +@ScheduledForRemoval(inVersion = "Before 1.0") @Deprecated("Please use TextField(state) instead. If you want to observe text changes, use snapshotFlow { state.text }") @Composable public fun TextField( @@ -168,6 +162,7 @@ public fun TextField( textStyle: TextStyle = JewelTheme.defaultTextStyle, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { + @Suppress("DEPRECATION") InputField( value = value, onValueChange = onValueChange, From e66de7b1c6ef340ef9986e30eac05b6774ece73e Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Thu, 22 Aug 2024 20:16:42 +0200 Subject: [PATCH 12/14] Fix Scrollbars [part 14/n] * Fix native crash when TextField/TextArea don't have a fixed width * Redo the entire BTF2-based InputField implementation, better * Expose all the new parameters that BTF2-based InputField has to the users of TextField and TextArea * Fix TextArea decoration and scrollbars --- .../jewel/bridge/theme/IntUiBridge.kt | 2 +- ui/api/ui.api | 4 +- .../jewel/ui/component/InputField.kt | 159 +++++++----------- .../jewel/ui/component/ScrollableContainer.kt | 29 ++++ .../jetbrains/jewel/ui/component/TextArea.kt | 122 +++++++++++--- .../jetbrains/jewel/ui/component/TextField.kt | 121 ++++++------- 6 files changed, 243 insertions(+), 194 deletions(-) diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt index da3841968..e60e31764 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt @@ -968,7 +968,7 @@ private fun readTextFieldStyle(): TextFieldStyle { TextFieldMetrics( cornerSize = CornerSize(DarculaUIUtil.COMPONENT_ARC.dp / 2), contentPadding = PaddingValues(horizontal = 9.dp, vertical = 2.dp), - minSize = DpSize(minimumSize.width, minimumSize.height), + minSize = DpSize(144.dp, minimumSize.height), borderWidth = DarculaUIUtil.LW.dp, ), ) diff --git a/ui/api/ui.api b/ui/api/ui.api index e2fa6a3f9..e915be729 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -880,13 +880,13 @@ public final class org/jetbrains/jewel/ui/component/TabsKt { } public final class org/jetbrains/jewel/ui/component/TextAreaKt { - public static final fun TextArea (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/foundation/text/KeyboardOptions;ILorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/ui/Modifier;ZLorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;III)V + public static final fun TextArea (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLandroidx/compose/foundation/text/input/InputTransformation;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/input/KeyboardActionHandler;Landroidx/compose/foundation/text/input/TextFieldLineLimits;Lkotlin/jvm/functions/Function2;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Lorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/text/input/OutputTransformation;ZLandroidx/compose/foundation/ScrollState;Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle;Landroidx/compose/runtime/Composer;III)V public static final fun TextArea (Landroidx/compose/ui/text/input/TextFieldValue;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLkotlin/jvm/functions/Function2;ZLorg/jetbrains/jewel/ui/Outline;Landroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;III)V public static final fun TextArea (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;ILkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextAreaStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;III)V } public final class org/jetbrains/jewel/ui/component/TextFieldKt { - public static final fun TextField (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/foundation/text/KeyboardOptions;Lorg/jetbrains/jewel/ui/component/styling/TextFieldStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;III)V + public static final fun TextField (Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZZLandroidx/compose/foundation/text/input/InputTransformation;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/input/KeyboardActionHandler;Lkotlin/jvm/functions/Function2;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/TextFieldStyle;Lorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/foundation/text/input/OutputTransformation;ZLandroidx/compose/runtime/Composer;III)V public static final fun TextField (Landroidx/compose/ui/text/input/TextFieldValue;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextFieldStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;III)V public static final fun TextField (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/Outline;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ZLandroidx/compose/ui/text/input/VisualTransformation;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/foundation/text/KeyboardActions;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/jewel/ui/component/styling/TextFieldStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;III)V } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt index 98355021e..0f873ef16 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt @@ -1,28 +1,26 @@ package org.jetbrains.jewel.ui.component +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.interaction.FocusInteraction import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine -import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.KeyboardActionHandler +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.TextFieldDecorator +import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.isSpecified @@ -30,7 +28,7 @@ import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.Density import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import org.jetbrains.jewel.foundation.Stroke import org.jetbrains.jewel.foundation.modifier.border @@ -42,57 +40,53 @@ import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Pressed import org.jetbrains.jewel.foundation.state.FocusableComponentState import org.jetbrains.jewel.ui.Outline import org.jetbrains.jewel.ui.component.styling.InputFieldStyle -import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle import org.jetbrains.jewel.ui.focusOutline import org.jetbrains.jewel.ui.outline import org.jetbrains.jewel.ui.util.thenIf -@Suppress("DuplicatedCode") // The dupe is deprecated and is scheduled for removal @Composable internal fun InputField( state: TextFieldState, + modifier: Modifier, enabled: Boolean, readOnly: Boolean, - outline: Outline, - undecorated: Boolean, + inputTransformation: InputTransformation?, + textStyle: TextStyle, keyboardOptions: KeyboardOptions, - singleLine: Boolean, - maxLines: Int, + onKeyboardAction: KeyboardActionHandler?, + lineLimits: TextFieldLineLimits, + onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)?, interactionSource: MutableInteractionSource, style: InputFieldStyle, - textStyle: TextStyle, - scrollbarStyle: ScrollbarStyle?, - showScrollbar: Boolean, - modifier: Modifier, - decorationBox: @Composable (innerTextField: @Composable () -> Unit, state: InputFieldState) -> Unit, + outline: Outline, + outputTransformation: OutputTransformation?, + decorator: TextFieldDecorator?, + scrollState: ScrollState, ) { - var inputState by remember(interactionSource) { - mutableStateOf(InputFieldState.of(enabled = enabled)) - } - remember(enabled) { inputState = inputState.copy(enabled = enabled) } + var inputFieldState by remember(interactionSource) { mutableStateOf(InputFieldState.of(enabled = enabled)) } + remember(enabled) { inputFieldState = inputFieldState.copy(enabled = enabled) } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { - is FocusInteraction.Focus -> inputState = inputState.copy(focused = true) - is FocusInteraction.Unfocus -> inputState = inputState.copy(focused = false) + is FocusInteraction.Focus -> inputFieldState = inputFieldState.copy(focused = true) + is FocusInteraction.Unfocus -> inputFieldState = inputFieldState.copy(focused = false) } } } val colors = style.colors - val backgroundColor by colors.backgroundFor(inputState) + val backgroundColor by colors.backgroundFor(inputFieldState) val shape = RoundedCornerShape(style.metrics.cornerSize) + val isUndecorated = decorator == null val backgroundModifier = - Modifier.thenIf(!undecorated && backgroundColor.isSpecified) { - background(backgroundColor, shape) - } + Modifier.thenIf(!isUndecorated && backgroundColor.isSpecified) { background(backgroundColor, shape) } - val borderColor by style.colors.borderFor(inputState) + val borderColor by style.colors.borderFor(inputFieldState) val hasNoOutline = outline == Outline.None val borderModifier = - Modifier.thenIf(!undecorated && borderColor.isSpecified && hasNoOutline) { + Modifier.thenIf(!isUndecorated && borderColor.isSpecified && hasNoOutline) { border( alignment = Stroke.Alignment.Center, width = style.metrics.borderWidth, @@ -101,68 +95,44 @@ internal fun InputField( ) } - val contentColor by colors.contentFor(inputState) + val contentColor by colors.contentFor(inputFieldState) val mergedTextStyle = textStyle.copy(color = contentColor) - val caretColor by colors.caretFor(inputState) - - val lineLimits = - when { - singleLine -> SingleLine - else -> MultiLine(maxLines) - } + val caretColor by colors.caretFor(inputFieldState) - val scrollState = rememberScrollState() - val canScroll by remember { - derivedStateOf { - scrollState.canScrollBackward || scrollState.canScrollForward - } - } - - Box( - modifier = modifier - .then(backgroundModifier) - .then(borderModifier) - .thenIf(!undecorated && hasNoOutline) { focusOutline(inputState, shape) } - .outline(inputState, outline, shape, Stroke.Alignment.Center), - ) { - BasicTextField( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.CenterStart) - .thenIf(canScroll && showScrollbar) { padding(end = 12.dp) }, - state = state, - enabled = enabled, - readOnly = readOnly, - textStyle = mergedTextStyle, - cursorBrush = SolidColor(caretColor), - keyboardOptions = keyboardOptions, - lineLimits = lineLimits, - interactionSource = interactionSource, - decorator = { innerTextField: @Composable () -> Unit -> decorationBox(innerTextField, inputState) }, - scrollState = scrollState, - ) - - if (showScrollbar && scrollbarStyle != null) { - VerticalScrollbar( - scrollState = scrollState, - modifier = Modifier.align(Alignment.CenterEnd), - interactionSource = interactionSource, - style = scrollbarStyle - ) - } - } + BasicTextField( + state = state, + modifier = + modifier + .then(backgroundModifier) + .then(borderModifier) + .thenIf(!isUndecorated && hasNoOutline) { focusOutline(inputFieldState, shape) } + .outline(inputFieldState, outline, shape, Stroke.Alignment.Center), + enabled = enabled, + readOnly = readOnly, + inputTransformation = inputTransformation, + textStyle = mergedTextStyle, + keyboardOptions = keyboardOptions, + onKeyboardAction = onKeyboardAction, + lineLimits = lineLimits, + onTextLayout = onTextLayout, + interactionSource = interactionSource, + cursorBrush = SolidColor(caretColor), + outputTransformation = outputTransformation, + decorator = decorator, + scrollState = scrollState, + ) } -@ScheduledForRemoval(inVersion = "Before 1.0") -@Suppress("DuplicatedCode") // This is deprecated and will be removed before 1.0 -@Deprecated("Please use InputField(state) instead. If you want to observe text changes, use snapshotFlow { state.text }") +@Deprecated("NO") +@ScheduledForRemoval @Composable internal fun InputField( value: TextFieldValue, onValueChange: (TextFieldValue) -> Unit, + modifier: Modifier, enabled: Boolean, - outline: Outline, readOnly: Boolean, + outline: Outline, undecorated: Boolean, visualTransformation: VisualTransformation, keyboardOptions: KeyboardOptions, @@ -173,7 +143,6 @@ internal fun InputField( interactionSource: MutableInteractionSource, style: InputFieldStyle, textStyle: TextStyle, - modifier: Modifier, decorationBox: @Composable (innerTextField: @Composable () -> Unit, state: InputFieldState) -> Unit, ) { var inputState by remember(interactionSource) { mutableStateOf(InputFieldState.of(enabled = enabled)) } @@ -193,9 +162,7 @@ internal fun InputField( val shape = RoundedCornerShape(style.metrics.cornerSize) val backgroundModifier = - Modifier.thenIf(!undecorated && backgroundColor.isSpecified) { - background(backgroundColor, shape) - } + Modifier.thenIf(!undecorated && backgroundColor.isSpecified) { background(backgroundColor, shape) } val borderColor by style.colors.borderFor(inputState) val hasNoOutline = outline == Outline.None @@ -233,9 +200,8 @@ internal fun InputField( interactionSource = interactionSource, singleLine = singleLine, maxLines = maxLines, - decorationBox = @Composable { innerTextField: @Composable () -> Unit -> - decorationBox(innerTextField, inputState) - }, + decorationBox = + @Composable { innerTextField: @Composable () -> Unit -> decorationBox(innerTextField, inputState) }, ) } @@ -263,14 +229,7 @@ public value class InputFieldState(public val state: ULong) : FocusableComponent pressed: Boolean = isPressed, hovered: Boolean = isHovered, active: Boolean = isActive, - ): InputFieldState = - of( - enabled = enabled, - focused = focused, - pressed = pressed, - hovered = hovered, - active = active, - ) + ): InputFieldState = of(enabled = enabled, focused = focused, pressed = pressed, hovered = hovered, active = active) override fun toString(): String = "${javaClass.simpleName}(isEnabled=$isEnabled, isFocused=$isFocused, " + @@ -290,7 +249,7 @@ public value class InputFieldState(public val state: ULong) : FocusableComponent (if (focused) Focused else 0UL) or (if (hovered) Hovered else 0UL) or (if (pressed) Pressed else 0UL) or - (if (active) Active else 0UL), + (if (active) Active else 0UL) ) } } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt index df2b3fdb7..c3a97c96e 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -18,6 +18,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable @@ -64,6 +66,33 @@ public fun VerticallyScrollableContainer( } } +@Composable +internal fun TextAreaScrollableContainer( + scrollState: ScrollState, + style: ScrollbarStyle, + contentModifier: Modifier, + content: @Composable () -> Unit, +) { + var keepVisible by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + + ScrollableContainerImpl( + verticalScrollbar = { + VerticalScrollbar( + scrollState, + style = style, + modifier = Modifier.pointerHoverIcon(PointerIcon.Default), + keepVisible = keepVisible, + ) + }, + horizontalScrollbar = null, + modifier = Modifier.withKeepVisible(style.scrollbarVisibility.lingerDuration, scope) { keepVisible = it }, + scrollbarStyle = style, + ) { + Box(contentModifier.layoutId(ID_CONTENT)) { content() } + } +} + @Composable public fun VerticallyScrollableContainer( scrollState: LazyListState, diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt index 3acb28fc9..6a50efdf3 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt @@ -1,12 +1,21 @@ package org.jetbrains.jewel.ui.component +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.KeyboardActionHandler +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.TextFieldDecorator +import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -20,11 +29,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import org.jetbrains.jewel.foundation.theme.JewelTheme @@ -42,44 +55,103 @@ public fun TextArea( modifier: Modifier = Modifier, enabled: Boolean = true, readOnly: Boolean = false, - outline: Outline = Outline.None, - placeholder: @Composable (() -> Unit)? = null, - undecorated: Boolean = false, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, - maxLines: Int = Int.MAX_VALUE, - style: TextAreaStyle = JewelTheme.textAreaStyle, + inputTransformation: InputTransformation? = null, textStyle: TextStyle = JewelTheme.defaultTextStyle, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + onKeyboardAction: KeyboardActionHandler? = null, + lineLimits: TextFieldLineLimits = TextFieldLineLimits.MultiLine(), + onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + style: TextAreaStyle = JewelTheme.textAreaStyle, + outline: Outline = Outline.None, + placeholder: @Composable (() -> Unit)? = null, decorationBoxModifier: Modifier = Modifier, - showScrollbar: Boolean = true, - scrollbarStyle: ScrollbarStyle = JewelTheme.scrollbarStyle, + outputTransformation: OutputTransformation? = null, + undecorated: Boolean = false, + scrollState: ScrollState = rememberScrollState(), + scrollbarStyle: ScrollbarStyle? = JewelTheme.scrollbarStyle, ) { val minSize = style.metrics.minSize InputField( state = state, + modifier = modifier, enabled = enabled, readOnly = readOnly, - outline = outline, - undecorated = undecorated, + inputTransformation = inputTransformation, + textStyle = textStyle, keyboardOptions = keyboardOptions, - singleLine = false, - maxLines = maxLines, + onKeyboardAction = onKeyboardAction, + lineLimits = lineLimits, + onTextLayout = onTextLayout, interactionSource = interactionSource, style = style, - textStyle = textStyle, - scrollbarStyle = scrollbarStyle, - showScrollbar = showScrollbar, - modifier = modifier.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height), - decorationBox = { innerTextField, _ -> - TextAreaDecorationBox( - innerTextField = innerTextField, - contentPadding = style.metrics.contentPadding, - placeholderTextColor = style.colors.placeholder, - placeholder = if (state.text.isEmpty()) placeholder else null, - textStyle = textStyle, - modifier = decorationBoxModifier, - ) + outline = outline, + outputTransformation = outputTransformation, + decorator = + if (undecorated) { + null + } else { + TextAreaDecorator( + style, + state, + placeholder, + textStyle, + decorationBoxModifier, + minSize, + scrollbarStyle, + scrollState, + ) + }, + scrollState = scrollState, + ) +} + +@Composable +private fun TextAreaDecorator( + style: TextAreaStyle, + state: TextFieldState, + placeholder: @Composable (() -> Unit)?, + textStyle: TextStyle, + decorationBoxModifier: Modifier, + minSize: DpSize, + scrollbarStyle: ScrollbarStyle?, + scrollState: ScrollState, +) = TextFieldDecorator { innerTextField -> + val (contentPadding, innerEndPadding) = + if (scrollbarStyle != null) { + with(style.metrics.contentPadding) { + val direction = LocalLayoutDirection.current + val paddingValues = + PaddingValues( + calculateStartPadding(direction), + calculateTopPadding(), + 0.dp, + calculateBottomPadding(), + ) + paddingValues to calculateEndPadding(direction) + } + } else { + style.metrics.contentPadding to 0.dp + } + + TextAreaDecorationBox( + innerTextField = { + if (scrollbarStyle != null) { + TextAreaScrollableContainer( + scrollState = scrollState, + style = scrollbarStyle, + contentModifier = Modifier.padding(end = innerEndPadding), + content = innerTextField, + ) + } else { + innerTextField() + } }, + contentPadding = contentPadding, + placeholderTextColor = style.colors.placeholder, + placeholder = if (state.text.isEmpty()) placeholder else null, + textStyle = textStyle, + modifier = decorationBoxModifier.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height), ) } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt index 5cb5ee061..d7b417eb9 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt @@ -4,8 +4,14 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.KeyboardActionHandler +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.TextFieldDecorator +import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -24,7 +30,9 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.offset +import kotlin.math.max import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.LocalContentColor @@ -32,7 +40,6 @@ import org.jetbrains.jewel.foundation.theme.LocalTextStyle import org.jetbrains.jewel.ui.Outline import org.jetbrains.jewel.ui.component.styling.TextFieldStyle import org.jetbrains.jewel.ui.theme.textFieldStyle -import kotlin.math.max @Suppress("DuplicatedCode") // The dupe is scheduled for removal @Composable @@ -41,46 +48,56 @@ public fun TextField( modifier: Modifier = Modifier, enabled: Boolean = true, readOnly: Boolean = false, + inputTransformation: InputTransformation? = null, + textStyle: TextStyle = JewelTheme.defaultTextStyle, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + onKeyboardAction: KeyboardActionHandler? = null, + onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + style: TextFieldStyle = JewelTheme.textFieldStyle, outline: Outline = Outline.None, placeholder: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, + outputTransformation: OutputTransformation? = null, undecorated: Boolean = false, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, - style: TextFieldStyle = JewelTheme.textFieldStyle, - textStyle: TextStyle = JewelTheme.defaultTextStyle, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { InputField( state = state, + modifier = modifier, enabled = enabled, readOnly = readOnly, - outline = outline, - undecorated = undecorated, + inputTransformation = inputTransformation, + textStyle = textStyle, keyboardOptions = keyboardOptions, - singleLine = true, - maxLines = 1, + onKeyboardAction = onKeyboardAction, + lineLimits = TextFieldLineLimits.SingleLine, + onTextLayout = onTextLayout, interactionSource = interactionSource, style = style, - textStyle = textStyle, - showScrollbar = false, - scrollbarStyle = null, - modifier = modifier, - decorationBox = { innerTextField, _ -> - val minSize = style.metrics.minSize + outline = outline, + outputTransformation = outputTransformation, + decorator = + if (!undecorated) { + TextFieldDecorator { innerTextField -> + val minSize = style.metrics.minSize - TextFieldDecorationBox( - modifier = Modifier - .defaultMinSize(minWidth = minSize.width, minHeight = minSize.height) - .padding(style.metrics.contentPadding), - innerTextField = innerTextField, - textStyle = textStyle, - placeholderTextColor = style.colors.placeholder, - placeholder = if (state.text.isEmpty()) placeholder else null, - leadingIcon = leadingIcon, - trailingIcon = trailingIcon, - ) - }, + TextFieldDecorationBox( + modifier = + Modifier.defaultMinSize(minWidth = minSize.width, minHeight = minSize.height) + .padding(style.metrics.contentPadding), + innerTextField = innerTextField, + textStyle = textStyle, + placeholderTextColor = style.colors.placeholder, + placeholder = if (state.text.isEmpty()) placeholder else null, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + ) + } + } else { + null + }, + scrollState = rememberScrollState(), ) } @@ -211,14 +228,10 @@ private fun TextFieldDecorationBox( modifier = modifier, content = { if (leadingIcon != null) { - Box(modifier = Modifier.layoutId(LEADING_ID), contentAlignment = Alignment.Center) { - leadingIcon() - } + Box(modifier = Modifier.layoutId(LEADING_ID), contentAlignment = Alignment.Center) { leadingIcon() } } if (trailingIcon != null) { - Box(modifier = Modifier.layoutId(TRAILING_ID), contentAlignment = Alignment.Center) { - trailingIcon() - } + Box(modifier = Modifier.layoutId(TRAILING_ID), contentAlignment = Alignment.Center) { trailingIcon() } } if (placeholder != null) { Box(modifier = Modifier.layoutId(PLACEHOLDER_ID), contentAlignment = Alignment.Center) { @@ -230,9 +243,7 @@ private fun TextFieldDecorationBox( } } - Box(modifier = Modifier.layoutId(TEXT_FIELD_ID), propagateMinConstraints = true) { - innerTextField() - } + Box(modifier = Modifier.layoutId(TEXT_FIELD_ID), propagateMinConstraints = true) { innerTextField() } }, ) { measurables, incomingConstraints -> // used to calculate the constraints for measuring elements that will be placed in a row @@ -240,40 +251,22 @@ private fun TextFieldDecorationBox( val iconConstraints = incomingConstraints.copy(minWidth = 0, minHeight = 0) // measure trailing icon - val trailingPlaceable = - measurables.find { it.layoutId == TRAILING_ID }?.measure(iconConstraints) + val trailingPlaceable = measurables.find { it.layoutId == TRAILING_ID }?.measure(iconConstraints) val leadingPlaceable = measurables.find { it.layoutId == LEADING_ID }?.measure(iconConstraints) occupiedSpaceHorizontally += trailingPlaceable?.width ?: 0 occupiedSpaceHorizontally += leadingPlaceable?.width ?: 0 val textFieldConstraints = - incomingConstraints.offset(horizontal = -occupiedSpaceHorizontally) - .copy(minHeight = 0) - val textFieldPlaceable = - measurables.single { it.layoutId == TEXT_FIELD_ID } - .measure(textFieldConstraints) + incomingConstraints.offset(horizontal = -occupiedSpaceHorizontally).copy(minHeight = 0) + val textFieldPlaceable = measurables.single { it.layoutId == TEXT_FIELD_ID }.measure(textFieldConstraints) // measure placeholder val placeholderConstraints = textFieldConstraints.copy(minWidth = 0) - val placeholderPlaceable = - measurables.find { it.layoutId == PLACEHOLDER_ID } - ?.measure(placeholderConstraints) + val placeholderPlaceable = measurables.find { it.layoutId == PLACEHOLDER_ID }?.measure(placeholderConstraints) - val width = - calculateWidth( - leadingPlaceable, - trailingPlaceable, - textFieldPlaceable, - incomingConstraints, - ) - val height = - calculateHeight( - leadingPlaceable, - trailingPlaceable, - textFieldPlaceable, - incomingConstraints, - ) + val width = calculateWidth(leadingPlaceable, trailingPlaceable, textFieldPlaceable, incomingConstraints) + val height = calculateHeight(leadingPlaceable, trailingPlaceable, textFieldPlaceable, incomingConstraints) layout(width, height) { place( @@ -295,8 +288,7 @@ private fun calculateWidth( constraints: Constraints, ): Int { val middleSection = textFieldPlaceable.width - val wrappedWidth = - middleSection + (trailingPlaceable?.width ?: 0) + (leadingPlaceable?.width ?: 0) + val wrappedWidth = middleSection + (trailingPlaceable?.width ?: 0) + (leadingPlaceable?.width ?: 0) return max(wrappedWidth, constraints.minWidth) } @@ -322,10 +314,7 @@ private fun Placeable.PlacementScope.place( placeholderPlaceable: Placeable?, ) { // placed center vertically and to the end edge horizontally - leadingPlaceable?.placeRelative( - 0, - Alignment.CenterVertically.align(leadingPlaceable.height, height), - ) + leadingPlaceable?.placeRelative(0, Alignment.CenterVertically.align(leadingPlaceable.height, height)) trailingPlaceable?.placeRelative( width - trailingPlaceable.width, Alignment.CenterVertically.align(trailingPlaceable.height, height), From 31d1b986ed045f85e07e9798a7c56d73b13fcaa5 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Fri, 23 Aug 2024 16:22:41 +0200 Subject: [PATCH 13/14] Fix Scrollbars [part 15/n] * Fix scrollbars on Windows and Linux * Fix TextArea end padding to account for scrollbar --- ide-laf-bridge/api/ide-laf-bridge.api | 8 +- .../jewel/bridge/theme/ScrollbarBridge.kt | 29 ++- .../api/int-ui-standalone.api | 23 +-- .../styling/IntUiScrollbarStyling.kt | 171 +++++++++--------- .../styling/IntUiTabStripScrollbarStyling.kt | 10 +- .../standalone/view/component/Scrollbars.kt | 8 +- ui/api/ui.api | 4 +- .../jewel/ui/component/ScrollableContainer.kt | 47 +++-- .../jetbrains/jewel/ui/component/Scrollbar.kt | 30 +-- .../jetbrains/jewel/ui/component/TextArea.kt | 7 +- .../ui/component/styling/ScrollbarStyling.kt | 4 +- 11 files changed, 196 insertions(+), 145 deletions(-) diff --git a/ide-laf-bridge/api/ide-laf-bridge.api b/ide-laf-bridge/api/ide-laf-bridge.api index 8786180b3..f86286c20 100644 --- a/ide-laf-bridge/api/ide-laf-bridge.api +++ b/ide-laf-bridge/api/ide-laf-bridge.api @@ -144,12 +144,12 @@ public final class org/jetbrains/jewel/bridge/theme/ScrollbarBridgeKt { public static final fun default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; public static final fun macOs-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; public static synthetic fun macOs-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; - public static final fun macOs-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; - public static synthetic fun macOs-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun macOs-fYp4AQw (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun macOs-fYp4AQw$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; public static final fun windowsAndLinux-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; public static synthetic fun windowsAndLinux-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; - public static final fun windowsAndLinux-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; - public static synthetic fun windowsAndLinux-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun windowsAndLinux-tYhzLtE (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun windowsAndLinux-tYhzLtE$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; } public final class org/jetbrains/jewel/bridge/theme/SwingBridgeThemeKt { diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt index 8f8bb3065..870b3eac8 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/ScrollbarBridge.kt @@ -288,10 +288,31 @@ public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( trackThickness: Dp = 14.dp, trackPadding: PaddingValues = PaddingValues(2.dp), thumbColorAnimationDuration: Duration = 330.milliseconds, -): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) + scrollbarBackgroundColorLight: Color = retrieveColor("ScrollBar.background", Color(0xFFF5F5F5)), + scrollbarBackgroundColorDark: Color = retrieveColor("ScrollBar.background", Color(0xFF3F4244)), +): ScrollbarVisibility.AlwaysVisible = + ScrollbarVisibility.AlwaysVisible( + trackThickness = trackThickness, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPadding, + thumbColorAnimationDuration = thumbColorAnimationDuration, + trackColorAnimationDuration = 0.milliseconds, + scrollbarBackgroundColorLight = scrollbarBackgroundColorLight, + scrollbarBackgroundColorDark = scrollbarBackgroundColorDark, + ) public fun ScrollbarVisibility.AlwaysVisible.Companion.windowsAndLinux( - trackThickness: Dp = 18.dp, - trackPadding: PaddingValues = PaddingValues(), + trackThickness: Dp = 10.dp, + trackPadding: PaddingValues = PaddingValues(0.5.dp), thumbColorAnimationDuration: Duration = 330.milliseconds, -): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) + trackColorAnimationDuration: Duration = thumbColorAnimationDuration, +): ScrollbarVisibility.AlwaysVisible = + ScrollbarVisibility.AlwaysVisible( + trackThickness, + trackPadding, + trackPadding, + thumbColorAnimationDuration, + trackColorAnimationDuration, + Color.Unspecified, + Color.Unspecified, + ) diff --git a/int-ui/int-ui-standalone/api/int-ui-standalone.api b/int-ui/int-ui-standalone/api/int-ui-standalone.api index 20fb3c639..4f246279d 100644 --- a/int-ui/int-ui-standalone/api/int-ui-standalone.api +++ b/int-ui/int-ui-standalone/api/int-ui-standalone.api @@ -255,12 +255,11 @@ 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 (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; public static final fun default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; - public static final fun default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static final fun default-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; + public static synthetic fun default-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; public static final fun light (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle$Companion;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; - public static final fun macOs-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; - public static synthetic fun macOs-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; - public static final fun macOs-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; - public static synthetic fun macOs-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun macOs-fYp4AQw (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun macOs-fYp4AQw$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; public static final fun macOs-wH6b6FI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;F)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; public static synthetic fun macOs-wH6b6FI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FILjava/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; @@ -271,18 +270,16 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarSt 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-zwkVjRg (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; public static synthetic fun macOsLight-zwkVjRg$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; - public static final fun windowsAndLinux-TZvXluI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; - public static synthetic fun windowsAndLinux-TZvXluI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;FFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling; - public static final fun windowsAndLinux-kLn_5LQ (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;J)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; - public static synthetic fun windowsAndLinux-kLn_5LQ$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static final fun windowsAndLinux-tYhzLtE (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; + public static synthetic fun windowsAndLinux-tYhzLtE$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion;FLandroidx/compose/foundation/layout/PaddingValues;JJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible; public static final fun windowsAndLinux-wH6b6FI (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;F)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; public static synthetic fun windowsAndLinux-wH6b6FI$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics$Companion;Landroidx/compose/foundation/shape/CornerSize;FILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarMetrics; - public static final fun windowsAndLinuxDark (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 windowsAndLinuxDark$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 windowsAndLinuxDark (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$AlwaysVisible;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; + public static synthetic fun windowsAndLinuxDark$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$AlwaysVisible;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; public static final fun windowsAndLinuxDark-zwkVjRg (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; public static synthetic fun windowsAndLinuxDark-zwkVjRg$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; - public static final fun windowsAndLinuxLight (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 windowsAndLinuxLight$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 windowsAndLinuxLight (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$AlwaysVisible;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; + public static synthetic fun windowsAndLinuxLight$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$AlwaysVisible;ILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarStyle; public static final fun windowsAndLinuxLight-zwkVjRg (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; public static synthetic fun windowsAndLinuxLight-zwkVjRg$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors$Companion;JJJJJJJJJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarColors; } diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt index 26ced606c..eb59a9733 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiScrollbarStyling.kt @@ -9,6 +9,8 @@ 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.ScrollbarVisibility.AlwaysVisible +import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility.WhenScrolling import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior import org.jetbrains.skiko.hostOs import kotlin.time.Duration @@ -32,7 +34,7 @@ public fun ScrollbarStyle.Companion.macOsLight( colors: ScrollbarColors = ScrollbarColors.macOsLight(), metrics: ScrollbarMetrics = ScrollbarMetrics.macOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), + scrollbarVisibility: ScrollbarVisibility = WhenScrolling.default(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -45,7 +47,7 @@ public fun ScrollbarStyle.Companion.macOsDark( colors: ScrollbarColors = ScrollbarColors.macOsDark(), metrics: ScrollbarMetrics = ScrollbarMetrics.macOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), + scrollbarVisibility: ScrollbarVisibility = WhenScrolling.default(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -58,7 +60,7 @@ public fun ScrollbarStyle.Companion.windowsAndLinuxLight( colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxLight(), metrics: ScrollbarMetrics = ScrollbarMetrics.windowsAndLinux(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.macOs(), + scrollbarVisibility: AlwaysVisible = AlwaysVisible.windowsAndLinux(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -71,7 +73,7 @@ public fun ScrollbarStyle.Companion.windowsAndLinuxDark( colors: ScrollbarColors = ScrollbarColors.windowsAndLinuxDark(), metrics: ScrollbarMetrics = ScrollbarMetrics.windowsAndLinux(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.JumpToSpot, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.AlwaysVisible.macOs(), + scrollbarVisibility: AlwaysVisible = AlwaysVisible.windowsAndLinux(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -80,74 +82,6 @@ public fun ScrollbarStyle.Companion.windowsAndLinuxDark( scrollbarVisibility = scrollbarVisibility, ) -public fun ScrollbarVisibility.AlwaysVisible.Companion.default(): ScrollbarVisibility.AlwaysVisible = - if (hostOs.isMacOS) { - ScrollbarVisibility.AlwaysVisible.macOs() - } else { - ScrollbarVisibility.AlwaysVisible.windowsAndLinux() - } - -public fun ScrollbarVisibility.AlwaysVisible.Companion.macOs( - trackThickness: Dp = 14.dp, - trackPadding: PaddingValues = PaddingValues(2.dp), - thumbColorAnimationDuration: Duration = 330.milliseconds, -): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) - -public fun ScrollbarVisibility.AlwaysVisible.Companion.windowsAndLinux( - trackThickness: Dp = 18.dp, - trackPadding: PaddingValues = PaddingValues(), - thumbColorAnimationDuration: Duration = 330.milliseconds, -): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible(trackThickness, trackPadding, trackPadding, thumbColorAnimationDuration) - -public fun ScrollbarVisibility.WhenScrolling.Companion.default(): ScrollbarVisibility.WhenScrolling = - if (hostOs.isMacOS) { - ScrollbarVisibility.WhenScrolling.macOs() - } else { - ScrollbarVisibility.WhenScrolling.windowsAndLinux() - } - -public fun ScrollbarVisibility.WhenScrolling.Companion.macOs( - trackThickness: Dp = 11.dp, - trackThicknessExpanded: Dp = 14.dp, - trackPadding: PaddingValues = PaddingValues(2.dp), - trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), - trackColorAnimationDuration: Duration = 125.milliseconds, - expandAnimationDuration: Duration = trackColorAnimationDuration, - thumbColorAnimationDuration: Duration = trackColorAnimationDuration, - lingerDuration: Duration = 700.milliseconds, -): ScrollbarVisibility.WhenScrolling = - ScrollbarVisibility.WhenScrolling( - trackThickness = trackThickness, - trackThicknessExpanded = trackThicknessExpanded, - trackPadding = trackPadding, - trackPaddingWithBorder = trackPaddingWithBorder, - trackColorAnimationDuration = trackColorAnimationDuration, - expandAnimationDuration = expandAnimationDuration, - thumbColorAnimationDuration = thumbColorAnimationDuration, - lingerDuration = lingerDuration, - ) - -public fun ScrollbarVisibility.WhenScrolling.Companion.windowsAndLinux( - trackThickness: Dp = 11.dp, - trackThicknessExpanded: Dp = 14.dp, - trackPadding: PaddingValues = PaddingValues(), - trackPaddingWithBorder: PaddingValues = trackPadding, - trackColorAnimationDuration: Duration = 125.milliseconds, - expandAnimationDuration: Duration = trackColorAnimationDuration, - thumbColorAnimationDuration: Duration = trackColorAnimationDuration, - lingerDuration: Duration = 700.milliseconds, -): ScrollbarVisibility.WhenScrolling = - ScrollbarVisibility.WhenScrolling( - trackThickness = trackThickness, - trackThicknessExpanded = trackThicknessExpanded, - trackPadding = trackPadding, - trackPaddingWithBorder = trackPaddingWithBorder, - trackColorAnimationDuration = trackColorAnimationDuration, - expandAnimationDuration = expandAnimationDuration, - thumbColorAnimationDuration = thumbColorAnimationDuration, - lingerDuration = lingerDuration, - ) - public fun ScrollbarColors.Companion.macOsLight( thumbBackground: Color = Color(0x00000000), thumbBackgroundActive: Color = Color(0x80000000), @@ -177,14 +111,14 @@ public fun ScrollbarColors.Companion.macOsLight( trackOpaqueBackgroundHovered = trackOpaqueBackgroundHovered, ) -public fun ScrollbarColors.Companion.windowsAndLinuxLight( - thumbBackground: Color = Color(0x33737373), - thumbBackgroundActive: Color = Color(0x47737373), - thumbOpaqueBackground: Color = thumbBackground, +public fun ScrollbarColors.Companion.macOsDark( + thumbBackground: Color = Color(0x00808080), + thumbBackgroundActive: Color = Color(0x8C808080), + thumbOpaqueBackground: Color = Color(0x59808080), thumbOpaqueBackgroundHovered: Color = thumbBackgroundActive, - thumbBorder: Color = Color(0x33595959), - thumbBorderActive: Color = Color(0x47595959), - thumbOpaqueBorder: Color = thumbBorder, + thumbBorder: Color = Color(0x00262626), + thumbBorderActive: Color = Color(0x8C262626), + thumbOpaqueBorder: Color = Color(0x59262626), thumbOpaqueBorderHovered: Color = thumbBorderActive, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), @@ -206,19 +140,19 @@ public fun ScrollbarColors.Companion.windowsAndLinuxLight( trackOpaqueBackgroundHovered = trackOpaqueBackgroundHovered, ) -public fun ScrollbarColors.Companion.macOsDark( - thumbBackground: Color = Color(0x00808080), - thumbBackgroundActive: Color = Color(0x8C808080), - thumbOpaqueBackground: Color = Color(0x59808080), +public fun ScrollbarColors.Companion.windowsAndLinuxLight( + thumbBackground: Color = Color(0x33737373), + thumbBackgroundActive: Color = Color(0x47737373), + thumbOpaqueBackground: Color = thumbBackground, thumbOpaqueBackgroundHovered: Color = thumbBackgroundActive, - thumbBorder: Color = Color(0x00262626), - thumbBorderActive: Color = Color(0x8C262626), - thumbOpaqueBorder: Color = Color(0x59262626), + thumbBorder: Color = Color(0x33595959), + thumbBorderActive: Color = Color(0x47595959), + thumbOpaqueBorder: Color = thumbBorder, thumbOpaqueBorderHovered: Color = thumbBorderActive, trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackBackground, + trackOpaqueBackgroundHovered: Color = trackBackgroundHovered, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, @@ -247,7 +181,7 @@ public fun ScrollbarColors.Companion.windowsAndLinuxDark( trackBackground: Color = Color(0x00808080), trackBackgroundHovered: Color = Color(0x1A808080), trackOpaqueBackground: Color = trackBackground, - trackOpaqueBackgroundHovered: Color = trackBackground, + trackOpaqueBackgroundHovered: Color = trackBackgroundHovered, ): ScrollbarColors = ScrollbarColors( thumbBackground = thumbBackground, @@ -273,3 +207,64 @@ public fun ScrollbarMetrics.Companion.windowsAndLinux( thumbCornerSize: CornerSize = CornerSize(0), minThumbLength: Dp = 20.dp, ): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) + +public fun AlwaysVisible.Companion.default(): AlwaysVisible = + if (hostOs.isMacOS) { + AlwaysVisible.macOs() + } else { + AlwaysVisible.windowsAndLinux() + } + +public fun AlwaysVisible.Companion.macOs( + trackThickness: Dp = 14.dp, + trackPadding: PaddingValues = PaddingValues(2.dp), + thumbColorAnimationDuration: Duration = 330.milliseconds, + scrollbarBackgroundColorLight: Color = Color(0xFFF5F5F5), + scrollbarBackgroundColorDark: Color = Color(0xFF3F4244), +): AlwaysVisible = + AlwaysVisible( + trackThickness = trackThickness, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPadding, + thumbColorAnimationDuration = thumbColorAnimationDuration, + trackColorAnimationDuration = 0.milliseconds, + scrollbarBackgroundColorLight = scrollbarBackgroundColorLight, + scrollbarBackgroundColorDark = scrollbarBackgroundColorDark, + ) + +public fun AlwaysVisible.Companion.windowsAndLinux( + trackThickness: Dp = 10.dp, + trackPadding: PaddingValues = PaddingValues(0.5.dp), + thumbColorAnimationDuration: Duration = 330.milliseconds, + trackColorAnimationDuration: Duration = thumbColorAnimationDuration, +): AlwaysVisible = + AlwaysVisible( + trackThickness = trackThickness, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPadding, + thumbColorAnimationDuration = thumbColorAnimationDuration, + trackColorAnimationDuration = trackColorAnimationDuration, + scrollbarBackgroundColorLight = Color.Unspecified, + scrollbarBackgroundColorDark = Color.Unspecified, + ) + +public fun WhenScrolling.Companion.default( + trackThickness: Dp = 11.dp, + trackThicknessExpanded: Dp = 14.dp, + trackPadding: PaddingValues = PaddingValues(2.dp), + trackPaddingWithBorder: PaddingValues = PaddingValues(1.dp), + trackColorAnimationDuration: Duration = 125.milliseconds, + expandAnimationDuration: Duration = trackColorAnimationDuration, + thumbColorAnimationDuration: Duration = trackColorAnimationDuration, + lingerDuration: Duration = 700.milliseconds, +): WhenScrolling = + WhenScrolling( + trackThickness = trackThickness, + trackThicknessExpanded = trackThicknessExpanded, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPaddingWithBorder, + trackColorAnimationDuration = trackColorAnimationDuration, + expandAnimationDuration = expandAnimationDuration, + thumbColorAnimationDuration = thumbColorAnimationDuration, + lingerDuration = lingerDuration, + ) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt index a96bce94e..a609b72ad 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt @@ -2,6 +2,7 @@ package org.jetbrains.jewel.intui.standalone.styling import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.shape.CornerSize +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 @@ -30,7 +31,7 @@ public fun ScrollbarStyle.Companion.tabStripMacOsLight( colors: ScrollbarColors = ScrollbarColors.macOsLight(), metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -43,7 +44,7 @@ public fun ScrollbarStyle.Companion.tabStripMacOsDark( colors: ScrollbarColors = ScrollbarColors.macOsDark(), metrics: ScrollbarMetrics = ScrollbarMetrics.tabStripMacOs(), trackClickBehavior: TrackClickBehavior = TrackClickBehavior.NextPage, - scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.macOs(), + scrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.WhenScrolling.default(), ): ScrollbarStyle = ScrollbarStyle( colors = colors, @@ -85,7 +86,7 @@ public fun ScrollbarMetrics.Companion.tabStripMacOs( public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( thumbCornerSize: CornerSize = CornerSize(0), - minThumbLength: Dp = 16.dp, + minThumbLength: Dp = 20.dp, ): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( @@ -98,4 +99,7 @@ public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( trackPadding, trackPaddingWithBorder, thumbColorAnimationDuration = 0.milliseconds, + trackColorAnimationDuration = 0.milliseconds, + scrollbarBackgroundColorLight = Color.Unspecified, + scrollbarBackgroundColorDark = Color.Unspecified, ) diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index fb0070c0d..715856e2b 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -47,6 +47,8 @@ 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.jewel.ui.theme.textAreaStyle +import org.jetbrains.skiko.OS +import org.jetbrains.skiko.hostOs import java.util.Locale @Composable @@ -55,7 +57,7 @@ fun Scrollbars() { val isDark = JewelTheme.isDark val baseStyle = remember(isDark) { if (isDark) ScrollbarStyle.dark() else ScrollbarStyle.light() } - var alwaysVisible by remember { mutableStateOf(false) } + var alwaysVisible by remember { mutableStateOf(hostOs != OS.MacOS) } var clickBehavior by remember { mutableStateOf(baseStyle.trackClickBehavior) } SettingsRow(alwaysVisible, clickBehavior, { alwaysVisible = it }, { clickBehavior = it }) @@ -86,10 +88,10 @@ fun Scrollbars() { verticalAlignment = Alignment.CenterVertically, ) { LazyColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) - ColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) +// ColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) } - HorizontalScrollbarContent(style, Modifier.fillMaxWidth()) +// HorizontalScrollbarContent(style, Modifier.fillMaxWidth()) } } diff --git a/ui/api/ui.api b/ui/api/ui.api index e915be729..8b063f572 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -1941,10 +1941,12 @@ public abstract interface class org/jetbrains/jewel/ui/component/styling/Scrollb 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 Companion Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$AlwaysVisible$Companion; - public synthetic fun (FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;JJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public fun getExpandAnimationDuration-UwyO8pc ()J public fun getLingerDuration-UwyO8pc ()J + public final fun getScrollbarBackgroundColorDark-0d7_KjU ()J + public final fun getScrollbarBackgroundColorLight-0d7_KjU ()J public fun getThumbColorAnimationDuration-UwyO8pc ()J public fun getTrackColorAnimationDuration-UwyO8pc ()J public fun getTrackPadding ()Landroidx/compose/foundation/layout/PaddingValues; diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt index c3a97c96e..656ce1675 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ScrollableContainer.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import kotlin.time.Duration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -38,6 +37,9 @@ 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.theme.scrollbarStyle +import org.jetbrains.skiko.OS +import org.jetbrains.skiko.hostOs +import kotlin.time.Duration private const val ID_CONTENT = "VerticallyScrollableContainer_content" private const val ID_VERTICAL_SCROLLBAR = "VerticallyScrollableContainer_verticalScrollbar" @@ -375,10 +377,12 @@ private fun ScrollableContainerImpl( horizontalScrollbarMeasurable.measure(horizontalScrollbarConstraints) } else null + val isMacOs = hostOs == OS.MacOS val contentMeasurable = measurables.find { it.layoutId == ID_CONTENT } ?: error("Content not provided") val contentConstraints = computeContentConstraints( scrollbarStyle, + isMacOs, incomingConstraints, verticalScrollbarPlaceable, horizontalScrollbarPlaceable, @@ -386,10 +390,20 @@ private fun ScrollableContainerImpl( val contentPlaceable = contentMeasurable.measure(contentConstraints) val isAlwaysVisible = scrollbarStyle.scrollbarVisibility is AlwaysVisible - val vScrollbarWidth = if (isAlwaysVisible) verticalScrollbarPlaceable?.width ?: 0 else 0 + val vScrollbarWidth = + when { + !isMacOs -> 0 + isAlwaysVisible -> verticalScrollbarPlaceable?.width ?: 0 + else -> 0 + } val width = contentPlaceable.width + vScrollbarWidth - val hScrollbarHeight = if (isAlwaysVisible) horizontalScrollbarPlaceable?.height ?: 0 else 0 + val hScrollbarHeight = + when { + !isMacOs -> 0 + isAlwaysVisible -> horizontalScrollbarPlaceable?.height ?: 0 + else -> 0 + } val height = contentPlaceable.height + hScrollbarHeight layout(width, height) { @@ -406,16 +420,21 @@ private fun ScrollableContainerImpl( private fun computeContentConstraints( scrollbarStyle: ScrollbarStyle, + isMacOs: Boolean, incomingConstraints: Constraints, verticalScrollbarPlaceable: Placeable?, horizontalScrollbarPlaceable: Placeable?, ): Constraints { + val visibility = scrollbarStyle.scrollbarVisibility + fun width() = if (incomingConstraints.hasBoundedWidth) { val maxWidth = incomingConstraints.maxWidth - when (scrollbarStyle.scrollbarVisibility) { - is AlwaysVisible -> maxWidth - (verticalScrollbarPlaceable?.width ?: 0) - is WhenScrolling -> maxWidth + when { + !isMacOs -> maxWidth // Scrollbars on Win/Linux are always overlaid + visibility is AlwaysVisible -> maxWidth - (verticalScrollbarPlaceable?.width ?: 0) + visibility is WhenScrolling -> maxWidth + else -> error("Unsupported visibility style: $visibility") } } else { error("Incoming constraints have infinite width, should not use fixed width") @@ -424,9 +443,11 @@ private fun computeContentConstraints( fun height() = if (incomingConstraints.hasBoundedHeight) { val maxHeight = incomingConstraints.maxHeight - when (scrollbarStyle.scrollbarVisibility) { - is AlwaysVisible -> maxHeight - (horizontalScrollbarPlaceable?.height ?: 0) - is WhenScrolling -> maxHeight + when { + !isMacOs -> maxHeight // Scrollbars on Win/Linux are always overlaid + visibility is AlwaysVisible -> maxHeight - (horizontalScrollbarPlaceable?.height ?: 0) + visibility is WhenScrolling -> maxHeight + else -> error("Unsupported visibility style: $visibility") } } else { error("Incoming constraints have infinite height, should not use fixed height") @@ -455,7 +476,9 @@ private fun computeContentConstraints( */ @Composable public fun scrollbarContentSafePadding(style: ScrollbarStyle = JewelTheme.scrollbarStyle): Dp = - when (style.scrollbarVisibility) { - is AlwaysVisible -> 0.dp - is WhenScrolling -> style.scrollbarVisibility.trackThicknessExpanded + when { + hostOs != OS.MacOS -> style.scrollbarVisibility.trackThicknessExpanded + style.scrollbarVisibility is AlwaysVisible -> 0.dp + style.scrollbarVisibility is WhenScrolling -> style.scrollbarVisibility.trackThicknessExpanded + else -> error("Unsupported visibility: ${style.scrollbarVisibility}") } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt index 5b8efab5d..ab763b0d2 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Scrollbar.kt @@ -61,7 +61,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp -import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job @@ -78,6 +77,7 @@ 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 org.jetbrains.jewel.ui.util.thenIf +import kotlin.math.roundToInt @Composable public fun VerticalScrollbar( @@ -150,7 +150,8 @@ private fun BaseScrollbar( val isHovered by interactionSource.collectIsHoveredAsState() var showScrollbar by remember { mutableStateOf(false) } - val isScrolling = scrollState.isScrollInProgress || dragInteraction.value != null + val isDragging = dragInteraction.value != null + val isScrolling = scrollState.isScrollInProgress || isDragging val isActive = isOpaque || isScrolling || (keepVisible && showScrollbar) if (isHovered && showScrollbar) isExpanded = true @@ -226,8 +227,8 @@ private fun BaseScrollbar( val trackBackground by animateColorAsState( - targetValue = getTrackColor(isOpaque, isHovered, style, isExpanded), - animationSpec = trackColorTween(showScrollbar, visibilityStyle), + targetValue = getTrackColor(isOpaque, isDragging, isHovered, style, isExpanded), + animationSpec = trackColorTween(visibilityStyle), label = "scrollbar_trackBackground", ) @@ -266,9 +267,15 @@ private fun BaseScrollbar( } } -private fun getTrackColor(isOpaque: Boolean, isHovered: Boolean, style: ScrollbarStyle, isExpanded: Boolean) = +private fun getTrackColor( + isOpaque: Boolean, + isDragging: Boolean, + isHovered: Boolean, + style: ScrollbarStyle, + isExpanded: Boolean, +) = if (isOpaque) { - if (isHovered) { + if (isHovered || isDragging) { style.colors.trackOpaqueBackgroundHovered } else { style.colors.trackOpaqueBackground @@ -394,15 +401,8 @@ private fun Modifier.drawThumb( } } -private fun trackColorTween(showScrollbar: Boolean, visibility: ScrollbarVisibility) = - tween( - if (showScrollbar) { - 0 - } else { - visibility.trackColorAnimationDuration.inWholeMilliseconds.toInt() - }, - easing = LinearEasing, - ) +private fun trackColorTween(visibility: ScrollbarVisibility) = + tween(visibility.trackColorAnimationDuration.inWholeMilliseconds.toInt(), easing = LinearEasing) private fun thumbColorTween(showScrollbar: Boolean, visibility: ScrollbarVisibility) = tween( diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt index 6a50efdf3..57376aab9 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt @@ -128,7 +128,12 @@ private fun TextAreaDecorator( 0.dp, calculateBottomPadding(), ) - paddingValues to calculateEndPadding(direction) + val scrollbarExtraPadding = + if (scrollState.canScrollForward || scrollState.canScrollBackward) { + scrollbarContentSafePadding(scrollbarStyle) + } else 0.dp + + paddingValues to calculateEndPadding(direction) + scrollbarExtraPadding } } else { style.metrics.contentPadding to 0.dp diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt index 850e64d22..75f66b643 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/ScrollbarStyling.kt @@ -67,9 +67,11 @@ public sealed interface ScrollbarVisibility { public override val trackPadding: PaddingValues, public override val trackPaddingWithBorder: PaddingValues, public override val thumbColorAnimationDuration: Duration, + public override val trackColorAnimationDuration: Duration, + public val scrollbarBackgroundColorLight: Color, + public val scrollbarBackgroundColorDark: Color, ) : ScrollbarVisibility { public override val trackThicknessExpanded: Dp = trackThickness - public override val trackColorAnimationDuration: Duration = 0.milliseconds public override val expandAnimationDuration: Duration = 0.milliseconds public override val lingerDuration: Duration = 0.milliseconds From 67947cea7cff6dc36ad7db6cdd09fe86e1c121ab Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Fri, 23 Aug 2024 18:53:53 +0200 Subject: [PATCH 14/14] Fix Scrollbars [part 16/16] * Clean up code * Add scrollbar to undecorated TextAreas --- .../styling/IntUiTabStripScrollbarStyling.kt | 8 +-- .../standalone/view/component/Scrollbars.kt | 4 +- .../jewel/ui/component/InputField.kt | 8 +-- .../jetbrains/jewel/ui/component/TextArea.kt | 72 +++++++++++++------ 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt index a609b72ad..79561c630 100644 --- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt +++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/styling/IntUiTabStripScrollbarStyling.kt @@ -90,14 +90,14 @@ public fun ScrollbarMetrics.Companion.tabStripWindowsAndLinux( ): ScrollbarMetrics = ScrollbarMetrics(thumbCornerSize, minThumbLength) public fun ScrollbarVisibility.AlwaysVisible.Companion.tabStrip( - thumbThickness: Dp = 4.dp, + trackThickness: Dp = 4.dp, trackPadding: PaddingValues = PaddingValues(), trackPaddingWithBorder: PaddingValues = trackPadding, ): ScrollbarVisibility.AlwaysVisible = ScrollbarVisibility.AlwaysVisible( - thumbThickness, - trackPadding, - trackPaddingWithBorder, + trackThickness = trackThickness, + trackPadding = trackPadding, + trackPaddingWithBorder = trackPaddingWithBorder, thumbColorAnimationDuration = 0.milliseconds, trackColorAnimationDuration = 0.milliseconds, scrollbarBackgroundColorLight = Color.Unspecified, diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt index 715856e2b..dd56a9a9f 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Scrollbars.kt @@ -88,10 +88,10 @@ fun Scrollbars() { verticalAlignment = Alignment.CenterVertically, ) { LazyColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) -// ColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) + ColumnWithScrollbar(style, Modifier.height(200.dp).weight(1f)) } -// HorizontalScrollbarContent(style, Modifier.fillMaxWidth()) + HorizontalScrollbarContent(style, Modifier.fillMaxWidth()) } } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt index 0f873ef16..21f0ad04a 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/InputField.kt @@ -61,6 +61,7 @@ internal fun InputField( outline: Outline, outputTransformation: OutputTransformation?, decorator: TextFieldDecorator?, + undecorated: Boolean = decorator == null, scrollState: ScrollState, ) { var inputFieldState by remember(interactionSource) { mutableStateOf(InputFieldState.of(enabled = enabled)) } @@ -79,14 +80,13 @@ internal fun InputField( val backgroundColor by colors.backgroundFor(inputFieldState) val shape = RoundedCornerShape(style.metrics.cornerSize) - val isUndecorated = decorator == null val backgroundModifier = - Modifier.thenIf(!isUndecorated && backgroundColor.isSpecified) { background(backgroundColor, shape) } + Modifier.thenIf(!undecorated && backgroundColor.isSpecified) { background(backgroundColor, shape) } val borderColor by style.colors.borderFor(inputFieldState) val hasNoOutline = outline == Outline.None val borderModifier = - Modifier.thenIf(!isUndecorated && borderColor.isSpecified && hasNoOutline) { + Modifier.thenIf(!undecorated && borderColor.isSpecified && hasNoOutline) { border( alignment = Stroke.Alignment.Center, width = style.metrics.borderWidth, @@ -105,7 +105,7 @@ internal fun InputField( modifier .then(backgroundModifier) .then(borderModifier) - .thenIf(!isUndecorated && hasNoOutline) { focusOutline(inputFieldState, shape) } + .thenIf(!undecorated && hasNoOutline) { focusOutline(inputFieldState, shape) } .outline(inputFieldState, outline, shape, Stroke.Alignment.Center), enabled = enabled, readOnly = readOnly, diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt index 57376aab9..5d8bda2a6 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextArea.kt @@ -36,7 +36,9 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval @@ -89,7 +91,7 @@ public fun TextArea( outputTransformation = outputTransformation, decorator = if (undecorated) { - null + NoTextAreaDecorator(style, scrollbarStyle, scrollState) } else { TextAreaDecorator( style, @@ -102,10 +104,29 @@ public fun TextArea( scrollState, ) }, + undecorated = undecorated, scrollState = scrollState, ) } +@Composable +private fun NoTextAreaDecorator(style: TextAreaStyle, scrollbarStyle: ScrollbarStyle?, scrollState: ScrollState) = + TextFieldDecorator { innerTextField -> + val (contentPadding, innerEndPadding) = + calculatePaddings(scrollbarStyle, style, scrollState, LocalLayoutDirection.current) + + if (scrollbarStyle != null) { + TextAreaScrollableContainer( + scrollState = scrollState, + style = scrollbarStyle, + contentModifier = Modifier.padding(end = innerEndPadding), + content = { Box(Modifier.padding(contentPadding)) { innerTextField() } }, + ) + } else { + Box(Modifier.padding(contentPadding)) { innerTextField() } + } + } + @Composable private fun TextAreaDecorator( style: TextAreaStyle, @@ -118,26 +139,7 @@ private fun TextAreaDecorator( scrollState: ScrollState, ) = TextFieldDecorator { innerTextField -> val (contentPadding, innerEndPadding) = - if (scrollbarStyle != null) { - with(style.metrics.contentPadding) { - val direction = LocalLayoutDirection.current - val paddingValues = - PaddingValues( - calculateStartPadding(direction), - calculateTopPadding(), - 0.dp, - calculateBottomPadding(), - ) - val scrollbarExtraPadding = - if (scrollState.canScrollForward || scrollState.canScrollBackward) { - scrollbarContentSafePadding(scrollbarStyle) - } else 0.dp - - paddingValues to calculateEndPadding(direction) + scrollbarExtraPadding - } - } else { - style.metrics.contentPadding to 0.dp - } + calculatePaddings(scrollbarStyle, style, scrollState, LocalLayoutDirection.current) TextAreaDecorationBox( innerTextField = { @@ -160,6 +162,34 @@ private fun TextAreaDecorator( ) } +@Composable +private fun calculatePaddings( + scrollbarStyle: ScrollbarStyle?, + style: TextAreaStyle, + scrollState: ScrollState, + layoutDirection: LayoutDirection, +): Pair = + if (scrollbarStyle != null) { + with(style.metrics.contentPadding) { + val paddingValues = + PaddingValues( + start = calculateStartPadding(layoutDirection), + top = calculateTopPadding(), + end = 0.dp, + bottom = calculateBottomPadding(), + ) + + val scrollbarExtraPadding = + if (scrollState.canScrollForward || scrollState.canScrollBackward) { + scrollbarContentSafePadding(scrollbarStyle) + } else 0.dp + + paddingValues to calculateEndPadding(layoutDirection) + scrollbarExtraPadding + } + } else { + style.metrics.contentPadding to 0.dp + } + @ScheduledForRemoval(inVersion = "Before 1.0") @Deprecated("Please use TextArea(state) instead. If you want to observe text changes, use snapshotFlow { state.text }") @Composable