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