2 * Copyright (c) 2010, Google Inc. All rights reserved.
3 * Copyright (C) 2008, 2011 Apple Inc. All Rights Reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
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
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.
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.
33 #include "platform/scroll/ScrollableArea.h"
35 #include "platform/graphics/GraphicsLayer.h"
36 #include "platform/geometry/FloatPoint.h"
37 #include "platform/scroll/ProgrammaticScrollAnimator.h"
38 #include "platform/scroll/ScrollbarTheme.h"
39 #include "wtf/PassOwnPtr.h"
41 #include "platform/TraceEvent.h"
43 static const int kPixelsPerLineStep = 40;
44 static const float kMinFractionToStepWhenPaging = 0.875f;
48 struct SameSizeAsScrollableArea {
49 virtual ~SameSizeAsScrollableArea();
50 IntRect scrollbarDamage[2];
52 unsigned bitfields : 16;
56 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small);
58 int ScrollableArea::pixelsPerLineStep()
60 return kPixelsPerLineStep;
63 float ScrollableArea::minFractionToStepWhenPaging()
65 return kMinFractionToStepWhenPaging;
68 int ScrollableArea::maxOverlapBetweenPages()
70 static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages();
71 return maxOverlapBetweenPages;
74 ScrollableArea::ScrollableArea()
75 : m_constrainsScrollingToContentEdge(true)
76 , m_inLiveResize(false)
77 , m_verticalScrollElasticity(ScrollElasticityNone)
78 , m_horizontalScrollElasticity(ScrollElasticityNone)
79 , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault)
80 , m_scrollOriginChanged(false)
84 ScrollableArea::~ScrollableArea()
88 ScrollAnimator* ScrollableArea::scrollAnimator() const
91 m_animators = adoptPtr(new ScrollableAreaAnimators);
93 if (!m_animators->scrollAnimator)
94 m_animators->scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea*>(this));
96 return m_animators->scrollAnimator.get();
99 ProgrammaticScrollAnimator* ScrollableArea::programmaticScrollAnimator() const
102 m_animators = adoptPtr(new ScrollableAreaAnimators);
104 if (!m_animators->programmaticScrollAnimator)
105 m_animators->programmaticScrollAnimator = ProgrammaticScrollAnimator::create(const_cast<ScrollableArea*>(this));
107 return m_animators->programmaticScrollAnimator.get();
110 void ScrollableArea::setScrollOrigin(const IntPoint& origin)
112 if (m_scrollOrigin != origin) {
113 m_scrollOrigin = origin;
114 m_scrollOriginChanged = true;
118 GraphicsLayer* ScrollableArea::layerForContainer() const
120 return layerForScrolling() ? layerForScrolling()->parent() : 0;
123 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float delta)
125 ScrollbarOrientation orientation;
127 if (direction == ScrollUp || direction == ScrollDown)
128 orientation = VerticalScrollbar;
130 orientation = HorizontalScrollbar;
132 if (!userInputScrollable(orientation))
135 cancelProgrammaticScrollAnimation();
138 switch (granularity) {
140 step = lineStep(orientation);
143 step = pageStep(orientation);
145 case ScrollByDocument:
146 step = documentStep(orientation);
149 case ScrollByPrecisePixel:
150 step = pixelStep(orientation);
154 if (direction == ScrollUp || direction == ScrollLeft)
157 return scrollAnimator()->scroll(orientation, granularity, step, delta);
160 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
162 cancelProgrammaticScrollAnimation();
163 scrollAnimator()->scrollToOffsetWithoutAnimation(offset);
166 void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset)
168 if (orientation == HorizontalScrollbar)
169 scrollToOffsetWithoutAnimation(FloatPoint(offset, scrollAnimator()->currentPosition().y()));
171 scrollToOffsetWithoutAnimation(FloatPoint(scrollAnimator()->currentPosition().x(), offset));
174 void ScrollableArea::programmaticallyScrollSmoothlyToOffset(const FloatPoint& offset)
176 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
177 scrollAnimator->cancelAnimations();
178 programmaticScrollAnimator()->animateToOffset(offset);
181 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position)
183 scrollPositionChanged(position);
184 scrollAnimator()->setCurrentPosition(position);
187 void ScrollableArea::scrollPositionChanged(const IntPoint& position)
189 TRACE_EVENT0("blink", "ScrollableArea::scrollPositionChanged");
191 IntPoint oldPosition = scrollPosition();
192 // Tell the derived class to scroll its contents.
193 setScrollOffset(position);
195 Scrollbar* verticalScrollbar = this->verticalScrollbar();
197 // Tell the scrollbars to update their thumb postions.
198 if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
199 horizontalScrollbar->offsetDidChange();
200 if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) {
201 if (!verticalScrollbar)
202 horizontalScrollbar->invalidate();
204 // If there is both a horizontalScrollbar and a verticalScrollbar,
205 // then we must also invalidate the corner between them.
206 IntRect boundsAndCorner = horizontalScrollbar->boundsRect();
207 boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width());
208 horizontalScrollbar->invalidateRect(boundsAndCorner);
212 if (verticalScrollbar) {
213 verticalScrollbar->offsetDidChange();
214 if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar())
215 verticalScrollbar->invalidate();
218 if (scrollPosition() != oldPosition)
219 scrollAnimator()->notifyContentAreaScrolled(scrollPosition() - oldPosition);
222 bool ScrollableArea::scrollBehaviorFromString(const String& behaviorString, ScrollBehavior& behavior)
224 if (behaviorString == "auto")
225 behavior = ScrollBehaviorAuto;
226 else if (behaviorString == "instant")
227 behavior = ScrollBehaviorInstant;
228 else if (behaviorString == "smooth")
229 behavior = ScrollBehaviorSmooth;
236 bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
238 // ctrl+wheel events are used to trigger zooming, not scrolling.
239 if (wheelEvent.modifiers() & PlatformEvent::CtrlKey)
242 cancelProgrammaticScrollAnimation();
243 return scrollAnimator()->handleWheelEvent(wheelEvent);
246 // NOTE: Only called from Internals for testing.
247 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset)
249 setScrollOffsetFromAnimation(offset);
252 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset)
254 scrollPositionChanged(offset);
257 void ScrollableArea::willStartLiveResize()
261 m_inLiveResize = true;
262 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
263 scrollAnimator->willStartLiveResize();
266 void ScrollableArea::willEndLiveResize()
270 m_inLiveResize = false;
271 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
272 scrollAnimator->willEndLiveResize();
275 void ScrollableArea::contentAreaWillPaint() const
277 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
278 scrollAnimator->contentAreaWillPaint();
281 void ScrollableArea::mouseEnteredContentArea() const
283 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
284 scrollAnimator->mouseEnteredContentArea();
287 void ScrollableArea::mouseExitedContentArea() const
289 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
290 scrollAnimator->mouseEnteredContentArea();
293 void ScrollableArea::mouseMovedInContentArea() const
295 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
296 scrollAnimator->mouseMovedInContentArea();
299 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
301 scrollAnimator()->mouseEnteredScrollbar(scrollbar);
304 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
306 scrollAnimator()->mouseExitedScrollbar(scrollbar);
309 void ScrollableArea::contentAreaDidShow() const
311 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
312 scrollAnimator->contentAreaDidShow();
315 void ScrollableArea::contentAreaDidHide() const
317 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
318 scrollAnimator->contentAreaDidHide();
321 void ScrollableArea::finishCurrentScrollAnimations() const
323 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
324 scrollAnimator->finishCurrentScrollAnimations();
327 void ScrollableArea::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
329 if (orientation == VerticalScrollbar)
330 scrollAnimator()->didAddVerticalScrollbar(scrollbar);
332 scrollAnimator()->didAddHorizontalScrollbar(scrollbar);
334 // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar
335 setScrollbarOverlayStyle(scrollbarOverlayStyle());
338 void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
340 if (orientation == VerticalScrollbar)
341 scrollAnimator()->willRemoveVerticalScrollbar(scrollbar);
343 scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar);
346 void ScrollableArea::contentsResized()
348 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
349 scrollAnimator->contentsResized();
352 bool ScrollableArea::hasOverlayScrollbars() const
354 Scrollbar* vScrollbar = verticalScrollbar();
355 if (vScrollbar && vScrollbar->isOverlayScrollbar())
357 Scrollbar* hScrollbar = horizontalScrollbar();
358 return hScrollbar && hScrollbar->isOverlayScrollbar();
361 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle)
363 m_scrollbarOverlayStyle = overlayStyle;
365 if (Scrollbar* scrollbar = horizontalScrollbar()) {
366 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(scrollbar);
367 scrollbar->invalidate();
370 if (Scrollbar* scrollbar = verticalScrollbar()) {
371 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(scrollbar);
372 scrollbar->invalidate();
376 void ScrollableArea::invalidateScrollbar(Scrollbar* scrollbar, const IntRect& rect)
378 if (scrollbar == horizontalScrollbar()) {
379 if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) {
380 graphicsLayer->setNeedsDisplay();
381 graphicsLayer->setContentsNeedsDisplay();
384 } else if (scrollbar == verticalScrollbar()) {
385 if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) {
386 graphicsLayer->setNeedsDisplay();
387 graphicsLayer->setContentsNeedsDisplay();
391 invalidateScrollbarRect(scrollbar, rect);
394 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
396 if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
397 graphicsLayer->setNeedsDisplay();
400 invalidateScrollCornerRect(rect);
403 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
405 return layerForHorizontalScrollbar();
408 bool ScrollableArea::hasLayerForVerticalScrollbar() const
410 return layerForVerticalScrollbar();
413 bool ScrollableArea::hasLayerForScrollCorner() const
415 return layerForScrollCorner();
418 void ScrollableArea::serviceScrollAnimations(double monotonicTime)
420 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
421 scrollAnimator->serviceScrollAnimations();
422 if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator())
423 programmaticScrollAnimator->tickAnimation(monotonicTime);
426 void ScrollableArea::cancelProgrammaticScrollAnimation()
428 if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator())
429 programmaticScrollAnimator->cancelAnimation();
432 IntRect ScrollableArea::visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const
434 int verticalScrollbarWidth = 0;
435 int horizontalScrollbarHeight = 0;
437 if (scrollbarInclusion == IncludeScrollbars) {
438 if (Scrollbar* verticalBar = verticalScrollbar())
439 verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0;
440 if (Scrollbar* horizontalBar = horizontalScrollbar())
441 horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0;
444 return IntRect(scrollPosition().x(),
445 scrollPosition().y(),
446 std::max(0, visibleWidth() + verticalScrollbarWidth),
447 std::max(0, visibleHeight() + horizontalScrollbarHeight));
450 IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const
452 return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition());
455 int ScrollableArea::lineStep(ScrollbarOrientation) const
457 return pixelsPerLineStep();
460 int ScrollableArea::pageStep(ScrollbarOrientation orientation) const
462 int length = (orientation == HorizontalScrollbar) ? visibleWidth() : visibleHeight();
463 int minPageStep = static_cast<float>(length) * minFractionToStepWhenPaging();
464 int pageStep = std::max(minPageStep, length - maxOverlapBetweenPages());
466 return std::max(pageStep, 1);
469 int ScrollableArea::documentStep(ScrollbarOrientation orientation) const
471 return scrollSize(orientation);
474 float ScrollableArea::pixelStep(ScrollbarOrientation) const