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/mac/ThemeMac.h"
     37 #include "platform/scroll/ScrollbarThemeClient.h"
     38 #include "public/platform/Platform.h"
     39 #include "public/platform/WebRect.h"
     40 #include "public/platform/WebThemeEngine.h"
     41 #include "skia/ext/skia_utils_mac.h"
     42 #include <Carbon/Carbon.h>
     43 
     44 namespace blink {
     45 
     46 // FIXME: Get these numbers from CoreUI.
     47 static int cRealButtonLength[] = { 28, 21 };
     48 static int cButtonHitInset[] = { 3, 2 };
     49 // cRealButtonLength - cButtonInset
     50 static int cButtonLength[] = { 14, 10 };
     51 static int cScrollbarThickness[] = { 15, 11 };
     52 static int cButtonInset[] = { 14, 11 };
     53 static int cThumbMinLength[] = { 26, 20 };
     54 
     55 static int cOuterButtonLength[] = { 16, 14 }; // The outer button in a double button pair is a bit bigger.
     56 static int cOuterButtonOverlap = 2;
     57 
     58 static ScrollbarButtonsPlacement gButtonPlacement = ScrollbarButtonsDoubleEnd;
     59 
     60 void ScrollbarThemeMacNonOverlayAPI::updateButtonPlacement()
     61 {
     62     NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
     63     if ([buttonPlacement isEqualToString:@"Single"])
     64         gButtonPlacement = ScrollbarButtonsSingle;
     65     else if ([buttonPlacement isEqualToString:@"DoubleMin"])
     66         gButtonPlacement = ScrollbarButtonsDoubleStart;
     67     else if ([buttonPlacement isEqualToString:@"DoubleBoth"])
     68         gButtonPlacement = ScrollbarButtonsDoubleBoth;
     69     else {
     70         gButtonPlacement = ScrollbarButtonsDoubleEnd;
     71     }
     72 }
     73 
     74 // Override ScrollbarThemeMacCommon::paint() to add support for the following:
     75 //     - drawing using WebThemeEngine functions
     76 //     - drawing tickmarks
     77 //     - Skia specific changes
     78 bool ScrollbarThemeMacNonOverlayAPI::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* context, const IntRect& damageRect)
     79 {
     80     // Get the tickmarks for the frameview.
     81     Vector<IntRect> tickmarks;
     82     scrollbar->getTickmarks(tickmarks);
     83 
     84     HIThemeTrackDrawInfo trackInfo;
     85     trackInfo.version = 0;
     86     trackInfo.kind = scrollbar->controlSize() == RegularScrollbar ? kThemeMediumScrollBar : kThemeSmallScrollBar;
     87     trackInfo.bounds = scrollbar->frameRect();
     88     trackInfo.min = 0;
     89     trackInfo.max = scrollbar->maximum();
     90     trackInfo.value = scrollbar->currentPos();
     91     trackInfo.trackInfo.scrollbar.viewsize = scrollbar->visibleSize();
     92     trackInfo.attributes = hasThumb(scrollbar) ? kThemeTrackShowThumb : 0;
     93 
     94     if (scrollbar->orientation() == HorizontalScrollbar)
     95         trackInfo.attributes |= kThemeTrackHorizontal;
     96 
     97     if (!scrollbar->enabled())
     98         trackInfo.enableState = kThemeTrackDisabled;
     99     else
    100         trackInfo.enableState = scrollbar->isScrollableAreaActive() ? kThemeTrackActive : kThemeTrackInactive;
    101 
    102     if (!hasButtons(scrollbar))
    103         trackInfo.enableState = kThemeTrackNothingToScroll;
    104     trackInfo.trackInfo.scrollbar.pressState = scrollbarPartToHIPressedState(scrollbar->pressedPart());
    105 
    106     SkCanvas* canvas = context->canvas();
    107     CGAffineTransform currentCTM = gfx::SkMatrixToCGAffineTransform(canvas->getTotalMatrix());
    108 
    109     // The Aqua scrollbar is buggy when rotated and scaled.  We will just draw into a bitmap if we detect a scale or rotation.
    110     bool canDrawDirectly = currentCTM.a == 1.0f && currentCTM.b == 0.0f && currentCTM.c == 0.0f && (currentCTM.d == 1.0f || currentCTM.d == -1.0f);
    111     GraphicsContext* drawingContext = context;
    112     OwnPtr<ImageBuffer> imageBuffer;
    113     if (!canDrawDirectly) {
    114         trackInfo.bounds = IntRect(IntPoint(), scrollbar->frameRect().size());
    115 
    116         IntRect bufferRect(scrollbar->frameRect());
    117         bufferRect.intersect(damageRect);
    118         bufferRect.move(-scrollbar->frameRect().x(), -scrollbar->frameRect().y());
    119 
    120         imageBuffer = ImageBuffer::create(bufferRect.size());
    121         if (!imageBuffer)
    122             return true;
    123 
    124         drawingContext = imageBuffer->context();
    125     }
    126 
    127     // Draw the track and its thumb.
    128     gfx::SkiaBitLocker bitLocker(
    129         drawingContext->canvas(),
    130         ThemeMac::inflateRectForAA(scrollbar->frameRect()),
    131         drawingContext->deviceScaleFactor());
    132     CGContextRef cgContext = bitLocker.cgContext();
    133     HIThemeDrawTrack(&trackInfo, 0, cgContext, kHIThemeOrientationNormal);
    134 
    135     IntRect tickmarkTrackRect = trackRect(scrollbar, false);
    136     if (!canDrawDirectly) {
    137         tickmarkTrackRect.setX(0);
    138         tickmarkTrackRect.setY(0);
    139     }
    140     // The ends are rounded and the thumb doesn't go there.
    141     tickmarkTrackRect.inflateY(-tickmarkTrackRect.width());
    142     // Inset a bit.
    143     tickmarkTrackRect.setX(tickmarkTrackRect.x() + 2);
    144     tickmarkTrackRect.setWidth(tickmarkTrackRect.width() - 5);
    145     paintGivenTickmarks(drawingContext, scrollbar, tickmarkTrackRect, tickmarks);
    146 
    147     if (!canDrawDirectly) {
    148         ASSERT(imageBuffer);
    149         context->drawImageBuffer(imageBuffer.get(),
    150             FloatRect(scrollbar->frameRect().location(), imageBuffer->size()));
    151     }
    152 
    153     return true;
    154 }
    155 
    156 int ScrollbarThemeMacNonOverlayAPI::scrollbarThickness(ScrollbarControlSize controlSize)
    157 {
    158     return cScrollbarThickness[controlSize];
    159 }
    160 
    161 ScrollbarButtonsPlacement ScrollbarThemeMacNonOverlayAPI::buttonsPlacement() const
    162 {
    163     return gButtonPlacement;
    164 }
    165 
    166 bool ScrollbarThemeMacNonOverlayAPI::hasButtons(ScrollbarThemeClient* scrollbar)
    167 {
    168     return scrollbar->enabled() && buttonsPlacement() != ScrollbarButtonsNone
    169              && (scrollbar->orientation() == HorizontalScrollbar
    170              ? scrollbar->width()
    171              : scrollbar->height()) >= 2 * (cRealButtonLength[scrollbar->controlSize()] - cButtonHitInset[scrollbar->controlSize()]);
    172 }
    173 
    174 bool ScrollbarThemeMacNonOverlayAPI::hasThumb(ScrollbarThemeClient* scrollbar)
    175 {
    176     int minLengthForThumb = 2 * cButtonInset[scrollbar->controlSize()] + cThumbMinLength[scrollbar->controlSize()] + 1;
    177     return scrollbar->enabled() && (scrollbar->orientation() == HorizontalScrollbar ?
    178              scrollbar->width() :
    179              scrollbar->height()) >= minLengthForThumb;
    180 }
    181 
    182 static IntRect buttonRepaintRect(const IntRect& buttonRect, ScrollbarOrientation orientation, ScrollbarControlSize controlSize, bool start)
    183 {
    184     ASSERT(gButtonPlacement != ScrollbarButtonsNone);
    185 
    186     IntRect paintRect(buttonRect);
    187     if (orientation == HorizontalScrollbar) {
    188         paintRect.setWidth(cRealButtonLength[controlSize]);
    189         if (!start)
    190             paintRect.setX(buttonRect.x() - (cRealButtonLength[controlSize] - buttonRect.width()));
    191     } else {
    192         paintRect.setHeight(cRealButtonLength[controlSize]);
    193         if (!start)
    194             paintRect.setY(buttonRect.y() - (cRealButtonLength[controlSize] - buttonRect.height()));
    195     }
    196 
    197     return paintRect;
    198 }
    199 
    200 IntRect ScrollbarThemeMacNonOverlayAPI::backButtonRect(ScrollbarThemeClient* scrollbar, ScrollbarPart part, bool painting)
    201 {
    202     IntRect result;
    203 
    204     if (part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd))
    205         return result;
    206 
    207     if (part == BackButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsSingle))
    208         return result;
    209 
    210     int thickness = scrollbarThickness(scrollbar->controlSize());
    211     bool outerButton = part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
    212     if (outerButton) {
    213         if (scrollbar->orientation() == HorizontalScrollbar)
    214             result = IntRect(scrollbar->x(), scrollbar->y(), cOuterButtonLength[scrollbar->controlSize()] + (painting ? cOuterButtonOverlap : 0), thickness);
    215         else
    216             result = IntRect(scrollbar->x(), scrollbar->y(), thickness, cOuterButtonLength[scrollbar->controlSize()] + (painting ? cOuterButtonOverlap : 0));
    217         return result;
    218     }
    219 
    220     // Our repaint rect is slightly larger, since we are a button that is adjacent to the track.
    221     if (scrollbar->orientation() == HorizontalScrollbar) {
    222         int start = part == BackButtonStartPart ? scrollbar->x() : scrollbar->x() + scrollbar->width() - cOuterButtonLength[scrollbar->controlSize()] - cButtonLength[scrollbar->controlSize()];
    223         result = IntRect(start, scrollbar->y(), cButtonLength[scrollbar->controlSize()], thickness);
    224     } else {
    225         int start = part == BackButtonStartPart ? scrollbar->y() : scrollbar->y() + scrollbar->height() - cOuterButtonLength[scrollbar->controlSize()] - cButtonLength[scrollbar->controlSize()];
    226         result = IntRect(scrollbar->x(), start, thickness, cButtonLength[scrollbar->controlSize()]);
    227     }
    228 
    229     if (painting)
    230         return buttonRepaintRect(result, scrollbar->orientation(), scrollbar->controlSize(), part == BackButtonStartPart);
    231     return result;
    232 }
    233 
    234 IntRect ScrollbarThemeMacNonOverlayAPI::forwardButtonRect(ScrollbarThemeClient* scrollbar, ScrollbarPart part, bool painting)
    235 {
    236     IntRect result;
    237 
    238     if (part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart))
    239         return result;
    240 
    241     if (part == ForwardButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsSingle))
    242         return result;
    243 
    244     int thickness = scrollbarThickness(scrollbar->controlSize());
    245     int outerButtonLength = cOuterButtonLength[scrollbar->controlSize()];
    246     int buttonLength = cButtonLength[scrollbar->controlSize()];
    247 
    248     bool outerButton = part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
    249     if (outerButton) {
    250         if (scrollbar->orientation() == HorizontalScrollbar) {
    251             result = IntRect(scrollbar->x() + scrollbar->width() - outerButtonLength, scrollbar->y(), outerButtonLength, thickness);
    252             if (painting)
    253                 result.inflateX(cOuterButtonOverlap);
    254         } else {
    255             result = IntRect(scrollbar->x(), scrollbar->y() + scrollbar->height() - outerButtonLength, thickness, outerButtonLength);
    256             if (painting)
    257                 result.inflateY(cOuterButtonOverlap);
    258         }
    259         return result;
    260     }
    261 
    262     if (scrollbar->orientation() == HorizontalScrollbar) {
    263         int start = part == ForwardButtonEndPart ? scrollbar->x() + scrollbar->width() - buttonLength : scrollbar->x() + outerButtonLength;
    264         result = IntRect(start, scrollbar->y(), buttonLength, thickness);
    265     } else {
    266         int start = part == ForwardButtonEndPart ? scrollbar->y() + scrollbar->height() - buttonLength : scrollbar->y() + outerButtonLength;
    267         result = IntRect(scrollbar->x(), start, thickness, buttonLength);
    268     }
    269     if (painting)
    270         return buttonRepaintRect(result, scrollbar->orientation(), scrollbar->controlSize(), part == ForwardButtonStartPart);
    271     return result;
    272 }
    273 
    274 IntRect ScrollbarThemeMacNonOverlayAPI::trackRect(ScrollbarThemeClient* scrollbar, bool painting)
    275 {
    276     if (painting || !hasButtons(scrollbar))
    277         return scrollbar->frameRect();
    278 
    279     IntRect result;
    280     int thickness = scrollbarThickness(scrollbar->controlSize());
    281     int startWidth = 0;
    282     int endWidth = 0;
    283     int outerButtonLength = cOuterButtonLength[scrollbar->controlSize()];
    284     int buttonLength = cButtonLength[scrollbar->controlSize()];
    285     int doubleButtonLength = outerButtonLength + buttonLength;
    286     switch (buttonsPlacement()) {
    287         case ScrollbarButtonsSingle:
    288             startWidth = buttonLength;
    289             endWidth = buttonLength;
    290             break;
    291         case ScrollbarButtonsDoubleStart:
    292             startWidth = doubleButtonLength;
    293             break;
    294         case ScrollbarButtonsDoubleEnd:
    295             endWidth = doubleButtonLength;
    296             break;
    297         case ScrollbarButtonsDoubleBoth:
    298             startWidth = doubleButtonLength;
    299             endWidth = doubleButtonLength;
    300             break;
    301         default:
    302             break;
    303     }
    304 
    305     int totalWidth = startWidth + endWidth;
    306     if (scrollbar->orientation() == HorizontalScrollbar)
    307         return IntRect(scrollbar->x() + startWidth, scrollbar->y(), scrollbar->width() - totalWidth, thickness);
    308     return IntRect(scrollbar->x(), scrollbar->y() + startWidth, thickness, scrollbar->height() - totalWidth);
    309 }
    310 
    311 int ScrollbarThemeMacNonOverlayAPI::minimumThumbLength(ScrollbarThemeClient* scrollbar)
    312 {
    313     return cThumbMinLength[scrollbar->controlSize()];
    314 }
    315 
    316 }
    317