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