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