qskinny/src/controls/QskScrollView.cpp

473 lines
12 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskScrollView.h"
#include "QskPanGestureRecognizer.h"
#include "QskFlickAnimator.h"
#include "QskBoxBorderMetrics.h"
2017-07-21 18:21:34 +02:00
#include "QskGesture.h"
#include "QskAspect.h"
#include "QskEvent.h"
#include <QSGNode>
QSK_SUBCONTROL( QskScrollView, Panel )
QSK_SUBCONTROL( QskScrollView, Viewport )
QSK_SUBCONTROL( QskScrollView, HorizontalScrollBar )
QSK_SUBCONTROL( QskScrollView, HorizontalScrollHandle )
QSK_SUBCONTROL( QskScrollView, VerticalScrollBar )
QSK_SUBCONTROL( QskScrollView, VerticalScrollHandle )
QSK_STATE( QskScrollView, VerticalHandlePressed, QskAspect::FirstSystemState << 1 )
QSK_STATE( QskScrollView, HorizontalHandlePressed, QskAspect::FirstSystemState << 2 )
namespace
{
class FlickAnimator : public QskFlickAnimator
{
public:
FlickAnimator()
{
setDuration( 1000 );
setEasingCurve( QEasingCurve::OutCubic );
}
void setScrollView( QskScrollView* scrollView )
{
m_scrollView = scrollView;
}
virtual void translate( qreal dx, qreal dy ) override final
{
const QPointF pos = m_scrollView->scrollPos();
m_scrollView->setScrollPos( pos - QPointF( dx, -dy ) );
}
private:
QskScrollView* m_scrollView;
};
}
class QskScrollView::PrivateData
{
public:
PrivateData():
horizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ),
verticalScrollBarPolicy( Qt::ScrollBarAsNeeded ),
scrollableSize( 0.0, 0.0 ),
isScrolling( 0 )
{
}
Qt::ScrollBarPolicy horizontalScrollBarPolicy;
Qt::ScrollBarPolicy verticalScrollBarPolicy;
QPointF scrollPos;
QSizeF scrollableSize;
QskPanGestureRecognizer panRecognizer;
FlickAnimator flicker;
qreal scrollPressPos;
int isScrolling;
};
QskScrollView::QskScrollView( QQuickItem* parent ):
Inherited( parent ),
m_data( new PrivateData() )
{
m_data->flicker.setScrollView( this );
m_data->panRecognizer.setWatchedItem( this );
m_data->panRecognizer.setOrientations( Qt::Horizontal | Qt::Vertical );
m_data->panRecognizer.setTimeout( 200 );
setAcceptedMouseButtons( Qt::LeftButton );
setWheelEnabled( true );
setFocusPolicy( Qt::StrongFocus );
2017-07-21 18:21:34 +02:00
}
QskScrollView::~QskScrollView()
{
}
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();
}
void QskScrollView::setVerticalScrollBarPolicy( Qt::ScrollBarPolicy policy )
{
if ( policy != m_data->verticalScrollBarPolicy )
{
m_data->verticalScrollBarPolicy = policy;
update();
Q_EMIT verticalScrollBarPolicyChanged();
}
}
Qt::ScrollBarPolicy QskScrollView::verticalScrollBarPolicy() const
{
return m_data->verticalScrollBarPolicy;
}
void QskScrollView::setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy policy )
{
if ( policy != m_data->horizontalScrollBarPolicy )
{
m_data->horizontalScrollBarPolicy = policy;
update();
Q_EMIT horizontalScrollBarPolicyChanged();
}
}
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;
}
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;
setScrollPos( m_data->scrollPos ); // scroll pos might need to be re-bounded
update();
}
}
QSizeF QskScrollView::scrollableSize() const
{
return m_data->scrollableSize;
}
QRectF QskScrollView::viewContentsRect() const
{
// This code should be done in the skinlet. TODO ...
2017-10-18 20:00:06 +02:00
const qreal bw = boxBorderMetricsHint( Viewport ).widthAt( Qt::TopEdge );
2017-07-21 18:21:34 +02:00
const QRectF r = subControlRect( Viewport );
2017-07-21 18:21:34 +02:00
return r.adjusted( bw, bw, -bw, -bw );
}
QRectF QskScrollView::gestureRect() const
{
return subControlRect( Viewport );
2017-07-21 18:21:34 +02:00
}
void QskScrollView::geometryChangeEvent( QskGeometryChangeEvent* event )
{
if ( event->isResized() )
setScrollPos( scrollPos() );
Inherited::geometryChangeEvent( event );
}
void QskScrollView::mousePressEvent( QMouseEvent* event )
{
if ( subControlRect( VerticalScrollBar ).contains( event->pos() ) )
2017-07-21 18:21:34 +02:00
{
const QRectF handleRect = subControlRect( VerticalScrollHandle );
2017-07-21 18:21:34 +02:00
if ( handleRect.contains( event->pos() ) )
{
m_data->isScrolling = Qt::Vertical;
m_data->scrollPressPos = event->y();
setSkinStateFlag( VerticalHandlePressed, true );
}
else
{
const QRectF vRect = viewContentsRect();
qreal y = m_data->scrollPos.y();
if ( event->y() < handleRect.top() )
y -= vRect.height();
else
y += vRect.height();
setScrollPos( QPointF( m_data->scrollPos.x(), y ) );
}
return;
}
if ( subControlRect( HorizontalScrollBar ).contains( event->pos() ) )
2017-07-21 18:21:34 +02:00
{
const QRectF handleRect = subControlRect( HorizontalScrollHandle );
2017-07-21 18:21:34 +02:00
if ( handleRect.contains( event->pos() ) )
{
m_data->isScrolling = Qt::Horizontal;
m_data->scrollPressPos = event->x();
setSkinStateFlag( HorizontalHandlePressed, true );
}
else
{
const QRectF vRect = viewContentsRect();
qreal x = m_data->scrollPos.x();
if ( event->x() < handleRect.left() )
x -= vRect.width();
else
x += vRect.width();
setScrollPos( QPointF( x, m_data->scrollPos.y() ) );
}
}
}
void QskScrollView::mouseMoveEvent( QMouseEvent* event )
{
if ( !m_data->isScrolling )
{
Inherited::mouseMoveEvent( event );
return;
}
QPointF pos = m_data->scrollPos;
if ( m_data->isScrolling == Qt::Horizontal )
{
const qreal dx = event->x() - m_data->scrollPressPos;
const qreal w = subControlRect( HorizontalScrollBar ).width();
2017-07-21 18:21:34 +02:00
pos.rx() += dx / w * m_data->scrollableSize.width();
m_data->scrollPressPos = event->x();
}
else if ( m_data->isScrolling == Qt::Vertical )
{
const qreal dy = event->y() - m_data->scrollPressPos;
const qreal h = subControlRect( VerticalScrollBar ).height();
2017-07-21 18:21:34 +02:00
pos.ry() += dy / h * m_data->scrollableSize.height();
m_data->scrollPressPos = event->y();
}
if ( pos != m_data->scrollPos )
setScrollPos( pos );
}
void QskScrollView::mouseReleaseEvent( QMouseEvent* )
2017-07-21 18:21:34 +02:00
{
if ( m_data->isScrolling )
{
m_data->isScrolling = 0;
m_data->scrollPressPos = 0;
setSkinStateFlag( HorizontalHandlePressed, false );
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 )
{
const qreal stepSize = 20.0; // how to find this value
QPointF offset;
if ( subControlRect( Viewport ).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;
}
}
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 );
}
}
if ( !offset.isNull() )
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
if ( event->inverted() )
offset = -offset;
#endif
setScrollPos( m_data->scrollPos - offset );
}
2017-07-21 18:21:34 +02:00
}
#endif
bool QskScrollView::gestureFilter( QQuickItem* item, QEvent* event )
{
const auto o = m_data->panRecognizer.orientations();
if ( o )
{
bool maybeGesture = m_data->panRecognizer.state() > QskGestureRecognizer::Idle;
if ( !maybeGesture && ( event->type() == QEvent::MouseButtonPress ) )
{
const QRectF vr = viewContentsRect();
const QSizeF& scrollableSize = m_data->scrollableSize;
if ( ( ( o & Qt::Vertical ) && ( vr.height() < scrollableSize.height() ) )
|| ( ( o & Qt::Horizontal ) && ( vr.width() < scrollableSize.width() ) ) )
{
maybeGesture = true;
}
}
if ( maybeGesture )
return m_data->panRecognizer.processEvent( item, event );
}
return false;
}
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
{
const QRectF vr = contentsRect();
Qt::ScrollBarPolicy policyV = m_data->verticalScrollBarPolicy;
Qt::ScrollBarPolicy policyH = m_data->horizontalScrollBarPolicy;
if ( policyV == Qt::ScrollBarAsNeeded )
{
qreal h = vr.height();
if ( policyH == Qt::ScrollBarAlwaysOn )
h -= metric( HorizontalScrollBar | QskAspect::Size );
2017-07-21 18:21:34 +02:00
if ( m_data->scrollableSize.height() > h )
policyV = Qt::ScrollBarAlwaysOn;
}
if ( policyH == Qt::ScrollBarAsNeeded )
{
qreal w = vr.width();
if ( policyV == Qt::ScrollBarAlwaysOn )
w -= metric( VerticalScrollBar | QskAspect::Size );
2017-07-21 18:21:34 +02:00
if ( m_data->scrollableSize.width() > w )
{
policyH = Qt::ScrollBarAlwaysOn;
// we have to check the vertical once more
if ( ( policyV == Qt::ScrollBarAsNeeded ) &&
( m_data->scrollableSize.height() > vr.height() - metric( HorizontalScrollBar | QskAspect::Size ) ) )
2017-07-21 18:21:34 +02:00
{
policyV = Qt::ScrollBarAlwaysOn;
}
}
}
Qt::Orientations o;
if ( policyH == Qt::ScrollBarAlwaysOn )
o |= Qt::Horizontal;
if ( policyV == Qt::ScrollBarAlwaysOn )
o |= Qt::Vertical;
return o;
}
#include "moc_QskScrollView.cpp"