Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / platform / scroll / ScrollbarThemeMacCommon.mm
1 /*
2  * Copyright (C) 2008, 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/RuntimeEnabledFeatures.h"
28 #include "platform/scroll/ScrollbarThemeMacCommon.h"
29
30 #include <Carbon/Carbon.h>
31 #include "platform/PlatformMouseEvent.h"
32 #include "platform/graphics/Gradient.h"
33 #include "platform/graphics/GraphicsContext.h"
34 #include "platform/graphics/GraphicsContextStateSaver.h"
35 #include "platform/graphics/GraphicsLayer.h"
36 #include "platform/graphics/ImageBuffer.h"
37 #include "platform/graphics/Pattern.h"
38 #include "platform/mac/ColorMac.h"
39 #include "platform/mac/LocalCurrentGraphicsContext.h"
40 #include "platform/mac/NSScrollerImpDetails.h"
41 #include "platform/mac/ScrollAnimatorMac.h"
42 #include "platform/scroll/ScrollbarThemeClient.h"
43 #include "platform/scroll/ScrollbarThemeMacNonOverlayAPI.h"
44 #include "platform/scroll/ScrollbarThemeMacOverlayAPI.h"
45 #include "public/platform/WebThemeEngine.h"
46 #include "public/platform/Platform.h"
47 #include "public/platform/WebRect.h"
48 #include "skia/ext/skia_utils_mac.h"
49 #include "wtf/HashSet.h"
50 #include "wtf/StdLibExtras.h"
51 #include "wtf/TemporaryChange.h"
52
53 // FIXME: There are repainting problems due to Aqua scroll bar buttons' visual overflow.
54
55 using namespace blink;
56
57 @interface NSColor (WebNSColorDetails)
58 + (NSImage *)_linenPatternImage;
59 @end
60
61 namespace blink {
62
63 typedef HashSet<ScrollbarThemeClient*> ScrollbarSet;
64
65 static ScrollbarSet& scrollbarSet()
66 {
67     DEFINE_STATIC_LOCAL(ScrollbarSet, set, ());
68     return set;
69 }
70
71 static float gInitialButtonDelay = 0.5f;
72 static float gAutoscrollButtonDelay = 0.05f;
73 static NSScrollerStyle gPreferredScrollerStyle = NSScrollerStyleLegacy;
74
75 ScrollbarTheme* ScrollbarTheme::nativeTheme()
76 {
77     static ScrollbarThemeMacCommon* theme = NULL;
78     if (theme)
79         return theme;
80     if (ScrollbarThemeMacCommon::isOverlayAPIAvailable()) {
81         DEFINE_STATIC_LOCAL(ScrollbarThemeMacOverlayAPI, overlayTheme, ());
82         theme = &overlayTheme;
83     } else {
84         DEFINE_STATIC_LOCAL(ScrollbarThemeMacNonOverlayAPI, nonOverlayTheme, ());
85         theme = &nonOverlayTheme;
86     }
87     return theme;
88 }
89
90 void ScrollbarThemeMacCommon::registerScrollbar(ScrollbarThemeClient* scrollbar)
91 {
92     scrollbarSet().add(scrollbar);
93 }
94
95 void ScrollbarThemeMacCommon::unregisterScrollbar(ScrollbarThemeClient* scrollbar)
96 {
97     scrollbarSet().remove(scrollbar);
98 }
99
100 void ScrollbarThemeMacCommon::paintGivenTickmarks(GraphicsContext* context, ScrollbarThemeClient* scrollbar, const IntRect& rect, const Vector<IntRect>& tickmarks)
101 {
102     if (scrollbar->orientation() != VerticalScrollbar)
103         return;
104
105     if (rect.height() <= 0 || rect.width() <= 0)
106         return;  // nothing to draw on.
107
108     if (!tickmarks.size())
109         return;
110
111     GraphicsContextStateSaver stateSaver(*context);
112     context->setShouldAntialias(false);
113     context->setStrokeColor(Color(0xCC, 0xAA, 0x00, 0xFF));
114     context->setFillColor(Color(0xFF, 0xDD, 0x00, 0xFF));
115
116     for (Vector<IntRect>::const_iterator i = tickmarks.begin(); i != tickmarks.end(); ++i) {
117         // Calculate how far down (in %) the tick-mark should appear.
118         const float percent = static_cast<float>(i->y()) / scrollbar->totalSize();
119         if (percent < 0.0 || percent > 1.0)
120             continue;
121
122         // Calculate how far down (in pixels) the tick-mark should appear.
123         const int yPos = rect.y() + (rect.height() * percent);
124
125         // Paint.
126         FloatRect tickRect(rect.x(), yPos, rect.width(), 2);
127         context->fillRect(tickRect);
128         context->strokeRect(tickRect, 1);
129     }
130 }
131
132 void ScrollbarThemeMacCommon::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
133 {
134     const bool hasHorizontalOverhang = !horizontalOverhangRect.isEmpty();
135     const bool hasVerticalOverhang = !verticalOverhangRect.isEmpty();
136
137     GraphicsContextStateSaver stateSaver(*context);
138
139     if (!m_overhangPattern) {
140         // Lazily load the linen pattern image used for overhang drawing.
141         RefPtr<Image> patternImage = Image::loadPlatformResource("overhangPattern");
142         m_overhangPattern = Pattern::createBitmapPattern(patternImage);
143     }
144     context->setFillPattern(m_overhangPattern);
145     if (hasHorizontalOverhang)
146         context->fillRect(intersection(horizontalOverhangRect, dirtyRect));
147     if (hasVerticalOverhang)
148         context->fillRect(intersection(verticalOverhangRect, dirtyRect));
149 }
150
151 void ScrollbarThemeMacCommon::paintOverhangShadows(GraphicsContext* context, const IntSize& scrollOffset, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
152 {
153     // The extent of each shadow in pixels.
154     const int kShadowSize = 4;
155     // Offset of negative one pixel to make the gradient blend with the toolbar's bottom border.
156     const int kToolbarShadowOffset = -1;
157     const struct {
158         float stop;
159         Color color;
160     } kShadowColors[] = {
161         { 0.000, Color(0, 0, 0, 255) },
162         { 0.125, Color(0, 0, 0, 57) },
163         { 0.375, Color(0, 0, 0, 41) },
164         { 0.625, Color(0, 0, 0, 18) },
165         { 0.875, Color(0, 0, 0, 6) },
166         { 1.000, Color(0, 0, 0, 0) }
167     };
168     const unsigned kNumShadowColors = sizeof(kShadowColors)/sizeof(kShadowColors[0]);
169
170     const bool hasHorizontalOverhang = !horizontalOverhangRect.isEmpty();
171     const bool hasVerticalOverhang = !verticalOverhangRect.isEmpty();
172     // Prefer non-additive shadows, but degrade to additive shadows if there is vertical overhang.
173     const bool useAdditiveShadows = hasVerticalOverhang;
174
175     GraphicsContextStateSaver stateSaver(*context);
176
177     FloatPoint shadowCornerOrigin;
178     FloatPoint shadowCornerOffset;
179
180     // Draw the shadow for the horizontal overhang.
181     if (hasHorizontalOverhang) {
182         int toolbarShadowHeight = kShadowSize;
183         RefPtr<Gradient> gradient;
184         IntRect shadowRect = horizontalOverhangRect;
185         shadowRect.setHeight(kShadowSize);
186         if (scrollOffset.height() < 0) {
187             if (useAdditiveShadows) {
188                 toolbarShadowHeight = std::min(horizontalOverhangRect.height(), kShadowSize);
189             } else if (horizontalOverhangRect.height() < 2 * kShadowSize + kToolbarShadowOffset) {
190                 // Split the overhang area between the web content shadow and toolbar shadow if it's too small.
191                 shadowRect.setHeight((horizontalOverhangRect.height() + 1) / 2);
192                 toolbarShadowHeight = horizontalOverhangRect.height() - shadowRect.height() - kToolbarShadowOffset;
193             }
194             shadowRect.setY(horizontalOverhangRect.maxY() - shadowRect.height());
195             gradient = Gradient::create(FloatPoint(0, shadowRect.maxY()), FloatPoint(0, shadowRect.maxY() - kShadowSize));
196             shadowCornerOrigin.setY(shadowRect.maxY());
197             shadowCornerOffset.setY(-kShadowSize);
198         } else {
199             gradient = Gradient::create(FloatPoint(0, shadowRect.y()), FloatPoint(0, shadowRect.maxY()));
200             shadowCornerOrigin.setY(shadowRect.y());
201         }
202         if (hasVerticalOverhang) {
203             shadowRect.setWidth(shadowRect.width() - verticalOverhangRect.width());
204             if (scrollOffset.width() < 0) {
205                 shadowRect.setX(shadowRect.x() + verticalOverhangRect.width());
206                 shadowCornerOrigin.setX(shadowRect.x());
207                 shadowCornerOffset.setX(-kShadowSize);
208             } else {
209                 shadowCornerOrigin.setX(shadowRect.maxX());
210             }
211         }
212         for (unsigned i = 0; i < kNumShadowColors; i++)
213             gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color);
214         context->setFillGradient(gradient);
215         context->fillRect(intersection(shadowRect, dirtyRect));
216
217         // Draw a drop-shadow from the toolbar.
218         if (scrollOffset.height() < 0) {
219             shadowRect.setY(kToolbarShadowOffset);
220             shadowRect.setHeight(toolbarShadowHeight);
221             gradient = Gradient::create(FloatPoint(0, shadowRect.y()), FloatPoint(0, shadowRect.y() + kShadowSize));
222             for (unsigned i = 0; i < kNumShadowColors; i++)
223                 gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color);
224             context->setFillGradient(gradient);
225             context->fillRect(intersection(shadowRect, dirtyRect));
226         }
227     }
228
229     // Draw the shadow for the vertical overhang.
230     if (hasVerticalOverhang) {
231         RefPtr<Gradient> gradient;
232         IntRect shadowRect = verticalOverhangRect;
233         shadowRect.setWidth(kShadowSize);
234         if (scrollOffset.width() < 0) {
235             shadowRect.setX(verticalOverhangRect.maxX() - shadowRect.width());
236             gradient = Gradient::create(FloatPoint(shadowRect.maxX(), 0), FloatPoint(shadowRect.x(), 0));
237         } else {
238             gradient = Gradient::create(FloatPoint(shadowRect.x(), 0), FloatPoint(shadowRect.maxX(), 0));
239         }
240         for (unsigned i = 0; i < kNumShadowColors; i++)
241             gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color);
242         context->setFillGradient(gradient);
243         context->fillRect(intersection(shadowRect, dirtyRect));
244
245         // Draw a drop-shadow from the toolbar.
246         shadowRect = verticalOverhangRect;
247         shadowRect.setY(kToolbarShadowOffset);
248         shadowRect.setHeight(kShadowSize);
249         gradient = Gradient::create(FloatPoint(0, shadowRect.y()), FloatPoint(0, shadowRect.maxY()));
250         for (unsigned i = 0; i < kNumShadowColors; i++)
251             gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color);
252         context->setFillGradient(gradient);
253         context->fillRect(intersection(shadowRect, dirtyRect));
254     }
255
256     // If both rectangles present, draw a radial gradient for the corner.
257     if (hasHorizontalOverhang && hasVerticalOverhang) {
258         RefPtr<Gradient> gradient = Gradient::create(shadowCornerOrigin, 0, shadowCornerOrigin, kShadowSize);
259         for (unsigned i = 0; i < kNumShadowColors; i++)
260             gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color);
261         context->setFillGradient(gradient);
262         context->fillRect(FloatRect(shadowCornerOrigin.x() + shadowCornerOffset.x(), shadowCornerOrigin.y() + shadowCornerOffset.y(), kShadowSize, kShadowSize));
263     }
264 }
265
266 void ScrollbarThemeMacCommon::paintTickmarks(GraphicsContext* context, ScrollbarThemeClient* scrollbar, const IntRect& rect)
267 {
268     // Note: This is only used for css-styled scrollbars on mac.
269     if (scrollbar->orientation() != VerticalScrollbar)
270         return;
271
272     if (rect.height() <= 0 || rect.width() <= 0)
273         return;
274
275     Vector<IntRect> tickmarks;
276     scrollbar->getTickmarks(tickmarks);
277     if (!tickmarks.size())
278         return;
279
280     // Inset a bit.
281     IntRect tickmarkTrackRect = rect;
282     tickmarkTrackRect.setX(tickmarkTrackRect.x() + 1);
283     tickmarkTrackRect.setWidth(tickmarkTrackRect.width() - 2);
284     paintGivenTickmarks(context, scrollbar, tickmarkTrackRect, tickmarks);
285 }
286
287 ScrollbarThemeMacCommon::~ScrollbarThemeMacCommon()
288 {
289 }
290
291 void ScrollbarThemeMacCommon::preferencesChanged(float initialButtonDelay, float autoscrollButtonDelay, NSScrollerStyle preferredScrollerStyle, bool redraw)
292 {
293     updateButtonPlacement();
294     gInitialButtonDelay = initialButtonDelay;
295     gAutoscrollButtonDelay = autoscrollButtonDelay;
296     bool sendScrollerStyleNotification = gPreferredScrollerStyle != preferredScrollerStyle;
297     gPreferredScrollerStyle = preferredScrollerStyle;
298     if (redraw && !scrollbarSet().isEmpty()) {
299         ScrollbarSet::iterator end = scrollbarSet().end();
300         for (ScrollbarSet::iterator it = scrollbarSet().begin(); it != end; ++it) {
301             (*it)->styleChanged();
302             (*it)->invalidate();
303         }
304     }
305     if (sendScrollerStyleNotification) {
306         [[NSNotificationCenter defaultCenter]
307             postNotificationName:@"NSPreferredScrollerStyleDidChangeNotification"
308                           object:nil
309                         userInfo:@{ @"NSScrollerStyle" : @(gPreferredScrollerStyle) }];
310     }
311 }
312
313 double ScrollbarThemeMacCommon::initialAutoscrollTimerDelay()
314 {
315     return gInitialButtonDelay;
316 }
317
318 double ScrollbarThemeMacCommon::autoscrollTimerDelay()
319 {
320     return gAutoscrollButtonDelay;
321 }
322
323 bool ScrollbarThemeMacCommon::shouldDragDocumentInsteadOfThumb(ScrollbarThemeClient*, const PlatformMouseEvent& event)
324 {
325     return event.altKey();
326 }
327
328 int ScrollbarThemeMacCommon::scrollbarPartToHIPressedState(ScrollbarPart part)
329 {
330     switch (part) {
331         case BackButtonStartPart:
332             return kThemeTopOutsideArrowPressed;
333         case BackButtonEndPart:
334             return kThemeTopOutsideArrowPressed; // This does not make much sense.  For some reason the outside constant is required.
335         case ForwardButtonStartPart:
336             return kThemeTopInsideArrowPressed;
337         case ForwardButtonEndPart:
338             return kThemeBottomOutsideArrowPressed;
339         case ThumbPart:
340             return kThemeThumbPressed;
341         default:
342             return 0;
343     }
344 }
345
346 // static
347 NSScrollerStyle ScrollbarThemeMacCommon::recommendedScrollerStyle()
348 {
349     if (RuntimeEnabledFeatures::overlayScrollbarsEnabled())
350         return NSScrollerStyleOverlay;
351     return gPreferredScrollerStyle;
352 }
353
354 // static
355 bool ScrollbarThemeMacCommon::isOverlayAPIAvailable()
356 {
357     static bool apiAvailable =
358         [NSClassFromString(@"NSScrollerImp") respondsToSelector:@selector(scrollerImpWithStyle:controlSize:horizontal:replacingScrollerImp:)]
359         && [NSClassFromString(@"NSScrollerImpPair") instancesRespondToSelector:@selector(scrollerStyle)];
360     return apiAvailable;
361 }
362
363 } // namespace blink