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