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