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 42 static const int kPixelsPerLineStep = 40; 43 static const float kMinFractionToStepWhenPaging = 0.875f; 44 45 namespace WebCore { 46 47 struct SameSizeAsScrollableArea { 48 virtual ~SameSizeAsScrollableArea(); 49 unsigned damageBits : 2; 50 IntRect scrollbarDamage[2]; 51 void* pointer; 52 unsigned bitfields : 16; 53 IntPoint origin; 54 }; 55 56 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small); 57 58 int ScrollableArea::pixelsPerLineStep() 59 { 60 return kPixelsPerLineStep; 61 } 62 63 float ScrollableArea::minFractionToStepWhenPaging() 64 { 65 return kMinFractionToStepWhenPaging; 66 } 67 68 int ScrollableArea::maxOverlapBetweenPages() 69 { 70 static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages(); 71 return maxOverlapBetweenPages; 72 } 73 74 ScrollableArea::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 86 ScrollableArea::~ScrollableArea() 87 { 88 } 89 90 ScrollAnimator* 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 98 void ScrollableArea::setScrollOrigin(const IntPoint& origin) 99 { 100 if (m_scrollOrigin != origin) { 101 m_scrollOrigin = origin; 102 m_scrollOriginChanged = true; 103 } 104 } 105 106 GraphicsLayer* ScrollableArea::layerForContainer() const 107 { 108 return layerForScrolling() ? layerForScrolling()->parent() : 0; 109 } 110 111 bool 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 146 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset) 147 { 148 scrollAnimator()->scrollToOffsetWithoutAnimation(offset); 149 } 150 151 void 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 159 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position) 160 { 161 scrollPositionChanged(position); 162 scrollAnimator()->setCurrentPosition(position); 163 } 164 165 void 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 200 bool 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 214 bool 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. 224 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset) 225 { 226 setScrollOffsetFromAnimation(offset); 227 } 228 229 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset) 230 { 231 scrollPositionChanged(offset); 232 } 233 234 void ScrollableArea::willStartLiveResize() 235 { 236 if (m_inLiveResize) 237 return; 238 m_inLiveResize = true; 239 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 240 scrollAnimator->willStartLiveResize(); 241 } 242 243 void ScrollableArea::willEndLiveResize() 244 { 245 if (!m_inLiveResize) 246 return; 247 m_inLiveResize = false; 248 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 249 scrollAnimator->willEndLiveResize(); 250 } 251 252 void ScrollableArea::contentAreaWillPaint() const 253 { 254 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 255 scrollAnimator->contentAreaWillPaint(); 256 } 257 258 void ScrollableArea::mouseEnteredContentArea() const 259 { 260 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 261 scrollAnimator->mouseEnteredContentArea(); 262 } 263 264 void ScrollableArea::mouseExitedContentArea() const 265 { 266 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 267 scrollAnimator->mouseEnteredContentArea(); 268 } 269 270 void ScrollableArea::mouseMovedInContentArea() const 271 { 272 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 273 scrollAnimator->mouseMovedInContentArea(); 274 } 275 276 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const 277 { 278 scrollAnimator()->mouseEnteredScrollbar(scrollbar); 279 } 280 281 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const 282 { 283 scrollAnimator()->mouseExitedScrollbar(scrollbar); 284 } 285 286 void ScrollableArea::contentAreaDidShow() const 287 { 288 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 289 scrollAnimator->contentAreaDidShow(); 290 } 291 292 void ScrollableArea::contentAreaDidHide() const 293 { 294 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 295 scrollAnimator->contentAreaDidHide(); 296 } 297 298 void ScrollableArea::finishCurrentScrollAnimations() const 299 { 300 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 301 scrollAnimator->finishCurrentScrollAnimations(); 302 } 303 304 void 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 315 void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation) 316 { 317 if (orientation == VerticalScrollbar) 318 scrollAnimator()->willRemoveVerticalScrollbar(scrollbar); 319 else 320 scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar); 321 } 322 323 void ScrollableArea::contentsResized() 324 { 325 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 326 scrollAnimator->contentsResized(); 327 } 328 329 bool 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 338 void 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 353 void 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 371 void ScrollableArea::invalidateScrollCorner(const IntRect& rect) 372 { 373 if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) { 374 graphicsLayer->setNeedsDisplay(); 375 return; 376 } 377 invalidateScrollCornerRect(rect); 378 } 379 380 bool ScrollableArea::hasLayerForHorizontalScrollbar() const 381 { 382 return layerForHorizontalScrollbar(); 383 } 384 385 bool ScrollableArea::hasLayerForVerticalScrollbar() const 386 { 387 return layerForVerticalScrollbar(); 388 } 389 390 bool ScrollableArea::hasLayerForScrollCorner() const 391 { 392 return layerForScrollCorner(); 393 } 394 395 void ScrollableArea::serviceScrollAnimations() 396 { 397 if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) 398 scrollAnimator->serviceScrollAnimations(); 399 } 400 401 IntRect 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 419 IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const 420 { 421 return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition()); 422 } 423 424 int ScrollableArea::lineStep(ScrollbarOrientation) const 425 { 426 return pixelsPerLineStep(); 427 } 428 429 int 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 438 int ScrollableArea::documentStep(ScrollbarOrientation orientation) const 439 { 440 return scrollSize(orientation); 441 } 442 443 float ScrollableArea::pixelStep(ScrollbarOrientation) const 444 { 445 return 1; 446 } 447 448 } // namespace WebCore 449