Files
openide/platform/ui.jcef/jcef/JBCefScrollbarsHelper.java
Daniil Ovchinnikov 36b34c4e18 extract intellij.platform.ui.jcef module
GitOrigin-RevId: 560932e829c0bbf85d51b22a80963a978cf89553
2024-09-03 17:43:45 +00:00

322 lines
13 KiB
Java

// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui.jcef;
import com.intellij.diagnostic.LoadingState;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsUtils;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.ui.components.ScrollBarPainter;
import com.intellij.util.LazyInitializer;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Provides tools for JCEF browser scrollbars styling.
* <p>
* By default, CEF browser gets scrollbars that look like native for the OS.
* To bring into a CEF-based application scrollbars with Look and Feel like IntelliJ UI an action is required.
* <p>
* This utility class offers two ways to style the browser scrollbars accordingly.
* <ol>
* <li>
* (Prefered)Use WebKit scrollbar facilities. JBCefScrollbarsHelper class has {@link JBCefScrollbarsHelper#buildScrollbarsStyle()}
* method. This method returns CSS code to be integrated into the web page in order to style all scrollbars on the page according to
* the current colour scheme. Impossible to have overlay (semi-transparent) scrollbars.
* </li>
* <li>
* Employ a third-party library - <a href="https://kingsora.github.io/OverlayScrollbars/">OverlayScrollbars</a>.
* It makes possible to have overlay scrollbars. It requires to integrate into the page:
* <ul>
* <li>The library JS source code(see {@link JBCefScrollbarsHelper#getOverlayScrollbarsSourceJS()})</li>
* <li>The library CSS code(see {@link JBCefScrollbarsHelper#getOverlayScrollbarsSourceCSS()})</li>
* <li>CSS code for styling scrollbars according current IntelliJ color scheme
* (see {@link JBCefScrollbarsHelper#getOverlayScrollbarStyle()})</li>
* </ul>
* </li>
* <ol/>
*/
public final class JBCefScrollbarsHelper {
private static final LazyInitializer.LazyValue<@NotNull String> OVERLAY_SCROLLBARS_CSS = LazyInitializer.create(() -> {
return readResource("resources/overlayscrollbars/overlayscrollbars.css");
});
private static final LazyInitializer.LazyValue<@NotNull String> OVERLAY_SCROLLBARS_JS = LazyInitializer.create(() -> {
return readResource("resources/overlayscrollbars/overlayscrollbars.browser.es6.js");
});
private static final String TRANSPARENT_CSS_COLOR = "rgba(0, 0, 0, 0.0)";
/**
* Returns <a href="https://developer.chrome.com/docs/css-ui/scrollbar-styling">scrollbars CSS code</a>
* adapting the browser scrollbars look and feel to the IDE.
* <p>
* This styling is based on <a href="https://developer.chrome.com/docs/css-ui/scrollbar-styling">WebKit scrollbar styling</a> facilities
* and doesn't require using third party libraries.
*/
public static @NotNull String buildScrollbarsStyle() {
final String transparent = "rgba(0, 0, 0, 0)";
var thumbColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_BACKGROUND);
var thumbHoveredColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_HOVERED_BACKGROUND);
var thumbBorderColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_FOREGROUND);
if (thumbBorderColor.equals(thumbColor)) {
// See com.intellij.ui.components.ScrollBarPainter#Thumb. In this case we ignore the borders
thumbBorderColor = TRANSPARENT_CSS_COLOR;
}
var thumbBorderHoveredColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_HOVERED_FOREGROUND);
if (thumbBorderHoveredColor.equals(thumbHoveredColor)) {
// See com.intellij.ui.components.ScrollBarPainter#Thumb. In this case we ignore the borders
thumbBorderHoveredColor = TRANSPARENT_CSS_COLOR;
}
int trackSizePx = getTrackSizePx();
int thumbPaddingPx = getThumbPaddingPx();
int thumbRadiusPx = getThumbRadiusPx();
return
String.format(
Locale.ROOT,
"""
::-webkit-scrollbar {
width: %dpx;
height: %dpx;
background-color: %s;
}
""", trackSizePx, trackSizePx, transparent) +
String.format(
Locale.ROOT,
"""
::-webkit-scrollbar-track {
background-color: %s;
}
""", transparent) +
String.format(
Locale.ROOT,
"""
::-webkit-scrollbar-track:hover {
background-color: %s;
}
""", transparent) +
String.format(
Locale.ROOT,
"""
::-webkit-scrollbar-thumb {
background-color: %s;
border-radius: %dpx;
border-width: %dpx;
border-style: solid;
border-color: %s;
background-clip: padding-box;
outline: 1px solid %s;
outline-offset: -%dpx;
}
""", thumbColor, thumbRadiusPx, thumbPaddingPx, transparent, thumbBorderColor, thumbPaddingPx) +
String.format(
Locale.ROOT,
"""
::-webkit-scrollbar-thumb:hover {
background-color: %s;
border-radius: %dpx;
border-width: %dpx;
border-style: solid;
border-color: %s;
background-clip: padding-box;
outline: 1px solid %s;
outline-offset: -%dpx;
}
""", thumbHoveredColor, thumbRadiusPx, thumbPaddingPx, transparent, thumbBorderHoveredColor, thumbPaddingPx) +
String.format(
Locale.ROOT,
"""
::-webkit-scrollbar-corner {
background-color: %s;
}
""", transparent) +
"""
::-webkit-scrollbar-button {
display:none;
}
""";
}
/**
* Returns the content of the OverlayScrollbars library CSS code.
* <p>
* This code is to be integrated into the page to use <a href="https://kingsora.github.io/OverlayScrollbars/">OverlayScrollbars</a>.
* <p>
* To be used along with {@link JBCefScrollbarsHelper#getOverlayScrollbarsSourceJS()}.
*
* @see JBCefScrollbarsHelper#getOverlayScrollbarsSourceJS()
* @see JBCefScrollbarsHelper#buildScrollbarsStyle()
*/
public static @NotNull String getOverlayScrollbarsSourceCSS() {
return OVERLAY_SCROLLBARS_CSS.get();
}
/**
* Returns OverlayScrollbars library JavaScript code.
* <p>
* This code is to be integrated into the page to use <a href="https://kingsora.github.io/OverlayScrollbars/">OverlayScrollbars</a>.
* <p>
* To apply the scrollbars styles to an HTML element, some initialization is needed.
* It could look like:
* <pre><code>
* const overlayScrollbars = OverlayScrollbars(document.getElementById('view_port'), {});
* </code></pre>
* The example applies the scrollbar style to {@code view_port} element.
* Check OverlayScrollbars documentation for more examples.
* <p>
* To be used along with {@link JBCefScrollbarsHelper#getOverlayScrollbarsSourceCSS()}.
*
* @see JBCefScrollbarsHelper#getOverlayScrollbarsSourceCSS()
* @see JBCefScrollbarsHelper#buildScrollbarsStyle()
*/
public static @NotNull String getOverlayScrollbarsSourceJS() {
return OVERLAY_SCROLLBARS_JS.get();
}
/**
* Returns the styles adapting <a href="https://kingsora.github.io/OverlayScrollbars/">OverlayScrollbars</a> look and feel to the IDE.
* It must be included in the page along with {@link JBCefScrollbarsHelper#getOverlayScrollbarsSourceJS()} and
* {@link JBCefScrollbarsHelper#getOverlayScrollbarsSourceCSS()}.
* <p>
* Check <a href="https://kingsora.github.io/OverlayScrollbars/">OverlayScrollbars page</a> for manuals and instructions.
*
* @see JBCefScrollbarsHelper#getOverlayScrollbarsSourceCSS()
* @see JBCefScrollbarsHelper#getOverlayScrollbarsSourceJS()
*/
public static @NotNull String getOverlayScrollbarStyle() {
var trackColor = getCssColor(ScrollBarPainter.TRACK_OPAQUE_BACKGROUND);
var trackHoveredColor = getCssColor(ScrollBarPainter.TRACK_OPAQUE_HOVERED_BACKGROUND);
var thumbColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_BACKGROUND);
var thumbHoveredColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_HOVERED_BACKGROUND);
var thumbBorderColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_FOREGROUND);
var thumbBorderHoveredColor = getCssColor(ScrollBarPainter.THUMB_OPAQUE_HOVERED_FOREGROUND);
if (thumbBorderColor.equals(thumbColor)) {
// See com.intellij.ui.components.ScrollBarPainter#Thumb. In this case we ignore the borders
thumbBorderColor = TRANSPARENT_CSS_COLOR;
}
if (thumbBorderHoveredColor.equals(thumbHoveredColor)) {
// See com.intellij.ui.components.ScrollBarPainter#Thumb. In this case we ignore the borders
thumbBorderHoveredColor = TRANSPARENT_CSS_COLOR;
}
final int thumbBorderWidthPx = 1;
int trackSizePx = getTrackSizePx();
int thumbPaddingPx = getThumbPaddingPx();
int thumbRadiusPx = getThumbRadiusPx();
int thumbSizePercent = 100;
return ".os-scrollbar {\n" +
" --os-size: " + trackSizePx + "px;\n" +
" --os-padding-perpendicular: " + (thumbPaddingPx + thumbBorderWidthPx) + "px;\n" +
" --os-handle-border-radius: " + thumbRadiusPx + "px;\n" +
" --os-track-border-radius: 0;" +
" --os-track-bg: " + trackColor + ";\n" +
" --os-track-bg-active: " + trackColor + ";\n" +
" --os-track-bg-hover: " + trackHoveredColor + ";\n" +
" --os-handle-bg: " + thumbColor + ";\n" +
" --os-handle-bg-active: " + thumbColor + ";\n" +
" --os-handle-bg-hover: " + thumbHoveredColor + ";\n" +
" --os-handle-perpendicular-size: " + thumbSizePercent + "%;\n" +
" --os-handle-perpendicular-size-hover: " + thumbSizePercent + "%;\n" +
" --os-handle-perpendicular-size-active: " + thumbSizePercent + "%;\n" +
"}\n" +
".os-scrollbar-handle {" +
" outline: " + thumbBorderWidthPx + "px solid " + thumbBorderColor + ";\n" +
"}" +
".os-scrollbar-handle:hover {" +
" outline: " + thumbBorderWidthPx + "px solid " + thumbBorderHoveredColor + ";\n" +
"}" +
".os-scrollbar-handle:active {" +
" outline: " + thumbBorderWidthPx + "px solid " + thumbBorderHoveredColor + ";\n" +
"}"
;
}
private static int getTrackSizePx() {
return (int)(JBCefApp.normalizeScaledSize(SystemInfo.isMac ? 14 : 10) * UISettingsUtils.getInstance().getCurrentIdeScale());
}
private static int getThumbPaddingPx() {
return (int)(JBCefApp.normalizeScaledSize(SystemInfo.isMac ? 3 : 1) * UISettingsUtils.getInstance().getCurrentIdeScale());
}
private static int getThumbRadiusPx() {
return (int)(JBCefApp.normalizeScaledSize(SystemInfo.isMac ? 7 : 0) * UISettingsUtils.getInstance().getCurrentIdeScale());
}
private static @Nullable Integer getScrollbarAlpha(ColorKey colorKey) {
if (!LoadingState.CONFIGURATION_STORE_INITIALIZED.isOccurred() || !UISettings.getInstance().getUseContrastScrollbars()) {
return null;
}
final var contrastElementsKeys = List.of(
ScrollBarPainter.THUMB_OPAQUE_FOREGROUND,
ScrollBarPainter.THUMB_OPAQUE_BACKGROUND,
ScrollBarPainter.THUMB_OPAQUE_HOVERED_FOREGROUND,
ScrollBarPainter.THUMB_OPAQUE_HOVERED_BACKGROUND,
ScrollBarPainter.THUMB_FOREGROUND,
ScrollBarPainter.THUMB_BACKGROUND,
ScrollBarPainter.THUMB_HOVERED_FOREGROUND,
ScrollBarPainter.THUMB_HOVERED_BACKGROUND
);
if (!contrastElementsKeys.contains(colorKey)) {
return null;
}
int lightAlpha = SystemInfo.isMac ? 120 : 160;
int darkAlpha = SystemInfo.isMac ? 255 : 180;
int alpha = Registry.intValue("contrast.scrollbars.alpha.level");
if (alpha > 0) {
return Integer.min(alpha, 255);
}
return UIUtil.isUnderDarcula() ? darkAlpha : lightAlpha;
}
private static @NotNull String getCssColor(ColorKey key) {
EditorColorsScheme colorsScheme = EditorColorsManager.getInstance().getSchemeForCurrentUITheme();
Color color = ObjectUtils.notNull(colorsScheme.getColor(key), key.getDefaultColor());
double alpha = ObjectUtils.notNull(getScrollbarAlpha(key), color.getAlpha()) / 255.0;
return String.format(Locale.ROOT, "rgba(%d, %d, %d, %f)", color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
private static @NotNull String readResource(@NotNull String path) {
try {
return new String(FileUtil.loadBytes(Objects.requireNonNull(
JBCefApp.class.getResourceAsStream(path))), StandardCharsets.UTF_8);
}
catch (IOException | NullPointerException e) {
Logger.getInstance(JBCefScrollbarsHelper.class).error("couldn't find " + path, e);
}
return "";
}
}