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 "ChromeClient.h"
30 #include "Frame.h"
31 #include "FrameView.h"
32 #include "GraphicsContext.h"
33 #include "Page.h"
34 #include "PlatformMouseEvent.h"
35 #include "Scrollbar.h"
36 #include "ScrollbarClient.h"
37 #include "Settings.h"
38
39 namespace WebCore {
40
41 #if PLATFORM(WIN)
pageForScrollView(ScrollView * view)42 static Page* pageForScrollView(ScrollView* view)
43 {
44 if (!view)
45 return 0;
46 if (!view->isFrameView())
47 return 0;
48 FrameView* frameView = static_cast<FrameView*>(view);
49 if (!frameView->frame())
50 return 0;
51 return frameView->frame()->page();
52 }
53 #endif
54
paint(Scrollbar * scrollbar,GraphicsContext * graphicsContext,const IntRect & damageRect)55 bool ScrollbarThemeComposite::paint(Scrollbar* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect)
56 {
57 // Create the ScrollbarControlPartMask based on the damageRect
58 ScrollbarControlPartMask scrollMask = NoPart;
59
60 IntRect backButtonStartPaintRect;
61 IntRect backButtonEndPaintRect;
62 IntRect forwardButtonStartPaintRect;
63 IntRect forwardButtonEndPaintRect;
64 if (hasButtons(scrollbar)) {
65 backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
66 if (damageRect.intersects(backButtonStartPaintRect))
67 scrollMask |= BackButtonStartPart;
68 backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
69 if (damageRect.intersects(backButtonEndPaintRect))
70 scrollMask |= BackButtonEndPart;
71 forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
72 if (damageRect.intersects(forwardButtonStartPaintRect))
73 scrollMask |= ForwardButtonStartPart;
74 forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
75 if (damageRect.intersects(forwardButtonEndPaintRect))
76 scrollMask |= ForwardButtonEndPart;
77 }
78
79 IntRect startTrackRect;
80 IntRect thumbRect;
81 IntRect endTrackRect;
82 IntRect trackPaintRect = trackRect(scrollbar, true);
83 if (damageRect.intersects(trackPaintRect))
84 scrollMask |= TrackBGPart;
85 bool thumbPresent = hasThumb(scrollbar);
86 if (thumbPresent) {
87 IntRect track = trackRect(scrollbar);
88 splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect);
89 if (damageRect.intersects(thumbRect))
90 scrollMask |= ThumbPart;
91 if (damageRect.intersects(startTrackRect))
92 scrollMask |= BackTrackPart;
93 if (damageRect.intersects(endTrackRect))
94 scrollMask |= ForwardTrackPart;
95 }
96
97 #if PLATFORM(WIN)
98 // FIXME: This API makes the assumption that the custom scrollbar's metrics will match
99 // the theme's metrics. This is not a valid assumption. The ability for a client to paint
100 // custom scrollbars should be removed once scrollbars can be styled via CSS.
101 if (Page* page = pageForScrollView(scrollbar->parent())) {
102 if (page->settings()->shouldPaintCustomScrollbars()) {
103 float proportion = static_cast<float>(scrollbar->visibleSize()) / scrollbar->totalSize();
104 float value = scrollbar->currentPos() / static_cast<float>(scrollbar->maximum());
105 ScrollbarControlState s = 0;
106 if (scrollbar->client()->isActive())
107 s |= ActiveScrollbarState;
108 if (scrollbar->enabled())
109 s |= EnabledScrollbarState;
110 if (scrollbar->pressedPart() != NoPart)
111 s |= PressedScrollbarState;
112 if (page->chrome()->client()->paintCustomScrollbar(graphicsContext,
113 scrollbar->frameRect(),
114 scrollbar->controlSize(),
115 s,
116 scrollbar->pressedPart(),
117 scrollbar->orientation() == VerticalScrollbar,
118 value,
119 proportion,
120 scrollMask))
121 return true;
122 }
123 }
124 #endif
125
126 // Paint the scrollbar background (only used by custom CSS scrollbars).
127 paintScrollbarBackground(graphicsContext, scrollbar);
128
129 // Paint the back and forward buttons.
130 if (scrollMask & BackButtonStartPart)
131 paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
132 if (scrollMask & BackButtonEndPart)
133 paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
134 if (scrollMask & ForwardButtonStartPart)
135 paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
136 if (scrollMask & ForwardButtonEndPart)
137 paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
138
139 if (scrollMask & TrackBGPart)
140 paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
141
142 if ((scrollMask & ForwardTrackPart) || (scrollMask & BackTrackPart)) {
143 // Paint the track pieces above and below the thumb.
144 if (scrollMask & BackTrackPart)
145 paintTrackPiece(graphicsContext, scrollbar, startTrackRect, BackTrackPart);
146 if (scrollMask & ForwardTrackPart)
147 paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart);
148
149 paintTickmarks(graphicsContext, scrollbar, trackPaintRect);
150 }
151
152 // Paint the thumb.
153 if (scrollMask & ThumbPart)
154 paintThumb(graphicsContext, scrollbar, thumbRect);
155
156 return true;
157 }
158
hitTest(Scrollbar * scrollbar,const PlatformMouseEvent & evt)159 ScrollbarPart ScrollbarThemeComposite::hitTest(Scrollbar* scrollbar, const PlatformMouseEvent& evt)
160 {
161 ScrollbarPart result = NoPart;
162 if (!scrollbar->enabled())
163 return result;
164
165 IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.pos());
166 mousePosition.move(scrollbar->x(), scrollbar->y());
167
168 if (!scrollbar->frameRect().contains(mousePosition))
169 return NoPart;
170
171 result = ScrollbarBGPart;
172
173 IntRect track = trackRect(scrollbar);
174 if (track.contains(mousePosition)) {
175 IntRect beforeThumbRect;
176 IntRect thumbRect;
177 IntRect afterThumbRect;
178 splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect);
179 if (thumbRect.contains(mousePosition))
180 result = ThumbPart;
181 else if (beforeThumbRect.contains(mousePosition))
182 result = BackTrackPart;
183 else if (afterThumbRect.contains(mousePosition))
184 result = ForwardTrackPart;
185 else
186 result = TrackBGPart;
187 } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(mousePosition))
188 result = BackButtonStartPart;
189 else if (backButtonRect(scrollbar, BackButtonEndPart).contains(mousePosition))
190 result = BackButtonEndPart;
191 else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(mousePosition))
192 result = ForwardButtonStartPart;
193 else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(mousePosition))
194 result = ForwardButtonEndPart;
195 return result;
196 }
197
invalidatePart(Scrollbar * scrollbar,ScrollbarPart part)198 void ScrollbarThemeComposite::invalidatePart(Scrollbar* scrollbar, ScrollbarPart part)
199 {
200 if (part == NoPart)
201 return;
202
203 IntRect result;
204 switch (part) {
205 case BackButtonStartPart:
206 result = backButtonRect(scrollbar, BackButtonStartPart, true);
207 break;
208 case BackButtonEndPart:
209 result = backButtonRect(scrollbar, BackButtonEndPart, true);
210 break;
211 case ForwardButtonStartPart:
212 result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
213 break;
214 case ForwardButtonEndPart:
215 result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
216 break;
217 case TrackBGPart:
218 result = trackRect(scrollbar, true);
219 break;
220 case ScrollbarBGPart:
221 result = scrollbar->frameRect();
222 break;
223 default: {
224 IntRect beforeThumbRect, thumbRect, afterThumbRect;
225 splitTrack(scrollbar, trackRect(scrollbar), beforeThumbRect, thumbRect, afterThumbRect);
226 if (part == BackTrackPart)
227 result = beforeThumbRect;
228 else if (part == ForwardTrackPart)
229 result = afterThumbRect;
230 else
231 result = thumbRect;
232 }
233 }
234 result.move(-scrollbar->x(), -scrollbar->y());
235 scrollbar->invalidateRect(result);
236 }
237
splitTrack(Scrollbar * scrollbar,const IntRect & unconstrainedTrackRect,IntRect & beforeThumbRect,IntRect & thumbRect,IntRect & afterThumbRect)238 void ScrollbarThemeComposite::splitTrack(Scrollbar* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect)
239 {
240 // This function won't even get called unless we're big enough to have some combination of these three rects where at least
241 // one of them is non-empty.
242 IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
243 int thickness = scrollbar->orientation() == HorizontalScrollbar ? scrollbar->height() : scrollbar->width();
244 int thumbPos = thumbPosition(scrollbar);
245 if (scrollbar->orientation() == HorizontalScrollbar) {
246 thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y() + (trackRect.height() - thickness) / 2, thumbLength(scrollbar), thickness);
247 beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height());
248 afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.right() - beforeThumbRect.right(), trackRect.height());
249 } else {
250 thumbRect = IntRect(trackRect.x() + (trackRect.width() - thickness) / 2, trackRect.y() + thumbPos, thickness, thumbLength(scrollbar));
251 beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2);
252 afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.bottom() - beforeThumbRect.bottom());
253 }
254 }
255
thumbPosition(Scrollbar * scrollbar)256 int ScrollbarThemeComposite::thumbPosition(Scrollbar* scrollbar)
257 {
258 if (scrollbar->enabled())
259 return scrollbar->currentPos() * (trackLength(scrollbar) - thumbLength(scrollbar)) / scrollbar->maximum();
260 return 0;
261 }
262
thumbLength(Scrollbar * scrollbar)263 int ScrollbarThemeComposite::thumbLength(Scrollbar* scrollbar)
264 {
265 if (!scrollbar->enabled())
266 return 0;
267
268 float proportion = (float)scrollbar->visibleSize() / scrollbar->totalSize();
269 int trackLen = trackLength(scrollbar);
270 int length = proportion * trackLen;
271 length = max(length, minimumThumbLength(scrollbar));
272 if (length > trackLen)
273 length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track).
274 return length;
275 }
276
minimumThumbLength(Scrollbar * scrollbar)277 int ScrollbarThemeComposite::minimumThumbLength(Scrollbar* scrollbar)
278 {
279 return scrollbarThickness(scrollbar->controlSize());
280 }
281
trackPosition(Scrollbar * scrollbar)282 int ScrollbarThemeComposite::trackPosition(Scrollbar* 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(Scrollbar * scrollbar)288 int ScrollbarThemeComposite::trackLength(Scrollbar* scrollbar)
289 {
290 IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar));
291 return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height();
292 }
293
paintScrollCorner(ScrollView * view,GraphicsContext * context,const IntRect & cornerRect)294 void ScrollbarThemeComposite::paintScrollCorner(ScrollView* view, GraphicsContext* context, const IntRect& cornerRect)
295 {
296 FrameView* frameView = static_cast<FrameView*>(view);
297 Page* page = frameView->frame() ? frameView->frame()->page() : 0;
298 if (page && page->settings()->shouldPaintCustomScrollbars()) {
299 if (!page->chrome()->client()->paintCustomScrollCorner(context, cornerRect))
300 context->fillRect(cornerRect, Color::white);
301 }
302 }
303
304 }
305