Skip to content

Commit

Permalink
New iteration on scrollbars (#515)
Browse files Browse the repository at this point in the history
* iterate on scrollbars

* update API files

* add Scrollbars demo to Standalone

* fix popup border width regression

Reference #515 (comment)

Signed-off-by: Ivan Morgillo <[email protected]>

* remove unnecessary platform check in ScrollbarBridge

Reference #515 (comment)

Signed-off-by: Ivan Morgillo <[email protected]>

* fix wrong thumb background color

Reference #515 (comment)

Signed-off-by: Ivan Morgillo <[email protected]>

---------

Signed-off-by: Ivan Morgillo <[email protected]>
  • Loading branch information
hamen authored Aug 5, 2024
1 parent 68721cf commit aadf5b4
Show file tree
Hide file tree
Showing 15 changed files with 1,512 additions and 176 deletions.
5 changes: 5 additions & 0 deletions ide-laf-bridge/api/ide-laf-bridge.api
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ public final class org/jetbrains/jewel/bridge/theme/IntUiBridgeKt {
public static final fun retrieveEditorTextStyle ()Landroidx/compose/ui/text/TextStyle;
}

public final class org/jetbrains/jewel/bridge/theme/ScrollbarBridgeKt {
public static final fun defaults-6ksGUsA (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;JJJJ)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling;
public static synthetic fun defaults-6ksGUsA$default (Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling$Companion;JJJJILjava/lang/Object;)Lorg/jetbrains/jewel/ui/component/styling/ScrollbarVisibility$WhenScrolling;
}

public final class org/jetbrains/jewel/bridge/theme/SwingBridgeThemeKt {
public static final fun SwingBridgeTheme (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.takeOrElse
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil
import com.intellij.ide.ui.laf.intellij.IdeaPopupMenuUI
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.colors.ColorKey
import com.intellij.openapi.editor.colors.EditorFontType
import com.intellij.openapi.util.registry.Registry
Expand Down Expand Up @@ -49,6 +48,7 @@ import org.jetbrains.jewel.foundation.Stroke
import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
import org.jetbrains.jewel.foundation.theme.ThemeDefinition
import org.jetbrains.jewel.foundation.theme.ThemeIconData
import org.jetbrains.jewel.foundation.util.JewelLogger
import org.jetbrains.jewel.ui.ComponentStyling
import org.jetbrains.jewel.ui.DefaultComponentStyling
import org.jetbrains.jewel.ui.component.styling.ButtonColors
Expand Down Expand Up @@ -96,9 +96,6 @@ import org.jetbrains.jewel.ui.component.styling.RadioButtonColors
import org.jetbrains.jewel.ui.component.styling.RadioButtonIcons
import org.jetbrains.jewel.ui.component.styling.RadioButtonMetrics
import org.jetbrains.jewel.ui.component.styling.RadioButtonStyle
import org.jetbrains.jewel.ui.component.styling.ScrollbarColors
import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonColors
import org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonMetrics
import org.jetbrains.jewel.ui.component.styling.SegmentedControlButtonStyle
Expand Down Expand Up @@ -128,7 +125,7 @@ import org.jetbrains.jewel.ui.icons.AllIconsKeys
import javax.swing.UIManager
import kotlin.time.Duration.Companion.milliseconds

private val logger = Logger.getInstance("JewelIntUiBridge")
private val logger = JewelLogger.getInstance("JewelIntUiBridge")

internal val uiDefaults
get() = UIManager.getDefaults()
Expand Down Expand Up @@ -178,13 +175,14 @@ public fun retrieveConsoleTextStyle(): TextStyle {
)
}

private val isDark: Boolean
get() = !JBColor.isBright()

internal fun createBridgeThemeDefinition(
textStyle: TextStyle,
editorTextStyle: TextStyle,
consoleTextStyle: TextStyle,
): ThemeDefinition {
val isDark = !JBColor.isBright()

logger.debug("Obtaining theme definition from Swing...")

return ThemeDefinition(
Expand Down Expand Up @@ -697,7 +695,7 @@ private fun readMenuStyle(): MenuStyle {
)

return MenuStyle(
isDark = !JBColor.isBright(),
isDark = isDark,
colors = colors,
metrics =
MenuMetrics(
Expand Down Expand Up @@ -810,31 +808,6 @@ private object NewUiRadioButtonMetrics : BridgeRadioButtonMetrics {
override val iconContentGap = 4.dp
}

private fun readScrollbarStyle(isDark: Boolean) =
ScrollbarStyle(
colors =
ScrollbarColors(
// See ScrollBarPainter.THUMB_OPAQUE_BACKGROUND
thumbBackground =
retrieveColorOrUnspecified("ScrollBar.Mac.Transparent.thumbColor")
.let { if (it.alpha == 0f) Color.Unspecified else it } // See https://github.com/JetBrains/jewel/issues/259
.takeOrElse { if (isDark) Color(0x59808080) else Color(0x33000000) },
// See ScrollBarPainter.THUMB_OPAQUE_HOVERED_BACKGROUND
thumbBackgroundHovered =
retrieveColorOrUnspecified("ScrollBar.Mac.Transparent.hoverThumbColor")
.let { if (it.alpha == 0f) Color.Unspecified else it } // See https://github.com/JetBrains/jewel/issues/259
.takeOrElse { if (isDark) Color(0x8C808080) else Color(0x80000000) },
),
metrics =
ScrollbarMetrics(
thumbCornerSize = CornerSize(100),
thumbThickness = 8.dp,
minThumbLength = 16.dp,
trackPadding = PaddingValues(start = 7.dp, end = 3.dp),
),
hoverDuration = 300.milliseconds,
)

private fun readSegmentedControlButtonStyle(): SegmentedControlButtonStyle {
val selectedBackground = SolidColor(JBUI.CurrentTheme.SegmentedButton.SELECTED_BUTTON_COLOR.toComposeColor())

Expand Down Expand Up @@ -1094,6 +1067,7 @@ private fun readDefaultTabStyle(): TabStyle {
contentHovered = 1f,
contentSelected = 1f,
),
scrollbarStyle = readScrollbarStyle(isDark),
)
}

Expand Down Expand Up @@ -1150,6 +1124,7 @@ private fun readEditorTabStyle(): TabStyle {
contentHovered = 1f,
contentSelected = 1f,
),
scrollbarStyle = readScrollbarStyle(isDark),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package org.jetbrains.jewel.bridge.theme

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.unit.dp
import com.intellij.ui.mac.foundation.Foundation
import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified
import org.jetbrains.jewel.ui.component.styling.ScrollbarColors
import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics
import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior
import org.jetbrains.skiko.hostOs
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

internal fun readScrollbarStyle(isDark: Boolean): ScrollbarStyle =
ScrollbarStyle(
colors = readScrollbarColors(isDark),
metrics = readScrollbarMetrics(),
trackClickBehavior = readTrackClickBehavior(),
scrollbarVisibility = readScrollbarVisibility(),
)

private fun readScrollbarVisibility() =
if (hostOs.isMacOS) {
readMacScrollbarStyle()
} else {
ScrollbarVisibility.AlwaysVisible
}

private fun readScrollbarColors(isDark: Boolean) =
if (hostOs.isMacOS) {
readScrollbarMacColors(isDark)
} else {
readScrollbarWinColors(isDark)
}

private fun readTrackClickBehavior() =
if (hostOs.isMacOS) {
readMacScrollbarBehavior()
} else {
TrackClickBehavior.JumpToSpot
}

private fun readScrollbarWinColors(isDark: Boolean): ScrollbarColors =
ScrollbarColors(
thumbBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Transparent.thumbColor",
0x33737373,
0x47A6A6A6,
),
thumbBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbColor",
0x47737373,
0x59A6A6A6,
),
thumbBackgroundPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbColor",
0x47737373,
0x59A6A6A6,
),
thumbBorder =
readScrollBarColorForKey(
isDark,
"ScrollBar.thumbBorderColor",
0x33595959,
0x47383838,
),
thumbBorderHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbBorderColor",
0x47595959,
0x59383838,
),
thumbBorderPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.hoverThumbBorderColor",
0x47595959,
0x59383838,
),
trackBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Transparent.trackColor",
0x00808080,
0x00808080,
),
trackBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Transparent.hoverTrackColor",
0x1A808080,
0x1A808080,
),
)

private fun readScrollbarMacColors(isDark: Boolean): ScrollbarColors =
ScrollbarColors(
thumbBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.Transparent.thumbColor",
0x00000000,
0x00808080,
),
thumbBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbColor",
0x80000000,
0x8C808080,
),
thumbBackgroundPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbColor",
0x80000000,
0x8C808080,
),
thumbBorder =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.thumbBorderColor",
0x33000000,
0x59262626,
),
thumbBorderHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbBorderColor",
0x80000000,
0x8C262626,
),
thumbBorderPressed =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.hoverThumbBorderColor",
0x80000000,
0x8C262626,
),
trackBackground =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.Transparent.trackColor",
0x00808080,
0x00808080,
),
trackBackgroundHovered =
readScrollBarColorForKey(
isDark,
"ScrollBar.Mac.Transparent.hoverTrackColor",
0x1A808080,
0x1A808080,
),
)

private fun readScrollBarColorForKey(
isDark: Boolean,
colorKey: String,
fallbackLight: Long,
fallbackDark: Long,
) = retrieveColorOrUnspecified(colorKey)
.takeOrElse { if (isDark) Color(fallbackDark) else Color(fallbackLight) }

private fun readScrollbarMetrics(): ScrollbarMetrics =
if (hostOs.isMacOS) {
ScrollbarMetrics(
thumbCornerSize = CornerSize(percent = 100),
thumbThickness = 8.dp,
thumbThicknessExpanded = 14.dp,
minThumbLength = 20.dp,
trackPadding = PaddingValues(2.dp),
trackPaddingExpanded = PaddingValues(2.dp),
)
} else {
ScrollbarMetrics(
thumbCornerSize = CornerSize(0),
thumbThickness = 8.dp,
thumbThicknessExpanded = 8.dp,
minThumbLength = 16.dp,
trackPadding = PaddingValues(),
trackPaddingExpanded = PaddingValues(),
)
}

private fun readMacScrollbarStyle(): ScrollbarVisibility {
val nsScroller =
Foundation
.invoke(Foundation.getObjcClass("NSScroller"), "preferredScrollerStyle")

val visibility: ScrollbarVisibility =
if (1 == nsScroller.toInt()) {
ScrollbarVisibility.WhenScrolling.Companion.defaults()
} else {
ScrollbarVisibility.AlwaysVisible
}
return visibility
}

private fun readMacScrollbarBehavior(): TrackClickBehavior {
val defaults = Foundation.invoke("NSUserDefaults", "standardUserDefaults")
Foundation.invoke(defaults, "synchronize")
return Foundation
.invoke(defaults, "boolForKey:", Foundation.nsString("AppleScrollerPagingBehavior"))
.run { if (toInt() == 1) TrackClickBehavior.JumpToSpot else TrackClickBehavior.NextPage }
}

public fun ScrollbarVisibility.WhenScrolling.Companion.defaults(
appearAnimationDuration: Duration = 125.milliseconds,
disappearAnimationDuration: Duration = 125.milliseconds,
expandAnimationDuration: Duration = 125.milliseconds,
lingerDuration: Duration = 700.milliseconds,
): ScrollbarVisibility.WhenScrolling =
ScrollbarVisibility.WhenScrolling(
appearAnimationDuration = appearAnimationDuration,
disappearAnimationDuration = disappearAnimationDuration,
expandAnimationDuration = expandAnimationDuration,
lingerDuration = lingerDuration,
)
Loading

0 comments on commit aadf5b4

Please sign in to comment.