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 "RuntimeEnabledFeatures.h"
     30 #include "platform/scroll/ScrollbarThemeClient.h"
     31 #include "platform/scroll/ScrollbarThemeMock.h"
     32 #include "platform/scroll/ScrollbarThemeOverlayMock.h"
     33 
     34 namespace WebCore {
     35 
     36 ScrollbarTheme* ScrollbarTheme::theme()
     37 {
     38     if (ScrollbarTheme::mockScrollbarsEnabled()) {
     39         if (RuntimeEnabledFeatures::overlayScrollbarsEnabled()) {
     40             DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, overlayMockTheme, ());
     41             return &overlayMockTheme;
     42         }
     43 
     44         DEFINE_STATIC_LOCAL(ScrollbarThemeMock, mockTheme, ());
     45         return &mockTheme;
     46     }
     47     return nativeTheme();
     48 }
     49 
     50 bool ScrollbarTheme::gMockScrollbarsEnabled = false;
     51 
     52 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag)
     53 {
     54     gMockScrollbarsEnabled = flag;
     55 }
     56 
     57 bool ScrollbarTheme::mockScrollbarsEnabled()
     58 {
     59     return gMockScrollbarsEnabled;
     60 }
     61 
     62 bool ScrollbarTheme::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect)
     63 {
     64     // Create the ScrollbarControlPartMask based on the damageRect
     65     ScrollbarControlPartMask scrollMask = NoPart;
     66 
     67     IntRect backButtonStartPaintRect;
     68     IntRect backButtonEndPaintRect;
     69     IntRect forwardButtonStartPaintRect;
     70     IntRect forwardButtonEndPaintRect;
     71     if (hasButtons(scrollbar)) {
     72         backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
     73         if (damageRect.intersects(backButtonStartPaintRect))
     74             scrollMask |= BackButtonStartPart;
     75         backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
     76         if (damageRect.intersects(backButtonEndPaintRect))
     77             scrollMask |= BackButtonEndPart;
     78         forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
     79         if (damageRect.intersects(forwardButtonStartPaintRect))
     80             scrollMask |= ForwardButtonStartPart;
     81         forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
     82         if (damageRect.intersects(forwardButtonEndPaintRect))
     83             scrollMask |= ForwardButtonEndPart;
     84     }
     85 
     86     IntRect startTrackRect;
     87     IntRect thumbRect;
     88     IntRect endTrackRect;
     89     IntRect trackPaintRect = trackRect(scrollbar, true);
     90     if (damageRect.intersects(trackPaintRect))
     91         scrollMask |= TrackBGPart;
     92     bool thumbPresent = hasThumb(scrollbar);
     93     if (thumbPresent) {
     94         IntRect track = trackRect(scrollbar);
     95         splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
     96         if (damageRect.intersects(thumbRect))
     97             scrollMask |= ThumbPart;
     98         if (damageRect.intersects(startTrackRect))
     99             scrollMask |= BackTrackPart;
    100         if (damageRect.intersects(endTrackRect))
    101             scrollMask |= ForwardTrackPart;
    102     }
    103 
    104     // Paint the scrollbar background (only used by custom CSS scrollbars).
    105     paintScrollbarBackground(graphicsContext, scrollbar);
    106 
    107     // Paint the back and forward buttons.
    108     if (scrollMask & BackButtonStartPart)
    109         paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
    110     if (scrollMask & BackButtonEndPart)
    111         paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
    112     if (scrollMask & ForwardButtonStartPart)
    113         paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
    114     if (scrollMask & ForwardButtonEndPart)
    115         paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
    116 
    117     if (scrollMask & TrackBGPart)
    118         paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
    119 
    120     if ((scrollMask & ForwardTrackPart) || (scrollMask & BackTrackPart)) {
    121         // Paint the track pieces above and below the thumb.
    122         if (scrollMask & BackTrackPart)
    123             paintTrackPiece(graphicsContext, scrollbar, startTrackRect, BackTrackPart);
    124         if (scrollMask & ForwardTrackPart)
    125             paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart);
    126 
    127         paintTickmarks(graphicsContext, scrollbar, trackPaintRect);
    128     }
    129 
    130     // Paint the thumb.
    131     if (scrollMask & ThumbPart)
    132         paintThumb(graphicsContext, scrollbar, thumbRect);
    133 
    134     return true;
    135 }
    136 
    137 ScrollbarPart ScrollbarTheme::hitTest(ScrollbarThemeClient* scrollbar, const IntPoint& position)
    138 {
    139     ScrollbarPart result = NoPart;
    140     if (!scrollbar->enabled())
    141         return result;
    142 
    143     IntPoint testPosition = scrollbar->convertFromContainingWindow(position);
    144     testPosition.move(scrollbar->x(), scrollbar->y());
    145 
    146     if (!scrollbar->frameRect().contains(testPosition))
    147         return NoPart;
    148 
    149     result = ScrollbarBGPart;
    150 
    151     IntRect track = trackRect(scrollbar);
    152     if (track.contains(testPosition)) {
    153         IntRect beforeThumbRect;
    154         IntRect thumbRect;
    155         IntRect afterThumbRect;
    156         splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect);
    157         if (thumbRect.contains(testPosition))
    158             result = ThumbPart;
    159         else if (beforeThumbRect.contains(testPosition))
    160             result = BackTrackPart;
    161         else if (afterThumbRect.contains(testPosition))
    162             result = ForwardTrackPart;
    163         else
    164             result = TrackBGPart;
    165     } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(testPosition)) {
    166         result = BackButtonStartPart;
    167     } else if (backButtonRect(scrollbar, BackButtonEndPart).contains(testPosition)) {
    168         result = BackButtonEndPart;
    169     } else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(testPosition)) {
    170         result = ForwardButtonStartPart;
    171     } else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(testPosition)) {
    172         result = ForwardButtonEndPart;
    173     }
    174     return result;
    175 }
    176 
    177 void ScrollbarTheme::invalidatePart(ScrollbarThemeClient* scrollbar, ScrollbarPart part)
    178 {
    179     if (part == NoPart)
    180         return;
    181 
    182     IntRect result;
    183     switch (part) {
    184     case BackButtonStartPart:
    185         result = backButtonRect(scrollbar, BackButtonStartPart, true);
    186         break;
    187     case BackButtonEndPart:
    188         result = backButtonRect(scrollbar, BackButtonEndPart, true);
    189         break;
    190     case ForwardButtonStartPart:
    191         result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
    192         break;
    193     case ForwardButtonEndPart:
    194         result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
    195         break;
    196     case TrackBGPart:
    197         result = trackRect(scrollbar, true);
    198         break;
    199     case ScrollbarBGPart:
    200         result = scrollbar->frameRect();
    201         break;
    202     default: {
    203         IntRect beforeThumbRect, thumbRect, afterThumbRect;
    204         splitTrack(scrollbar, trackRect(scrollbar), beforeThumbRect, thumbRect, afterThumbRect);
    205         if (part == BackTrackPart)
    206             result = beforeThumbRect;
    207         else if (part == ForwardTrackPart)
    208             result = afterThumbRect;
    209         else
    210             result = thumbRect;
    211     }
    212     }
    213     result.moveBy(-scrollbar->location());
    214     scrollbar->invalidateRect(result);
    215 }
    216 
    217 void ScrollbarTheme::splitTrack(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
    218 {
    219     // This function won't even get called unless we're big enough to have some combination of these three rects where at least
    220     // one of them is non-empty.
    221     IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
    222     int thumbPos = thumbPosition(scrollbar);
    223     if (scrollbar->orientation() == HorizontalScrollbar) {
    224         thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y(), thumbLength(scrollbar), scrollbar->height());
    225         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height());
    226         afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.maxX() - beforeThumbRect.maxX(), trackRect.height());
    227     } else {
    228         thumbRect = IntRect(trackRect.x(), trackRect.y() + thumbPos, scrollbar->width(), thumbLength(scrollbar));
    229         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2);
    230         afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.maxY() - beforeThumbRect.maxY());
    231     }
    232 }
    233 
    234 // Returns the size represented by track taking into account scrolling past
    235 // the end of the document.
    236 static float usedTotalSize(ScrollbarThemeClient* scrollbar)
    237 {
    238     float overhangAtStart = -scrollbar->currentPos();
    239     float overhangAtEnd = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
    240     float overhang = std::max(0.0f, std::max(overhangAtStart, overhangAtEnd));
    241     return scrollbar->totalSize() + overhang;
    242 }
    243 
    244 int ScrollbarTheme::thumbPosition(ScrollbarThemeClient* scrollbar)
    245 {
    246     if (scrollbar->enabled()) {
    247         float size = usedTotalSize(scrollbar) - scrollbar->visibleSize();
    248         // Avoid doing a floating point divide by zero and return 1 when usedTotalSize == visibleSize.
    249         if (!size)
    250             return 1;
    251         float pos = std::max(0.0f, scrollbar->currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / size;
    252         return (pos < 1 && pos > 0) ? 1 : pos;
    253     }
    254     return 0;
    255 }
    256 
    257 int ScrollbarTheme::thumbLength(ScrollbarThemeClient* scrollbar)
    258 {
    259     if (!scrollbar->enabled())
    260         return 0;
    261 
    262     float overhang = 0;
    263     if (scrollbar->currentPos() < 0)
    264         overhang = -scrollbar->currentPos();
    265     else if (scrollbar->visibleSize() + scrollbar->currentPos() > scrollbar->totalSize())
    266         overhang = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
    267     float proportion = (scrollbar->visibleSize() - overhang) / usedTotalSize(scrollbar);
    268     int trackLen = trackLength(scrollbar);
    269     int length = round(proportion * trackLen);
    270     length = std::max(length, minimumThumbLength(scrollbar));
    271     if (length > trackLen)
    272         length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track).
    273     return length;
    274 }
    275 
    276 int ScrollbarTheme::minimumThumbLength(ScrollbarThemeClient* scrollbar)
    277 {
    278     return scrollbarThickness(scrollbar->controlSize());
    279 }
    280 
    281 int ScrollbarTheme::trackPosition(ScrollbarThemeClient* scrollbar)
    282 {
    283     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
    284     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y();
    285 }
    286 
    287 int ScrollbarTheme::trackLength(ScrollbarThemeClient* scrollbar)
    288 {
    289     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
    290     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
    291 }
    292 
    293 void ScrollbarTheme::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect)
    294 {
    295     context->fillRect(cornerRect, Color::white);
    296 }
    297 
    298 IntRect ScrollbarTheme::thumbRect(ScrollbarThemeClient* scrollbar)
    299 {
    300     if (!hasThumb(scrollbar))
    301         return IntRect();
    302 
    303     IntRect track = trackRect(scrollbar);
    304     IntRect startTrackRect;
    305     IntRect thumbRect;
    306     IntRect endTrackRect;
    307     splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
    308 
    309     return thumbRect;
    310 }
    311 
    312 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar)
    313 {
    314     IntRect track = trackRect(scrollbar);
    315     return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width();
    316 }
    317 
    318 void ScrollbarTheme::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
    319 {
    320     context->setFillColor(Color::white);
    321     if (!horizontalOverhangRect.isEmpty())
    322         context->fillRect(intersection(horizontalOverhangRect, dirtyRect));
    323     if (!verticalOverhangRect.isEmpty())
    324         context->fillRect(intersection(verticalOverhangRect, dirtyRect));
    325 }
    326 
    327 }
    328