Skip to content

Commit

Permalink
Nicer UX when clicking over overlapping lower/upper handles
Browse files Browse the repository at this point in the history
In this case the direction of the subsequent drag dictates which slider
will be moved -- a move to the left will affect the lower part of the range,
while a move to the right will change the upper part of the range.

This allows users to set the range even when the widget initially starts
with a zero-width (or small width) range
  • Loading branch information
nyalldawson committed Nov 26, 2020
1 parent f0cbbae commit 292a08d
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 21 deletions.
130 changes: 110 additions & 20 deletions src/gui/qgsrangeslider.cpp
Expand Up @@ -131,6 +131,36 @@ bool QgsRangeSlider::event( QEvent *event )
return QWidget::event( event );
}

int QgsRangeSlider::pick( const QPoint &pt ) const
{
return mStyleOption.orientation == Qt::Horizontal ? pt.x() : pt.y();
}

int QgsRangeSlider::pixelPosToRangeValue( int pos ) const
{
QRect gr = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderGroove, this );
QRect sr = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
int sliderMin, sliderMax, sliderLength;
if ( mStyleOption.orientation == Qt::Horizontal )
{
sliderLength = sr.width();
sliderMin = gr.x();
sliderMax = gr.right() - sliderLength + 1;
}
else
{
sliderLength = sr.height();
sliderMin = gr.y();
sliderMax = gr.bottom() - sliderLength + 1;
}

int value = QStyle::sliderValueFromPosition( mStyleOption.minimum, mStyleOption.maximum, pos - sliderMin,
sliderMax - sliderMin );
if ( mInverted )
value = mStyleOption.maximum - value;
return value;
}

bool QgsRangeSlider::updateHoverControl( const QPoint &pos )
{
const QRect lastHoverRect = mHoverRect;
Expand Down Expand Up @@ -325,43 +355,101 @@ void QgsRangeSlider::mousePressEvent( QMouseEvent *event )

event->accept();

mActiveControl = None;

mStyleOption.sliderPosition = mInverted ? mStyleOption.maximum - mLowerValue : mLowerValue;
if ( style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) == QStyle::SC_SliderHandle )
const bool overLowerControl = style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) == QStyle::SC_SliderHandle;
const QRect lowerSliderRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
mStyleOption.sliderPosition = mInverted ? mStyleOption.maximum - mUpperValue : mUpperValue;
const bool overUpperControl = style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) == QStyle::SC_SliderHandle;
const QRect upperSliderRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );

mLowerClickOffset = pick( event->pos() - lowerSliderRect.topLeft() );
mUpperClickOffset = pick( event->pos() - upperSliderRect.topLeft() );

if ( overLowerControl && overUpperControl )
{
mActiveControl = Both;
mPreDragLowerValue = mLowerValue;
mPreDragUpperValue = mUpperValue;
}
else if ( overLowerControl )
mActiveControl = Lower;
else if ( overUpperControl )
mActiveControl = Upper;
else
mActiveControl = None;

if ( mActiveControl != None )
{
mStyleOption.sliderPosition = mInverted ? mStyleOption.maximum - mUpperValue : mUpperValue;
if ( style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) )
mActiveControl = Upper;
mStartDragPos = pixelPosToRangeValue( pick( event->pos() ) );
}
}

void QgsRangeSlider::mouseMoveEvent( QMouseEvent *event )
{
const int distance = mStyleOption.maximum - mStyleOption.minimum;
if ( mActiveControl == None )
{
event->ignore();
return;
}

int pos = style()->sliderValueFromPosition( 0, distance,
mStyleOption.orientation == Qt::Horizontal ? event->pos().x() : event->pos().y(),
mStyleOption.orientation == Qt::Horizontal ? rect().width() : rect().height() );
event->accept();

if ( mInverted )
pos = mStyleOption.maximum - pos;
int newPosition = pixelPosToRangeValue( pick( event->pos() ) );

bool changed = false;
switch ( mActiveControl )
Control destControl = mActiveControl;
if ( destControl == Both )
{
// if click was over both handles, then the direction of the drag changes which control is affected
if ( newPosition < mStartDragPos )
{
destControl = Lower;
if ( mUpperValue != mPreDragUpperValue )
{
changed = true;
mUpperValue = mPreDragUpperValue;
}
}
else if ( newPosition > mStartDragPos )
{
destControl = Upper;
if ( mLowerValue != mPreDragLowerValue )
{
changed = true;
mLowerValue = mPreDragLowerValue;
}
}
else
{
destControl = None;
if ( mUpperValue != mPreDragUpperValue )
{
changed = true;
mUpperValue = mPreDragUpperValue;
}
if ( mLowerValue != mPreDragLowerValue )
{
changed = true;
mLowerValue = mPreDragLowerValue;
}
}
}

switch ( destControl )
{
case None:
return;
case Both:
break;

case Lower:
{
if ( pos <= mUpperValue )
// adjust value to account for lower handle click offset
newPosition = pixelPosToRangeValue( pick( event->pos() ) - mLowerClickOffset );
if ( newPosition <= mUpperValue )
{
if ( mLowerValue != pos )
if ( mLowerValue != newPosition )
{
mLowerValue = pos;
mLowerValue = newPosition;
changed = true;
}
}
Expand All @@ -370,11 +458,13 @@ void QgsRangeSlider::mouseMoveEvent( QMouseEvent *event )

case Upper:
{
if ( pos >= mLowerValue )
// adjust value to account for upper handle click offset
newPosition = pixelPosToRangeValue( pick( event->pos() ) - mUpperClickOffset );
if ( newPosition >= mLowerValue )
{
if ( mUpperValue != pos )
if ( mUpperValue != newPosition )
{
mUpperValue = pos;
mUpperValue = newPosition;
changed = true;
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/gui/qgsrangeslider.h
Expand Up @@ -207,6 +207,8 @@ class GUI_EXPORT QgsRangeSlider : public QWidget

private:

int pick( const QPoint &pt ) const;
int pixelPosToRangeValue( int pos ) const;
bool updateHoverControl( const QPoint &pos );
bool newHoverControl( const QPoint &pos );

Expand All @@ -218,9 +220,15 @@ class GUI_EXPORT QgsRangeSlider : public QWidget
{
None,
Lower,
Upper
Upper,
Both
};
Control mActiveControl = None;
int mStartDragPos = -1;
int mLowerClickOffset = 0;
int mUpperClickOffset = 0;
int mPreDragLowerValue = -1;
int mPreDragUpperValue = -1;
Control mHoverControl = None;
QStyle::SubControl mHoverSubControl = QStyle::SC_None;
QRect mHoverRect;
Expand Down

0 comments on commit 292a08d

Please sign in to comment.