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 WebCore {
     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     if (context->paintingDisabled())
    214         return;
    215     blink::Platform::current()->themeEngine()->paint(context->canvas(), blink::WebThemeEngine::PartScrollbarCorner, blink::WebThemeEngine::StateNormal, blink::WebRect(cornerRect), 0);
    216 #endif
    217 }
    218 
    219 void ScrollbarTheme::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
    220 {
    221     context->setFillColor(Color::white);
    222     if (!horizontalOverhangRect.isEmpty())
    223         context->fillRect(intersection(horizontalOverhangRect, dirtyRect));
    224     if (!verticalOverhangRect.isEmpty())
    225         context->fillRect(intersection(verticalOverhangRect, dirtyRect));
    226 }
    227 
    228 bool ScrollbarTheme::shouldCenterOnThumb(ScrollbarThemeClient* scrollbar, const PlatformMouseEvent& evt)
    229 {
    230     return blink::Platform::current()->scrollbarBehavior()->shouldCenterOnThumb(static_cast<blink::WebScrollbarBehavior::Button>(evt.button()), evt.shiftKey(), evt.altKey());
    231 }
    232 
    233 bool ScrollbarTheme::shouldSnapBackToDragOrigin(ScrollbarThemeClient* scrollbar, const PlatformMouseEvent& evt)
    234 {
    235     IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.position());
    236     mousePosition.move(scrollbar->x(), scrollbar->y());
    237     return blink::Platform::current()->scrollbarBehavior()->shouldSnapBackToDragOrigin(mousePosition, trackRect(scrollbar), scrollbar->orientation() == HorizontalScrollbar);
    238 }
    239 
    240 // Returns the size represented by track taking into account scrolling past
    241 // the end of the document.
    242 static float usedTotalSize(ScrollbarThemeClient* scrollbar)
    243 {
    244     float overhangAtStart = -scrollbar->currentPos();
    245     float overhangAtEnd = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
    246     float overhang = std::max(0.0f, std::max(overhangAtStart, overhangAtEnd));
    247     return scrollbar->totalSize() + overhang;
    248 }
    249 
    250 int ScrollbarTheme::thumbPosition(ScrollbarThemeClient* scrollbar)
    251 {
    252     if (scrollbar->enabled()) {
    253         float size = usedTotalSize(scrollbar) - scrollbar->visibleSize();
    254         // Avoid doing a floating point divide by zero and return 1 when usedTotalSize == visibleSize.
    255         if (!size)
    256             return 1;
    257         float pos = std::max(0.0f, scrollbar->currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / size;
    258         return (pos < 1 && pos > 0) ? 1 : pos;
    259     }
    260     return 0;
    261 }
    262 
    263 int ScrollbarTheme::thumbLength(ScrollbarThemeClient* scrollbar)
    264 {
    265     if (!scrollbar->enabled())
    266         return 0;
    267 
    268     float overhang = 0;
    269     if (scrollbar->currentPos() < 0)
    270         overhang = -scrollbar->currentPos();
    271     else if (scrollbar->visibleSize() + scrollbar->currentPos() > scrollbar->totalSize())
    272         overhang = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
    273     float proportion = (scrollbar->visibleSize() - overhang) / usedTotalSize(scrollbar);
    274     int trackLen = trackLength(scrollbar);
    275     int length = round(proportion * trackLen);
    276     length = std::max(length, minimumThumbLength(scrollbar));
    277     if (length > trackLen)
    278         length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track).
    279     return length;
    280 }
    281 
    282 int ScrollbarTheme::trackPosition(ScrollbarThemeClient* scrollbar)
    283 {
    284     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
    285     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y();
    286 }
    287 
    288 int ScrollbarTheme::trackLength(ScrollbarThemeClient* scrollbar)
    289 {
    290     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
    291     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
    292 }
    293 
    294 IntRect ScrollbarTheme::thumbRect(ScrollbarThemeClient* scrollbar)
    295 {
    296     if (!hasThumb(scrollbar))
    297         return IntRect();
    298 
    299     IntRect track = trackRect(scrollbar);
    300     IntRect startTrackRect;
    301     IntRect thumbRect;
    302     IntRect endTrackRect;
    303     splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
    304 
    305     return thumbRect;
    306 }
    307 
    308 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar)
    309 {
    310     IntRect track = trackRect(scrollbar);
    311     return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width();
    312 }
    313 
    314 int ScrollbarTheme::minimumThumbLength(ScrollbarThemeClient* scrollbar)
    315 {
    316     return scrollbarThickness(scrollbar->controlSize());
    317 }
    318 
    319 void ScrollbarTheme::splitTrack(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
    320 {
    321     // This function won't even get called unless we're big enough to have some combination of these three rects where at least
    322     // one of them is non-empty.
    323     IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
    324     int thumbPos = thumbPosition(scrollbar);
    325     if (scrollbar->orientation() == HorizontalScrollbar) {
    326         thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y(), thumbLength(scrollbar), scrollbar->height());
    327         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height());
    328         afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.maxX() - beforeThumbRect.maxX(), trackRect.height());
    329     } else {
    330         thumbRect = IntRect(trackRect.x(), trackRect.y() + thumbPos, scrollbar->width(), thumbLength(scrollbar));
    331         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2);
    332         afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.maxY() - beforeThumbRect.maxY());
    333     }
    334 }
    335 
    336 ScrollbarTheme* ScrollbarTheme::theme()
    337 {
    338     if (ScrollbarTheme::mockScrollbarsEnabled()) {
    339         if (RuntimeEnabledFeatures::overlayScrollbarsEnabled()) {
    340             DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, overlayMockTheme, ());
    341             return &overlayMockTheme;
    342         }
    343 
    344         DEFINE_STATIC_LOCAL(ScrollbarThemeMock, mockTheme, ());
    345         return &mockTheme;
    346     }
    347     return nativeTheme();
    348 }
    349 
    350 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag)
    351 {
    352     gMockScrollbarsEnabled = flag;
    353 }
    354 
    355 bool ScrollbarTheme::mockScrollbarsEnabled()
    356 {
    357     return gMockScrollbarsEnabled;
    358 }
    359 
    360 }
    361