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 "platform/scroll/ScrollableArea.h" 34 35 #include "platform/HostWindow.h" 36 #include "platform/Logging.h" 37 #include "platform/graphics/GraphicsLayer.h" 38 #include "platform/geometry/FloatPoint.h" 39 #include "platform/scroll/ProgrammaticScrollAnimator.h" 40 #include "platform/scroll/ScrollbarTheme.h" 41 #include "wtf/PassOwnPtr.h" 42 43 #include "platform/TraceEvent.h" 44 45 static const int kPixelsPerLineStep = 40; 46 static const float kMinFractionToStepWhenPaging = 0.875f; 47 48 namespace blink { 49 50 struct SameSizeAsScrollableArea { 51 virtual ~SameSizeAsScrollableArea(); 52 IntRect scrollbarDamage[2]; 53 void* pointer; 54 unsigned bitfields : 16; 55 IntPoint origin; 56 }; 57 58 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small); 59 60 int ScrollableArea::pixelsPerLineStep() 61 { 62 return kPixelsPerLineStep; 63 } 64 65 float ScrollableArea::minFractionToStepWhenPaging() 66 { 67 return kMinFractionToStepWhenPaging; 68 } 69 70 int ScrollableArea::maxOverlapBetweenPages() 71 { 72 static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages(); 73 return maxOverlapBetweenPages; 74 } 75 76 ScrollableArea::ScrollableArea() 77 : m_constrainsScrollingToContentEdge(true) 78 , m_inLiveResize(false) 79 , m_verticalScrollElasticity(ScrollElasticityNone) 80 , m_horizontalScrollElasticity(ScrollElasticityNone) 81 , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault) 82 , m_scrollOriginChanged(false) 83 { 84 } 85 86 ScrollableArea::~ScrollableArea() 87 { 88 } 89 90 ScrollAnimator* ScrollableArea::scrollAnimator() const 91 { 92 if (!m_animators) 93 m_animators = adoptPtr(new ScrollableAreaAnimators); 94 95 if (!m_animators->scrollAnimator) 96 m_animators->scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea*>(this)); 97 98 return m_animators->scrollAnimator.get(); 99 } 100 101 ProgrammaticScrollAnimator* ScrollableArea::programmaticScrollAnimator() const 102 { 103 if (!m_animators) 104 m_animators = adoptPtr(new ScrollableAreaAnimators); 105 106 if (!m_animators->programmaticScrollAnimator) 107 m_animators->programmaticScrollAnimator = ProgrammaticScrollAnimator::create(const_cast<ScrollableArea*>(this)); 108 109 return m_animators->programmaticScrollAnimator.get(); 110 } 111 112 void ScrollableArea::setScrollOrigin(const IntPoint& origin) 113 { 114 if (m_scrollOrigin != origin) { 115 m_scrollOrigin = origin; 116 m_scrollOriginChanged = true; 117 } 118 } 119 120 GraphicsLayer* ScrollableArea::layerForContainer() const 121 { 122 return layerForScrolling() ? layerForScrolling()->parent() : 0; 123 } 124 125 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float delta) 126 { 127 ScrollbarOrientation orientation; 128 129 if (direction == ScrollUp || direction == ScrollDown) 130 orientation = VerticalScrollbar; 131 else 132 orientation = HorizontalScrollbar; 133 134 if (!userInputScrollable(orientation)) 135 return false; 136 137 cancelProgrammaticScrollAnimation(); 138 139 float step = 0; 140 switch (granularity) { 141 case ScrollByLine: 142 step = lineStep(orientation); 143 break; 144 case ScrollByPage: 145 step = pageStep(orientation); 146 break; 147 case ScrollByDocument: 148 step = documentStep(orientation); 149 break; 150 case ScrollByPixel: 151 case ScrollByPrecisePixel: 152 step = pixelStep(orientation); 153 break; 154 } 155 156 if (direction == ScrollUp || direction == ScrollLeft) 157 delta = -delta; 158 159 return scrollAnimator()->scroll(orientation, granularity, step, delta); 160 } 161 162 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset) 163 { 164 cancelProgrammaticScrollAnimation(); 165 scrollAnimator()->scrollToOffsetWithoutAnimation(offset); 166 } 167 168 void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset) 169 { 170 if (orientation == HorizontalScrollbar) 171 scrollToOffsetWithoutAnimation(FloatPoint(offset, scrollAnimator()->currentPosition().y())); 172 else 173 scrollToOffsetWithoutAnimation(FloatPoint(scrollAnimator()->currentPosition().x(), offset)); 174 } 175 176 void ScrollableArea::programmaticallyScrollSmoothlyToOffset(const FloatPoint& offset) 177 { 178 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 179 scrollAnimator->cancelAnimations(); 180 programmaticScrollAnimator()->animateToOffset(offset); 181 } 182 183 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position) 184 { 185 scrollPositionChanged(position); 186 scrollAnimator()->setCurrentPosition(position); 187 } 188 189 void ScrollableArea::scrollPositionChanged(const IntPoint& position) 190 { 191 TRACE_EVENT0("blink", "ScrollableArea::scrollPositionChanged"); 192 193 IntPoint oldPosition = scrollPosition(); 194 // Tell the derived class to scroll its contents. 195 setScrollOffset(position); 196 197 Scrollbar* verticalScrollbar = this->verticalScrollbar(); 198 199 // Tell the scrollbars to update their thumb postions. 200 if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) { 201 horizontalScrollbar->offsetDidChange(); 202 if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) { 203 if (!verticalScrollbar) 204 horizontalScrollbar->invalidate(); 205 else { 206 // If there is both a horizontalScrollbar and a verticalScrollbar, 207 // then we must also invalidate the corner between them. 208 IntRect boundsAndCorner = horizontalScrollbar->boundsRect(); 209 boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width()); 210 horizontalScrollbar->invalidateRect(boundsAndCorner); 211 } 212 } 213 } 214 if (verticalScrollbar) { 215 verticalScrollbar->offsetDidChange(); 216 if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar()) 217 verticalScrollbar->invalidate(); 218 } 219 220 if (scrollPosition() != oldPosition) 221 scrollAnimator()->notifyContentAreaScrolled(scrollPosition() - oldPosition); 222 } 223 224 bool ScrollableArea::scrollBehaviorFromString(const String& behaviorString, ScrollBehavior& behavior) 225 { 226 if (behaviorString == "auto") 227 behavior = ScrollBehaviorAuto; 228 else if (behaviorString == "instant") 229 behavior = ScrollBehaviorInstant; 230 else if (behaviorString == "smooth") 231 behavior = ScrollBehaviorSmooth; 232 else 233 return false; 234 235 return true; 236 } 237 238 bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent) 239 { 240 // ctrl+wheel events are used to trigger zooming, not scrolling. 241 if (wheelEvent.modifiers() & PlatformEvent::CtrlKey) 242 return false; 243 244 cancelProgrammaticScrollAnimation(); 245 return scrollAnimator()->handleWheelEvent(wheelEvent); 246 } 247 248 // NOTE: Only called from Internals for testing. 249 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset) 250 { 251 setScrollOffsetFromAnimation(offset); 252 } 253 254 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset) 255 { 256 scrollPositionChanged(offset); 257 } 258 259 void ScrollableArea::willStartLiveResize() 260 { 261 if (m_inLiveResize) 262 return; 263 m_inLiveResize = true; 264 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 265 scrollAnimator->willStartLiveResize(); 266 } 267 268 void ScrollableArea::willEndLiveResize() 269 { 270 if (!m_inLiveResize) 271 return; 272 m_inLiveResize = false; 273 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 274 scrollAnimator->willEndLiveResize(); 275 } 276 277 void ScrollableArea::contentAreaWillPaint() const 278 { 279 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 280 scrollAnimator->contentAreaWillPaint(); 281 } 282 283 void ScrollableArea::mouseEnteredContentArea() const 284 { 285 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 286 scrollAnimator->mouseEnteredContentArea(); 287 } 288 289 void ScrollableArea::mouseExitedContentArea() const 290 { 291 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 292 scrollAnimator->mouseEnteredContentArea(); 293 } 294 295 void ScrollableArea::mouseMovedInContentArea() const 296 { 297 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 298 scrollAnimator->mouseMovedInContentArea(); 299 } 300 301 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const 302 { 303 scrollAnimator()->mouseEnteredScrollbar(scrollbar); 304 } 305 306 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const 307 { 308 scrollAnimator()->mouseExitedScrollbar(scrollbar); 309 } 310 311 void ScrollableArea::contentAreaDidShow() const 312 { 313 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 314 scrollAnimator->contentAreaDidShow(); 315 } 316 317 void ScrollableArea::contentAreaDidHide() const 318 { 319 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 320 scrollAnimator->contentAreaDidHide(); 321 } 322 323 void ScrollableArea::finishCurrentScrollAnimations() const 324 { 325 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 326 scrollAnimator->finishCurrentScrollAnimations(); 327 } 328 329 void ScrollableArea::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation) 330 { 331 if (orientation == VerticalScrollbar) 332 scrollAnimator()->didAddVerticalScrollbar(scrollbar); 333 else 334 scrollAnimator()->didAddHorizontalScrollbar(scrollbar); 335 336 // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar 337 setScrollbarOverlayStyle(scrollbarOverlayStyle()); 338 } 339 340 void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation) 341 { 342 if (orientation == VerticalScrollbar) 343 scrollAnimator()->willRemoveVerticalScrollbar(scrollbar); 344 else 345 scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar); 346 } 347 348 void ScrollableArea::contentsResized() 349 { 350 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 351 scrollAnimator->contentsResized(); 352 } 353 354 bool ScrollableArea::hasOverlayScrollbars() const 355 { 356 Scrollbar* vScrollbar = verticalScrollbar(); 357 if (vScrollbar && vScrollbar->isOverlayScrollbar()) 358 return true; 359 Scrollbar* hScrollbar = horizontalScrollbar(); 360 return hScrollbar && hScrollbar->isOverlayScrollbar(); 361 } 362 363 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle) 364 { 365 m_scrollbarOverlayStyle = overlayStyle; 366 367 if (Scrollbar* scrollbar = horizontalScrollbar()) { 368 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(scrollbar); 369 scrollbar->invalidate(); 370 } 371 372 if (Scrollbar* scrollbar = verticalScrollbar()) { 373 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(scrollbar); 374 scrollbar->invalidate(); 375 } 376 } 377 378 void ScrollableArea::invalidateScrollbar(Scrollbar* scrollbar, const IntRect& rect) 379 { 380 if (scrollbar == horizontalScrollbar()) { 381 if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) { 382 graphicsLayer->setNeedsDisplay(); 383 graphicsLayer->setContentsNeedsDisplay(); 384 return; 385 } 386 } else if (scrollbar == verticalScrollbar()) { 387 if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) { 388 graphicsLayer->setNeedsDisplay(); 389 graphicsLayer->setContentsNeedsDisplay(); 390 return; 391 } 392 } 393 invalidateScrollbarRect(scrollbar, rect); 394 } 395 396 void ScrollableArea::invalidateScrollCorner(const IntRect& rect) 397 { 398 if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) { 399 graphicsLayer->setNeedsDisplay(); 400 return; 401 } 402 invalidateScrollCornerRect(rect); 403 } 404 405 bool ScrollableArea::hasLayerForHorizontalScrollbar() const 406 { 407 return layerForHorizontalScrollbar(); 408 } 409 410 bool ScrollableArea::hasLayerForVerticalScrollbar() const 411 { 412 return layerForVerticalScrollbar(); 413 } 414 415 bool ScrollableArea::hasLayerForScrollCorner() const 416 { 417 return layerForScrollCorner(); 418 } 419 420 bool ScrollableArea::scheduleAnimation() 421 { 422 if (HostWindow* window = hostWindow()) { 423 window->scheduleAnimation(); 424 return true; 425 } 426 return false; 427 } 428 429 void ScrollableArea::serviceScrollAnimations(double monotonicTime) 430 { 431 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 432 scrollAnimator->serviceScrollAnimations(); 433 if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator()) 434 programmaticScrollAnimator->tickAnimation(monotonicTime); 435 } 436 437 void ScrollableArea::cancelProgrammaticScrollAnimation() 438 { 439 if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator()) 440 programmaticScrollAnimator->cancelAnimation(); 441 } 442 443 IntRect ScrollableArea::visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const 444 { 445 int verticalScrollbarWidth = 0; 446 int horizontalScrollbarHeight = 0; 447 448 if (scrollbarInclusion == IncludeScrollbars) { 449 if (Scrollbar* verticalBar = verticalScrollbar()) 450 verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0; 451 if (Scrollbar* horizontalBar = horizontalScrollbar()) 452 horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0; 453 } 454 455 return IntRect(scrollPosition().x(), 456 scrollPosition().y(), 457 std::max(0, visibleWidth() + verticalScrollbarWidth), 458 std::max(0, visibleHeight() + horizontalScrollbarHeight)); 459 } 460 461 IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const 462 { 463 return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition()); 464 } 465 466 int ScrollableArea::lineStep(ScrollbarOrientation) const 467 { 468 return pixelsPerLineStep(); 469 } 470 471 int ScrollableArea::pageStep(ScrollbarOrientation orientation) const 472 { 473 int length = (orientation == HorizontalScrollbar) ? visibleWidth() : visibleHeight(); 474 int minPageStep = static_cast<float>(length) * minFractionToStepWhenPaging(); 475 int pageStep = std::max(minPageStep, length - maxOverlapBetweenPages()); 476 477 return std::max(pageStep, 1); 478 } 479 480 int ScrollableArea::documentStep(ScrollbarOrientation orientation) const 481 { 482 return scrollSize(orientation); 483 } 484 485 float ScrollableArea::pixelStep(ScrollbarOrientation) const 486 { 487 return 1; 488 } 489 490 } // namespace blink 491