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