| /* |
| * Copyright (c) 2008, 2009, Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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 "platform/scroll/ScrollbarThemeAura.h" |
| |
| #include "platform/LayoutTestSupport.h" |
| #include "platform/PlatformMouseEvent.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/graphics/GraphicsContext.h" |
| #include "platform/graphics/paint/DrawingRecorder.h" |
| #include "platform/scroll/ScrollableArea.h" |
| #include "platform/scroll/Scrollbar.h" |
| #include "platform/scroll/ScrollbarThemeOverlay.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebRect.h" |
| #include "public/platform/WebThemeEngine.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| static bool useMockTheme() { |
| return LayoutTestSupport::isRunningLayoutTest(); |
| } |
| |
| // Contains a flag indicating whether WebThemeEngine should paint a UI widget |
| // for a scrollbar part, and if so, what part and state apply. |
| // |
| // If the PartPaintingParams are not affected by a change in the scrollbar |
| // state, then the corresponding scrollbar part does not need to be repainted. |
| struct PartPaintingParams { |
| PartPaintingParams() |
| : shouldPaint(false), |
| part(WebThemeEngine::PartScrollbarDownArrow), |
| state(WebThemeEngine::StateNormal) {} |
| PartPaintingParams(WebThemeEngine::Part part, WebThemeEngine::State state) |
| : shouldPaint(true), part(part), state(state) {} |
| |
| bool shouldPaint; |
| WebThemeEngine::Part part; |
| WebThemeEngine::State state; |
| }; |
| |
| bool operator==(const PartPaintingParams& a, const PartPaintingParams& b) { |
| return (!a.shouldPaint && !b.shouldPaint) || |
| std::tie(a.shouldPaint, a.part, a.state) == |
| std::tie(b.shouldPaint, b.part, b.state); |
| } |
| |
| bool operator!=(const PartPaintingParams& a, const PartPaintingParams& b) { |
| return !(a == b); |
| } |
| |
| PartPaintingParams buttonPartPaintingParams( |
| const ScrollbarThemeClient& scrollbar, |
| float position, |
| ScrollbarPart part) { |
| WebThemeEngine::Part paintPart; |
| WebThemeEngine::State state = WebThemeEngine::StateNormal; |
| bool checkMin = false; |
| bool checkMax = false; |
| |
| if (scrollbar.orientation() == HorizontalScrollbar) { |
| if (part == BackButtonStartPart) { |
| paintPart = WebThemeEngine::PartScrollbarLeftArrow; |
| checkMin = true; |
| } else if (useMockTheme() && part != ForwardButtonEndPart) { |
| return PartPaintingParams(); |
| } else { |
| paintPart = WebThemeEngine::PartScrollbarRightArrow; |
| checkMax = true; |
| } |
| } else { |
| if (part == BackButtonStartPart) { |
| paintPart = WebThemeEngine::PartScrollbarUpArrow; |
| checkMin = true; |
| } else if (useMockTheme() && part != ForwardButtonEndPart) { |
| return PartPaintingParams(); |
| } else { |
| paintPart = WebThemeEngine::PartScrollbarDownArrow; |
| checkMax = true; |
| } |
| } |
| |
| if (useMockTheme() && !scrollbar.enabled()) { |
| state = WebThemeEngine::StateDisabled; |
| } else if (!useMockTheme() && |
| ((checkMin && (position <= 0)) || |
| (checkMax && position >= scrollbar.maximum()))) { |
| state = WebThemeEngine::StateDisabled; |
| } else { |
| if (part == scrollbar.pressedPart()) |
| state = WebThemeEngine::StatePressed; |
| else if (part == scrollbar.hoveredPart()) |
| state = WebThemeEngine::StateHover; |
| } |
| |
| return PartPaintingParams(paintPart, state); |
| } |
| |
| static int getScrollbarThickness() { |
| return Platform::current() |
| ->themeEngine() |
| ->getSize(WebThemeEngine::PartScrollbarVerticalThumb) |
| .width; |
| } |
| |
| } // namespace |
| |
| ScrollbarTheme& ScrollbarTheme::nativeTheme() { |
| if (RuntimeEnabledFeatures::overlayScrollbarsEnabled()) { |
| DEFINE_STATIC_LOCAL( |
| ScrollbarThemeOverlay, theme, |
| (getScrollbarThickness(), 0, ScrollbarThemeOverlay::AllowHitTest)); |
| return theme; |
| } |
| |
| DEFINE_STATIC_LOCAL(ScrollbarThemeAura, theme, ()); |
| return theme; |
| } |
| |
| int ScrollbarThemeAura::scrollbarThickness(ScrollbarControlSize controlSize) { |
| // Horiz and Vert scrollbars are the same thickness. |
| // In unit tests we don't have the mock theme engine (because of layering |
| // violations), so we hard code the size (see bug 327470). |
| if (useMockTheme()) |
| return 15; |
| IntSize scrollbarSize = Platform::current()->themeEngine()->getSize( |
| WebThemeEngine::PartScrollbarVerticalTrack); |
| return scrollbarSize.width(); |
| } |
| |
| bool ScrollbarThemeAura::hasThumb(const ScrollbarThemeClient& scrollbar) { |
| // This method is just called as a paint-time optimization to see if |
| // painting the thumb can be skipped. We don't have to be exact here. |
| return thumbLength(scrollbar) > 0; |
| } |
| |
| IntRect ScrollbarThemeAura::backButtonRect( |
| const ScrollbarThemeClient& scrollbar, |
| ScrollbarPart part, |
| bool) { |
| // Windows and Linux just have single arrows. |
| if (part == BackButtonEndPart) |
| return IntRect(); |
| |
| IntSize size = buttonSize(scrollbar); |
| return IntRect(scrollbar.x(), scrollbar.y(), size.width(), size.height()); |
| } |
| |
| IntRect ScrollbarThemeAura::forwardButtonRect( |
| const ScrollbarThemeClient& scrollbar, |
| ScrollbarPart part, |
| bool) { |
| // Windows and Linux just have single arrows. |
| if (part == ForwardButtonStartPart) |
| return IntRect(); |
| |
| IntSize size = buttonSize(scrollbar); |
| int x, y; |
| if (scrollbar.orientation() == HorizontalScrollbar) { |
| x = scrollbar.x() + scrollbar.width() - size.width(); |
| y = scrollbar.y(); |
| } else { |
| x = scrollbar.x(); |
| y = scrollbar.y() + scrollbar.height() - size.height(); |
| } |
| return IntRect(x, y, size.width(), size.height()); |
| } |
| |
| IntRect ScrollbarThemeAura::trackRect(const ScrollbarThemeClient& scrollbar, |
| bool) { |
| // The track occupies all space between the two buttons. |
| IntSize bs = buttonSize(scrollbar); |
| if (scrollbar.orientation() == HorizontalScrollbar) { |
| if (scrollbar.width() <= 2 * bs.width()) |
| return IntRect(); |
| return IntRect(scrollbar.x() + bs.width(), scrollbar.y(), |
| scrollbar.width() - 2 * bs.width(), scrollbar.height()); |
| } |
| if (scrollbar.height() <= 2 * bs.height()) |
| return IntRect(); |
| return IntRect(scrollbar.x(), scrollbar.y() + bs.height(), scrollbar.width(), |
| scrollbar.height() - 2 * bs.height()); |
| } |
| |
| int ScrollbarThemeAura::minimumThumbLength( |
| const ScrollbarThemeClient& scrollbar) { |
| if (scrollbar.orientation() == VerticalScrollbar) { |
| IntSize size = Platform::current()->themeEngine()->getSize( |
| WebThemeEngine::PartScrollbarVerticalThumb); |
| return size.height(); |
| } |
| |
| IntSize size = Platform::current()->themeEngine()->getSize( |
| WebThemeEngine::PartScrollbarHorizontalThumb); |
| return size.width(); |
| } |
| |
| void ScrollbarThemeAura::paintTrackBackground(GraphicsContext& context, |
| const Scrollbar& scrollbar, |
| const IntRect& rect) { |
| // Just assume a forward track part. We only paint the track as a single piece |
| // when there is no thumb. |
| if (!hasThumb(scrollbar) && !rect.isEmpty()) |
| paintTrackPiece(context, scrollbar, rect, ForwardTrackPart); |
| } |
| |
| void ScrollbarThemeAura::paintTrackPiece(GraphicsContext& gc, |
| const Scrollbar& scrollbar, |
| const IntRect& rect, |
| ScrollbarPart partType) { |
| DisplayItem::Type displayItemType = trackPiecePartToDisplayItemType(partType); |
| if (DrawingRecorder::useCachedDrawingIfPossible(gc, scrollbar, |
| displayItemType)) |
| return; |
| |
| DrawingRecorder recorder(gc, scrollbar, displayItemType, rect); |
| |
| WebThemeEngine::State state = scrollbar.hoveredPart() == partType |
| ? WebThemeEngine::StateHover |
| : WebThemeEngine::StateNormal; |
| |
| if (useMockTheme() && !scrollbar.enabled()) |
| state = WebThemeEngine::StateDisabled; |
| |
| IntRect alignRect = trackRect(scrollbar, false); |
| WebThemeEngine::ExtraParams extraParams; |
| extraParams.scrollbarTrack.isBack = (partType == BackTrackPart); |
| extraParams.scrollbarTrack.trackX = alignRect.x(); |
| extraParams.scrollbarTrack.trackY = alignRect.y(); |
| extraParams.scrollbarTrack.trackWidth = alignRect.width(); |
| extraParams.scrollbarTrack.trackHeight = alignRect.height(); |
| Platform::current()->themeEngine()->paint( |
| gc.canvas(), scrollbar.orientation() == HorizontalScrollbar |
| ? WebThemeEngine::PartScrollbarHorizontalTrack |
| : WebThemeEngine::PartScrollbarVerticalTrack, |
| state, WebRect(rect), &extraParams); |
| } |
| |
| void ScrollbarThemeAura::paintButton(GraphicsContext& gc, |
| const Scrollbar& scrollbar, |
| const IntRect& rect, |
| ScrollbarPart part) { |
| DisplayItem::Type displayItemType = buttonPartToDisplayItemType(part); |
| if (DrawingRecorder::useCachedDrawingIfPossible(gc, scrollbar, |
| displayItemType)) |
| return; |
| PartPaintingParams params = |
| buttonPartPaintingParams(scrollbar, scrollbar.currentPos(), part); |
| if (!params.shouldPaint) |
| return; |
| DrawingRecorder recorder(gc, scrollbar, displayItemType, rect); |
| Platform::current()->themeEngine()->paint( |
| gc.canvas(), params.part, params.state, WebRect(rect), nullptr); |
| } |
| |
| void ScrollbarThemeAura::paintThumb(GraphicsContext& gc, |
| const Scrollbar& scrollbar, |
| const IntRect& rect) { |
| if (DrawingRecorder::useCachedDrawingIfPossible(gc, scrollbar, |
| DisplayItem::kScrollbarThumb)) |
| return; |
| |
| DrawingRecorder recorder(gc, scrollbar, DisplayItem::kScrollbarThumb, rect); |
| |
| WebThemeEngine::State state; |
| WebCanvas* canvas = gc.canvas(); |
| if (scrollbar.pressedPart() == ThumbPart) |
| state = WebThemeEngine::StatePressed; |
| else if (scrollbar.hoveredPart() == ThumbPart) |
| state = WebThemeEngine::StateHover; |
| else |
| state = WebThemeEngine::StateNormal; |
| |
| Platform::current()->themeEngine()->paint( |
| canvas, scrollbar.orientation() == HorizontalScrollbar |
| ? WebThemeEngine::PartScrollbarHorizontalThumb |
| : WebThemeEngine::PartScrollbarVerticalThumb, |
| state, WebRect(rect), nullptr); |
| } |
| |
| bool ScrollbarThemeAura::shouldRepaintAllPartsOnInvalidation() const { |
| // This theme can separately handle thumb invalidation. |
| return false; |
| } |
| |
| ScrollbarPart ScrollbarThemeAura::invalidateOnThumbPositionChange( |
| const ScrollbarThemeClient& scrollbar, |
| float oldPosition, |
| float newPosition) const { |
| ScrollbarPart invalidParts = NoPart; |
| ASSERT(buttonsPlacement() == WebScrollbarButtonsPlacementSingle); |
| static const ScrollbarPart kButtonParts[] = {BackButtonStartPart, |
| ForwardButtonEndPart}; |
| for (ScrollbarPart part : kButtonParts) { |
| if (buttonPartPaintingParams(scrollbar, oldPosition, part) != |
| buttonPartPaintingParams(scrollbar, newPosition, part)) |
| invalidParts = static_cast<ScrollbarPart>(invalidParts | part); |
| } |
| return invalidParts; |
| } |
| |
| bool ScrollbarThemeAura::hasScrollbarButtons( |
| ScrollbarOrientation orientation) const { |
| WebThemeEngine* themeEngine = Platform::current()->themeEngine(); |
| if (orientation == VerticalScrollbar) { |
| return !themeEngine->getSize(WebThemeEngine::PartScrollbarDownArrow) |
| .isEmpty(); |
| } |
| return !themeEngine->getSize(WebThemeEngine::PartScrollbarLeftArrow) |
| .isEmpty(); |
| }; |
| |
| IntSize ScrollbarThemeAura::buttonSize(const ScrollbarThemeClient& scrollbar) { |
| if (!hasScrollbarButtons(scrollbar.orientation())) |
| return IntSize(0, 0); |
| |
| if (scrollbar.orientation() == VerticalScrollbar) { |
| int squareSize = scrollbar.width(); |
| return IntSize(squareSize, scrollbar.height() < 2 * squareSize |
| ? scrollbar.height() / 2 |
| : squareSize); |
| } |
| |
| // HorizontalScrollbar |
| int squareSize = scrollbar.height(); |
| return IntSize( |
| scrollbar.width() < 2 * squareSize ? scrollbar.width() / 2 : squareSize, |
| squareSize); |
| } |
| |
| } // namespace blink |