• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 namespace WebCore {
35 
theme()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 
setMockScrollbarsEnabled(bool flag)52 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag)
53 {
54     gMockScrollbarsEnabled = flag;
55 }
56 
mockScrollbarsEnabled()57 bool ScrollbarTheme::mockScrollbarsEnabled()
58 {
59     return gMockScrollbarsEnabled;
60 }
61 
paint(ScrollbarThemeClient * scrollbar,GraphicsContext * graphicsContext,const IntRect & damageRect)62 bool ScrollbarTheme::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* graphicsContext, 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, true);
76         if (damageRect.intersects(backButtonEndPaintRect))
77             scrollMask |= BackButtonEndPart;
78         forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
79         if (damageRect.intersects(forwardButtonStartPaintRect))
80             scrollMask |= ForwardButtonStartPart;
81         forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, 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, BackButtonStartPart);
110     if (scrollMask & BackButtonEndPart)
111         paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
112     if (scrollMask & ForwardButtonStartPart)
113         paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
114     if (scrollMask & ForwardButtonEndPart)
115         paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
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, BackTrackPart);
124         if (scrollMask & ForwardTrackPart)
125             paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart);
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 
hitTest(ScrollbarThemeClient * scrollbar,const IntPoint & position)137 ScrollbarPart ScrollbarTheme::hitTest(ScrollbarThemeClient* scrollbar, const IntPoint& 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(testPosition)) {
166         result = BackButtonStartPart;
167     } else if (backButtonRect(scrollbar, BackButtonEndPart).contains(testPosition)) {
168         result = BackButtonEndPart;
169     } else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(testPosition)) {
170         result = ForwardButtonStartPart;
171     } else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(testPosition)) {
172         result = ForwardButtonEndPart;
173     }
174     return result;
175 }
176 
invalidatePart(ScrollbarThemeClient * scrollbar,ScrollbarPart part)177 void ScrollbarTheme::invalidatePart(ScrollbarThemeClient* scrollbar, ScrollbarPart 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 
splitTrack(ScrollbarThemeClient * scrollbar,const IntRect & unconstrainedTrackRect,IntRect & beforeThumbRect,IntRect & thumbRect,IntRect & afterThumbRect)217 void ScrollbarTheme::splitTrack(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
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, unconstrainedTrackRect);
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 + thumbRect.width() / 2, trackRect.height());
226         afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.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.
usedTotalSize(ScrollbarThemeClient * scrollbar)236 static float usedTotalSize(ScrollbarThemeClient* scrollbar)
237 {
238     float overhangAtStart = -scrollbar->currentPos();
239     float overhangAtEnd = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
240     float overhang = std::max(0.0f, std::max(overhangAtStart, overhangAtEnd));
241     return scrollbar->totalSize() + overhang;
242 }
243 
thumbPosition(ScrollbarThemeClient * scrollbar)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 usedTotalSize == visibleSize.
249         if (!size)
250             return 1;
251         float pos = std::max(0.0f, scrollbar->currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / size;
252         return (pos < 1 && pos > 0) ? 1 : pos;
253     }
254     return 0;
255 }
256 
thumbLength(ScrollbarThemeClient * scrollbar)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->totalSize())
266         overhang = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize();
267     float proportion = (scrollbar->visibleSize() - overhang) / usedTotalSize(scrollbar);
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 away (to make more room for the track).
273     return length;
274 }
275 
minimumThumbLength(ScrollbarThemeClient * scrollbar)276 int ScrollbarTheme::minimumThumbLength(ScrollbarThemeClient* scrollbar)
277 {
278     return scrollbarThickness(scrollbar->controlSize());
279 }
280 
trackPosition(ScrollbarThemeClient * scrollbar)281 int ScrollbarTheme::trackPosition(ScrollbarThemeClient* scrollbar)
282 {
283     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
284     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y();
285 }
286 
trackLength(ScrollbarThemeClient * scrollbar)287 int ScrollbarTheme::trackLength(ScrollbarThemeClient* scrollbar)
288 {
289     IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
290     return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
291 }
292 
paintScrollCorner(GraphicsContext * context,const IntRect & cornerRect)293 void ScrollbarTheme::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect)
294 {
295     context->fillRect(cornerRect, Color::white);
296 }
297 
thumbRect(ScrollbarThemeClient * scrollbar)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 
thumbThickness(ScrollbarThemeClient * scrollbar)312 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar)
313 {
314     IntRect track = trackRect(scrollbar);
315     return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width();
316 }
317 
paintOverhangBackground(GraphicsContext * context,const IntRect & horizontalOverhangRect,const IntRect & verticalOverhangRect,const IntRect & dirtyRect)318 void ScrollbarTheme::paintOverhangBackground(GraphicsContext* context, const IntRect& 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 }
328