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