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