ScrollbarThemeAdwaita.cpp   [plain text]


/*
 * Copyright (C) 2014, 2020 Igalia S.L.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "ScrollbarThemeAdwaita.h"

#include "Color.h"
#include "FloatRoundedRect.h"
#include "GraphicsContext.h"
#include "PlatformMouseEvent.h"
#include "ScrollableArea.h"
#include "Scrollbar.h"
#include "ThemeAdwaita.h"

namespace WebCore {

static const unsigned scrollbarSize = 13;
static const unsigned hoveredScrollbarBorderSize = 1;
static const unsigned thumbBorderSize = 1;
static const unsigned overlayThumbSize = 5;
static const unsigned minimumThumbSize = 40;
static const unsigned thumbSize = 6;
static const double scrollbarOpacity = 0.8;
static constexpr auto scrollbarBackgroundColor = SRGBA<uint8_t> { 252, 252, 252 };
static constexpr auto scrollbarBorderColor = SRGBA<uint8_t> { 220, 223, 227 };
static constexpr auto overlayThumbBorderColor = Color::white.colorWithAlphaByte(100);
static constexpr auto overlayThumbColor = SRGBA<uint8_t> { 46, 52, 54, 100 };
static constexpr auto thumbHoveredColor = SRGBA<uint8_t> { 86, 91, 92 };
static constexpr auto thumbPressedColor = SRGBA<uint8_t> { 27, 106, 203 };
static constexpr auto thumbColor = SRGBA<uint8_t> { 126, 129, 130 };

bool ScrollbarThemeAdwaita::usesOverlayScrollbars() const
{
#if PLATFORM(GTK)
    static bool shouldUuseOverlayScrollbars = g_strcmp0(g_getenv("GTK_OVERLAY_SCROLLING"), "0");
    return shouldUuseOverlayScrollbars;
#else
    return true;
#endif
}

int ScrollbarThemeAdwaita::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
{
    return scrollbarSize;
}

int ScrollbarThemeAdwaita::minimumThumbLength(Scrollbar&)
{
    return minimumThumbSize;
}

bool ScrollbarThemeAdwaita::hasButtons(Scrollbar&)
{
    return false;
}

bool ScrollbarThemeAdwaita::hasThumb(Scrollbar& scrollbar)
{
    return thumbLength(scrollbar) > 0;
}

IntRect ScrollbarThemeAdwaita::backButtonRect(Scrollbar&, ScrollbarPart, bool)
{
    return { };
}

IntRect ScrollbarThemeAdwaita::forwardButtonRect(Scrollbar&, ScrollbarPart, bool)
{
    return { };
}

IntRect ScrollbarThemeAdwaita::trackRect(Scrollbar& scrollbar, bool)
{
    return scrollbar.frameRect();
}

bool ScrollbarThemeAdwaita::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
{
    if (graphicsContext.paintingDisabled())
        return false;

    if (!scrollbar.enabled())
        return true;

    IntRect rect = scrollbar.frameRect();
    if (!rect.intersects(damageRect))
        return true;

    double opacity;
    if (usesOverlayScrollbars())
        opacity = scrollbar.hoveredPart() == NoPart ? scrollbar.opacity() : scrollbarOpacity;
    else
        opacity = 1;
    if (!opacity)
        return true;

    GraphicsContextStateSaver stateSaver(graphicsContext);
    if (opacity != 1) {
        graphicsContext.clip(damageRect);
        graphicsContext.beginTransparencyLayer(opacity);
    }

    if (scrollbar.hoveredPart() != NoPart || !usesOverlayScrollbars()) {
        graphicsContext.fillRect(rect, scrollbarBackgroundColor);

        IntRect frame = rect;
        if (scrollbar.orientation() == VerticalScrollbar) {
            if (scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
                frame.move(frame.width() - hoveredScrollbarBorderSize, 0);
            frame.setWidth(hoveredScrollbarBorderSize);
        } else
            frame.setHeight(hoveredScrollbarBorderSize);
        graphicsContext.fillRect(frame, scrollbarBorderColor);
    }

    int thumbPos = thumbPosition(scrollbar);
    int thumbLen = thumbLength(scrollbar);
    IntRect thumb = rect;
    if (scrollbar.hoveredPart() == NoPart && usesOverlayScrollbars()) {
        if (scrollbar.orientation() == VerticalScrollbar) {
            if (scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
                thumb.move(hoveredScrollbarBorderSize, thumbPos + thumbBorderSize);
            else
                thumb.move(scrollbarSize - (overlayThumbSize + thumbBorderSize) + hoveredScrollbarBorderSize, thumbPos + thumbBorderSize);
            thumb.setWidth(overlayThumbSize);
            thumb.setHeight(thumbLen - thumbBorderSize * 2);
        } else {
            thumb.move(thumbPos + thumbBorderSize, scrollbarSize - (overlayThumbSize + thumbBorderSize) + hoveredScrollbarBorderSize);
            thumb.setWidth(thumbLen - thumbBorderSize * 2);
            thumb.setHeight(overlayThumbSize);
        }
    } else {
        if (scrollbar.orientation() == VerticalScrollbar) {
            if (scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft())
                thumb.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2) - hoveredScrollbarBorderSize, thumbPos + thumbBorderSize);
            else
                thumb.move(scrollbarSize - (scrollbarSize / 2 + thumbSize / 2), thumbPos + thumbBorderSize);
            thumb.setWidth(thumbSize);
            thumb.setHeight(thumbLen - thumbBorderSize * 2);
        } else {
            thumb.move(thumbPos + thumbBorderSize, scrollbarSize - (scrollbarSize / 2 + thumbSize / 2));
            thumb.setWidth(thumbLen - thumbBorderSize * 2);
            thumb.setHeight(thumbSize);
        }
    }

    FloatSize corner(4, 4);
    Path path;
    if (scrollbar.hoveredPart() == NoPart && usesOverlayScrollbars()) {
        path.addRoundedRect(thumb, corner);
        thumb.inflate(-1);
        path.addRoundedRect(thumb, corner);
        graphicsContext.setFillRule(WindRule::EvenOdd);
        graphicsContext.setFillColor(overlayThumbBorderColor);
        graphicsContext.fillPath(path);
        path.clear();
    }

    path.addRoundedRect(thumb, corner);
    graphicsContext.setFillRule(WindRule::NonZero);
    if (scrollbar.hoveredPart() == NoPart && usesOverlayScrollbars())
        graphicsContext.setFillColor(overlayThumbColor);
    else if (scrollbar.pressedPart() == ThumbPart)
        graphicsContext.setFillColor(static_cast<ThemeAdwaita&>(Theme::singleton()).activeSelectionBackgroundColor());
    else if (scrollbar.hoveredPart() == ThumbPart)
        graphicsContext.setFillColor(thumbHoveredColor);
    else
        graphicsContext.setFillColor(thumbColor);
    graphicsContext.fillPath(path);

    if (opacity != 1)
        graphicsContext.endTransparencyLayer();

    return true;
}

ScrollbarButtonPressAction ScrollbarThemeAdwaita::handleMousePressEvent(Scrollbar&, const PlatformMouseEvent& event, ScrollbarPart pressedPart)
{
    switch (pressedPart) {
    case BackTrackPart:
    case ForwardTrackPart:
        // The shift key or middle/right button reverses the sense.
        if (event.shiftKey() || event.button() != LeftButton)
            return ScrollbarButtonPressAction::CenterOnThumb;
        return ScrollbarButtonPressAction::Scroll;
    case ThumbPart:
        if (event.button() != RightButton)
            return ScrollbarButtonPressAction::StartDrag;
        break;
    case BackButtonStartPart:
    case ForwardButtonStartPart:
    case BackButtonEndPart:
    case ForwardButtonEndPart:
        return ScrollbarButtonPressAction::Scroll;
    default:
        break;
    }

    return ScrollbarButtonPressAction::None;
}

#if !PLATFORM(GTK) || USE(GTK4)
ScrollbarTheme& ScrollbarTheme::nativeTheme()
{
    static ScrollbarThemeAdwaita theme;
    return theme;
}
#endif

} // namespace WebCore