Home | History | Annotate | Download | only in scroll
      1 /*
      2  * Copyright (C) 2011 Apple Inc. All Rights Reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "platform/scroll/ScrollbarTheme.h"
     28 
     29 #include "platform/PlatformMouseEvent.h"
     30 #include "platform/RuntimeEnabledFeatures.h"
     31 #include "platform/graphics/Color.h"
     32 #include "platform/graphics/GraphicsContext.h"
     33 #include "platform/scroll/ScrollbarThemeClient.h"
     34 #include "platform/scroll/ScrollbarThemeMock.h"
     35 #include "platform/scroll/ScrollbarThemeOverlayMock.h"
     36 #include "public/platform/Platform.h"
     37 #include "public/platform/WebPoint.h"
     38 #include "public/platform/WebRect.h"
     39 #include "public/platform/WebScrollbarBehavior.h"
     40 
     41 #if !OS(MACOSX)
     42 #include "public/platform/WebRect.h"
     43 #include "public/platform/WebThemeEngine.h"
     44 #endif
     45 
     46 namespace blink {
     47 
     48 bool ScrollbarTheme::gMockScrollbarsEnabled = false;
     49 
     50 bool ScrollbarTheme::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect)
     51 {
     52     // Create the ScrollbarControlPartMask based on the damageRect
     53     ScrollbarControlPartMask scrollMask = NoPart;
     54 
     55     IntRect backButtonStartPaintRect;
     56     IntRect backButtonEndPaintRect;
     57     IntRect forwardButtonStartPaintRect;
     58     IntRect forwardButtonEndPaintRect;
     59     if (hasButtons(scrollbar)) {
     60         backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
     61         if (damageRect.intersects(backButtonStartPaintRect))
     62             scrollMask |= BackButtonStartPart;
     63         backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
     64         if (damageRect.intersects(backButtonEndPaintRect))
     65             scrollMask |= BackButtonEndPart;
     66         forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
     67         if (damageRect.intersects(forwardButtonStartPaintRect))
     68             scrollMask |= ForwardButtonStartPart;
     69         forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
     70         if (damageRect.intersects(forwardButtonEndPaintRect))
     71             scrollMask |= ForwardButtonEndPart;
     72     }
     73 
     74     IntRect startTrackRect;
     75     IntRect thumbRect;
     76     IntRect endTrackRect;
     77     IntRect trackPaintRect = trackRect(scrollbar, true);
     78     if (damageRect.intersects(trackPaintRect))
     79         scrollMask |= TrackBGPart;
     80     bool thumbPresent = hasThumb(scrollbar);
     81     if (thumbPresent) {
     82         IntRect track = trackRect(scrollbar);
     83         splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
     84         if (damageRect.intersects(thumbRect))
     85             scrollMask |= ThumbPart;
     86         if (damageRect.intersects(startTrackRect))
     87             scrollMask |= BackTrackPart;
     88         if (damageRect.intersects(endTrackRect))
     89             scrollMask |= ForwardTrackPart;
     90     }
     91 
     92     // Paint the scrollbar background (only used by custom CSS scrollbars).
     93     paintScrollbarBackground(graphicsContext, scrollbar);
     94 
     95     // Paint the back and forward buttons.
     96     if (scrollMask & BackButtonStartPart)
     97         paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
     98     if (scrollMask & BackButtonEndPart)
     99         paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
    100     if (scrollMask & ForwardButtonStartPart)
    101         paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
    102     if (scrollMask & ForwardButtonEndPart)
    103         paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
    104 
    105     if (scrollMask & TrackBGPart)
    106         paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
    107 
    108     if ((scrollMask & ForwardTrackPart) || (scrollMask & BackTrackPart)) {
    109         // Paint the track pieces above and below the thumb.
    110         if (scrollMask & BackTrackPart)
    111             paintTrackPiece(graphicsContext, scrollbar, startTrackRect, BackTrackPart);
    112         if (scrollMask & ForwardTrackPart)
    113             paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart);
    114 
    115         paintTickmarks(graphicsContext, scrollbar, trackPaintRect);
    116     }
    117 
    118     // Paint the thumb.
    119     if (scrollMask & ThumbPart)
    120         paintThumb(graphicsContext, scrollbar, thumbRect);
    121 
    122     return true;
    123 }
    124 
    125 ScrollbarPart ScrollbarTheme::hitTest(ScrollbarThemeClient* scrollbar, const IntPoint& position)
    126 {
    127     ScrollbarPart result = NoPart;
    128     if (!scrollbar->enabled())
    129         return result;
    130 
    131     IntPoint testPosition = scrollbar->convertFromContainingWindow(position);
    132     testPosition.move(scrollbar->x(), scrollbar->y());
    133 
    134     if (!scrollbar->frameRect().contains(testPosition))
    135         return NoPart;
    136 
    137     result = ScrollbarBGPart;
    138 
    139     IntRect track = trackRect(scrollbar);
    140     if (track.contains(testPosition)) {
    141         IntRect beforeThumbRect;
    142         IntRect thumbRect;
    143         IntRect afterThumbRect;
    144         splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect);
    145         if (thumbRect.contains(testPosition))
    146             result = ThumbPart;
    147         else if (beforeThumbRect.contains(testPosition))
    148             result = BackTrackPart;
    149         else if (afterThumbRect.contains(testPosition))
    150             result = ForwardTrackPart;
    151         else
    152             result = TrackBGPart;
    153     } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(testPosition)) {
    154         result = BackButtonStartPart;
    155     } else if (backButtonRect(scrollbar, BackButtonEndPart).contains(testPosition)) {
    156         result = BackButtonEndPart;
    157     } else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(testPosition)) {
    158         result = ForwardButtonStartPart;
    159     } else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(testPosition)) {
    160         result = ForwardButtonEndPart;
    161     }
    162     return result;
    163 }
    164 
    165 void ScrollbarTheme::invalidatePart(ScrollbarThemeClient* scrollbar, ScrollbarPart part)
    166 {
    167     if (part == NoPart)
    168         return;
    169 
    170     IntRect result;
    171     switch (part) {
    172     case BackButtonStartPart:
    173         result = backButtonRect(scrollbar, BackButtonStartPart, true);
    174         break;
    175     case BackButtonEndPart:
    176         result = backButtonRect(scrollbar, BackButtonEndPart, true);
    177         break;
    178     case ForwardButtonStartPart:
    179         result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
    180         break;
    181     case ForwardButtonEndPart:
    182         result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
    183         break;
    184     case TrackBGPart:
    185         result = trackRect(scrollbar, true);
    186         break;
    187     case ScrollbarBGPart:
    188         result = scrollbar->frameRect();
    189         break;
    190     default: {
    191         IntRect beforeThumbRect, thumbRect, afterThumbRect;
    192         splitTrack(scrollbar, trackRect(scrollbar), beforeThumbRect, thumbRect, afterThumbRect);
    193         if (part == BackTrackPart)
    194             result = beforeThumbRect;
    195         else if (part == ForwardTrackPart)
    196             result = afterThumbRect;
    197         else
    198             result = thumbRect;
    199     }
    200     }
    201     result.moveBy(-scrollbar->location());
    202     scrollbar->invalidateRect(result);
    203 }
    204 
    205 void ScrollbarTheme::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect)
    206 {
    207     if (cornerRect.isEmpty())
    208         return;
    209 
    210 #if OS(MACOSX)
    211     context->fillRect(cornerRect, Color::white);
    212 #else
    213     blink::Platform::current()->themeEngine()->paint(context->canvas(), blink::WebThemeEngine::PartScrollbarCorner, blink::WebThemeEngine::StateNormal, blink::WebRect(cornerRect), 0);
    214 #endif
    215 }
    216 
    217 void ScrollbarTheme::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
    218 {
    219     context->setFillColor(Color::white);
    220     if (!horizontalOverhangRect.isEmpty())
    221         context->fillRect(intersection(horizontalOverhangRect, dirtyRect));
    222     if (!verticalOverhangRect.isEmpty())
    223         context->fillRect(intersection(verticalOverhangRect, dirtyRect));
    224 }
    225 
    226 bool ScrollbarTheme::shouldCenterOnThumb(ScrollbarThemeClient* scrollbar, const PlatformMouseEvent& evt)
    227 {
    228     return blink::Platform::current()->scrollbarBehavior()->shouldCenterOnThumb(static_cast<blink::WebScrollbarBehavior::Button>(evt.button()), evt.shiftKey(), evt.altKey());
    229 }
    230 
    231 bool ScrollbarTheme::shouldSnapBackToDragOrigin(ScrollbarThemeClient* scrollbar, const PlatformMouseEvent& evt)
    232 {
    233     IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.position());
    234     mousePosition.move(scrollbar->x(), scrollbar->y());
    235     return blink::Platform::current()->scrollbarBehavior()->shouldSnapBackToDragOrigin(mousePosition, trackRect(scrollbar), scrollbar->orientation() == HorizontalScrollbar);
    236 }
    237 
    238 // Returns the size represented by track taking into account scrolling past
    239 // the end of the document.
    240 static float usedTotalSize(ScrollbarThemeClient* scrollbar)
    241 {
    242     float overhangAtStart = -scrollbar->currentPos();
    243     float overhangAtEnd = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
    244     float overhang = std::max(0.0f, std::max(overhangAtStart, overhangAtEnd));
    245     return scrollbar->totalSize() + overhang;
    246 }
    247 
    248 int ScrollbarTheme::thumbPosition(ScrollbarThemeClient* scrollbar)
    249 {
    250     if (scrollbar->enabled()) {
    251         float size = usedTotalSize(scrollbar) - scrollbar->visibleSize();
    252         // Avoid doing a floating point divide by zero and return 1 when usedTotalSize == visibleSize.
    253         if (!size)
    254             return 1;
    255         float pos = std::max(0.0f, scrollbar->currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / size;
    256         return (pos < 1 && pos > 0) ? 1 : pos;
    257     }
    258     return 0;
    259 }
    260 
    261 int ScrollbarTheme::thumbLength(ScrollbarThemeClient* scrollbar)
    262 {
    263     if (!scrollbar->enabled())
    264         return 0;
    265 
    266     float overhang = 0;
    267     if (scrollbar->currentPos() < 0)
    268         overhang = -scrollbar->currentPos();
    269     else if (scrollbar->visibleSize() + scrollbar->currentPos() > scrollbar->totalSize())
    270         overhang = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
    271     float proportion = 0.0f;
    272     float totalSize = usedTotalSize(scrollbar);
    273     if (totalSize > 0.0f) {
    274         proportion = (scrollbar->visibleSize() - overhang) / totalSize;
    275     }
    276     int trackLen = trackLength(scrollbar);
    277     int length = round(proportion * trackLen);
    278     length = std::max(length, minimumThumbLength(scrollbar));
    279     if (length > trackLen)
    280         length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track).
    281     return length;
    282 }
    283 
    284 int ScrollbarTheme::trackPosition(ScrollbarThemeClient* scrollbar)
    285 {
    286     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
    287     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y();
    288 }
    289 
    290 int ScrollbarTheme::trackLength(ScrollbarThemeClient* scrollbar)
    291 {
    292     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
    293     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
    294 }
    295 
    296 IntRect ScrollbarTheme::thumbRect(ScrollbarThemeClient* scrollbar)
    297 {
    298     if (!hasThumb(scrollbar))
    299         return IntRect();
    300 
    301     IntRect track = trackRect(scrollbar);
    302     IntRect startTrackRect;
    303     IntRect thumbRect;
    304     IntRect endTrackRect;
    305     splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
    306 
    307     return thumbRect;
    308 }
    309 
    310 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar)
    311 {
    312     IntRect track = trackRect(scrollbar);
    313     return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width();
    314 }
    315 
    316 int ScrollbarTheme::minimumThumbLength(ScrollbarThemeClient* scrollbar)
    317 {
    318     return scrollbarThickness(scrollbar->controlSize());
    319 }
    320 
    321 void ScrollbarTheme::splitTrack(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
    322 {
    323     // This function won't even get called unless we're big enough to have some combination of these three rects where at least
    324     // one of them is non-empty.
    325     IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
    326     int thumbPos = thumbPosition(scrollbar);
    327     if (scrollbar->orientation() == HorizontalScrollbar) {
    328         thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y(), thumbLength(scrollbar), scrollbar->height());
    329         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height());
    330         afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.maxX() - beforeThumbRect.maxX(), trackRect.height());
    331     } else {
    332         thumbRect = IntRect(trackRect.x(), trackRect.y() + thumbPos, scrollbar->width(), thumbLength(scrollbar));
    333         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2);
    334         afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.maxY() - beforeThumbRect.maxY());
    335     }
    336 }
    337 
    338 ScrollbarTheme* ScrollbarTheme::theme()
    339 {
    340     if (ScrollbarTheme::mockScrollbarsEnabled()) {
    341         if (RuntimeEnabledFeatures::overlayScrollbarsEnabled()) {
    342             DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, overlayMockTheme, ());
    343             return &overlayMockTheme;
    344         }
    345 
    346         DEFINE_STATIC_LOCAL(ScrollbarThemeMock, mockTheme, ());
    347         return &mockTheme;
    348     }
    349     return nativeTheme();
    350 }
    351 
    352 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag)
    353 {
    354     gMockScrollbarsEnabled = flag;
    355 }
    356 
    357 bool ScrollbarTheme::mockScrollbarsEnabled()
    358 {
    359     return gMockScrollbarsEnabled;
    360 }
    361 
    362 }
    363