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 45static const int kPixelsPerLineStep = 40; 46static const float kMinFractionToStepWhenPaging = 0.875f; 47 48namespace blink { 49 50struct SameSizeAsScrollableArea { 51 virtual ~SameSizeAsScrollableArea(); 52 IntRect scrollbarDamage[2]; 53 void* pointer; 54 unsigned bitfields : 16; 55 IntPoint origin; 56}; 57 58COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small); 59 60int ScrollableArea::pixelsPerLineStep() 61{ 62 return kPixelsPerLineStep; 63} 64 65float ScrollableArea::minFractionToStepWhenPaging() 66{ 67 return kMinFractionToStepWhenPaging; 68} 69 70int ScrollableArea::maxOverlapBetweenPages() 71{ 72 static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages(); 73 return maxOverlapBetweenPages; 74} 75 76ScrollableArea::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 86ScrollableArea::~ScrollableArea() 87{ 88} 89 90ScrollAnimator* 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 101ProgrammaticScrollAnimator* 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 112void ScrollableArea::setScrollOrigin(const IntPoint& origin) 113{ 114 if (m_scrollOrigin != origin) { 115 m_scrollOrigin = origin; 116 m_scrollOriginChanged = true; 117 } 118} 119 120GraphicsLayer* ScrollableArea::layerForContainer() const 121{ 122 return layerForScrolling() ? layerForScrolling()->parent() : 0; 123} 124 125bool 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 162void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset) 163{ 164 cancelProgrammaticScrollAnimation(); 165 scrollAnimator()->scrollToOffsetWithoutAnimation(offset); 166} 167 168void 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 176void ScrollableArea::programmaticallyScrollSmoothlyToOffset(const FloatPoint& offset) 177{ 178 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 179 scrollAnimator->cancelAnimations(); 180 programmaticScrollAnimator()->animateToOffset(offset); 181} 182 183void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position) 184{ 185 scrollPositionChanged(position); 186 scrollAnimator()->setCurrentPosition(position); 187} 188 189void 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 224bool 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 238bool 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. 249void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset) 250{ 251 setScrollOffsetFromAnimation(offset); 252} 253 254void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset) 255{ 256 scrollPositionChanged(offset); 257} 258 259void ScrollableArea::willStartLiveResize() 260{ 261 if (m_inLiveResize) 262 return; 263 m_inLiveResize = true; 264 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 265 scrollAnimator->willStartLiveResize(); 266} 267 268void ScrollableArea::willEndLiveResize() 269{ 270 if (!m_inLiveResize) 271 return; 272 m_inLiveResize = false; 273 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 274 scrollAnimator->willEndLiveResize(); 275} 276 277void ScrollableArea::contentAreaWillPaint() const 278{ 279 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 280 scrollAnimator->contentAreaWillPaint(); 281} 282 283void ScrollableArea::mouseEnteredContentArea() const 284{ 285 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 286 scrollAnimator->mouseEnteredContentArea(); 287} 288 289void ScrollableArea::mouseExitedContentArea() const 290{ 291 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 292 scrollAnimator->mouseEnteredContentArea(); 293} 294 295void ScrollableArea::mouseMovedInContentArea() const 296{ 297 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 298 scrollAnimator->mouseMovedInContentArea(); 299} 300 301void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const 302{ 303 scrollAnimator()->mouseEnteredScrollbar(scrollbar); 304} 305 306void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const 307{ 308 scrollAnimator()->mouseExitedScrollbar(scrollbar); 309} 310 311void ScrollableArea::contentAreaDidShow() const 312{ 313 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 314 scrollAnimator->contentAreaDidShow(); 315} 316 317void ScrollableArea::contentAreaDidHide() const 318{ 319 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 320 scrollAnimator->contentAreaDidHide(); 321} 322 323void ScrollableArea::finishCurrentScrollAnimations() const 324{ 325 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 326 scrollAnimator->finishCurrentScrollAnimations(); 327} 328 329void 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 340void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation) 341{ 342 if (orientation == VerticalScrollbar) 343 scrollAnimator()->willRemoveVerticalScrollbar(scrollbar); 344 else 345 scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar); 346} 347 348void ScrollableArea::contentsResized() 349{ 350 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 351 scrollAnimator->contentsResized(); 352} 353 354bool 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 363void 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 378void 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 396void ScrollableArea::invalidateScrollCorner(const IntRect& rect) 397{ 398 if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) { 399 graphicsLayer->setNeedsDisplay(); 400 return; 401 } 402 invalidateScrollCornerRect(rect); 403} 404 405bool ScrollableArea::hasLayerForHorizontalScrollbar() const 406{ 407 return layerForHorizontalScrollbar(); 408} 409 410bool ScrollableArea::hasLayerForVerticalScrollbar() const 411{ 412 return layerForVerticalScrollbar(); 413} 414 415bool ScrollableArea::hasLayerForScrollCorner() const 416{ 417 return layerForScrollCorner(); 418} 419 420bool ScrollableArea::scheduleAnimation() 421{ 422 if (HostWindow* window = hostWindow()) { 423 window->scheduleAnimation(); 424 return true; 425 } 426 return false; 427} 428 429void ScrollableArea::serviceScrollAnimations(double monotonicTime) 430{ 431 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 432 scrollAnimator->serviceScrollAnimations(); 433 if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator()) 434 programmaticScrollAnimator->tickAnimation(monotonicTime); 435} 436 437void ScrollableArea::cancelProgrammaticScrollAnimation() 438{ 439 if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator()) 440 programmaticScrollAnimator->cancelAnimation(); 441} 442 443IntRect 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 461IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const 462{ 463 return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition()); 464} 465 466int ScrollableArea::lineStep(ScrollbarOrientation) const 467{ 468 return pixelsPerLineStep(); 469} 470 471int 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 480int ScrollableArea::documentStep(ScrollbarOrientation orientation) const 481{ 482 return scrollSize(orientation); 483} 484 485float ScrollableArea::pixelStep(ScrollbarOrientation) const 486{ 487 return 1; 488} 489 490} // namespace blink 491