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