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
paint(ScrollbarThemeClient * scrollbar,GraphicsContext * graphicsContext,const IntRect & damageRect)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
hitTest(ScrollbarThemeClient * scrollbar,const IntPoint & position)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
invalidatePart(ScrollbarThemeClient * scrollbar,ScrollbarPart part)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
paintScrollCorner(GraphicsContext * context,const IntRect & cornerRect)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
paintOverhangBackground(GraphicsContext * context,const IntRect & horizontalOverhangRect,const IntRect & verticalOverhangRect,const IntRect & dirtyRect)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
shouldCenterOnThumb(ScrollbarThemeClient * scrollbar,const PlatformMouseEvent & evt)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
shouldSnapBackToDragOrigin(ScrollbarThemeClient * scrollbar,const PlatformMouseEvent & evt)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.
usedTotalSize(ScrollbarThemeClient * scrollbar)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
thumbPosition(ScrollbarThemeClient * scrollbar)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
thumbLength(ScrollbarThemeClient * scrollbar)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
trackPosition(ScrollbarThemeClient * scrollbar)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
trackLength(ScrollbarThemeClient * scrollbar)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
thumbRect(ScrollbarThemeClient * scrollbar)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
thumbThickness(ScrollbarThemeClient * scrollbar)308 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar)
309 {
310 IntRect track = trackRect(scrollbar);
311 return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width();
312 }
313
minimumThumbLength(ScrollbarThemeClient * scrollbar)314 int ScrollbarTheme::minimumThumbLength(ScrollbarThemeClient* scrollbar)
315 {
316 return scrollbarThickness(scrollbar->controlSize());
317 }
318
splitTrack(ScrollbarThemeClient * scrollbar,const IntRect & unconstrainedTrackRect,IntRect & beforeThumbRect,IntRect & thumbRect,IntRect & afterThumbRect)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
theme()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
setMockScrollbarsEnabled(bool flag)350 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag)
351 {
352 gMockScrollbarsEnabled = flag;
353 }
354
mockScrollbarsEnabled()355 bool ScrollbarTheme::mockScrollbarsEnabled()
356 {
357 return gMockScrollbarsEnabled;
358 }
359
360 }
361