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