QskScrollBox added
This commit is contained in:
parent
911538180e
commit
2981a47fed
441
src/controls/QskScrollBox.cpp
Normal file
441
src/controls/QskScrollBox.cpp
Normal 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"
|
74
src/controls/QskScrollBox.h
Normal file
74
src/controls/QskScrollBox.h
Normal 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
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 \
|
||||
|
Loading…
x
Reference in New Issue
Block a user