Home | History | Annotate | Download | only in scroll
      1 /*
      2  * Copyright (C) 2013 Google 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 are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "platform/scroll/ScrollbarThemeMacNonOverlayAPI.h"
     33 
     34 #include "platform/graphics/GraphicsContext.h"
     35 #include "platform/graphics/ImageBuffer.h"
     36 #include "platform/scroll/ScrollbarThemeClient.h"
     37 #include "public/platform/Platform.h"
     38 #include "public/platform/WebRect.h"
     39 #include "public/platform/WebThemeEngine.h"
     40 #include "skia/ext/skia_utils_mac.h"
     41 #include <Carbon/Carbon.h>
     42 
     43 namespace WebCore {
     44 
     45 // FIXME: Get these numbers from CoreUI.
     46 static int cRealButtonLength[] = { 28, 21 };
     47 static int cButtonHitInset[] = { 3, 2 };
     48 // cRealButtonLength - cButtonInset
     49 static int cButtonLength[] = { 14, 10 };
     50 static int cScrollbarThickness[] = { 15, 11 };
     51 static int cButtonInset[] = { 14, 11 };
     52 static int cThumbMinLength[] = { 26, 20 };
     53 
     54 static int cOuterButtonLength[] = { 16, 14 }; // The outer button in a double button pair is a bit bigger.
     55 static int cOuterButtonOverlap = 2;
     56 
     57 static ScrollbarButtonsPlacement gButtonPlacement = ScrollbarButtonsDoubleEnd;
     58 
     59 void ScrollbarThemeMacNonOverlayAPI::updateButtonPlacement()
     60 {
     61     NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
     62     if ([buttonPlacement isEqualToString:@"Single"])
     63         gButtonPlacement = ScrollbarButtonsSingle;
     64     else if ([buttonPlacement isEqualToString:@"DoubleMin"])
     65         gButtonPlacement = ScrollbarButtonsDoubleStart;
     66     else if ([buttonPlacement isEqualToString:@"DoubleBoth"])
     67         gButtonPlacement = ScrollbarButtonsDoubleBoth;
     68     else {
     69         gButtonPlacement = ScrollbarButtonsDoubleEnd;
     70     }
     71 }
     72 
     73 static blink::WebThemeEngine::State scrollbarStateToThemeState(ScrollbarThemeClient* scrollbar)
     74 {
     75     if (!scrollbar->enabled())
     76         return blink::WebThemeEngine::StateDisabled;
     77     if (!scrollbar->isScrollableAreaActive())
     78         return blink::WebThemeEngine::StateInactive;
     79     if (scrollbar->pressedPart() == ThumbPart)
     80         return blink::WebThemeEngine::StatePressed;
     81 
     82     return blink::WebThemeEngine::StateActive;
     83 }
     84 
     85 // Override ScrollbarThemeMacCommon::paint() to add support for the following:
     86 //     - drawing using WebThemeEngine functions
     87 //     - drawing tickmarks
     88 //     - Skia specific changes
     89 bool ScrollbarThemeMacNonOverlayAPI::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* context, const IntRect& damageRect)
     90 {
     91     if (context->paintingDisabled())
     92         return true;
     93     // Get the tickmarks for the frameview.
     94     Vector<IntRect> tickmarks;
     95     scrollbar->getTickmarks(tickmarks);
     96 
     97     HIThemeTrackDrawInfo trackInfo;
     98     trackInfo.version = 0;
     99     trackInfo.kind = scrollbar->controlSize() == RegularScrollbar ? kThemeMediumScrollBar : kThemeSmallScrollBar;
    100     trackInfo.bounds = scrollbar->frameRect();
    101     trackInfo.min = 0;
    102     trackInfo.max = scrollbar->maximum();
    103     trackInfo.value = scrollbar->currentPos();
    104     trackInfo.trackInfo.scrollbar.viewsize = scrollbar->visibleSize();
    105     trackInfo.attributes = 0;
    106     if (scrollbar->orientation() == HorizontalScrollbar)
    107         trackInfo.attributes |= kThemeTrackHorizontal;
    108 
    109     if (!scrollbar->enabled())
    110         trackInfo.enableState = kThemeTrackDisabled;
    111     else
    112         trackInfo.enableState = scrollbar->isScrollableAreaActive() ? kThemeTrackActive : kThemeTrackInactive;
    113 
    114     if (!hasButtons(scrollbar))
    115         trackInfo.enableState = kThemeTrackNothingToScroll;
    116     trackInfo.trackInfo.scrollbar.pressState = scrollbarPartToHIPressedState(scrollbar->pressedPart());
    117 
    118     SkCanvas* canvas = context->canvas();
    119     CGAffineTransform currentCTM = gfx::SkMatrixToCGAffineTransform(canvas->getTotalMatrix());
    120 
    121     // The Aqua scrollbar is buggy when rotated and scaled.  We will just draw into a bitmap if we detect a scale or rotation.
    122     bool canDrawDirectly = currentCTM.a == 1.0f && currentCTM.b == 0.0f && currentCTM.c == 0.0f && (currentCTM.d == 1.0f || currentCTM.d == -1.0f);
    123     GraphicsContext* drawingContext = context;
    124     OwnPtr<ImageBuffer> imageBuffer;
    125     if (!canDrawDirectly) {
    126         trackInfo.bounds = IntRect(IntPoint(), scrollbar->frameRect().size());
    127 
    128         IntRect bufferRect(scrollbar->frameRect());
    129         bufferRect.intersect(damageRect);
    130         bufferRect.move(-scrollbar->frameRect().x(), -scrollbar->frameRect().y());
    131 
    132         imageBuffer = ImageBuffer::create(bufferRect.size());
    133         if (!imageBuffer)
    134             return true;
    135 
    136         drawingContext = imageBuffer->context();
    137     }
    138 
    139     // Draw thumbless.
    140     gfx::SkiaBitLocker bitLocker(drawingContext->canvas());
    141     CGContextRef cgContext = bitLocker.cgContext();
    142     HIThemeDrawTrack(&trackInfo, 0, cgContext, kHIThemeOrientationNormal);
    143 
    144     IntRect tickmarkTrackRect = trackRect(scrollbar, false);
    145     if (!canDrawDirectly) {
    146         tickmarkTrackRect.setX(0);
    147         tickmarkTrackRect.setY(0);
    148     }
    149     // The ends are rounded and the thumb doesn't go there.
    150     tickmarkTrackRect.inflateY(-tickmarkTrackRect.width());
    151     // Inset a bit.
    152     tickmarkTrackRect.setX(tickmarkTrackRect.x() + 2);
    153     tickmarkTrackRect.setWidth(tickmarkTrackRect.width() - 5);
    154     paintGivenTickmarks(drawingContext, scrollbar, tickmarkTrackRect, tickmarks);
    155 
    156     if (hasThumb(scrollbar)) {
    157         blink::WebThemeEngine::ScrollbarInfo scrollbarInfo;
    158         scrollbarInfo.orientation = scrollbar->orientation() == HorizontalScrollbar ? blink::WebThemeEngine::ScrollbarOrientationHorizontal : blink::WebThemeEngine::ScrollbarOrientationVertical;
    159         scrollbarInfo.parent = scrollbar->isScrollViewScrollbar() ? blink::WebThemeEngine::ScrollbarParentScrollView : blink::WebThemeEngine::ScrollbarParentRenderLayer;
    160         scrollbarInfo.maxValue = scrollbar->maximum();
    161         scrollbarInfo.currentValue = scrollbar->currentPos();
    162         scrollbarInfo.visibleSize = scrollbar->visibleSize();
    163         scrollbarInfo.totalSize = scrollbar->totalSize();
    164 
    165         blink::WebCanvas* webCanvas = drawingContext->canvas();
    166         blink::Platform::current()->themeEngine()->paintScrollbarThumb(
    167             webCanvas,
    168             scrollbarStateToThemeState(scrollbar),
    169             scrollbar->controlSize() == RegularScrollbar ? blink::WebThemeEngine::SizeRegular : blink::WebThemeEngine::SizeSmall,
    170             blink::WebRect(scrollbar->frameRect()),
    171             scrollbarInfo);
    172     }
    173 
    174     if (!canDrawDirectly) {
    175         ASSERT(imageBuffer);
    176         context->drawImageBuffer(imageBuffer.get(),
    177             FloatRect(scrollbar->frameRect().location(), imageBuffer->size()));
    178     }
    179 
    180     return true;
    181 }
    182 
    183 int ScrollbarThemeMacNonOverlayAPI::scrollbarThickness(ScrollbarControlSize controlSize)
    184 {
    185     return cScrollbarThickness[controlSize];
    186 }
    187 
    188 ScrollbarButtonsPlacement ScrollbarThemeMacNonOverlayAPI::buttonsPlacement() const
    189 {
    190     return gButtonPlacement;
    191 }
    192 
    193 bool ScrollbarThemeMacNonOverlayAPI::hasButtons(ScrollbarThemeClient* scrollbar)
    194 {
    195     return scrollbar->enabled() && buttonsPlacement() != ScrollbarButtonsNone
    196              && (scrollbar->orientation() == HorizontalScrollbar
    197              ? scrollbar->width()
    198              : scrollbar->height()) >= 2 * (cRealButtonLength[scrollbar->controlSize()] - cButtonHitInset[scrollbar->controlSize()]);
    199 }
    200 
    201 bool ScrollbarThemeMacNonOverlayAPI::hasThumb(ScrollbarThemeClient* scrollbar)
    202 {
    203     int minLengthForThumb = 2 * cButtonInset[scrollbar->controlSize()] + cThumbMinLength[scrollbar->controlSize()] + 1;
    204     return scrollbar->enabled() && (scrollbar->orientation() == HorizontalScrollbar ?
    205              scrollbar->width() :
    206              scrollbar->height()) >= minLengthForThumb;
    207 }
    208 
    209 static IntRect buttonRepaintRect(const IntRect& buttonRect, ScrollbarOrientation orientation, ScrollbarControlSize controlSize, bool start)
    210 {
    211     ASSERT(gButtonPlacement != ScrollbarButtonsNone);
    212 
    213     IntRect paintRect(buttonRect);
    214     if (orientation == HorizontalScrollbar) {
    215         paintRect.setWidth(cRealButtonLength[controlSize]);
    216         if (!start)
    217             paintRect.setX(buttonRect.x() - (cRealButtonLength[controlSize] - buttonRect.width()));
    218     } else {
    219         paintRect.setHeight(cRealButtonLength[controlSize]);
    220         if (!start)
    221             paintRect.setY(buttonRect.y() - (cRealButtonLength[controlSize] - buttonRect.height()));
    222     }
    223 
    224     return paintRect;
    225 }
    226 
    227 IntRect ScrollbarThemeMacNonOverlayAPI::backButtonRect(ScrollbarThemeClient* scrollbar, ScrollbarPart part, bool painting)
    228 {
    229     IntRect result;
    230 
    231     if (part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd))
    232         return result;
    233 
    234     if (part == BackButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsSingle))
    235         return result;
    236 
    237     int thickness = scrollbarThickness(scrollbar->controlSize());
    238     bool outerButton = part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
    239     if (outerButton) {
    240         if (scrollbar->orientation() == HorizontalScrollbar)
    241             result = IntRect(scrollbar->x(), scrollbar->y(), cOuterButtonLength[scrollbar->controlSize()] + (painting ? cOuterButtonOverlap : 0), thickness);
    242         else
    243             result = IntRect(scrollbar->x(), scrollbar->y(), thickness, cOuterButtonLength[scrollbar->controlSize()] + (painting ? cOuterButtonOverlap : 0));
    244         return result;
    245     }
    246 
    247     // Our repaint rect is slightly larger, since we are a button that is adjacent to the track.
    248     if (scrollbar->orientation() == HorizontalScrollbar) {
    249         int start = part == BackButtonStartPart ? scrollbar->x() : scrollbar->x() + scrollbar->width() - cOuterButtonLength[scrollbar->controlSize()] - cButtonLength[scrollbar->controlSize()];
    250         result = IntRect(start, scrollbar->y(), cButtonLength[scrollbar->controlSize()], thickness);
    251     } else {
    252         int start = part == BackButtonStartPart ? scrollbar->y() : scrollbar->y() + scrollbar->height() - cOuterButtonLength[scrollbar->controlSize()] - cButtonLength[scrollbar->controlSize()];
    253         result = IntRect(scrollbar->x(), start, thickness, cButtonLength[scrollbar->controlSize()]);
    254     }
    255 
    256     if (painting)
    257         return buttonRepaintRect(result, scrollbar->orientation(), scrollbar->controlSize(), part == BackButtonStartPart);
    258     return result;
    259 }
    260 
    261 IntRect ScrollbarThemeMacNonOverlayAPI::forwardButtonRect(ScrollbarThemeClient* scrollbar, ScrollbarPart part, bool painting)
    262 {
    263     IntRect result;
    264 
    265     if (part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart))
    266         return result;
    267 
    268     if (part == ForwardButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsSingle))
    269         return result;
    270 
    271     int thickness = scrollbarThickness(scrollbar->controlSize());
    272     int outerButtonLength = cOuterButtonLength[scrollbar->controlSize()];
    273     int buttonLength = cButtonLength[scrollbar->controlSize()];
    274 
    275     bool outerButton = part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
    276     if (outerButton) {
    277         if (scrollbar->orientation() == HorizontalScrollbar) {
    278             result = IntRect(scrollbar->x() + scrollbar->width() - outerButtonLength, scrollbar->y(), outerButtonLength, thickness);
    279             if (painting)
    280                 result.inflateX(cOuterButtonOverlap);
    281         } else {
    282             result = IntRect(scrollbar->x(), scrollbar->y() + scrollbar->height() - outerButtonLength, thickness, outerButtonLength);
    283             if (painting)
    284                 result.inflateY(cOuterButtonOverlap);
    285         }
    286         return result;
    287     }
    288 
    289     if (scrollbar->orientation() == HorizontalScrollbar) {
    290         int start = part == ForwardButtonEndPart ? scrollbar->x() + scrollbar->width() - buttonLength : scrollbar->x() + outerButtonLength;
    291         result = IntRect(start, scrollbar->y(), buttonLength, thickness);
    292     } else {
    293         int start = part == ForwardButtonEndPart ? scrollbar->y() + scrollbar->height() - buttonLength : scrollbar->y() + outerButtonLength;
    294         result = IntRect(scrollbar->x(), start, thickness, buttonLength);
    295     }
    296     if (painting)
    297         return buttonRepaintRect(result, scrollbar->orientation(), scrollbar->controlSize(), part == ForwardButtonStartPart);
    298     return result;
    299 }
    300 
    301 IntRect ScrollbarThemeMacNonOverlayAPI::trackRect(ScrollbarThemeClient* scrollbar, bool painting)
    302 {
    303     if (painting || !hasButtons(scrollbar))
    304         return scrollbar->frameRect();
    305 
    306     IntRect result;
    307     int thickness = scrollbarThickness(scrollbar->controlSize());
    308     int startWidth = 0;
    309     int endWidth = 0;
    310     int outerButtonLength = cOuterButtonLength[scrollbar->controlSize()];
    311     int buttonLength = cButtonLength[scrollbar->controlSize()];
    312     int doubleButtonLength = outerButtonLength + buttonLength;
    313     switch (buttonsPlacement()) {
    314         case ScrollbarButtonsSingle:
    315             startWidth = buttonLength;
    316             endWidth = buttonLength;
    317             break;
    318         case ScrollbarButtonsDoubleStart:
    319             startWidth = doubleButtonLength;
    320             break;
    321         case ScrollbarButtonsDoubleEnd:
    322             endWidth = doubleButtonLength;
    323             break;
    324         case ScrollbarButtonsDoubleBoth:
    325             startWidth = doubleButtonLength;
    326             endWidth = doubleButtonLength;
    327             break;
    328         default:
    329             break;
    330     }
    331 
    332     int totalWidth = startWidth + endWidth;
    333     if (scrollbar->orientation() == HorizontalScrollbar)
    334         return IntRect(scrollbar->x() + startWidth, scrollbar->y(), scrollbar->width() - totalWidth, thickness);
    335     return IntRect(scrollbar->x(), scrollbar->y() + startWidth, thickness, scrollbar->height() - totalWidth);
    336 }
    337 
    338 int ScrollbarThemeMacNonOverlayAPI::minimumThumbLength(ScrollbarThemeClient* scrollbar)
    339 {
    340     return cThumbMinLength[scrollbar->controlSize()];
    341 }
    342 
    343 }
    344