QskScrollBox added

This commit is contained in:
Uwe Rathmann 2020-03-10 16:09:35 +01:00
parent 911538180e
commit 2981a47fed
5 changed files with 547 additions and 471 deletions

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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;
};

View File

@ -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 \