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