2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2009 Google Inc. All Rights Reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "ScrollbarThemeChromiumMac.h"
30 // FIXME: Remove this (always use WebThemeEngine) once we rebaseline tests
31 #define USE_WEB_THEME_ENGINE_TO_PAINT_THUMB 1
33 #if USE_WEB_THEME_ENGINE_TO_PAINT_THUMB
34 #include "ChromiumBridge.h"
35 #include "FrameView.h"
37 #include "ImageBuffer.h"
38 #include "PlatformMouseEvent.h"
39 #include "ScrollView.h"
40 #include <Carbon/Carbon.h>
41 #include <wtf/StdLibExtras.h>
42 #include <wtf/UnusedParam.h>
45 // FIXME: There are repainting problems due to Aqua scroll bar buttons' visual overflow.
48 using namespace WebCore;
50 // This file (and its associated .h file) is a clone of ScrollbarThemeMac.mm.
51 // Because we want to draw tickmarks in the scrollbar, we must maintain a fork.
52 // Please maintain this file by performing parallel changes to it.
54 // The only changes from ScrollbarThemeMac should be:
55 // - The classname change from ScrollbarThemeMac to ScrollbarThemeChromiumMac.
56 // - In paint() the code to paint the track, tickmarks, and thumb separately.
58 // For all other differences, if it was introduced in this file, then the
59 // maintainer forgot to include it in the list; otherwise it is an update that
60 // should have been applied to this file but was not.
62 static HashSet<Scrollbar*>* gScrollbars;
64 @interface ScrollbarPrefsObserver : NSObject
69 + (void)registerAsObserver;
70 + (void)appearancePrefsChanged:(NSNotification*)theNotification;
71 + (void)behaviorPrefsChanged:(NSNotification*)theNotification;
75 @implementation ScrollbarPrefsObserver
77 + (void)appearancePrefsChanged:(NSNotification*)unusedNotification
79 UNUSED_PARAM(unusedNotification);
81 static_cast<ScrollbarThemeChromiumMac*>(ScrollbarTheme::nativeTheme())->preferencesChanged();
84 HashSet<Scrollbar*>::iterator end = gScrollbars->end();
85 for (HashSet<Scrollbar*>::iterator it = gScrollbars->begin(); it != end; ++it) {
86 (*it)->styleChanged();
91 + (void)behaviorPrefsChanged:(NSNotification*)unusedNotification
93 UNUSED_PARAM(unusedNotification);
95 static_cast<ScrollbarThemeChromiumMac*>(ScrollbarTheme::nativeTheme())->preferencesChanged();
98 + (void)registerAsObserver
100 [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(appearancePrefsChanged:) name:@"AppleAquaScrollBarVariantChanged" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
101 [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(behaviorPrefsChanged:) name:@"AppleNoRedisplayAppearancePreferenceChanged" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorCoalesce];
108 ScrollbarTheme* ScrollbarTheme::nativeTheme()
110 DEFINE_STATIC_LOCAL(ScrollbarThemeChromiumMac, theme, ());
114 // FIXME: Get these numbers from CoreUI.
115 static int cScrollbarThickness[] = { 15, 11 };
116 static int cRealButtonLength[] = { 28, 21 };
117 static int cButtonInset[] = { 14, 11 };
118 static int cButtonHitInset[] = { 3, 2 };
119 // cRealButtonLength - cButtonInset
120 static int cButtonLength[] = { 14, 10 };
121 static int cThumbMinLength[] = { 26, 20 };
123 static int cOuterButtonLength[] = { 16, 14 }; // The outer button in a double button pair is a bit bigger.
124 static int cOuterButtonOverlap = 2;
126 static float gInitialButtonDelay = 0.5f;
127 static float gAutoscrollButtonDelay = 0.05f;
128 static bool gJumpOnTrackClick = false;
129 static ScrollbarButtonsPlacement gButtonPlacement = ScrollbarButtonsDoubleEnd;
131 static void updateArrowPlacement()
133 NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
134 if ([buttonPlacement isEqualToString:@"Single"])
135 gButtonPlacement = ScrollbarButtonsSingle;
136 else if ([buttonPlacement isEqualToString:@"DoubleMin"])
137 gButtonPlacement = ScrollbarButtonsDoubleStart;
138 else if ([buttonPlacement isEqualToString:@"DoubleBoth"])
139 gButtonPlacement = ScrollbarButtonsDoubleBoth;
141 gButtonPlacement = ScrollbarButtonsDoubleEnd; // The default is ScrollbarButtonsDoubleEnd.
144 void ScrollbarThemeChromiumMac::registerScrollbar(Scrollbar* scrollbar)
147 gScrollbars = new HashSet<Scrollbar*>;
148 gScrollbars->add(scrollbar);
151 void ScrollbarThemeChromiumMac::unregisterScrollbar(Scrollbar* scrollbar)
153 gScrollbars->remove(scrollbar);
154 if (gScrollbars->isEmpty()) {
160 ScrollbarThemeChromiumMac::ScrollbarThemeChromiumMac()
162 static bool initialized;
165 [ScrollbarPrefsObserver registerAsObserver];
166 preferencesChanged();
170 ScrollbarThemeChromiumMac::~ScrollbarThemeChromiumMac()
174 void ScrollbarThemeChromiumMac::preferencesChanged()
176 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
177 [defaults synchronize];
178 updateArrowPlacement();
179 gInitialButtonDelay = [defaults floatForKey:@"NSScrollerButtonDelay"];
180 gAutoscrollButtonDelay = [defaults floatForKey:@"NSScrollerButtonPeriod"];
181 gJumpOnTrackClick = [defaults boolForKey:@"AppleScrollerPagingBehavior"];
184 int ScrollbarThemeChromiumMac::scrollbarThickness(ScrollbarControlSize controlSize)
186 return cScrollbarThickness[controlSize];
189 double ScrollbarThemeChromiumMac::initialAutoscrollTimerDelay()
191 return gInitialButtonDelay;
194 double ScrollbarThemeChromiumMac::autoscrollTimerDelay()
196 return gAutoscrollButtonDelay;
199 ScrollbarButtonsPlacement ScrollbarThemeChromiumMac::buttonsPlacement() const
201 return gButtonPlacement;
204 bool ScrollbarThemeChromiumMac::hasButtons(Scrollbar* scrollbar)
206 return scrollbar->enabled() && (scrollbar->orientation() == HorizontalScrollbar ?
208 scrollbar->height()) >= 2 * (cRealButtonLength[scrollbar->controlSize()] - cButtonHitInset[scrollbar->controlSize()]);
211 bool ScrollbarThemeChromiumMac::hasThumb(Scrollbar* scrollbar)
213 return scrollbar->enabled() && (scrollbar->orientation() == HorizontalScrollbar ?
215 scrollbar->height()) >= 2 * cButtonInset[scrollbar->controlSize()] + cThumbMinLength[scrollbar->controlSize()] + 1;
218 static IntRect buttonRepaintRect(const IntRect& buttonRect, ScrollbarOrientation orientation, ScrollbarControlSize controlSize, bool start)
220 IntRect paintRect(buttonRect);
221 if (orientation == HorizontalScrollbar) {
222 paintRect.setWidth(cRealButtonLength[controlSize]);
224 paintRect.setX(buttonRect.x() - (cRealButtonLength[controlSize] - buttonRect.width()));
226 paintRect.setHeight(cRealButtonLength[controlSize]);
228 paintRect.setY(buttonRect.y() - (cRealButtonLength[controlSize] - buttonRect.height()));
234 IntRect ScrollbarThemeChromiumMac::backButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool painting)
238 if (part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd))
241 if (part == BackButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsSingle))
244 int thickness = scrollbarThickness(scrollbar->controlSize());
245 bool outerButton = part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
247 if (scrollbar->orientation() == HorizontalScrollbar)
248 result = IntRect(scrollbar->x(), scrollbar->y(), cOuterButtonLength[scrollbar->controlSize()] + painting ? cOuterButtonOverlap : 0, thickness);
250 result = IntRect(scrollbar->x(), scrollbar->y(), thickness, cOuterButtonLength[scrollbar->controlSize()] + painting ? cOuterButtonOverlap : 0);
254 // Our repaint rect is slightly larger, since we are a button that is adjacent to the track.
255 if (scrollbar->orientation() == HorizontalScrollbar) {
256 int start = part == BackButtonStartPart ? scrollbar->x() : scrollbar->x() + scrollbar->width() - cOuterButtonLength[scrollbar->controlSize()] - cButtonLength[scrollbar->controlSize()];
257 result = IntRect(start, scrollbar->y(), cButtonLength[scrollbar->controlSize()], thickness);
259 int start = part == BackButtonStartPart ? scrollbar->y() : scrollbar->y() + scrollbar->height() - cOuterButtonLength[scrollbar->controlSize()] - cButtonLength[scrollbar->controlSize()];
260 result = IntRect(scrollbar->x(), start, thickness, cButtonLength[scrollbar->controlSize()]);
264 return buttonRepaintRect(result, scrollbar->orientation(), scrollbar->controlSize(), part == BackButtonStartPart);
268 IntRect ScrollbarThemeChromiumMac::forwardButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool painting)
272 if (part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart))
275 if (part == ForwardButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsSingle))
278 int thickness = scrollbarThickness(scrollbar->controlSize());
279 int outerButtonLength = cOuterButtonLength[scrollbar->controlSize()];
280 int buttonLength = cButtonLength[scrollbar->controlSize()];
282 bool outerButton = part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
284 if (scrollbar->orientation() == HorizontalScrollbar) {
285 result = IntRect(scrollbar->x() + scrollbar->width() - outerButtonLength, scrollbar->y(), outerButtonLength, thickness);
287 result.inflateX(cOuterButtonOverlap);
289 result = IntRect(scrollbar->x(), scrollbar->y() + scrollbar->height() - outerButtonLength, thickness, outerButtonLength);
291 result.inflateY(cOuterButtonOverlap);
296 if (scrollbar->orientation() == HorizontalScrollbar) {
297 int start = part == ForwardButtonEndPart ? scrollbar->x() + scrollbar->width() - buttonLength : scrollbar->x() + outerButtonLength;
298 result = IntRect(start, scrollbar->y(), buttonLength, thickness);
300 int start = part == ForwardButtonEndPart ? scrollbar->y() + scrollbar->height() - buttonLength : scrollbar->y() + outerButtonLength;
301 result = IntRect(scrollbar->x(), start, thickness, buttonLength);
304 return buttonRepaintRect(result, scrollbar->orientation(), scrollbar->controlSize(), part == ForwardButtonStartPart);
308 IntRect ScrollbarThemeChromiumMac::trackRect(Scrollbar* scrollbar, bool painting)
310 if (painting || !hasButtons(scrollbar))
311 return scrollbar->frameRect();
314 int thickness = scrollbarThickness(scrollbar->controlSize());
317 int outerButtonLength = cOuterButtonLength[scrollbar->controlSize()];
318 int buttonLength = cButtonLength[scrollbar->controlSize()];
319 int doubleButtonLength = outerButtonLength + buttonLength;
320 switch (buttonsPlacement()) {
321 case ScrollbarButtonsSingle:
322 startWidth = buttonLength;
323 endWidth = buttonLength;
325 case ScrollbarButtonsDoubleStart:
326 startWidth = doubleButtonLength;
328 case ScrollbarButtonsDoubleEnd:
329 endWidth = doubleButtonLength;
331 case ScrollbarButtonsDoubleBoth:
332 startWidth = doubleButtonLength;
333 endWidth = doubleButtonLength;
339 int totalWidth = startWidth + endWidth;
340 if (scrollbar->orientation() == HorizontalScrollbar)
341 return IntRect(scrollbar->x() + startWidth, scrollbar->y(), scrollbar->width() - totalWidth, thickness);
342 return IntRect(scrollbar->x(), scrollbar->y() + startWidth, thickness, scrollbar->height() - totalWidth);
345 int ScrollbarThemeChromiumMac::minimumThumbLength(Scrollbar* scrollbar)
347 return cThumbMinLength[scrollbar->controlSize()];
350 bool ScrollbarThemeChromiumMac::shouldCenterOnThumb(Scrollbar*, const PlatformMouseEvent& evt)
352 if (evt.button() != LeftButton)
354 if (gJumpOnTrackClick)
355 return !evt.altKey();
359 static int scrollbarPartToHIPressedState(ScrollbarPart part)
362 case BackButtonStartPart:
363 return kThemeTopOutsideArrowPressed;
364 case BackButtonEndPart:
365 return kThemeTopOutsideArrowPressed; // This does not make much sense. For some reason the outside constant is required.
366 case ForwardButtonStartPart:
367 return kThemeTopInsideArrowPressed;
368 case ForwardButtonEndPart:
369 return kThemeBottomOutsideArrowPressed;
371 return kThemeThumbPressed;
377 #if USE_WEB_THEME_ENGINE_TO_PAINT_THUMB
378 static ChromiumBridge::ThemePaintState scrollbarStateToThemeState(Scrollbar* scrollbar) {
379 if (!scrollbar->enabled())
380 return ChromiumBridge::StateDisabled;
381 if (!scrollbar->client()->isActive())
382 return ChromiumBridge::StateInactive;
383 if (scrollbar->pressedPart() == ThumbPart)
384 return ChromiumBridge::StatePressed;
386 return ChromiumBridge::StateActive;
388 #endif // USE_WEB_THEME_ENGINE_TO_PAINT_THUMB
390 bool ScrollbarThemeChromiumMac::paint(Scrollbar* scrollbar, GraphicsContext* context, const IntRect& damageRect)
392 HIThemeTrackDrawInfo trackInfo;
393 trackInfo.version = 0;
394 trackInfo.kind = scrollbar->controlSize() == RegularScrollbar ? kThemeMediumScrollBar : kThemeSmallScrollBar;
395 trackInfo.bounds = scrollbar->frameRect();
397 trackInfo.max = scrollbar->maximum();
398 trackInfo.value = scrollbar->currentPos();
399 trackInfo.trackInfo.scrollbar.viewsize = scrollbar->visibleSize();
400 trackInfo.attributes = 0;
401 if (scrollbar->orientation() == HorizontalScrollbar)
402 trackInfo.attributes |= kThemeTrackHorizontal;
404 if (!scrollbar->enabled())
405 trackInfo.enableState = kThemeTrackDisabled;
407 trackInfo.enableState = scrollbar->client()->isActive() ? kThemeTrackActive : kThemeTrackInactive;
409 if (!hasButtons(scrollbar))
410 trackInfo.enableState = kThemeTrackNothingToScroll;
411 trackInfo.trackInfo.scrollbar.pressState = scrollbarPartToHIPressedState(scrollbar->pressedPart());
413 CGAffineTransform currentCTM = CGContextGetCTM(context->platformContext());
415 // The Aqua scrollbar is buggy when rotated and scaled. We will just draw into a bitmap if we detect a scale or rotation.
416 bool canDrawDirectly = currentCTM.a == 1.0f && currentCTM.b == 0.0f && currentCTM.c == 0.0f && (currentCTM.d == 1.0f || currentCTM.d == -1.0f);
417 GraphicsContext* drawingContext = context;
418 OwnPtr<ImageBuffer> imageBuffer;
419 if (!canDrawDirectly) {
420 trackInfo.bounds = IntRect(IntPoint(), scrollbar->frameRect().size());
422 IntRect bufferRect(scrollbar->frameRect());
423 bufferRect.intersect(damageRect);
424 bufferRect.move(-scrollbar->frameRect().x(), -scrollbar->frameRect().y());
426 imageBuffer = ImageBuffer::create(bufferRect.size());
430 drawingContext = imageBuffer->context();
434 HIThemeDrawTrack(&trackInfo, 0, drawingContext->platformContext(), kHIThemeOrientationNormal);
436 Vector<IntRect> tickmarks;
437 scrollbar->client()->getTickmarks(tickmarks);
438 if (scrollbar->orientation() == VerticalScrollbar && tickmarks.size()) {
439 drawingContext->save();
440 drawingContext->setShouldAntialias(false);
441 drawingContext->setStrokeColor(Color(0xCC, 0xAA, 0x00, 0xFF), ColorSpaceDeviceRGB);
442 drawingContext->setFillColor(Color(0xFF, 0xDD, 0x00, 0xFF), ColorSpaceDeviceRGB);
444 IntRect thumbArea = trackRect(scrollbar, false);
445 if (!canDrawDirectly) {
449 // The ends are rounded and the thumb doesn't go there.
450 thumbArea.inflateY(-thumbArea.width());
452 for (Vector<IntRect>::const_iterator i = tickmarks.begin(); i != tickmarks.end(); ++i) {
453 // Calculate how far down (in %) the tick-mark should appear.
454 const float percent = static_cast<float>(i->y()) / scrollbar->totalSize();
455 if (percent < 0.0 || percent > 1.0)
458 // Calculate how far down (in pixels) the tick-mark should appear.
459 const int yPos = static_cast<int>((thumbArea.topLeft().y() + (thumbArea.height() * percent))) & ~1;
462 const int indent = 2;
463 FloatRect tickRect(thumbArea.topLeft().x() + indent, yPos, thumbArea.width() - 2 * indent - 1, 2);
464 drawingContext->fillRect(tickRect);
465 drawingContext->strokeRect(tickRect, 1);
468 drawingContext->restore();
471 if (hasThumb(scrollbar)) {
472 #if USE_WEB_THEME_ENGINE_TO_PAINT_THUMB
473 ChromiumBridge::ThemePaintScrollbarInfo scrollbarInfo;
474 scrollbarInfo.orientation = scrollbar->orientation() == HorizontalScrollbar ? ChromiumBridge::ScrollbarOrientationHorizontal : ChromiumBridge::ScrollbarOrientationVertical;
475 scrollbarInfo.parent = scrollbar->parent()->isFrameView() && static_cast<FrameView*>(scrollbar->parent())->isScrollViewScrollbar(scrollbar) ? ChromiumBridge::ScrollbarParentScrollView : ChromiumBridge::ScrollbarParentRenderLayer;
476 scrollbarInfo.maxValue = scrollbar->maximum();
477 scrollbarInfo.currentValue = scrollbar->currentPos();
478 scrollbarInfo.visibleSize = scrollbar->visibleSize();
479 scrollbarInfo.totalSize = scrollbar->totalSize();
481 ChromiumBridge::paintScrollbarThumb(
483 scrollbarStateToThemeState(scrollbar),
484 scrollbar->controlSize() == RegularScrollbar ? ChromiumBridge::SizeRegular : ChromiumBridge::SizeSmall,
485 scrollbar->frameRect(),
488 trackInfo.attributes |= (kThemeTrackShowThumb | kThemeTrackHideTrack);
489 HIThemeDrawTrack(&trackInfo, 0, drawingContext->platformContext(), kHIThemeOrientationNormal);
493 if (!canDrawDirectly)
494 context->drawImageBuffer(imageBuffer.get(), ColorSpaceDeviceRGB, scrollbar->frameRect().location());