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