2 * Copyright (C) 2008, 2011 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "platform/RuntimeEnabledFeatures.h"
28 #include "platform/scroll/ScrollbarThemeMacCommon.h"
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"
53 // FIXME: There are repainting problems due to Aqua scroll bar buttons' visual overflow.
55 using namespace blink;
57 @interface NSColor (WebNSColorDetails)
58 + (NSImage *)_linenPatternImage;
63 typedef HashSet<ScrollbarThemeClient*> ScrollbarSet;
65 static ScrollbarSet& scrollbarSet()
67 DEFINE_STATIC_LOCAL(ScrollbarSet, set, ());
71 static float gInitialButtonDelay = 0.5f;
72 static float gAutoscrollButtonDelay = 0.05f;
73 static NSScrollerStyle gPreferredScrollerStyle = NSScrollerStyleLegacy;
75 ScrollbarTheme* ScrollbarTheme::nativeTheme()
77 static ScrollbarThemeMacCommon* theme = NULL;
80 if (ScrollbarThemeMacCommon::isOverlayAPIAvailable()) {
81 DEFINE_STATIC_LOCAL(ScrollbarThemeMacOverlayAPI, overlayTheme, ());
82 theme = &overlayTheme;
84 DEFINE_STATIC_LOCAL(ScrollbarThemeMacNonOverlayAPI, nonOverlayTheme, ());
85 theme = &nonOverlayTheme;
90 void ScrollbarThemeMacCommon::registerScrollbar(ScrollbarThemeClient* scrollbar)
92 scrollbarSet().add(scrollbar);
95 void ScrollbarThemeMacCommon::unregisterScrollbar(ScrollbarThemeClient* scrollbar)
97 scrollbarSet().remove(scrollbar);
100 void ScrollbarThemeMacCommon::paintGivenTickmarks(GraphicsContext* context, ScrollbarThemeClient* scrollbar, const IntRect& rect, const Vector<IntRect>& tickmarks)
102 if (scrollbar->orientation() != VerticalScrollbar)
105 if (rect.height() <= 0 || rect.width() <= 0)
106 return; // nothing to draw on.
108 if (!tickmarks.size())
111 GraphicsContextStateSaver stateSaver(*context);
112 context->setShouldAntialias(false);
113 context->setStrokeColor(Color(0xCC, 0xAA, 0x00, 0xFF));
114 context->setFillColor(Color(0xFF, 0xDD, 0x00, 0xFF));
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)
122 // Calculate how far down (in pixels) the tick-mark should appear.
123 const int yPos = rect.y() + (rect.height() * percent);
126 FloatRect tickRect(rect.x(), yPos, rect.width(), 2);
127 context->fillRect(tickRect);
128 context->strokeRect(tickRect, 1);
132 void ScrollbarThemeMacCommon::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
134 const bool hasHorizontalOverhang = !horizontalOverhangRect.isEmpty();
135 const bool hasVerticalOverhang = !verticalOverhangRect.isEmpty();
137 GraphicsContextStateSaver stateSaver(*context);
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);
144 context->setFillPattern(m_overhangPattern);
145 if (hasHorizontalOverhang)
146 context->fillRect(intersection(horizontalOverhangRect, dirtyRect));
147 if (hasVerticalOverhang)
148 context->fillRect(intersection(verticalOverhangRect, dirtyRect));
151 void ScrollbarThemeMacCommon::paintOverhangShadows(GraphicsContext* context, const IntSize& scrollOffset, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
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;
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) }
168 const unsigned kNumShadowColors = sizeof(kShadowColors)/sizeof(kShadowColors[0]);
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;
175 GraphicsContextStateSaver stateSaver(*context);
177 FloatPoint shadowCornerOrigin;
178 FloatPoint shadowCornerOffset;
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;
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);
199 gradient = Gradient::create(FloatPoint(0, shadowRect.y()), FloatPoint(0, shadowRect.maxY()));
200 shadowCornerOrigin.setY(shadowRect.y());
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);
209 shadowCornerOrigin.setX(shadowRect.maxX());
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));
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));
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));
238 gradient = Gradient::create(FloatPoint(shadowRect.x(), 0), FloatPoint(shadowRect.maxX(), 0));
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));
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));
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));
266 void ScrollbarThemeMacCommon::paintTickmarks(GraphicsContext* context, ScrollbarThemeClient* scrollbar, const IntRect& rect)
268 // Note: This is only used for css-styled scrollbars on mac.
269 if (scrollbar->orientation() != VerticalScrollbar)
272 if (rect.height() <= 0 || rect.width() <= 0)
275 Vector<IntRect> tickmarks;
276 scrollbar->getTickmarks(tickmarks);
277 if (!tickmarks.size())
281 IntRect tickmarkTrackRect = rect;
282 tickmarkTrackRect.setX(tickmarkTrackRect.x() + 1);
283 tickmarkTrackRect.setWidth(tickmarkTrackRect.width() - 2);
284 paintGivenTickmarks(context, scrollbar, tickmarkTrackRect, tickmarks);
287 ScrollbarThemeMacCommon::~ScrollbarThemeMacCommon()
291 void ScrollbarThemeMacCommon::preferencesChanged(float initialButtonDelay, float autoscrollButtonDelay, NSScrollerStyle preferredScrollerStyle, bool redraw)
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();
305 if (sendScrollerStyleNotification) {
306 [[NSNotificationCenter defaultCenter]
307 postNotificationName:@"NSPreferredScrollerStyleDidChangeNotification"
309 userInfo:@{ @"NSScrollerStyle" : @(gPreferredScrollerStyle) }];
313 double ScrollbarThemeMacCommon::initialAutoscrollTimerDelay()
315 return gInitialButtonDelay;
318 double ScrollbarThemeMacCommon::autoscrollTimerDelay()
320 return gAutoscrollButtonDelay;
323 bool ScrollbarThemeMacCommon::shouldDragDocumentInsteadOfThumb(ScrollbarThemeClient*, const PlatformMouseEvent& event)
325 return event.altKey();
328 int ScrollbarThemeMacCommon::scrollbarPartToHIPressedState(ScrollbarPart 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;
340 return kThemeThumbPressed;
347 NSScrollerStyle ScrollbarThemeMacCommon::recommendedScrollerStyle()
349 if (RuntimeEnabledFeatures::overlayScrollbarsEnabled())
350 return NSScrollerStyleOverlay;
351 return gPreferredScrollerStyle;
355 bool ScrollbarThemeMacCommon::isOverlayAPIAvailable()
357 static bool apiAvailable =
358 [NSClassFromString(@"NSScrollerImp") respondsToSelector:@selector(scrollerImpWithStyle:controlSize:horizontal:replacingScrollerImp:)]
359 && [NSClassFromString(@"NSScrollerImpPair") instancesRespondToSelector:@selector(scrollerStyle)];