/* * Copyright (C) 2008, 2011 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "platform/scroll/ScrollbarThemeMacCommon.h" #include #include "platform/PlatformMouseEvent.h" #include "platform/graphics/Gradient.h" #include "platform/graphics/GraphicsContext.h" #include "platform/graphics/GraphicsContextStateSaver.h" #include "platform/graphics/GraphicsLayer.h" #include "platform/graphics/ImageBuffer.h" #include "platform/mac/ColorMac.h" #include "platform/mac/LocalCurrentGraphicsContext.h" #include "platform/mac/NSScrollerImpDetails.h" #include "platform/mac/ScrollAnimatorMac.h" #include "platform/scroll/ScrollbarThemeClient.h" #include "platform/scroll/ScrollbarThemeMacNonOverlayAPI.h" #include "platform/scroll/ScrollbarThemeMacOverlayAPI.h" #include "public/platform/mac/WebThemeEngine.h" #include "public/platform/Platform.h" #include "public/platform/WebRect.h" #include "skia/ext/skia_utils_mac.h" #include "wtf/HashSet.h" #include "wtf/StdLibExtras.h" #include "wtf/TemporaryChange.h" // FIXME: There are repainting problems due to Aqua scroll bar buttons' visual overflow. using namespace std; using namespace WebCore; @interface NSColor (WebNSColorDetails) + (NSImage *)_linenPatternImage; @end namespace WebCore { typedef HashSet ScrollbarSet; static ScrollbarSet& scrollbarSet() { DEFINE_STATIC_LOCAL(ScrollbarSet, set, ()); return set; } } namespace WebCore { static float gInitialButtonDelay = 0.5f; static float gAutoscrollButtonDelay = 0.05f; static bool gJumpOnTrackClick = false; ScrollbarTheme* ScrollbarTheme::nativeTheme() { static ScrollbarThemeMacCommon* theme = NULL; if (theme) return theme; if (isScrollbarOverlayAPIAvailable()) { DEFINE_STATIC_LOCAL(ScrollbarThemeMacOverlayAPI, overlayTheme, ()); theme = &overlayTheme; } else { DEFINE_STATIC_LOCAL(ScrollbarThemeMacNonOverlayAPI, nonOverlayTheme, ()); theme = &nonOverlayTheme; } return theme; } void ScrollbarThemeMacCommon::registerScrollbar(ScrollbarThemeClient* scrollbar) { scrollbarSet().add(scrollbar); } void ScrollbarThemeMacCommon::unregisterScrollbar(ScrollbarThemeClient* scrollbar) { scrollbarSet().remove(scrollbar); } void ScrollbarThemeMacCommon::paintGivenTickmarks(GraphicsContext* context, ScrollbarThemeClient* scrollbar, const IntRect& rect, const Vector& tickmarks) { if (scrollbar->orientation() != VerticalScrollbar) return; if (rect.height() <= 0 || rect.width() <= 0) return; // nothing to draw on. if (!tickmarks.size()) return; GraphicsContextStateSaver stateSaver(*context); context->setShouldAntialias(false); context->setStrokeColor(Color(0xCC, 0xAA, 0x00, 0xFF)); context->setFillColor(Color(0xFF, 0xDD, 0x00, 0xFF)); for (Vector::const_iterator i = tickmarks.begin(); i != tickmarks.end(); ++i) { // Calculate how far down (in %) the tick-mark should appear. const float percent = static_cast(i->y()) / scrollbar->totalSize(); if (percent < 0.0 || percent > 1.0) continue; // Calculate how far down (in pixels) the tick-mark should appear. const int yPos = rect.y() + (rect.height() * percent); // Paint. FloatRect tickRect(rect.x(), yPos, rect.width(), 2); context->fillRect(tickRect); context->strokeRect(tickRect, 1); } } void ScrollbarThemeMacCommon::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect) { const bool hasHorizontalOverhang = !horizontalOverhangRect.isEmpty(); const bool hasVerticalOverhang = !verticalOverhangRect.isEmpty(); GraphicsContextStateSaver stateSaver(*context); if (!m_overhangPattern) { // Lazily load the linen pattern image used for overhang drawing. RefPtr patternImage = Image::loadPlatformResource("overhangPattern"); m_overhangPattern = Pattern::create(patternImage, true, true); } context->setFillPattern(m_overhangPattern); if (hasHorizontalOverhang) context->fillRect(intersection(horizontalOverhangRect, dirtyRect)); if (hasVerticalOverhang) context->fillRect(intersection(verticalOverhangRect, dirtyRect)); } void ScrollbarThemeMacCommon::paintOverhangShadows(GraphicsContext* context, const IntSize& scrollOffset, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect) { // The extent of each shadow in pixels. const int kShadowSize = 4; // Offset of negative one pixel to make the gradient blend with the toolbar's bottom border. const int kToolbarShadowOffset = -1; const struct { float stop; Color color; } kShadowColors[] = { { 0.000, Color(0, 0, 0, 255) }, { 0.125, Color(0, 0, 0, 57) }, { 0.375, Color(0, 0, 0, 41) }, { 0.625, Color(0, 0, 0, 18) }, { 0.875, Color(0, 0, 0, 6) }, { 1.000, Color(0, 0, 0, 0) } }; const unsigned kNumShadowColors = sizeof(kShadowColors)/sizeof(kShadowColors[0]); const bool hasHorizontalOverhang = !horizontalOverhangRect.isEmpty(); const bool hasVerticalOverhang = !verticalOverhangRect.isEmpty(); // Prefer non-additive shadows, but degrade to additive shadows if there is vertical overhang. const bool useAdditiveShadows = hasVerticalOverhang; GraphicsContextStateSaver stateSaver(*context); FloatPoint shadowCornerOrigin; FloatPoint shadowCornerOffset; // Draw the shadow for the horizontal overhang. if (hasHorizontalOverhang) { int toolbarShadowHeight = kShadowSize; RefPtr gradient; IntRect shadowRect = horizontalOverhangRect; shadowRect.setHeight(kShadowSize); if (scrollOffset.height() < 0) { if (useAdditiveShadows) { toolbarShadowHeight = std::min(horizontalOverhangRect.height(), kShadowSize); } else if (horizontalOverhangRect.height() < 2 * kShadowSize + kToolbarShadowOffset) { // Split the overhang area between the web content shadow and toolbar shadow if it's too small. shadowRect.setHeight((horizontalOverhangRect.height() + 1) / 2); toolbarShadowHeight = horizontalOverhangRect.height() - shadowRect.height() - kToolbarShadowOffset; } shadowRect.setY(horizontalOverhangRect.maxY() - shadowRect.height()); gradient = Gradient::create(FloatPoint(0, shadowRect.maxY()), FloatPoint(0, shadowRect.maxY() - kShadowSize)); shadowCornerOrigin.setY(shadowRect.maxY()); shadowCornerOffset.setY(-kShadowSize); } else { gradient = Gradient::create(FloatPoint(0, shadowRect.y()), FloatPoint(0, shadowRect.maxY())); shadowCornerOrigin.setY(shadowRect.y()); } if (hasVerticalOverhang) { shadowRect.setWidth(shadowRect.width() - verticalOverhangRect.width()); if (scrollOffset.width() < 0) { shadowRect.setX(shadowRect.x() + verticalOverhangRect.width()); shadowCornerOrigin.setX(shadowRect.x()); shadowCornerOffset.setX(-kShadowSize); } else { shadowCornerOrigin.setX(shadowRect.maxX()); } } for (unsigned i = 0; i < kNumShadowColors; i++) gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color); context->setFillGradient(gradient); context->fillRect(intersection(shadowRect, dirtyRect)); // Draw a drop-shadow from the toolbar. if (scrollOffset.height() < 0) { shadowRect.setY(kToolbarShadowOffset); shadowRect.setHeight(toolbarShadowHeight); gradient = Gradient::create(FloatPoint(0, shadowRect.y()), FloatPoint(0, shadowRect.y() + kShadowSize)); for (unsigned i = 0; i < kNumShadowColors; i++) gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color); context->setFillGradient(gradient); context->fillRect(intersection(shadowRect, dirtyRect)); } } // Draw the shadow for the vertical overhang. if (hasVerticalOverhang) { RefPtr gradient; IntRect shadowRect = verticalOverhangRect; shadowRect.setWidth(kShadowSize); if (scrollOffset.width() < 0) { shadowRect.setX(verticalOverhangRect.maxX() - shadowRect.width()); gradient = Gradient::create(FloatPoint(shadowRect.maxX(), 0), FloatPoint(shadowRect.x(), 0)); } else { gradient = Gradient::create(FloatPoint(shadowRect.x(), 0), FloatPoint(shadowRect.maxX(), 0)); } for (unsigned i = 0; i < kNumShadowColors; i++) gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color); context->setFillGradient(gradient); context->fillRect(intersection(shadowRect, dirtyRect)); // Draw a drop-shadow from the toolbar. shadowRect = verticalOverhangRect; shadowRect.setY(kToolbarShadowOffset); shadowRect.setHeight(kShadowSize); gradient = Gradient::create(FloatPoint(0, shadowRect.y()), FloatPoint(0, shadowRect.maxY())); for (unsigned i = 0; i < kNumShadowColors; i++) gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color); context->setFillGradient(gradient); context->fillRect(intersection(shadowRect, dirtyRect)); } // If both rectangles present, draw a radial gradient for the corner. if (hasHorizontalOverhang && hasVerticalOverhang) { RefPtr gradient = Gradient::create(shadowCornerOrigin, 0, shadowCornerOrigin, kShadowSize); for (unsigned i = 0; i < kNumShadowColors; i++) gradient->addColorStop(kShadowColors[i].stop, kShadowColors[i].color); context->setFillGradient(gradient); context->fillRect(FloatRect(shadowCornerOrigin.x() + shadowCornerOffset.x(), shadowCornerOrigin.y() + shadowCornerOffset.y(), kShadowSize, kShadowSize)); } } void ScrollbarThemeMacCommon::paintTickmarks(GraphicsContext* context, ScrollbarThemeClient* scrollbar, const IntRect& rect) { // Note: This is only used for css-styled scrollbars on mac. if (scrollbar->orientation() != VerticalScrollbar) return; if (rect.height() <= 0 || rect.width() <= 0) return; Vector tickmarks; scrollbar->getTickmarks(tickmarks); if (!tickmarks.size()) return; // Inset a bit. IntRect tickmarkTrackRect = rect; tickmarkTrackRect.setX(tickmarkTrackRect.x() + 1); tickmarkTrackRect.setWidth(tickmarkTrackRect.width() - 2); paintGivenTickmarks(context, scrollbar, tickmarkTrackRect, tickmarks); } ScrollbarThemeMacCommon::~ScrollbarThemeMacCommon() { } void ScrollbarThemeMacCommon::preferencesChanged(float initialButtonDelay, float autoscrollButtonDelay, bool jumpOnTrackClick, bool redraw) { updateButtonPlacement(); gInitialButtonDelay = initialButtonDelay; gAutoscrollButtonDelay = autoscrollButtonDelay; gJumpOnTrackClick = jumpOnTrackClick; if (redraw && !scrollbarSet().isEmpty()) { ScrollbarSet::iterator end = scrollbarSet().end(); for (ScrollbarSet::iterator it = scrollbarSet().begin(); it != end; ++it) { (*it)->styleChanged(); (*it)->invalidate(); } } } double ScrollbarThemeMacCommon::initialAutoscrollTimerDelay() { return gInitialButtonDelay; } double ScrollbarThemeMacCommon::autoscrollTimerDelay() { return gAutoscrollButtonDelay; } bool ScrollbarThemeMacCommon::shouldCenterOnThumb(ScrollbarThemeClient*, const PlatformMouseEvent& evt) { if (evt.button() != LeftButton) return false; if (gJumpOnTrackClick) return !evt.altKey(); return evt.altKey(); } bool ScrollbarThemeMacCommon::shouldDragDocumentInsteadOfThumb(ScrollbarThemeClient*, const PlatformMouseEvent& event) { return event.altKey(); } int ScrollbarThemeMacCommon::scrollbarPartToHIPressedState(ScrollbarPart part) { switch (part) { case BackButtonStartPart: return kThemeTopOutsideArrowPressed; case BackButtonEndPart: return kThemeTopOutsideArrowPressed; // This does not make much sense. For some reason the outside constant is required. case ForwardButtonStartPart: return kThemeTopInsideArrowPressed; case ForwardButtonEndPart: return kThemeBottomOutsideArrowPressed; case ThumbPart: return kThemeThumbPressed; default: return 0; } } } // namespace WebCore