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