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