diff --git a/src/controls/QskScrollBox.cpp b/src/controls/QskScrollBox.cpp new file mode 100644 index 00000000..b113917f --- /dev/null +++ b/src/controls/QskScrollBox.cpp @@ -0,0 +1,441 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskScrollBox.h" +#include "QskAnimationHint.h" +#include "QskEvent.h" +#include "QskFlickAnimator.h" +#include "QskGesture.h" +#include "QskPanGestureRecognizer.h" + +namespace +{ + class FlickAnimator final : public QskFlickAnimator + { + public: + FlickAnimator() + { + // skin hints: TODO + setDuration( 1000 ); + setEasingCurve( QEasingCurve::OutCubic ); + } + + void setScrollBox( QskScrollBox* scrollBox ) + { + m_scrollBox = scrollBox; + } + + void translate( qreal dx, qreal dy ) override + { + const QPointF pos = m_scrollBox->scrollPos(); + m_scrollBox->setScrollPos( pos - QPointF( dx, -dy ) ); + } + + private: + QskScrollBox* m_scrollBox; + }; + + class ScrollAnimator final : public QskAnimator + { + public: + ScrollAnimator() + : m_scrollBox( nullptr ) + { + } + + void setScrollBox( QskScrollBox* scrollBox ) + { + m_scrollBox = scrollBox; + } + + void scroll( const QPointF& from, const QPointF& to ) + { + if ( isRunning() ) + { + m_to = to; + return; + } + + if ( from == to || m_scrollBox == nullptr ) + { + return; + } + + m_from = from; + m_to = to; + + const auto hint = m_scrollBox->flickHint(); + + setDuration( hint.duration ); + setEasingCurve( hint.type ); + setWindow( m_scrollBox->window() ); + + start(); + } + + protected: + void advance( qreal value ) override + { + qreal x = m_from.x() + ( m_to.x() - m_from.x() ) * value; + qreal y = m_from.y() + ( m_to.y() - m_from.y() ) * value; + + m_scrollBox->setScrollPos( QPointF( x, y ) ); + } + + private: + QskScrollBox* m_scrollBox; + + QPointF m_from; + QPointF m_to; + }; +} + +class QskScrollBox::PrivateData +{ + public: + QPointF scrollPos; + QSizeF scrollableSize = QSize( 0.0, 0.0 ); + + QskPanGestureRecognizer panRecognizer; + int panRecognizerTimeout = 100; // value coming from the platform ??? + + FlickAnimator flicker; + ScrollAnimator scroller; + + const qreal viewportPadding = 10; +}; + +QskScrollBox::QskScrollBox( QQuickItem* parent ) + : Inherited( parent ) + , m_data( new PrivateData() ) +{ + m_data->flicker.setScrollBox( this ); + m_data->scroller.setScrollBox( this ); + + m_data->panRecognizer.setWatchedItem( this ); + m_data->panRecognizer.setOrientations( Qt::Horizontal | Qt::Vertical ); + + setFiltersChildMouseEvents( true ); + + setWheelEnabled( true ); + setFocusPolicy( Qt::StrongFocus ); +} + +QskScrollBox::~QskScrollBox() +{ +} + +void QskScrollBox::setFlickRecognizerTimeout( int timeout ) +{ + if ( timeout < 0 ) + timeout = -1; + + m_data->panRecognizerTimeout = timeout; +} + +int QskScrollBox::flickRecognizerTimeout() const +{ + return m_data->panRecognizerTimeout; +} + +void QskScrollBox::setFlickableOrientations( Qt::Orientations orientations ) +{ + if ( m_data->panRecognizer.orientations() != orientations ) + { + m_data->panRecognizer.setOrientations( orientations ); + Q_EMIT flickableOrientationsChanged(); + } +} + +Qt::Orientations QskScrollBox::flickableOrientations() const +{ + return m_data->panRecognizer.orientations(); +} + +void QskScrollBox::setScrollPos( const QPointF& pos ) +{ + const QPointF boundedPos = boundedScrollPos( pos ); + if ( boundedPos != m_data->scrollPos ) + { + m_data->scrollPos = boundedPos; + update(); + + Q_EMIT scrollPosChanged(); + Q_EMIT scrolledTo( boundedPos ); + } +} + +QPointF QskScrollBox::scrollPos() const +{ + return m_data->scrollPos; +} + +void QskScrollBox::scrollTo( const QPointF& pos ) +{ + m_data->scroller.scroll( scrollPos(), pos ); +} + +void QskScrollBox::setScrollableSize( const QSizeF& size ) +{ + const QSizeF boundedSize = size.expandedTo( QSizeF( 0, 0 ) ); + + if ( boundedSize != m_data->scrollableSize ) + { + m_data->scrollableSize = boundedSize; + Q_EMIT scrollableSizeChanged( m_data->scrollableSize ); + + setScrollPos( m_data->scrollPos ); // scroll pos might need to be re-bounded + + update(); + } +} + +QSizeF QskScrollBox::scrollableSize() const +{ + return m_data->scrollableSize; +} + +QRectF QskScrollBox::gestureRect() const +{ + return viewContentsRect(); +} + +void QskScrollBox::ensureVisible( const QPointF& pos ) +{ + const qreal margin = m_data->viewportPadding; + + QRectF r( scrollPos(), viewContentsRect().size() ); + r.adjust( margin, margin, -margin, -margin ); + + qreal x = r.x(); + qreal y = r.y(); + + if ( pos.x() < r.left() ) + { + x = pos.x(); + } + else if ( pos.x() > r.right() ) + { + x = pos.x() - r.width(); + } + + if ( pos.y() < r.top() ) + { + y = pos.y(); + } + else if ( y > r.right() ) + { + y = pos.y() - r.height(); + } + + const QPoint newPos( x - margin, y - margin ); + + if( isInitiallyPainted() && window() ) + scrollTo( newPos ); + else + setScrollPos( newPos ); +} + +void QskScrollBox::ensureVisible( const QRectF& itemRect ) +{ + const qreal margin = m_data->viewportPadding; + + QRectF r( scrollPos(), viewContentsRect().size() ); + r.adjust( margin, margin, -margin, -margin ); + + qreal x = r.x(); + qreal y = r.y(); + + if ( itemRect.width() > r.width() ) + { + x = itemRect.left() + 0.5 * ( itemRect.width() - r.width() ); + } + else if ( itemRect.right() > r.right() ) + { + x = itemRect.right() - r.width(); + } + else if ( itemRect.left() < r.left() ) + { + x = itemRect.left(); + } + + if ( itemRect.height() > r.height() ) + { + y = itemRect.top() + 0.5 * ( itemRect.height() - r.height() ); + } + else if ( itemRect.bottom() > r.bottom() ) + { + y = itemRect.bottom() - r.height(); + } + else if ( itemRect.top() < r.top() ) + { + y = itemRect.top(); + } + + const QPoint newPos( x - margin, y - margin ); + + if( isInitiallyPainted() && window() ) + scrollTo( newPos ); + else + setScrollPos( newPos ); +} + +void QskScrollBox::geometryChangeEvent( QskGeometryChangeEvent* event ) +{ + if ( event->isResized() ) + setScrollPos( scrollPos() ); + + Inherited::geometryChangeEvent( event ); +} + +void QskScrollBox::gestureEvent( QskGestureEvent* event ) +{ + if ( event->gesture()->type() == QskGesture::Pan ) + { + const auto gesture = static_cast< const QskPanGesture* >( event->gesture() ); + + switch ( gesture->state() ) + { + case QskGesture::Updated: + { + setScrollPos( scrollPos() - gesture->delta() ); + break; + } + case QskGesture::Finished: + { + m_data->flicker.setWindow( window() ); + m_data->flicker.accelerate( gesture->angle(), gesture->velocity() ); + break; + } + case QskGesture::Canceled: + { + // what to do here: maybe going back to the origin of the gesture ?? + break; + } + default: + break; + } + + return; + } + + Inherited::gestureEvent( event ); +} + +#ifndef QT_NO_WHEELEVENT + +QPointF QskScrollBox::scrollOffset( const QWheelEvent* event ) const +{ + if ( viewContentsRect().contains( event->posF() ) ) + { + /* + Not sure if that code makes sense, but I don't have an input device + that generates wheel events in both directions. TODO ... + */ + return event->angleDelta(); + } + + return QPointF(); +} + +void QskScrollBox::wheelEvent( QWheelEvent* event ) +{ + QPointF offset = scrollOffset( event ); + + if ( !offset.isNull() ) + { + constexpr qreal stepSize = 20.0; // how to find this value + offset *= stepSize / QWheelEvent::DefaultDeltasPerStep; + +#if QT_VERSION >= QT_VERSION_CHECK( 5, 7, 0 ) + if ( event->inverted() ) + offset = -offset; +#endif + + setScrollPos( m_data->scrollPos - offset ); + } +} + +#endif + +bool QskScrollBox::gestureFilter( QQuickItem* item, QEvent* event ) +{ + if ( event->type() == QEvent::MouseButtonPress ) + { + // Checking first if panning is possible at all + + bool maybeGesture = false; + + const auto orientations = m_data->panRecognizer.orientations(); + if ( orientations ) + { + const QSizeF viewSize = viewContentsRect().size(); + const QSizeF& scrollableSize = m_data->scrollableSize; + + if ( orientations & Qt::Vertical ) + { + if ( viewSize.height() < scrollableSize.height() ) + maybeGesture = true; + } + + if ( orientations & Qt::Horizontal ) + { + if ( viewSize.width() < scrollableSize.width() ) + maybeGesture = true; + } + } + + if ( !maybeGesture ) + return false; + } + + /* + This code is a bit tricky as the filter is called in different situations: + + a) The press was on a child of the view + b) The press was on the view + + In case of b) things are simple and we can let the recognizer + decide without timeout if it is was a gesture or not. + + In case of a) we give the recognizer some time to decide - usually + based on the distances of the following mouse events. If no decision + could be made the recognizer aborts and replays the mouse events, so + that the children can process them. + + But if a child does not accept a mouse event it will be sent to + its parent. So we might finally receive the reposted events, but then + we can proceed as in b). + */ + + auto& recognizer = m_data->panRecognizer; + + if ( event->type() == QEvent::MouseButtonPress ) + { + if ( ( item != this ) && ( recognizer.timeout() < 0 ) ) + { + const auto mouseEvent = static_cast< QMouseEvent* >( event ); + + if ( recognizer.hasProcessedBefore( mouseEvent ) ) + return false; + } + + recognizer.setTimeout( ( item == this ) ? -1 : m_data->panRecognizerTimeout ); + } + + return m_data->panRecognizer.processEvent( item, event ); +} + +QPointF QskScrollBox::boundedScrollPos( const QPointF& pos ) const +{ + const QRectF vr = viewContentsRect(); + + const qreal maxX = qMax( 0.0, scrollableSize().width() - vr.width() ); + const qreal maxY = qMax( 0.0, scrollableSize().height() - vr.height() ); + + return QPointF( qBound( 0.0, pos.x(), maxX ), qBound( 0.0, pos.y(), maxY ) ); +} + +#include "moc_QskScrollBox.cpp" diff --git a/src/controls/QskScrollBox.h b/src/controls/QskScrollBox.h new file mode 100644 index 00000000..e48b0ec3 --- /dev/null +++ b/src/controls/QskScrollBox.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_SCROLL_BOX_H +#define QSK_SCROLL_BOX_H + +#include "QskControl.h" + +class QSK_EXPORT QskScrollBox : public QskControl +{ + Q_OBJECT + + Q_PROPERTY( QPointF scrollPos READ scrollPos + WRITE setScrollPos NOTIFY scrollPosChanged FINAL ) + + Q_PROPERTY( Qt::Orientations flickableOrientations READ flickableOrientations + WRITE setFlickableOrientations NOTIFY flickableOrientationsChanged FINAL ) + + using Inherited = QskControl; + + public: + QskScrollBox( QQuickItem* parent = nullptr ); + ~QskScrollBox() override; + + void setFlickableOrientations( Qt::Orientations ); + Qt::Orientations flickableOrientations() const; + + int flickRecognizerTimeout() const; + void setFlickRecognizerTimeout( int timeout ); + + virtual QskAnimationHint flickHint() const = 0; + + QPointF scrollPos() const; + QSizeF scrollableSize() const; + + virtual QRectF viewContentsRect() const = 0; + QRectF gestureRect() const override; + + Q_SIGNALS: + void scrolledTo( const QPointF& ); + void scrollPosChanged(); + void scrollableSizeChanged( const QSizeF& ); + + void flickableOrientationsChanged(); + + public Q_SLOTS: + void setScrollPos( const QPointF& ); + void scrollTo( const QPointF& ); + + void ensureVisible( const QPointF& ); + void ensureVisible( const QRectF& ); + + protected: + void geometryChangeEvent( QskGeometryChangeEvent* ) override; + void gestureEvent( QskGestureEvent* ) override; + +#ifndef QT_NO_WHEELEVENT + void wheelEvent( QWheelEvent* ) override; + virtual QPointF scrollOffset( const QWheelEvent* ) const; +#endif + + bool gestureFilter( QQuickItem*, QEvent* ) override; + void setScrollableSize( const QSizeF& ); + + private: + QPointF boundedScrollPos( const QPointF& ) const; + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/controls/QskScrollView.cpp b/src/controls/QskScrollView.cpp index 357422f9..7827affa 100644 --- a/src/controls/QskScrollView.cpp +++ b/src/controls/QskScrollView.cpp @@ -5,12 +5,7 @@ #include "QskScrollView.h" #include "QskAnimationHint.h" -#include "QskAspect.h" #include "QskBoxBorderMetrics.h" -#include "QskEvent.h" -#include "QskFlickAnimator.h" -#include "QskGesture.h" -#include "QskPanGestureRecognizer.h" QSK_SUBCONTROL( QskScrollView, Panel ) QSK_SUBCONTROL( QskScrollView, Viewport ) @@ -22,98 +17,12 @@ QSK_SUBCONTROL( QskScrollView, VerticalScrollHandle ) QSK_SYSTEM_STATE( QskScrollView, VerticalHandlePressed, QskAspect::FirstSystemState << 1 ) QSK_SYSTEM_STATE( QskScrollView, HorizontalHandlePressed, QskAspect::FirstSystemState << 2 ) -namespace -{ - class FlickAnimator final : public QskFlickAnimator - { - public: - FlickAnimator() - { - // skin hints: TODO - setDuration( 1000 ); - setEasingCurve( QEasingCurve::OutCubic ); - } - - void setScrollView( QskScrollView* scrollView ) - { - m_scrollView = scrollView; - } - - void translate( qreal dx, qreal dy ) override - { - const QPointF pos = m_scrollView->scrollPos(); - m_scrollView->setScrollPos( pos - QPointF( dx, -dy ) ); - } - - private: - QskScrollView* m_scrollView; - }; - - class ScrollAnimator final : public QskAnimator - { - public: - ScrollAnimator() - : m_scrollView( nullptr ) - { - } - - void setScrollView( QskScrollView* scrollView ) - { - m_scrollView = scrollView; - } - - void scroll( const QPointF& from, const QPointF& to ) - { - if ( isRunning() ) - { - m_to = to; - return; - } - - if ( from == to || m_scrollView == nullptr ) - { - return; - } - - m_from = from; - m_to = to; - - const auto hint = m_scrollView->effectiveAnimation( - QskAspect::Metric, QskScrollView::Viewport, QskAspect::NoState ); - - setDuration( hint.duration ); - setEasingCurve( hint.type ); - setWindow( m_scrollView->window() ); - - start(); - } - - protected: - void advance( qreal value ) override - { - qreal x = m_from.x() + ( m_to.x() - m_from.x() ) * value; - qreal y = m_from.y() + ( m_to.y() - m_from.y() ) * value; - - m_scrollView->setScrollPos( QPointF( x, y ) ); - } - - private: - QskScrollView* m_scrollView; - - QPointF m_from; - QPointF m_to; - }; -} - class QskScrollView::PrivateData { public: PrivateData() : horizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ) , verticalScrollBarPolicy( Qt::ScrollBarAsNeeded ) - , scrollableSize( 0.0, 0.0 ) - , panRecognizerTimeout( 100 ) // value coming from the platform ??? - , viewportPadding( 10 ) , isScrolling( 0 ) { } @@ -121,17 +30,6 @@ class QskScrollView::PrivateData Qt::ScrollBarPolicy horizontalScrollBarPolicy; Qt::ScrollBarPolicy verticalScrollBarPolicy; - QPointF scrollPos; - QSizeF scrollableSize; - - QskPanGestureRecognizer panRecognizer; - int panRecognizerTimeout; - - FlickAnimator flicker; - ScrollAnimator scroller; - - qreal viewportPadding; - qreal scrollPressPos; int isScrolling; }; @@ -140,48 +38,17 @@ QskScrollView::QskScrollView( QQuickItem* parent ) : Inherited( parent ) , m_data( new PrivateData() ) { - m_data->flicker.setScrollView( this ); - m_data->scroller.setScrollView( this ); - - m_data->panRecognizer.setWatchedItem( this ); - m_data->panRecognizer.setOrientations( Qt::Horizontal | Qt::Vertical ); - setAcceptedMouseButtons( Qt::LeftButton ); - setFiltersChildMouseEvents( true ); - - setWheelEnabled( true ); - setFocusPolicy( Qt::StrongFocus ); } QskScrollView::~QskScrollView() { } -void QskScrollView::setFlickRecognizerTimeout( int timeout ) +QskAnimationHint QskScrollView::flickHint() const { - if ( timeout < 0 ) - timeout = -1; - - m_data->panRecognizerTimeout = timeout; -} - -int QskScrollView::flickRecognizerTimeout() const -{ - return m_data->panRecognizerTimeout; -} - -void QskScrollView::setFlickableOrientations( Qt::Orientations orientations ) -{ - if ( m_data->panRecognizer.orientations() != orientations ) - { - m_data->panRecognizer.setOrientations( orientations ); - Q_EMIT flickableOrientationsChanged(); - } -} - -Qt::Orientations QskScrollView::flickableOrientations() const -{ - return m_data->panRecognizer.orientations(); + return effectiveAnimation( QskAspect::Metric, + QskScrollView::Viewport, QskAspect::NoState ); } void QskScrollView::setVerticalScrollBarPolicy( Qt::ScrollBarPolicy policy ) @@ -216,134 +83,11 @@ Qt::ScrollBarPolicy QskScrollView::horizontalScrollBarPolicy() const return m_data->horizontalScrollBarPolicy; } -void QskScrollView::setScrollPos( const QPointF& pos ) -{ - const QPointF boundedPos = boundedScrollPos( pos ); - if ( boundedPos != m_data->scrollPos ) - { - m_data->scrollPos = boundedPos; - update(); - - Q_EMIT scrollPosChanged(); - Q_EMIT scrolledTo( boundedPos ); - } -} - -QPointF QskScrollView::scrollPos() const -{ - return m_data->scrollPos; -} - -void QskScrollView::scrollTo( const QPointF& pos ) -{ - m_data->scroller.scroll( scrollPos(), pos ); -} - bool QskScrollView::isScrolling( Qt::Orientation orientation ) const { return m_data->isScrolling == orientation; } -void QskScrollView::setScrollableSize( const QSizeF& size ) -{ - const QSizeF boundedSize = size.expandedTo( QSizeF( 0, 0 ) ); - - if ( boundedSize != m_data->scrollableSize ) - { - m_data->scrollableSize = boundedSize; - Q_EMIT scrollableSizeChanged( m_data->scrollableSize ); - - setScrollPos( m_data->scrollPos ); // scroll pos might need to be re-bounded - - update(); - } -} - -QSizeF QskScrollView::scrollableSize() const -{ - return m_data->scrollableSize; -} - -void QskScrollView::ensureVisible( const QPointF& pos ) -{ - const qreal margin = m_data->viewportPadding; - - QRectF r( scrollPos(), viewContentsRect().size() ); - r.adjust( margin, margin, -margin, -margin ); - - qreal x = r.x(); - qreal y = r.y(); - - if ( pos.x() < r.left() ) - { - x = pos.x(); - } - else if ( pos.x() > r.right() ) - { - x = pos.x() - r.width(); - } - - if ( pos.y() < r.top() ) - { - y = pos.y(); - } - else if ( y > r.right() ) - { - y = pos.y() - r.height(); - } - - const QPoint newPos( x - margin, y - margin ); - - if( isInitiallyPainted() && window() ) - scrollTo( newPos ); - else - setScrollPos( newPos ); -} - -void QskScrollView::ensureVisible( const QRectF& itemRect ) -{ - const qreal margin = m_data->viewportPadding; - - QRectF r( scrollPos(), viewContentsRect().size() ); - r.adjust( margin, margin, -margin, -margin ); - - qreal x = r.x(); - qreal y = r.y(); - - if ( itemRect.width() > r.width() ) - { - x = itemRect.left() + 0.5 * ( itemRect.width() - r.width() ); - } - else if ( itemRect.right() > r.right() ) - { - x = itemRect.right() - r.width(); - } - else if ( itemRect.left() < r.left() ) - { - x = itemRect.left(); - } - - if ( itemRect.height() > r.height() ) - { - y = itemRect.top() + 0.5 * ( itemRect.height() - r.height() ); - } - else if ( itemRect.bottom() > r.bottom() ) - { - y = itemRect.bottom() - r.height(); - } - else if ( itemRect.top() < r.top() ) - { - y = itemRect.top(); - } - - const QPoint newPos( x - margin, y - margin ); - - if( isInitiallyPainted() && window() ) - scrollTo( newPos ); - else - setScrollPos( newPos ); -} - QRectF QskScrollView::viewContentsRect() const { // This code should be done in the skinlet. TODO ... @@ -353,19 +97,6 @@ QRectF QskScrollView::viewContentsRect() const return r.adjusted( bw, bw, -bw, -bw ); } -QRectF QskScrollView::gestureRect() const -{ - return subControlRect( Viewport ); -} - -void QskScrollView::geometryChangeEvent( QskGeometryChangeEvent* event ) -{ - if ( event->isResized() ) - setScrollPos( scrollPos() ); - - Inherited::geometryChangeEvent( event ); -} - void QskScrollView::mousePressEvent( QMouseEvent* event ) { if ( subControlRect( VerticalScrollBar ).contains( event->pos() ) ) @@ -383,14 +114,14 @@ void QskScrollView::mousePressEvent( QMouseEvent* event ) { const QRectF vRect = viewContentsRect(); - qreal y = m_data->scrollPos.y(); + qreal y = scrollPos().y(); if ( event->y() < handleRect.top() ) y -= vRect.height(); else y += vRect.height(); - setScrollPos( QPointF( m_data->scrollPos.x(), y ) ); + setScrollPos( QPointF( scrollPos().x(), y ) ); } return; @@ -411,14 +142,14 @@ void QskScrollView::mousePressEvent( QMouseEvent* event ) { const QRectF vRect = viewContentsRect(); - qreal x = m_data->scrollPos.x(); + qreal x = scrollPos().x(); if ( event->x() < handleRect.left() ) x -= vRect.width(); else x += vRect.width(); - setScrollPos( QPointF( x, m_data->scrollPos.y() ) ); + setScrollPos( QPointF( x, scrollPos().y() ) ); } return; @@ -435,14 +166,14 @@ void QskScrollView::mouseMoveEvent( QMouseEvent* event ) return; } - QPointF pos = m_data->scrollPos; + QPointF pos = scrollPos(); if ( m_data->isScrolling == Qt::Horizontal ) { const qreal dx = event->x() - m_data->scrollPressPos; const qreal w = subControlRect( HorizontalScrollBar ).width(); - pos.rx() += dx / w * m_data->scrollableSize.width(); + pos.rx() += dx / w * scrollableSize().width(); m_data->scrollPressPos = event->x(); } else if ( m_data->isScrolling == Qt::Vertical ) @@ -450,11 +181,11 @@ void QskScrollView::mouseMoveEvent( QMouseEvent* event ) const qreal dy = event->y() - m_data->scrollPressPos; const qreal h = subControlRect( VerticalScrollBar ).height(); - pos.ry() += dy / h * m_data->scrollableSize.height(); + pos.ry() += dy / h * scrollableSize().height(); m_data->scrollPressPos = event->y(); } - if ( pos != m_data->scrollPos ) + if ( pos != scrollPos() ) setScrollPos( pos ); } @@ -473,167 +204,30 @@ void QskScrollView::mouseReleaseEvent( QMouseEvent* event ) setSkinStateFlag( VerticalHandlePressed, false ); } -void QskScrollView::gestureEvent( QskGestureEvent* event ) -{ - if ( event->gesture()->type() == QskGesture::Pan ) - { - const auto gesture = static_cast< const QskPanGesture* >( event->gesture() ); - switch ( gesture->state() ) - { - case QskGesture::Updated: - { - setScrollPos( scrollPos() - gesture->delta() ); - break; - } - case QskGesture::Finished: - { - m_data->flicker.setWindow( window() ); - m_data->flicker.accelerate( gesture->angle(), gesture->velocity() ); - break; - } - case QskGesture::Canceled: - { - // what to do here: maybe going back to the origin of the gesture ?? - break; - } - default: - break; - } - - return; - } - - Inherited::gestureEvent( event ); -} - #ifndef QT_NO_WHEELEVENT -void QskScrollView::wheelEvent( QWheelEvent* event ) +QPointF QskScrollView::scrollOffset( const QWheelEvent* event ) const { - const qreal stepSize = 20.0; // how to find this value - QPointF offset; - if ( subControlRect( Viewport ).contains( event->posF() ) ) + if ( subControlRect( VerticalScrollBar ).contains( event->posF() ) ) { - /* - Not sure if that code makes sense, but I don't have an input device - that generates wheel events in both directions. TODO ... - */ - - // offset = event->pixelDelta(); - - if ( offset.isNull() ) - { - offset = event->angleDelta(); - offset *= stepSize / QWheelEvent::DefaultDeltasPerStep; - } + offset.setY( event->delta() ); + } + else if ( subControlRect( HorizontalScrollBar ).contains( event->posF() ) ) + { + offset.setX( event->delta() ); } else { - const qreal delta = stepSize * event->delta() / QWheelEvent::DefaultDeltasPerStep; - - if ( subControlRect( VerticalScrollBar ).contains( event->posF() ) ) - { - offset.setY( delta ); - } - else if ( subControlRect( HorizontalScrollBar ).contains( event->posF() ) ) - { - offset.setX( delta ); - } + offset = Inherited::scrollOffset( event ); } - if ( !offset.isNull() ) - { -#if QT_VERSION >= QT_VERSION_CHECK( 5, 7, 0 ) - if ( event->inverted() ) - offset = -offset; -#endif - - setScrollPos( m_data->scrollPos - offset ); - } + return offset; } #endif -bool QskScrollView::gestureFilter( QQuickItem* item, QEvent* event ) -{ - if ( event->type() == QEvent::MouseButtonPress ) - { - // Checking first if panning is possible at all - - bool maybeGesture = false; - - const auto orientations = m_data->panRecognizer.orientations(); - if ( orientations ) - { - const QSizeF viewSize = viewContentsRect().size(); - const QSizeF& scrollableSize = m_data->scrollableSize; - - if ( orientations & Qt::Vertical ) - { - if ( viewSize.height() < scrollableSize.height() ) - maybeGesture = true; - } - - if ( orientations & Qt::Horizontal ) - { - if ( viewSize.width() < scrollableSize.width() ) - maybeGesture = true; - } - } - - if ( !maybeGesture ) - return false; - } - - /* - This code is a bit tricky as the filter is called in different situations: - - a) The press was on a child of the view - b) The press was on the view - - In case of b) things are simple and we can let the recognizer - decide without timeout if it is was a gesture or not. - - In case of a) we give the recognizer some time to decide - usually - based on the distances of the following mouse events. If no decision - could be made the recognizer aborts and replays the mouse events, so - that the children can process them. - - But if a child does not accept a mouse event it will be sent to - its parent. So we might finally receive the reposted events, but then - we can proceed as in b). - */ - - auto& recognizer = m_data->panRecognizer; - - if ( event->type() == QEvent::MouseButtonPress ) - { - if ( ( item != this ) && ( recognizer.timeout() < 0 ) ) - { - const auto mouseEvent = static_cast< QMouseEvent* >( event ); - - if ( recognizer.hasProcessedBefore( mouseEvent ) ) - return false; - } - - recognizer.setTimeout( ( item == this ) ? -1 : m_data->panRecognizerTimeout ); - } - - return m_data->panRecognizer.processEvent( item, event ); -} - -QPointF QskScrollView::boundedScrollPos( const QPointF& pos ) const -{ - const QRectF vr = viewContentsRect(); - - const qreal maxX = qMax( 0.0, scrollableSize().width() - vr.width() ); - const qreal maxY = qMax( 0.0, scrollableSize().height() - vr.height() ); - - return QPointF( qBound( 0.0, pos.x(), maxX ), qBound( 0.0, pos.y(), maxY ) ); -} - Qt::Orientations QskScrollView::scrollableOrientations() const { // layoutRect ??? @@ -649,7 +243,7 @@ Qt::Orientations QskScrollView::scrollableOrientations() const if ( policyHorizontal == Qt::ScrollBarAlwaysOn ) height -= metric( HorizontalScrollBar | QskAspect::Size ); - if ( m_data->scrollableSize.height() > height ) + if ( scrollableSize().height() > height ) policyVertical = Qt::ScrollBarAlwaysOn; } @@ -660,14 +254,14 @@ Qt::Orientations QskScrollView::scrollableOrientations() const if ( policyVertical == Qt::ScrollBarAlwaysOn ) width -= metric( VerticalScrollBar | QskAspect::Size ); - if ( m_data->scrollableSize.width() > width ) + if ( scrollableSize().width() > width ) { policyHorizontal = Qt::ScrollBarAlwaysOn; // we have to check the vertical once more if ( ( policyVertical == Qt::ScrollBarAsNeeded ) && - ( m_data->scrollableSize.height() > + ( scrollableSize().height() > vr.height() - metric( HorizontalScrollBar | QskAspect::Size ) ) ) { policyVertical = Qt::ScrollBarAlwaysOn; diff --git a/src/controls/QskScrollView.h b/src/controls/QskScrollView.h index bb54e414..551a6db7 100644 --- a/src/controls/QskScrollView.h +++ b/src/controls/QskScrollView.h @@ -6,25 +6,19 @@ #ifndef QSK_SCROLL_VIEW_H #define QSK_SCROLL_VIEW_H -#include "QskControl.h" +#include "QskScrollBox.h" -class QSK_EXPORT QskScrollView : public QskControl +class QSK_EXPORT QskScrollView : public QskScrollBox { Q_OBJECT - Q_PROPERTY( QPointF scrollPos READ scrollPos - WRITE setScrollPos NOTIFY scrollPosChanged FINAL ) - Q_PROPERTY( Qt::ScrollBarPolicy verticalScrollBarPolicy READ verticalScrollBarPolicy WRITE setVerticalScrollBarPolicy NOTIFY verticalScrollBarPolicyChanged FINAL ) Q_PROPERTY( Qt::ScrollBarPolicy horizontalScrollBarPolicy READ horizontalScrollBarPolicy WRITE setHorizontalScrollBarPolicy NOTIFY horizontalScrollBarPolicyChanged FINAL ) - Q_PROPERTY( Qt::Orientations flickableOrientations READ flickableOrientations - WRITE setFlickableOrientations NOTIFY flickableOrientationsChanged FINAL ) - - using Inherited = QskControl; + using Inherited = QskScrollBox; public: QSK_SUBCONTROLS( Panel, Viewport, @@ -42,56 +36,27 @@ class QSK_EXPORT QskScrollView : public QskControl void setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy ); Qt::ScrollBarPolicy horizontalScrollBarPolicy() const; - void setFlickableOrientations( Qt::Orientations ); - Qt::Orientations flickableOrientations() const; + Qt::Orientations scrollableOrientations() const; - int flickRecognizerTimeout() const; - void setFlickRecognizerTimeout( int timeout ); - - QPointF scrollPos() const; bool isScrolling( Qt::Orientation ) const; - Qt::Orientations scrollableOrientations() const; - QSizeF scrollableSize() const; - - QRectF viewContentsRect() const; - QRectF gestureRect() const override; + QRectF viewContentsRect() const override; + QskAnimationHint flickHint() const override; Q_SIGNALS: - void scrolledTo( const QPointF& ); - void scrollPosChanged(); - void scrollableSizeChanged( const QSizeF& ); - void verticalScrollBarPolicyChanged(); void horizontalScrollBarPolicyChanged(); - void flickableOrientationsChanged(); - - public Q_SLOTS: - void setScrollPos( const QPointF& ); - void scrollTo( const QPointF& ); - - void ensureVisible( const QPointF& ); - void ensureVisible( const QRectF& ); - protected: void mouseMoveEvent( QMouseEvent* ) override; void mousePressEvent( QMouseEvent* ) override; void mouseReleaseEvent( QMouseEvent* ) override; - void geometryChangeEvent( QskGeometryChangeEvent* ) override; - void gestureEvent( QskGestureEvent* ) override; #ifndef QT_NO_WHEELEVENT - void wheelEvent( QWheelEvent* ) override; + QPointF scrollOffset( const QWheelEvent* ) const override; #endif - bool gestureFilter( QQuickItem*, QEvent* ) override; - - void setScrollableSize( const QSizeF& ); - private: - QPointF boundedScrollPos( const QPointF& ) const; - class PrivateData; std::unique_ptr< PrivateData > m_data; }; diff --git a/src/src.pro b/src/src.pro index feb8a7a1..70b7ce3a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -138,6 +138,7 @@ HEADERS += \ controls/QskQuickItemPrivate.h \ controls/QskRangeControl.h \ controls/QskScrollArea.h \ + controls/QskScrollBox.h \ controls/QskScrollView.h \ controls/QskScrollViewSkinlet.h \ controls/QskSeparator.h \ @@ -205,6 +206,7 @@ SOURCES += \ controls/QskQuickItemPrivate.cpp \ controls/QskRangeControl.cpp \ controls/QskScrollArea.cpp \ + controls/QskScrollBox.cpp \ controls/QskScrollView.cpp \ controls/QskScrollViewSkinlet.cpp \ controls/QskSeparator.cpp \