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/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 
     60 int ScrollableArea::pixelsPerLineStep()
     61 {
     62     return kPixelsPerLineStep;
     63 }
     64 
     65 float ScrollableArea::minFractionToStepWhenPaging()
     66 {
     67     return kMinFractionToStepWhenPaging;
     68 }
     69 
     70 int ScrollableArea::maxOverlapBetweenPages()
     71 {
     72     static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages();
     73     return maxOverlapBetweenPages;
     74 }
     75 
     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 
     86 ScrollableArea::~ScrollableArea()
     87 {
     88 }
     89 
     90 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 
    101 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 
    112 void ScrollableArea::setScrollOrigin(const IntPoint& origin)
    113 {
    114     if (m_scrollOrigin != origin) {
    115         m_scrollOrigin = origin;
    116         m_scrollOriginChanged = true;
    117     }
    118 }
    119 
    120 GraphicsLayer* ScrollableArea::layerForContainer() const
    121 {
    122     return layerForScrolling() ? layerForScrolling()->parent() : 0;
    123 }
    124 
    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 
    162 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
    163 {
    164     cancelProgrammaticScrollAnimation();
    165     scrollAnimator()->scrollToOffsetWithoutAnimation(offset);
    166 }
    167 
    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 
    176 void ScrollableArea::programmaticallyScrollSmoothlyToOffset(const FloatPoint& offset)
    177 {
    178     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    179         scrollAnimator->cancelAnimations();
    180     programmaticScrollAnimator()->animateToOffset(offset);
    181 }
    182 
    183 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position)
    184 {
    185     scrollPositionChanged(position);
    186     scrollAnimator()->setCurrentPosition(position);
    187 }
    188 
    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 
    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 
    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.
    249 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset)
    250 {
    251     setScrollOffsetFromAnimation(offset);
    252 }
    253 
    254 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset)
    255 {
    256     scrollPositionChanged(offset);
    257 }
    258 
    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 
    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 
    277 void ScrollableArea::contentAreaWillPaint() const
    278 {
    279     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    280         scrollAnimator->contentAreaWillPaint();
    281 }
    282 
    283 void ScrollableArea::mouseEnteredContentArea() const
    284 {
    285     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    286         scrollAnimator->mouseEnteredContentArea();
    287 }
    288 
    289 void ScrollableArea::mouseExitedContentArea() const
    290 {
    291     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    292         scrollAnimator->mouseEnteredContentArea();
    293 }
    294 
    295 void ScrollableArea::mouseMovedInContentArea() const
    296 {
    297     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    298         scrollAnimator->mouseMovedInContentArea();
    299 }
    300 
    301 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
    302 {
    303     scrollAnimator()->mouseEnteredScrollbar(scrollbar);
    304 }
    305 
    306 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
    307 {
    308     scrollAnimator()->mouseExitedScrollbar(scrollbar);
    309 }
    310 
    311 void ScrollableArea::contentAreaDidShow() const
    312 {
    313     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    314         scrollAnimator->contentAreaDidShow();
    315 }
    316 
    317 void ScrollableArea::contentAreaDidHide() const
    318 {
    319     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    320         scrollAnimator->contentAreaDidHide();
    321 }
    322 
    323 void ScrollableArea::finishCurrentScrollAnimations() const
    324 {
    325     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    326         scrollAnimator->finishCurrentScrollAnimations();
    327 }
    328 
    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 
    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 
    348 void ScrollableArea::contentsResized()
    349 {
    350     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
    351         scrollAnimator->contentsResized();
    352 }
    353 
    354 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 
    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 
    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 
    396 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
    397 {
    398     if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
    399         graphicsLayer->setNeedsDisplay();
    400         return;
    401     }
    402     invalidateScrollCornerRect(rect);
    403 }
    404 
    405 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
    406 {
    407     return layerForHorizontalScrollbar();
    408 }
    409 
    410 bool ScrollableArea::hasLayerForVerticalScrollbar() const
    411 {
    412     return layerForVerticalScrollbar();
    413 }
    414 
    415 bool ScrollableArea::hasLayerForScrollCorner() const
    416 {
    417     return layerForScrollCorner();
    418 }
    419 
    420 bool ScrollableArea::scheduleAnimation()
    421 {
    422     if (HostWindow* window = hostWindow()) {
    423         window->scheduleAnimation();
    424         return true;
    425     }
    426     return false;
    427 }
    428 
    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 
    437 void ScrollableArea::cancelProgrammaticScrollAnimation()
    438 {
    439     if (ProgrammaticScrollAnimator* programmaticScrollAnimator = existingProgrammaticScrollAnimator())
    440         programmaticScrollAnimator->cancelAnimation();
    441 }
    442 
    443 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 
    461 IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const
    462 {
    463     return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition());
    464 }
    465 
    466 int ScrollableArea::lineStep(ScrollbarOrientation) const
    467 {
    468     return pixelsPerLineStep();
    469 }
    470 
    471 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 
    480 int ScrollableArea::documentStep(ScrollbarOrientation orientation) const
    481 {
    482     return scrollSize(orientation);
    483 }
    484 
    485 float ScrollableArea::pixelStep(ScrollbarOrientation) const
    486 {
    487     return 1;
    488 }
    489 
    490 } // namespace blink
    491