Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / platform / scroll / ScrollbarTheme.cpp
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 #if !OS(MACOSX)
35 #include "public/platform/Platform.h"
36 #include "public/platform/WebRect.h"
37 #include "public/platform/default/WebThemeEngine.h"
38 #endif
39
40 namespace WebCore {
41
42 ScrollbarTheme* ScrollbarTheme::theme()
43 {
44     if (ScrollbarTheme::mockScrollbarsEnabled()) {
45         if (RuntimeEnabledFeatures::overlayScrollbarsEnabled()) {
46             DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, overlayMockTheme, ());
47             return &overlayMockTheme;
48         }
49
50         DEFINE_STATIC_LOCAL(ScrollbarThemeMock, mockTheme, ());
51         return &mockTheme;
52     }
53     return nativeTheme();
54 }
55
56 bool ScrollbarTheme::gMockScrollbarsEnabled = false;
57
58 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag)
59 {
60     gMockScrollbarsEnabled = flag;
61 }
62
63 bool ScrollbarTheme::mockScrollbarsEnabled()
64 {
65     return gMockScrollbarsEnabled;
66 }
67
68 bool ScrollbarTheme::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect)
69 {
70     // Create the ScrollbarControlPartMask based on the damageRect
71     ScrollbarControlPartMask scrollMask = NoPart;
72
73     IntRect backButtonStartPaintRect;
74     IntRect backButtonEndPaintRect;
75     IntRect forwardButtonStartPaintRect;
76     IntRect forwardButtonEndPaintRect;
77     if (hasButtons(scrollbar)) {
78         backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
79         if (damageRect.intersects(backButtonStartPaintRect))
80             scrollMask |= BackButtonStartPart;
81         backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
82         if (damageRect.intersects(backButtonEndPaintRect))
83             scrollMask |= BackButtonEndPart;
84         forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
85         if (damageRect.intersects(forwardButtonStartPaintRect))
86             scrollMask |= ForwardButtonStartPart;
87         forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
88         if (damageRect.intersects(forwardButtonEndPaintRect))
89             scrollMask |= ForwardButtonEndPart;
90     }
91
92     IntRect startTrackRect;
93     IntRect thumbRect;
94     IntRect endTrackRect;
95     IntRect trackPaintRect = trackRect(scrollbar, true);
96     if (damageRect.intersects(trackPaintRect))
97         scrollMask |= TrackBGPart;
98     bool thumbPresent = hasThumb(scrollbar);
99     if (thumbPresent) {
100         IntRect track = trackRect(scrollbar);
101         splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
102         if (damageRect.intersects(thumbRect))
103             scrollMask |= ThumbPart;
104         if (damageRect.intersects(startTrackRect))
105             scrollMask |= BackTrackPart;
106         if (damageRect.intersects(endTrackRect))
107             scrollMask |= ForwardTrackPart;
108     }
109
110     // Paint the scrollbar background (only used by custom CSS scrollbars).
111     paintScrollbarBackground(graphicsContext, scrollbar);
112
113     // Paint the back and forward buttons.
114     if (scrollMask & BackButtonStartPart)
115         paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
116     if (scrollMask & BackButtonEndPart)
117         paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
118     if (scrollMask & ForwardButtonStartPart)
119         paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
120     if (scrollMask & ForwardButtonEndPart)
121         paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
122
123     if (scrollMask & TrackBGPart)
124         paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
125
126     if ((scrollMask & ForwardTrackPart) || (scrollMask & BackTrackPart)) {
127         // Paint the track pieces above and below the thumb.
128         if (scrollMask & BackTrackPart)
129             paintTrackPiece(graphicsContext, scrollbar, startTrackRect, BackTrackPart);
130         if (scrollMask & ForwardTrackPart)
131             paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart);
132
133         paintTickmarks(graphicsContext, scrollbar, trackPaintRect);
134     }
135
136     // Paint the thumb.
137     if (scrollMask & ThumbPart)
138         paintThumb(graphicsContext, scrollbar, thumbRect);
139
140     return true;
141 }
142
143 ScrollbarPart ScrollbarTheme::hitTest(ScrollbarThemeClient* scrollbar, const IntPoint& position)
144 {
145     ScrollbarPart result = NoPart;
146     if (!scrollbar->enabled())
147         return result;
148
149     IntPoint testPosition = scrollbar->convertFromContainingWindow(position);
150     testPosition.move(scrollbar->x(), scrollbar->y());
151
152     if (!scrollbar->frameRect().contains(testPosition))
153         return NoPart;
154
155     result = ScrollbarBGPart;
156
157     IntRect track = trackRect(scrollbar);
158     if (track.contains(testPosition)) {
159         IntRect beforeThumbRect;
160         IntRect thumbRect;
161         IntRect afterThumbRect;
162         splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect);
163         if (thumbRect.contains(testPosition))
164             result = ThumbPart;
165         else if (beforeThumbRect.contains(testPosition))
166             result = BackTrackPart;
167         else if (afterThumbRect.contains(testPosition))
168             result = ForwardTrackPart;
169         else
170             result = TrackBGPart;
171     } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(testPosition)) {
172         result = BackButtonStartPart;
173     } else if (backButtonRect(scrollbar, BackButtonEndPart).contains(testPosition)) {
174         result = BackButtonEndPart;
175     } else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(testPosition)) {
176         result = ForwardButtonStartPart;
177     } else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(testPosition)) {
178         result = ForwardButtonEndPart;
179     }
180     return result;
181 }
182
183 void ScrollbarTheme::invalidatePart(ScrollbarThemeClient* scrollbar, ScrollbarPart part)
184 {
185     if (part == NoPart)
186         return;
187
188     IntRect result;
189     switch (part) {
190     case BackButtonStartPart:
191         result = backButtonRect(scrollbar, BackButtonStartPart, true);
192         break;
193     case BackButtonEndPart:
194         result = backButtonRect(scrollbar, BackButtonEndPart, true);
195         break;
196     case ForwardButtonStartPart:
197         result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
198         break;
199     case ForwardButtonEndPart:
200         result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
201         break;
202     case TrackBGPart:
203         result = trackRect(scrollbar, true);
204         break;
205     case ScrollbarBGPart:
206         result = scrollbar->frameRect();
207         break;
208     default: {
209         IntRect beforeThumbRect, thumbRect, afterThumbRect;
210         splitTrack(scrollbar, trackRect(scrollbar), beforeThumbRect, thumbRect, afterThumbRect);
211         if (part == BackTrackPart)
212             result = beforeThumbRect;
213         else if (part == ForwardTrackPart)
214             result = afterThumbRect;
215         else
216             result = thumbRect;
217     }
218     }
219     result.moveBy(-scrollbar->location());
220     scrollbar->invalidateRect(result);
221 }
222
223 void ScrollbarTheme::splitTrack(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
224 {
225     // This function won't even get called unless we're big enough to have some combination of these three rects where at least
226     // one of them is non-empty.
227     IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
228     int thumbPos = thumbPosition(scrollbar);
229     if (scrollbar->orientation() == HorizontalScrollbar) {
230         thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y(), thumbLength(scrollbar), scrollbar->height());
231         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height());
232         afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.maxX() - beforeThumbRect.maxX(), trackRect.height());
233     } else {
234         thumbRect = IntRect(trackRect.x(), trackRect.y() + thumbPos, scrollbar->width(), thumbLength(scrollbar));
235         beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2);
236         afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.maxY() - beforeThumbRect.maxY());
237     }
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::minimumThumbLength(ScrollbarThemeClient* scrollbar)
283 {
284     return scrollbarThickness(scrollbar->controlSize());
285 }
286
287 int ScrollbarTheme::trackPosition(ScrollbarThemeClient* scrollbar)
288 {
289     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
290     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y();
291 }
292
293 int ScrollbarTheme::trackLength(ScrollbarThemeClient* scrollbar)
294 {
295     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
296     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
297 }
298
299 void ScrollbarTheme::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect)
300 {
301     if (cornerRect.isEmpty())
302         return;
303
304 #if OS(MACOSX)
305     context->fillRect(cornerRect, Color::white);
306 #else
307     blink::Platform::current()->themeEngine()->paint(context->canvas(), blink::WebThemeEngine::PartScrollbarCorner, blink::WebThemeEngine::StateNormal, blink::WebRect(cornerRect), 0);
308 #endif
309 }
310
311 IntRect ScrollbarTheme::thumbRect(ScrollbarThemeClient* scrollbar)
312 {
313     if (!hasThumb(scrollbar))
314         return IntRect();
315
316     IntRect track = trackRect(scrollbar);
317     IntRect startTrackRect;
318     IntRect thumbRect;
319     IntRect endTrackRect;
320     splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
321
322     return thumbRect;
323 }
324
325 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar)
326 {
327     IntRect track = trackRect(scrollbar);
328     return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width();
329 }
330
331 void ScrollbarTheme::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
332 {
333     context->setFillColor(Color::white);
334     if (!horizontalOverhangRect.isEmpty())
335         context->fillRect(intersection(horizontalOverhangRect, dirtyRect));
336     if (!verticalOverhangRect.isEmpty())
337         context->fillRect(intersection(verticalOverhangRect, dirtyRect));
338 }
339
340 }