2 * Copyright (C) 2011 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "platform/scroll/ScrollbarTheme.h"
29 #include "RuntimeEnabledFeatures.h"
30 #include "platform/scroll/ScrollbarThemeClient.h"
31 #include "platform/scroll/ScrollbarThemeMock.h"
32 #include "platform/scroll/ScrollbarThemeOverlayMock.h"
35 #include "public/platform/Platform.h"
36 #include "public/platform/WebRect.h"
37 #include "public/platform/default/WebThemeEngine.h"
42 ScrollbarTheme* ScrollbarTheme::theme()
44 if (ScrollbarTheme::mockScrollbarsEnabled()) {
45 if (RuntimeEnabledFeatures::overlayScrollbarsEnabled()) {
46 DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, overlayMockTheme, ());
47 return &overlayMockTheme;
50 DEFINE_STATIC_LOCAL(ScrollbarThemeMock, mockTheme, ());
56 bool ScrollbarTheme::gMockScrollbarsEnabled = false;
58 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag)
60 gMockScrollbarsEnabled = flag;
63 bool ScrollbarTheme::mockScrollbarsEnabled()
65 return gMockScrollbarsEnabled;
68 bool ScrollbarTheme::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect)
70 // Create the ScrollbarControlPartMask based on the damageRect
71 ScrollbarControlPartMask scrollMask = NoPart;
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;
92 IntRect startTrackRect;
95 IntRect trackPaintRect = trackRect(scrollbar, true);
96 if (damageRect.intersects(trackPaintRect))
97 scrollMask |= TrackBGPart;
98 bool thumbPresent = hasThumb(scrollbar);
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;
110 // Paint the scrollbar background (only used by custom CSS scrollbars).
111 paintScrollbarBackground(graphicsContext, scrollbar);
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);
123 if (scrollMask & TrackBGPart)
124 paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
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);
133 paintTickmarks(graphicsContext, scrollbar, trackPaintRect);
137 if (scrollMask & ThumbPart)
138 paintThumb(graphicsContext, scrollbar, thumbRect);
143 ScrollbarPart ScrollbarTheme::hitTest(ScrollbarThemeClient* scrollbar, const IntPoint& position)
145 ScrollbarPart result = NoPart;
146 if (!scrollbar->enabled())
149 IntPoint testPosition = scrollbar->convertFromContainingWindow(position);
150 testPosition.move(scrollbar->x(), scrollbar->y());
152 if (!scrollbar->frameRect().contains(testPosition))
155 result = ScrollbarBGPart;
157 IntRect track = trackRect(scrollbar);
158 if (track.contains(testPosition)) {
159 IntRect beforeThumbRect;
161 IntRect afterThumbRect;
162 splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect);
163 if (thumbRect.contains(testPosition))
165 else if (beforeThumbRect.contains(testPosition))
166 result = BackTrackPart;
167 else if (afterThumbRect.contains(testPosition))
168 result = ForwardTrackPart;
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;
183 void ScrollbarTheme::invalidatePart(ScrollbarThemeClient* scrollbar, ScrollbarPart part)
190 case BackButtonStartPart:
191 result = backButtonRect(scrollbar, BackButtonStartPart, true);
193 case BackButtonEndPart:
194 result = backButtonRect(scrollbar, BackButtonEndPart, true);
196 case ForwardButtonStartPart:
197 result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
199 case ForwardButtonEndPart:
200 result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
203 result = trackRect(scrollbar, true);
205 case ScrollbarBGPart:
206 result = scrollbar->frameRect();
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;
219 result.moveBy(-scrollbar->location());
220 scrollbar->invalidateRect(result);
223 void ScrollbarTheme::splitTrack(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
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());
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());
240 // Returns the size represented by track taking into account scrolling past
241 // the end of the document.
242 static float usedTotalSize(ScrollbarThemeClient* scrollbar)
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;
250 int ScrollbarTheme::thumbPosition(ScrollbarThemeClient* scrollbar)
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.
257 float pos = std::max(0.0f, scrollbar->currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / size;
258 return (pos < 1 && pos > 0) ? 1 : pos;
263 int ScrollbarTheme::thumbLength(ScrollbarThemeClient* scrollbar)
265 if (!scrollbar->enabled())
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).
282 int ScrollbarTheme::minimumThumbLength(ScrollbarThemeClient* scrollbar)
284 return scrollbarThickness(scrollbar->controlSize());
287 int ScrollbarTheme::trackPosition(ScrollbarThemeClient* scrollbar)
289 IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
290 return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y();
293 int ScrollbarTheme::trackLength(ScrollbarThemeClient* scrollbar)
295 IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
296 return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
299 void ScrollbarTheme::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect)
301 if (cornerRect.isEmpty())
305 context->fillRect(cornerRect, Color::white);
307 blink::Platform::current()->themeEngine()->paint(context->canvas(), blink::WebThemeEngine::PartScrollbarCorner, blink::WebThemeEngine::StateNormal, blink::WebRect(cornerRect), 0);
311 IntRect ScrollbarTheme::thumbRect(ScrollbarThemeClient* scrollbar)
313 if (!hasThumb(scrollbar))
316 IntRect track = trackRect(scrollbar);
317 IntRect startTrackRect;
319 IntRect endTrackRect;
320 splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
325 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar)
327 IntRect track = trackRect(scrollbar);
328 return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width();
331 void ScrollbarTheme::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
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));