1 /* 2 * Copyright (c) 2010, Google Inc. All rights reserved. 3 * Copyright (C) 2008, 2011 Apple Inc. All Rights Reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "config.h" 33 #include "core/platform/ScrollableArea.h" 34 35 #include "core/platform/ScrollAnimator.h" 36 #include "core/platform/ScrollbarTheme.h" 37 #include "core/platform/graphics/FloatPoint.h" 38 #include "core/platform/graphics/GraphicsLayer.h" 39 #include "wtf/PassOwnPtr.h" 40 41 #include "core/platform/chromium/TraceEvent.h" 42 43 static const int kPixelsPerLineStep = 40; 44 static const float kMinFractionToStepWhenPaging = 0.875f; 45 46 namespace WebCore { 47 48 struct SameSizeAsScrollableArea { 49 virtual ~SameSizeAsScrollableArea(); 50 void* pointer; 51 unsigned bitfields : 16; 52 IntPoint origin; 53 }; 54 55 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small); 56 57 int ScrollableArea::pixelsPerLineStep() 58 { 59 return kPixelsPerLineStep; 60 } 61 62 float ScrollableArea::minFractionToStepWhenPaging() 63 { 64 return kMinFractionToStepWhenPaging; 65 } 66 67 int ScrollableArea::maxOverlapBetweenPages() 68 { 69 static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages(); 70 return maxOverlapBetweenPages; 71 } 72 73 ScrollableArea::ScrollableArea() 74 : m_constrainsScrollingToContentEdge(true) 75 , m_inLiveResize(false) 76 , m_verticalScrollElasticity(ScrollElasticityNone) 77 , m_horizontalScrollElasticity(ScrollElasticityNone) 78 , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault) 79 , m_scrollOriginChanged(false) 80 { 81 } 82 83 ScrollableArea::~ScrollableArea() 84 { 85 } 86 87 ScrollAnimator* ScrollableArea::scrollAnimator() const 88 { 89 if (!m_scrollAnimator) 90 m_scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea*>(this)); 91 92 return m_scrollAnimator.get(); 93 } 94 95 void ScrollableArea::setScrollOrigin(const IntPoint& origin) 96 { 97 if (m_scrollOrigin != origin) { 98 m_scrollOrigin = origin; 99 m_scrollOriginChanged = true; 100 } 101 } 102 103 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier) 104 { 105 ScrollbarOrientation orientation; 106 107 if (direction == ScrollUp || direction == ScrollDown) 108 orientation = VerticalScrollbar; 109 else 110 orientation = HorizontalScrollbar; 111 112 if (!userInputScrollable(orientation)) 113 return false; 114 115 float step = 0; 116 switch (granularity) { 117 case ScrollByLine: 118 step = lineStep(orientation); 119 break; 120 case ScrollByPage: 121 step = pageStep(orientation); 122 break; 123 case ScrollByDocument: 124 step = documentStep(orientation); 125 break; 126 case ScrollByPixel: 127 case ScrollByPrecisePixel: 128 step = pixelStep(orientation); 129 break; 130 } 131 132 if (direction == ScrollUp || direction == ScrollLeft) 133 multiplier = -multiplier; 134 135 return scrollAnimator()->scroll(orientation, granularity, step, multiplier); 136 } 137 138 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset) 139 { 140 scrollAnimator()->scrollToOffsetWithoutAnimation(offset); 141 } 142 143 void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset) 144 { 145 if (orientation == HorizontalScrollbar) 146 scrollToOffsetWithoutAnimation(FloatPoint(offset, scrollAnimator()->currentPosition().y())); 147 else 148 scrollToOffsetWithoutAnimation(FloatPoint(scrollAnimator()->currentPosition().x(), offset)); 149 } 150 151 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position) 152 { 153 scrollPositionChanged(position); 154 scrollAnimator()->setCurrentPosition(position); 155 } 156 157 void ScrollableArea::scrollPositionChanged(const IntPoint& position) 158 { 159 TRACE_EVENT0("webkit", "ScrollableArea::scrollPositionChanged"); 160 161 IntPoint oldPosition = scrollPosition(); 162 // Tell the derived class to scroll its contents. 163 setScrollOffset(position); 164 165 Scrollbar* verticalScrollbar = this->verticalScrollbar(); 166 167 // Tell the scrollbars to update their thumb postions. 168 if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) { 169 horizontalScrollbar->offsetDidChange(); 170 if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) { 171 if (!verticalScrollbar) 172 horizontalScrollbar->invalidate(); 173 else { 174 // If there is both a horizontalScrollbar and a verticalScrollbar, 175 // then we must also invalidate the corner between them. 176 IntRect boundsAndCorner = horizontalScrollbar->boundsRect(); 177 boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width()); 178 horizontalScrollbar->invalidateRect(boundsAndCorner); 179 } 180 } 181 } 182 if (verticalScrollbar) { 183 verticalScrollbar->offsetDidChange(); 184 if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar()) 185 verticalScrollbar->invalidate(); 186 } 187 188 if (scrollPosition() != oldPosition) 189 scrollAnimator()->notifyContentAreaScrolled(scrollPosition() - oldPosition); 190 } 191 192 bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent) 193 { 194 return scrollAnimator()->handleWheelEvent(wheelEvent); 195 } 196 197 // NOTE: Only called from Internals for testing. 198 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset) 199 { 200 setScrollOffsetFromAnimation(offset); 201 } 202 203 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset) 204 { 205 if (requestScrollPositionUpdate(offset)) 206 return; 207 208 scrollPositionChanged(offset); 209 } 210 211 void ScrollableArea::willStartLiveResize() 212 { 213 if (m_inLiveResize) 214 return; 215 m_inLiveResize = true; 216 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 217 scrollAnimator->willStartLiveResize(); 218 } 219 220 void ScrollableArea::willEndLiveResize() 221 { 222 if (!m_inLiveResize) 223 return; 224 m_inLiveResize = false; 225 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 226 scrollAnimator->willEndLiveResize(); 227 } 228 229 void ScrollableArea::contentAreaWillPaint() const 230 { 231 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 232 scrollAnimator->contentAreaWillPaint(); 233 } 234 235 void ScrollableArea::mouseEnteredContentArea() const 236 { 237 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 238 scrollAnimator->mouseEnteredContentArea(); 239 } 240 241 void ScrollableArea::mouseExitedContentArea() const 242 { 243 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 244 scrollAnimator->mouseEnteredContentArea(); 245 } 246 247 void ScrollableArea::mouseMovedInContentArea() const 248 { 249 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 250 scrollAnimator->mouseMovedInContentArea(); 251 } 252 253 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const 254 { 255 scrollAnimator()->mouseEnteredScrollbar(scrollbar); 256 } 257 258 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const 259 { 260 scrollAnimator()->mouseExitedScrollbar(scrollbar); 261 } 262 263 void ScrollableArea::contentAreaDidShow() const 264 { 265 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 266 scrollAnimator->contentAreaDidShow(); 267 } 268 269 void ScrollableArea::contentAreaDidHide() const 270 { 271 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 272 scrollAnimator->contentAreaDidHide(); 273 } 274 275 void ScrollableArea::finishCurrentScrollAnimations() const 276 { 277 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 278 scrollAnimator->finishCurrentScrollAnimations(); 279 } 280 281 void ScrollableArea::didAddVerticalScrollbar(Scrollbar* scrollbar) 282 { 283 scrollAnimator()->didAddVerticalScrollbar(scrollbar); 284 285 // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar 286 setScrollbarOverlayStyle(scrollbarOverlayStyle()); 287 } 288 289 void ScrollableArea::willRemoveVerticalScrollbar(Scrollbar* scrollbar) 290 { 291 scrollAnimator()->willRemoveVerticalScrollbar(scrollbar); 292 } 293 294 void ScrollableArea::didAddHorizontalScrollbar(Scrollbar* scrollbar) 295 { 296 scrollAnimator()->didAddHorizontalScrollbar(scrollbar); 297 298 // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar 299 setScrollbarOverlayStyle(scrollbarOverlayStyle()); 300 } 301 302 void ScrollableArea::willRemoveHorizontalScrollbar(Scrollbar* scrollbar) 303 { 304 scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar); 305 } 306 307 void ScrollableArea::contentsResized() 308 { 309 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 310 scrollAnimator->contentsResized(); 311 } 312 313 bool ScrollableArea::hasOverlayScrollbars() const 314 { 315 return (verticalScrollbar() && verticalScrollbar()->isOverlayScrollbar()) 316 || (horizontalScrollbar() && horizontalScrollbar()->isOverlayScrollbar()); 317 } 318 319 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle) 320 { 321 m_scrollbarOverlayStyle = overlayStyle; 322 323 if (horizontalScrollbar()) { 324 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(horizontalScrollbar()); 325 horizontalScrollbar()->invalidate(); 326 } 327 328 if (verticalScrollbar()) { 329 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(verticalScrollbar()); 330 verticalScrollbar()->invalidate(); 331 } 332 } 333 334 void ScrollableArea::invalidateScrollbar(Scrollbar* scrollbar, const IntRect& rect) 335 { 336 if (scrollbar == horizontalScrollbar()) { 337 if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) { 338 graphicsLayer->setNeedsDisplay(); 339 graphicsLayer->setContentsNeedsDisplay(); 340 return; 341 } 342 } else if (scrollbar == verticalScrollbar()) { 343 if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) { 344 graphicsLayer->setNeedsDisplay(); 345 graphicsLayer->setContentsNeedsDisplay(); 346 return; 347 } 348 } 349 invalidateScrollbarRect(scrollbar, rect); 350 } 351 352 void ScrollableArea::invalidateScrollCorner(const IntRect& rect) 353 { 354 if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) { 355 graphicsLayer->setNeedsDisplay(); 356 return; 357 } 358 invalidateScrollCornerRect(rect); 359 } 360 361 bool ScrollableArea::hasLayerForHorizontalScrollbar() const 362 { 363 return layerForHorizontalScrollbar(); 364 } 365 366 bool ScrollableArea::hasLayerForVerticalScrollbar() const 367 { 368 return layerForVerticalScrollbar(); 369 } 370 371 bool ScrollableArea::hasLayerForScrollCorner() const 372 { 373 return layerForScrollCorner(); 374 } 375 376 void ScrollableArea::serviceScrollAnimations() 377 { 378 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 379 scrollAnimator->serviceScrollAnimations(); 380 } 381 382 IntRect ScrollableArea::visibleContentRect(VisibleContentRectIncludesScrollbars scrollbarInclusion) const 383 { 384 int verticalScrollbarWidth = 0; 385 int horizontalScrollbarHeight = 0; 386 387 if (scrollbarInclusion == IncludeScrollbars) { 388 if (Scrollbar* verticalBar = verticalScrollbar()) 389 verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0; 390 if (Scrollbar* horizontalBar = horizontalScrollbar()) 391 horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0; 392 } 393 394 return IntRect(scrollPosition().x(), 395 scrollPosition().y(), 396 std::max(0, visibleWidth() + verticalScrollbarWidth), 397 std::max(0, visibleHeight() + horizontalScrollbarHeight)); 398 } 399 400 IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const 401 { 402 return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition()); 403 } 404 405 int ScrollableArea::lineStep(ScrollbarOrientation) const 406 { 407 return pixelsPerLineStep(); 408 } 409 410 int ScrollableArea::documentStep(ScrollbarOrientation orientation) const 411 { 412 return scrollSize(orientation); 413 } 414 415 float ScrollableArea::pixelStep(ScrollbarOrientation) const 416 { 417 return 1; 418 } 419 420 } // namespace WebCore 421