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 43namespace WebCore { 44 45// FIXME: Get these numbers from CoreUI. 46static int cRealButtonLength[] = { 28, 21 }; 47static int cButtonHitInset[] = { 3, 2 }; 48// cRealButtonLength - cButtonInset 49static int cButtonLength[] = { 14, 10 }; 50static int cScrollbarThickness[] = { 15, 11 }; 51static int cButtonInset[] = { 14, 11 }; 52static int cThumbMinLength[] = { 26, 20 }; 53 54static int cOuterButtonLength[] = { 16, 14 }; // The outer button in a double button pair is a bit bigger. 55static int cOuterButtonOverlap = 2; 56 57static ScrollbarButtonsPlacement gButtonPlacement = ScrollbarButtonsDoubleEnd; 58 59void 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 73static 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 89bool 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 183int ScrollbarThemeMacNonOverlayAPI::scrollbarThickness(ScrollbarControlSize controlSize) 184{ 185 return cScrollbarThickness[controlSize]; 186} 187 188ScrollbarButtonsPlacement ScrollbarThemeMacNonOverlayAPI::buttonsPlacement() const 189{ 190 return gButtonPlacement; 191} 192 193bool 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 201bool 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 209static 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 227IntRect 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 261IntRect 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 301IntRect 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 338int ScrollbarThemeMacNonOverlayAPI::minimumThumbLength(ScrollbarThemeClient* scrollbar) 339{ 340 return cThumbMinLength[scrollbar->controlSize()]; 341} 342 343} 344