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