1/*
2 * Copyright (C) 2008 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 "ScrollbarThemeComposite.h"
28
29#include "GraphicsContext.h"
30#include "Scrollbar.h"
31
32namespace WebCore {
33
34bool ScrollbarThemeComposite::paint(Scrollbar& scrollbar, GraphicsContext& graphicsContext, const IntRect& damageRect)
35{
36 // Create the ScrollbarControlPartMask based on the damageRect
37 ScrollbarControlPartMask scrollMask = NoPart;
38
39 IntRect backButtonStartPaintRect;
40 IntRect backButtonEndPaintRect;
41 IntRect forwardButtonStartPaintRect;
42 IntRect forwardButtonEndPaintRect;
43 if (hasButtons(scrollbar)) {
44 backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
45 if (damageRect.intersects(backButtonStartPaintRect))
46 scrollMask |= BackButtonStartPart;
47 backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
48 if (damageRect.intersects(backButtonEndPaintRect))
49 scrollMask |= BackButtonEndPart;
50 forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
51 if (damageRect.intersects(forwardButtonStartPaintRect))
52 scrollMask |= ForwardButtonStartPart;
53 forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
54 if (damageRect.intersects(forwardButtonEndPaintRect))
55 scrollMask |= ForwardButtonEndPart;
56 }
57
58 IntRect startTrackRect;
59 IntRect thumbRect;
60 IntRect endTrackRect;
61 IntRect trackPaintRect = trackRect(scrollbar, true);
62 if (damageRect.intersects(trackPaintRect))
63 scrollMask |= TrackBGPart;
64 bool thumbPresent = hasThumb(scrollbar);
65 if (thumbPresent) {
66 IntRect track = trackRect(scrollbar);
67 splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
68 if (damageRect.intersects(thumbRect))
69 scrollMask |= ThumbPart;
70 if (damageRect.intersects(startTrackRect))
71 scrollMask |= BackTrackPart;
72 if (damageRect.intersects(endTrackRect))
73 scrollMask |= ForwardTrackPart;
74 }
75
76 willPaintScrollbar(graphicsContext, scrollbar);
77
78 // Paint the scrollbar background (only used by custom CSS scrollbars).
79 paintScrollbarBackground(graphicsContext, scrollbar);
80
81 // Paint the back and forward buttons.
82 if (scrollMask & BackButtonStartPart)
83 paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
84 if (scrollMask & BackButtonEndPart)
85 paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
86 if (scrollMask & ForwardButtonStartPart)
87 paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
88 if (scrollMask & ForwardButtonEndPart)
89 paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
90
91 if (scrollMask & TrackBGPart)
92 paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
93
94 if ((scrollMask & ForwardTrackPart) || (scrollMask & BackTrackPart)) {
95 // Paint the track pieces above and below the thumb.
96 if (scrollMask & BackTrackPart)
97 paintTrackPiece(graphicsContext, scrollbar, startTrackRect, BackTrackPart);
98 if (scrollMask & ForwardTrackPart)
99 paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart);
100
101 paintTickmarks(graphicsContext, scrollbar, trackPaintRect);
102 }
103
104 // Paint the thumb.
105 if (scrollMask & ThumbPart)
106 paintThumb(graphicsContext, scrollbar, thumbRect);
107
108 didPaintScrollbar(graphicsContext, scrollbar);
109 return true;
110}
111
112ScrollbarPart ScrollbarThemeComposite::hitTest(Scrollbar& scrollbar, const IntPoint& position)
113{
114 ScrollbarPart result = NoPart;
115 if (!scrollbar.enabled())
116 return result;
117
118 IntPoint testPosition = scrollbar.convertFromContainingWindow(position);
119 testPosition.move(scrollbar.x(), scrollbar.y());
120
121 if (!scrollbar.frameRect().contains(testPosition))
122 return NoPart;
123
124 result = ScrollbarBGPart;
125
126 IntRect track = trackRect(scrollbar);
127 if (track.contains(testPosition)) {
128 IntRect beforeThumbRect;
129 IntRect thumbRect;
130 IntRect afterThumbRect;
131 splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect);
132 if (thumbRect.contains(testPosition))
133 result = ThumbPart;
134 else if (beforeThumbRect.contains(testPosition))
135 result = BackTrackPart;
136 else if (afterThumbRect.contains(testPosition))
137 result = ForwardTrackPart;
138 else
139 result = TrackBGPart;
140 } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(testPosition))
141 result = BackButtonStartPart;
142 else if (backButtonRect(scrollbar, BackButtonEndPart).contains(testPosition))
143 result = BackButtonEndPart;
144 else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(testPosition))
145 result = ForwardButtonStartPart;
146 else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(testPosition))
147 result = ForwardButtonEndPart;
148 return result;
149}
150
151void ScrollbarThemeComposite::invalidatePart(Scrollbar& scrollbar, ScrollbarPart part)
152{
153 if (part == NoPart)
154 return;
155
156 IntRect result;
157 switch (part) {
158 case BackButtonStartPart:
159 result = backButtonRect(scrollbar, BackButtonStartPart, true);
160 break;
161 case BackButtonEndPart:
162 result = backButtonRect(scrollbar, BackButtonEndPart, true);
163 break;
164 case ForwardButtonStartPart:
165 result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
166 break;
167 case ForwardButtonEndPart:
168 result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
169 break;
170 case TrackBGPart:
171 result = trackRect(scrollbar, true);
172 break;
173 case ScrollbarBGPart:
174 result = scrollbar.frameRect();
175 break;
176 default: {
177 IntRect beforeThumbRect, thumbRect, afterThumbRect;
178 splitTrack(scrollbar, trackRect(scrollbar), beforeThumbRect, thumbRect, afterThumbRect);
179 if (part == BackTrackPart)
180 result = beforeThumbRect;
181 else if (part == ForwardTrackPart)
182 result = afterThumbRect;
183 else
184 result = thumbRect;
185 }
186 }
187 result.moveBy(-scrollbar.location());
188 scrollbar.invalidateRect(result);
189}
190
191void ScrollbarThemeComposite::splitTrack(Scrollbar& scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
192{
193 // This function won't even get called unless we're big enough to have some combination of these three rects where at least
194 // one of them is non-empty.
195 IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
196 int thickness = scrollbar.orientation() == HorizontalScrollbar ? scrollbar.height() : scrollbar.width();
197 int thumbPos = thumbPosition(scrollbar);
198 if (scrollbar.orientation() == HorizontalScrollbar) {
199 thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y() + (trackRect.height() - thickness) / 2, thumbLength(scrollbar), thickness);
200 beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height());
201 afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.maxX() - beforeThumbRect.maxX(), trackRect.height());
202 } else {
203 thumbRect = IntRect(trackRect.x() + (trackRect.width() - thickness) / 2, trackRect.y() + thumbPos, thickness, thumbLength(scrollbar));
204 beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2);
205 afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.maxY() - beforeThumbRect.maxY());
206 }
207}
208
209// Returns the size represented by track taking into account scrolling past
210// the end of the document.
211static float usedTotalSize(Scrollbar& scrollbar)
212{
213 float overhangAtStart = -scrollbar.currentPos();
214 float overhangAtEnd = scrollbar.currentPos() + scrollbar.visibleSize() - scrollbar.totalSize();
215 float overhang = std::max(0.0f, std::max(overhangAtStart, overhangAtEnd));
216 return scrollbar.totalSize() + overhang;
217}
218
219int ScrollbarThemeComposite::thumbPosition(Scrollbar& scrollbar)
220{
221 if (scrollbar.enabled()) {
222 float size = usedTotalSize(scrollbar) - scrollbar.visibleSize();
223 // Avoid doing a floating point divide by zero and return 1 when usedTotalSize == visibleSize.
224 if (!size)
225 return 1;
226 float pos = std::max(0.0f, scrollbar.currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / size;
227 return (pos < 1 && pos > 0) ? 1 : pos;
228 }
229 return 0;
230}
231
232int ScrollbarThemeComposite::thumbLength(Scrollbar& scrollbar)
233{
234 if (!scrollbar.enabled())
235 return 0;
236
237 float proportion = scrollbar.visibleSize() / usedTotalSize(scrollbar);
238 int trackLen = trackLength(scrollbar);
239 int length = round(proportion * trackLen);
240 length = std::max(length, minimumThumbLength(scrollbar));
241 if (length > trackLen)
242 length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track).
243 return length;
244}
245
246int ScrollbarThemeComposite::minimumThumbLength(Scrollbar& scrollbar)
247{
248 return scrollbarThickness(scrollbar.controlSize());
249}
250
251int ScrollbarThemeComposite::trackPosition(Scrollbar& scrollbar)
252{
253 IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
254 return (scrollbar.orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar.x() : constrainedTrackRect.y() - scrollbar.y();
255}
256
257int ScrollbarThemeComposite::trackLength(Scrollbar& scrollbar)
258{
259 IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
260 return (scrollbar.orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
261}
262
263IntRect ScrollbarThemeComposite::thumbRect(Scrollbar& scrollbar)
264{
265 if (!hasThumb(scrollbar))
266 return IntRect();
267
268 IntRect track = trackRect(scrollbar);
269 IntRect startTrackRect;
270 IntRect thumbRect;
271 IntRect endTrackRect;
272 splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
273
274 return thumbRect;
275}
276
277void ScrollbarThemeComposite::paintOverhangAreas(ScrollView&, GraphicsContext& context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
278{
279 context.setFillColor(Color::white);
280 if (!horizontalOverhangRect.isEmpty())
281 context.fillRect(intersection(horizontalOverhangRect, dirtyRect));
282
283 context.setFillColor(Color::white);
284 if (!verticalOverhangRect.isEmpty())
285 context.fillRect(intersection(verticalOverhangRect, dirtyRect));
286}
287
288}
289