Upstream version 8.37.180.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 "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 }