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