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
45static const int kPixelsPerLineStep = 40;
46static const float kMinFractionToStepWhenPaging = 0.875f;
47
48namespace blink {
49
50struct SameSizeAsScrollableArea {
51    virtual ~SameSizeAsScrollableArea();
52    IntRect scrollbarDamage[2];
53    void* pointer;
54    unsigned bitfields : 16;
55    IntPoint origin;
56};
57
58COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small);
59
60int ScrollableArea::pixelsPerLineStep()
61{
62    return kPixelsPerLineStep;
63}
64
65float ScrollableArea::minFractionToStepWhenPaging()
66{
67    return kMinFractionToStepWhenPaging;
68}
69
70int ScrollableArea::maxOverlapBetweenPages()
71{
72    static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages();
73    return maxOverlapBetweenPages;
74}
75
76ScrollableArea::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
86ScrollableArea::~ScrollableArea()
87{
88}
89
90ScrollAnimator* 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
101ProgrammaticScrollAnimator* 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
112void ScrollableArea::setScrollOrigin(const IntPoint& origin)
113{
114    if (m_scrollOrigin != origin) {
115        m_scrollOrigin = origin;
116        m_scrollOriginChanged = true;
117    }
118}
119
120GraphicsLayer* ScrollableArea::layerForContainer() const
121{
122    return layerForScrolling() ? layerForScrolling()->parent() : 0;
123}
124
125bool 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
162void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
163{
164    cancelProgrammaticScrollAnimation();
165    scrollAnimator()->scrollToOffsetWithoutAnimation(offset);
166}
167
168void 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
176void ScrollableArea::programmaticallyScrollSmoothlyToOffset(const FloatPoint& offset)
177{
178    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
179        scrollAnimator->cancelAnimations();
180    programmaticScrollAnimator()->animateToOffset(offset);
181}
182
183void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position)
184{
185    scrollPositionChanged(position);
186    scrollAnimator()->setCurrentPosition(position);
187}
188
189void 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
224bool 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
238bool 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.
249void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset)
250{
251    setScrollOffsetFromAnimation(offset);
252}
253
254void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset)
255{
256    scrollPositionChanged(offset);
257}
258
259void ScrollableArea::willStartLiveResize()
260{
261    if (m_inLiveResize)
262        return;
263    m_inLiveResize = true;
264    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
265        scrollAnimator->willStartLiveResize();
266}
267
268void ScrollableArea::willEndLiveResize()
269{
270    if (!m_inLiveResize)
271        return;
272    m_inLiveResize = false;
273    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
274        scrollAnimator->willEndLiveResize();
275}
276
277void ScrollableArea::contentAreaWillPaint() const
278{
279    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
280        scrollAnimator->contentAreaWillPaint();
281}
282
283void ScrollableArea::mouseEnteredContentArea() const
284{
285    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
286        scrollAnimator->mouseEnteredContentArea();
287}
288
289void ScrollableArea::mouseExitedContentArea() const
290{
291    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
292        scrollAnimator->mouseEnteredContentArea();
293}
294
295void ScrollableArea::mouseMovedInContentArea() const
296{
297    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
298        scrollAnimator->mouseMovedInContentArea();
299}
300
301void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
302{
303    scrollAnimator()->mouseEnteredScrollbar(scrollbar);
304}
305
306void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
307{
308    scrollAnimator()->mouseExitedScrollbar(scrollbar);
309}
310
311void ScrollableArea::contentAreaDidShow() const
312{
313    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
314        scrollAnimator->contentAreaDidShow();
315}
316
317void ScrollableArea::contentAreaDidHide() const
318{
319    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
320        scrollAnimator->contentAreaDidHide();
321}
322
323void ScrollableArea::finishCurrentScrollAnimations() const
324{
325    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
326        scrollAnimator->finishCurrentScrollAnimations();
327}
328
329void 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
340void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
341{
342    if (orientation == VerticalScrollbar)
343        scrollAnimator()->willRemoveVerticalScrollbar(scrollbar);
344    else
345        scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar);
346}
347
348void ScrollableArea::contentsResized()
349{
350    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
351        scrollAnimator->contentsResized();
352}
353
354bool 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
363void 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
378void 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
396void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
397{
398    if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
399        graphicsLayer->setNeedsDisplay();
400        return;
401    }
402    invalidateScrollCornerRect(rect);
403}
404
405bool ScrollableArea::hasLayerForHorizontalScrollbar() const
406{
407    return layerForHorizontalScrollbar();
408}
409
410bool ScrollableArea::hasLayerForVerticalScrollbar() const
411{
412    return layerForVerticalScrollbar();
413}
414
415bool ScrollableArea::hasLayerForScrollCorner() const
416{
417    return layerForScrollCorner();
418}
419
420bool ScrollableArea::scheduleAnimation()
421{
422    if (HostWindow* window = hostWindow()) {
423        window->scheduleAnimation();
424        return true;
425    }
426    return false;
427}
428
429void ScrollableArea::serviceScrollAnimations(double monotonicTime)
430{
431    if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
432        scrollAnimator->serviceScrollAnimations();
433    if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator())
434        programmaticScrollAnimator->tickAnimation(monotonicTime);
435}
436
437void ScrollableArea::cancelProgrammaticScrollAnimation()
438{
439    if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator())
440        programmaticScrollAnimator->cancelAnimation();
441}
442
443IntRect 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
461IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const
462{
463    return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition());
464}
465
466int ScrollableArea::lineStep(ScrollbarOrientation) const
467{
468    return pixelsPerLineStep();
469}
470
471int 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
480int ScrollableArea::documentStep(ScrollbarOrientation orientation) const
481{
482    return scrollSize(orientation);
483}
484
485float ScrollableArea::pixelStep(ScrollbarOrientation) const
486{
487    return 1;
488}
489
490} // namespace blink
491