1 /* 2 * Copyright (C) 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/scroll/ScrollbarTheme.h" 28 29 #include "platform/PlatformMouseEvent.h" 30 #include "platform/RuntimeEnabledFeatures.h" 31 #include "platform/graphics/Color.h" 32 #include "platform/graphics/GraphicsContext.h" 33 #include "platform/scroll/ScrollbarThemeClient.h" 34 #include "platform/scroll/ScrollbarThemeMock.h" 35 #include "platform/scroll/ScrollbarThemeOverlayMock.h" 36 #include "public/platform/Platform.h" 37 #include "public/platform/WebPoint.h" 38 #include "public/platform/WebRect.h" 39 #include "public/platform/WebScrollbarBehavior.h" 40 41 #if !OS(MACOSX) 42 #include "public/platform/WebRect.h" 43 #include "public/platform/WebThemeEngine.h" 44 #endif 45 46 namespace WebCore { 47 48 bool ScrollbarTheme::gMockScrollbarsEnabled = false; 49 50 bool ScrollbarTheme::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect) 51 { 52 // Create the ScrollbarControlPartMask based on the damageRect 53 ScrollbarControlPartMask scrollMask = NoPart; 54 55 IntRect backButtonStartPaintRect; 56 IntRect backButtonEndPaintRect; 57 IntRect forwardButtonStartPaintRect; 58 IntRect forwardButtonEndPaintRect; 59 if (hasButtons(scrollbar)) { 60 backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true); 61 if (damageRect.intersects(backButtonStartPaintRect)) 62 scrollMask |= BackButtonStartPart; 63 backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true); 64 if (damageRect.intersects(backButtonEndPaintRect)) 65 scrollMask |= BackButtonEndPart; 66 forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true); 67 if (damageRect.intersects(forwardButtonStartPaintRect)) 68 scrollMask |= ForwardButtonStartPart; 69 forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true); 70 if (damageRect.intersects(forwardButtonEndPaintRect)) 71 scrollMask |= ForwardButtonEndPart; 72 } 73 74 IntRect startTrackRect; 75 IntRect thumbRect; 76 IntRect endTrackRect; 77 IntRect trackPaintRect = trackRect(scrollbar, true); 78 if (damageRect.intersects(trackPaintRect)) 79 scrollMask |= TrackBGPart; 80 bool thumbPresent = hasThumb(scrollbar); 81 if (thumbPresent) { 82 IntRect track = trackRect(scrollbar); 83 splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect); 84 if (damageRect.intersects(thumbRect)) 85 scrollMask |= ThumbPart; 86 if (damageRect.intersects(startTrackRect)) 87 scrollMask |= BackTrackPart; 88 if (damageRect.intersects(endTrackRect)) 89 scrollMask |= ForwardTrackPart; 90 } 91 92 // Paint the scrollbar background (only used by custom CSS scrollbars). 93 paintScrollbarBackground(graphicsContext, scrollbar); 94 95 // Paint the back and forward buttons. 96 if (scrollMask & BackButtonStartPart) 97 paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart); 98 if (scrollMask & BackButtonEndPart) 99 paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart); 100 if (scrollMask & ForwardButtonStartPart) 101 paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart); 102 if (scrollMask & ForwardButtonEndPart) 103 paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart); 104 105 if (scrollMask & TrackBGPart) 106 paintTrackBackground(graphicsContext, scrollbar, trackPaintRect); 107 108 if ((scrollMask & ForwardTrackPart) || (scrollMask & BackTrackPart)) { 109 // Paint the track pieces above and below the thumb. 110 if (scrollMask & BackTrackPart) 111 paintTrackPiece(graphicsContext, scrollbar, startTrackRect, BackTrackPart); 112 if (scrollMask & ForwardTrackPart) 113 paintTrackPiece(graphicsContext, scrollbar, endTrackRect, ForwardTrackPart); 114 115 paintTickmarks(graphicsContext, scrollbar, trackPaintRect); 116 } 117 118 // Paint the thumb. 119 if (scrollMask & ThumbPart) 120 paintThumb(graphicsContext, scrollbar, thumbRect); 121 122 return true; 123 } 124 125 ScrollbarPart ScrollbarTheme::hitTest(ScrollbarThemeClient* scrollbar, const IntPoint& position) 126 { 127 ScrollbarPart result = NoPart; 128 if (!scrollbar->enabled()) 129 return result; 130 131 IntPoint testPosition = scrollbar->convertFromContainingWindow(position); 132 testPosition.move(scrollbar->x(), scrollbar->y()); 133 134 if (!scrollbar->frameRect().contains(testPosition)) 135 return NoPart; 136 137 result = ScrollbarBGPart; 138 139 IntRect track = trackRect(scrollbar); 140 if (track.contains(testPosition)) { 141 IntRect beforeThumbRect; 142 IntRect thumbRect; 143 IntRect afterThumbRect; 144 splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect); 145 if (thumbRect.contains(testPosition)) 146 result = ThumbPart; 147 else if (beforeThumbRect.contains(testPosition)) 148 result = BackTrackPart; 149 else if (afterThumbRect.contains(testPosition)) 150 result = ForwardTrackPart; 151 else 152 result = TrackBGPart; 153 } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(testPosition)) { 154 result = BackButtonStartPart; 155 } else if (backButtonRect(scrollbar, BackButtonEndPart).contains(testPosition)) { 156 result = BackButtonEndPart; 157 } else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(testPosition)) { 158 result = ForwardButtonStartPart; 159 } else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(testPosition)) { 160 result = ForwardButtonEndPart; 161 } 162 return result; 163 } 164 165 void ScrollbarTheme::invalidatePart(ScrollbarThemeClient* scrollbar, ScrollbarPart part) 166 { 167 if (part == NoPart) 168 return; 169 170 IntRect result; 171 switch (part) { 172 case BackButtonStartPart: 173 result = backButtonRect(scrollbar, BackButtonStartPart, true); 174 break; 175 case BackButtonEndPart: 176 result = backButtonRect(scrollbar, BackButtonEndPart, true); 177 break; 178 case ForwardButtonStartPart: 179 result = forwardButtonRect(scrollbar, ForwardButtonStartPart, true); 180 break; 181 case ForwardButtonEndPart: 182 result = forwardButtonRect(scrollbar, ForwardButtonEndPart, true); 183 break; 184 case TrackBGPart: 185 result = trackRect(scrollbar, true); 186 break; 187 case ScrollbarBGPart: 188 result = scrollbar->frameRect(); 189 break; 190 default: { 191 IntRect beforeThumbRect, thumbRect, afterThumbRect; 192 splitTrack(scrollbar, trackRect(scrollbar), beforeThumbRect, thumbRect, afterThumbRect); 193 if (part == BackTrackPart) 194 result = beforeThumbRect; 195 else if (part == ForwardTrackPart) 196 result = afterThumbRect; 197 else 198 result = thumbRect; 199 } 200 } 201 result.moveBy(-scrollbar->location()); 202 scrollbar->invalidateRect(result); 203 } 204 205 void ScrollbarTheme::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect) 206 { 207 if (cornerRect.isEmpty()) 208 return; 209 210 #if OS(MACOSX) 211 context->fillRect(cornerRect, Color::white); 212 #else 213 if (context->paintingDisabled()) 214 return; 215 blink::Platform::current()->themeEngine()->paint(context->canvas(), blink::WebThemeEngine::PartScrollbarCorner, blink::WebThemeEngine::StateNormal, blink::WebRect(cornerRect), 0); 216 #endif 217 } 218 219 void ScrollbarTheme::paintOverhangBackground(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect) 220 { 221 context->setFillColor(Color::white); 222 if (!horizontalOverhangRect.isEmpty()) 223 context->fillRect(intersection(horizontalOverhangRect, dirtyRect)); 224 if (!verticalOverhangRect.isEmpty()) 225 context->fillRect(intersection(verticalOverhangRect, dirtyRect)); 226 } 227 228 bool ScrollbarTheme::shouldCenterOnThumb(ScrollbarThemeClient* scrollbar, const PlatformMouseEvent& evt) 229 { 230 return blink::Platform::current()->scrollbarBehavior()->shouldCenterOnThumb(static_cast<blink::WebScrollbarBehavior::Button>(evt.button()), evt.shiftKey(), evt.altKey()); 231 } 232 233 bool ScrollbarTheme::shouldSnapBackToDragOrigin(ScrollbarThemeClient* scrollbar, const PlatformMouseEvent& evt) 234 { 235 IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.position()); 236 mousePosition.move(scrollbar->x(), scrollbar->y()); 237 return blink::Platform::current()->scrollbarBehavior()->shouldSnapBackToDragOrigin(mousePosition, trackRect(scrollbar), scrollbar->orientation() == HorizontalScrollbar); 238 } 239 240 // Returns the size represented by track taking into account scrolling past 241 // the end of the document. 242 static float usedTotalSize(ScrollbarThemeClient* scrollbar) 243 { 244 float overhangAtStart = -scrollbar->currentPos(); 245 float overhangAtEnd = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize(); 246 float overhang = std::max(0.0f, std::max(overhangAtStart, overhangAtEnd)); 247 return scrollbar->totalSize() + overhang; 248 } 249 250 int ScrollbarTheme::thumbPosition(ScrollbarThemeClient* scrollbar) 251 { 252 if (scrollbar->enabled()) { 253 float size = usedTotalSize(scrollbar) - scrollbar->visibleSize(); 254 // Avoid doing a floating point divide by zero and return 1 when usedTotalSize == visibleSize. 255 if (!size) 256 return 1; 257 float pos = std::max(0.0f, scrollbar->currentPos()) * (trackLength(scrollbar) - thumbLength(scrollbar)) / size; 258 return (pos < 1 && pos > 0) ? 1 : pos; 259 } 260 return 0; 261 } 262 263 int ScrollbarTheme::thumbLength(ScrollbarThemeClient* scrollbar) 264 { 265 if (!scrollbar->enabled()) 266 return 0; 267 268 float overhang = 0; 269 if (scrollbar->currentPos() < 0) 270 overhang = -scrollbar->currentPos(); 271 else if (scrollbar->visibleSize() + scrollbar->currentPos() > scrollbar->totalSize()) 272 overhang = scrollbar->currentPos() + scrollbar->visibleSize() - scrollbar->totalSize(); 273 float proportion = (scrollbar->visibleSize() - overhang) / usedTotalSize(scrollbar); 274 int trackLen = trackLength(scrollbar); 275 int length = round(proportion * trackLen); 276 length = std::max(length, minimumThumbLength(scrollbar)); 277 if (length > trackLen) 278 length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track). 279 return length; 280 } 281 282 int ScrollbarTheme::trackPosition(ScrollbarThemeClient* scrollbar) 283 { 284 IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar)); 285 return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.x() - scrollbar->x() : constrainedTrackRect.y() - scrollbar->y(); 286 } 287 288 int ScrollbarTheme::trackLength(ScrollbarThemeClient* scrollbar) 289 { 290 IntRect constrainedTrackRect = constrainTrackRectToTrackPieces(scrollbar, trackRect(scrollbar)); 291 return (scrollbar->orientation() == HorizontalScrollbar) ? constrainedTrackRect.width() : constrainedTrackRect.height(); 292 } 293 294 IntRect ScrollbarTheme::thumbRect(ScrollbarThemeClient* scrollbar) 295 { 296 if (!hasThumb(scrollbar)) 297 return IntRect(); 298 299 IntRect track = trackRect(scrollbar); 300 IntRect startTrackRect; 301 IntRect thumbRect; 302 IntRect endTrackRect; 303 splitTrack(scrollbar, track, startTrackRect, thumbRect, endTrackRect); 304 305 return thumbRect; 306 } 307 308 int ScrollbarTheme::thumbThickness(ScrollbarThemeClient* scrollbar) 309 { 310 IntRect track = trackRect(scrollbar); 311 return scrollbar->orientation() == HorizontalScrollbar ? track.height() : track.width(); 312 } 313 314 int ScrollbarTheme::minimumThumbLength(ScrollbarThemeClient* scrollbar) 315 { 316 return scrollbarThickness(scrollbar->controlSize()); 317 } 318 319 void ScrollbarTheme::splitTrack(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect) 320 { 321 // This function won't even get called unless we're big enough to have some combination of these three rects where at least 322 // one of them is non-empty. 323 IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect); 324 int thumbPos = thumbPosition(scrollbar); 325 if (scrollbar->orientation() == HorizontalScrollbar) { 326 thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y(), thumbLength(scrollbar), scrollbar->height()); 327 beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos + thumbRect.width() / 2, trackRect.height()); 328 afterThumbRect = IntRect(trackRect.x() + beforeThumbRect.width(), trackRect.y(), trackRect.maxX() - beforeThumbRect.maxX(), trackRect.height()); 329 } else { 330 thumbRect = IntRect(trackRect.x(), trackRect.y() + thumbPos, scrollbar->width(), thumbLength(scrollbar)); 331 beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos + thumbRect.height() / 2); 332 afterThumbRect = IntRect(trackRect.x(), trackRect.y() + beforeThumbRect.height(), trackRect.width(), trackRect.maxY() - beforeThumbRect.maxY()); 333 } 334 } 335 336 ScrollbarTheme* ScrollbarTheme::theme() 337 { 338 if (ScrollbarTheme::mockScrollbarsEnabled()) { 339 if (RuntimeEnabledFeatures::overlayScrollbarsEnabled()) { 340 DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, overlayMockTheme, ()); 341 return &overlayMockTheme; 342 } 343 344 DEFINE_STATIC_LOCAL(ScrollbarThemeMock, mockTheme, ()); 345 return &mockTheme; 346 } 347 return nativeTheme(); 348 } 349 350 void ScrollbarTheme::setMockScrollbarsEnabled(bool flag) 351 { 352 gMockScrollbarsEnabled = flag; 353 } 354 355 bool ScrollbarTheme::mockScrollbarsEnabled() 356 { 357 return gMockScrollbarsEnabled; 358 } 359 360 } 361