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