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 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
    364