Commit a569b096 authored by Ludovic Fauvet's avatar Ludovic Fauvet Committed by Jean-Baptiste Kempf

Qt: use a QStyle to draw the seek slider

This should be much cleaner than drawing ourselves inside the paintEvent
of the widget. Furthermore it is easier to fallback to the native slider
available in pre 2.0.
The use of a QStyle also fixes the clipping issue where the handle was
cropped while being at the start or the end of the slider.
Signed-off-by: Jean-Baptiste Kempf's avatarJean-Baptiste Kempf <jb@videolan.org>
parent 31f985a5
......@@ -96,6 +96,7 @@ nodist_SOURCES_qt4 = \
ui/messages_panel.h \
ui/about.h \
ui/update.h \
styles/seekstyle.moc.cpp \
dialogs/ml_configuration.moc.cpp \
components/playlist/ml_model.moc.cpp \
ui/sout.h
......@@ -315,7 +316,8 @@ SOURCES_qt4 = qt4.cpp \
util/pictureflow.cpp \
util/buttons/BrowseButton.cpp \
util/buttons/DeckButtonsLayout.cpp \
util/buttons/RoundButton.cpp
util/buttons/RoundButton.cpp \
styles/seekstyle.cpp
if HAVE_DARWIN
SOURCES_qt4 += util/searchlineedit_mac.mm
......@@ -398,7 +400,8 @@ noinst_HEADERS = \
util/singleton.hpp \
util/buttons/RoundButton.hpp \
util/buttons/DeckButtonsLayout.hpp \
util/buttons/BrowseButton.hpp
util/buttons/BrowseButton.hpp \
styles/seekstyle.hpp
EXTRA_DIST += \
......
/*****************************************************************************
* seekstyle.cpp : Seek slider style
****************************************************************************
* Copyright (C) 2011-2012 VLC authors and VideoLAN
*
* Authors: Ludovic Fauvet <etix@videolan.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include "seekstyle.hpp"
#include "util/input_slider.hpp"
#include "adapters/seekpoints.hpp"
#include <QWindowsStyle>
#include <QStyleOptionSlider>
#include <QPainter>
#include <QDebug>
#define RADIUS 3
#define CHAPTERSSPOTSIZE 3
int SeekStyle::pixelMetric( PixelMetric metric, const QStyleOption *option, const QWidget *widget ) const
{
const QStyleOptionSlider *slider;
if ( metric == PM_SliderLength && ( slider = qstyleoption_cast<const QStyleOptionSlider *>( option ) ) )
return slider->rect.height();
else
return QWindowsStyle::pixelMetric( metric, option, widget );
}
void SeekStyle::drawComplexControl( ComplexControl cc, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget ) const
{
if( cc == CC_Slider )
{
painter->setRenderHints( QPainter::Antialiasing );
if ( const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>( option ) )
{
const SeekSlider *seekSlider = qobject_cast<const SeekSlider*>( widget );
qreal sliderPos = -1;
/* Get the needed subcontrols to draw the slider */
QRect groove = subControlRect(CC_Slider, slider, SC_SliderGroove, widget);
QRect handle = subControlRect(CC_Slider, slider, SC_SliderHandle, widget);
/* Adjust the size of the groove so the handle stays centered */
groove.adjust( handle.width() / 2, 0, -handle.width() / 2, 0 );
/* Reduce the height of the groove */
// Note: in the original 2.0.0 code, the groove had a height of 9px and to
// comply with the original style (but still allow the widget to expand) I
// had to remove 1 from the rect bottom.
groove.adjust( 0, (qreal)groove.height() / 3.7, 0, (qreal)-groove.height() / 3.7 - 1 );
if ( ( slider->subControls & SC_SliderGroove ) && groove.isValid() )
{
sliderPos = ( ( (qreal)groove.width() ) / (qreal)slider->maximum )
* (qreal)slider->sliderPosition;
/* set the background color and gradient */
QColor backgroundBase( slider->palette.window().color() );
QLinearGradient backgroundGradient( 0, 0, 0, slider->rect.height() );
backgroundGradient.setColorAt( 0.0, backgroundBase.darker( 140 ) );
backgroundGradient.setColorAt( 1.0, backgroundBase );
/* set the foreground color and gradient */
QColor foregroundBase( 50, 156, 255 );
QLinearGradient foregroundGradient( 0, 0, 0, groove.height() );
foregroundGradient.setColorAt( 0.0, foregroundBase );
foregroundGradient.setColorAt( 1.0, foregroundBase.darker( 125 ) );
/* draw a slight 3d effect on the bottom */
painter->setPen( QColor( 230, 230, 230 ) );
painter->setBrush( Qt::NoBrush );
painter->drawRoundedRect( groove.adjusted( 0, 2, 0, 0 ), RADIUS, RADIUS );
/* draw background */
painter->setPen( Qt::NoPen );
painter->setBrush( backgroundGradient );
painter->drawRoundedRect( groove, RADIUS, RADIUS );
/* adjusted foreground rectangle */
QRect valueRect = groove.adjusted( 1, 1, -1, 0 );
valueRect.setWidth( sliderPos );
/* draw foreground */
if ( slider->sliderPosition > slider->minimum && slider->sliderPosition <= slider->maximum )
{
painter->setPen( Qt::NoPen );
painter->setBrush( foregroundGradient );
painter->drawRoundedRect( valueRect, RADIUS, RADIUS );
}
/* draw buffering overlay */
if ( seekSlider && seekSlider->f_buffering < 1.0 )
{
QRect innerRect = groove.adjusted( 1, 1,
groove.width() * ( -1.0 + seekSlider->f_buffering ) - 1, 0 );
QColor overlayColor = QColor( "Orange" );
overlayColor.setAlpha( 128 );
painter->setBrush( overlayColor );
painter->drawRoundedRect( innerRect, RADIUS, RADIUS );
}
}
if ( slider->subControls & SC_SliderTickmarks ) {
QStyleOptionSlider tmpSlider = *slider;
tmpSlider.subControls = SC_SliderTickmarks;
QWindowsStyle::drawComplexControl(cc, &tmpSlider, painter, widget);
}
if ( slider->subControls & SC_SliderHandle && handle.isValid() )
{
/* Useful for debugging */
//painter->setBrush( QColor( 0, 0, 255, 150 ) );
//painter->drawRect( handle );
if ( option->state & QStyle::State_MouseOver || (seekSlider && seekSlider->isAnimationRunning() ) )
{
QPalette p = slider->palette;
/* draw chapters tickpoints */
if ( seekSlider->chapters && seekSlider->inputLength && groove.width() )
{
QColor background = p.color( QPalette::Active, QPalette::Background );
QColor foreground = p.color( QPalette::Active, QPalette::WindowText );
foreground.setHsv( foreground.hue(),
( background.saturation() + foreground.saturation() ) / 2,
( background.value() + foreground.value() ) / 2 );
if ( slider->orientation == Qt::Horizontal ) /* TODO: vertical */
{
QList<SeekPoint> points = seekSlider->chapters->getPoints();
foreach( SeekPoint point, points )
{
int x = groove.x() + point.time / 1000000.0 / seekSlider->inputLength * groove.width();
painter->setPen( foreground );
painter->setBrush( Qt::NoBrush );
painter->drawLine( x, slider->rect.height(), x, slider->rect.height() - CHAPTERSSPOTSIZE );
}
}
}
/* draw handle */
if ( option->state & QStyle::State_Enabled && sliderPos != -1 )
{
QSize hSize = QSize( handle.height(), handle.height() ) - QSize( 6, 6 );;
QPoint pos = QPoint( handle.center().x() - ( hSize.width() / 2 ), handle.center().y() - ( hSize.height() / 2 ) );
QPoint shadowPos( pos - QPoint( 2, 2 ) );
QSize sSize( QSize( handle.height(), handle.height() ) - QSize( 2, 2 ) );
/* prepare the handle's gradient */
QLinearGradient handleGradient( 0, 0, 0, hSize.height() );
handleGradient.setColorAt( 0.0, p.window().color().lighter( 120 ) );
handleGradient.setColorAt( 0.9, p.window().color().darker( 120 ) );
/* prepare the handle's shadow gradient */
QColor shadowBase = p.shadow().color();
if( shadowBase.lightness() > 100 )
shadowBase = QColor( 60, 60, 60 ); // Palette's shadow is too bright
QColor shadowDark( shadowBase.darker( 150 ) );
QColor shadowLight( shadowBase.lighter( 180 ) );
shadowLight.setAlpha( 50 );
QRadialGradient shadowGradient( shadowPos.x() + ( sSize.width() / 2 ),
shadowPos.y() + ( sSize.height() / 2 ),
qMax( sSize.width(), sSize.height() ) / 2 );
shadowGradient.setColorAt( 0.4, shadowDark );
shadowGradient.setColorAt( 1.0, shadowLight );
painter->setPen( Qt::NoPen );
if ( seekSlider != NULL )
painter->setOpacity( seekSlider->mHandleOpacity );
/* draw the handle's shadow */
painter->setBrush( shadowGradient );
painter->drawEllipse( shadowPos.x(), shadowPos.y() + 1, sSize.width(), sSize.height() );
/* finally draw the handle */
painter->setBrush( handleGradient );
painter->drawEllipse( pos.x(), pos.y(), hSize.width(), hSize.height() );
}
}
}
}
}
else
{
qWarning() << "SeekStyle: Drawing an unmanaged control";
QWindowsStyle::drawComplexControl( cc, option, painter, widget );
}
}
/*****************************************************************************
* seekstyle.hpp : Seek slider style
****************************************************************************
* Copyright (C) 2011-2012 VLC authors and VideoLAN
*
* Authors: Ludovic Fauvet <etix@videolan.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef SEEKSTYLE_HPP
#define SEEKSTYLE_HPP
#include <QWindowsStyle>
class SeekStyle : public QWindowsStyle
{
Q_OBJECT
public:
SeekStyle() { }
virtual int pixelMetric(PixelMetric metric, const QStyleOption * option = 0, const QWidget * widget = 0) const;
virtual void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *widget) const;
};
#endif // SEEKSTYLE_HPP
......@@ -36,7 +36,6 @@
#include <QPaintEvent>
#include <QPainter>
#include <QBitmap>
#include <QPainter>
#include <QStyleOptionSlider>
#include <QLinearGradient>
#include <QTimer>
......@@ -62,6 +61,7 @@ SeekSlider::SeekSlider( Qt::Orientation q, QWidget *_parent, bool _static )
f_buffering = 1.0;
mHandleOpacity = 1.0;
chapters = NULL;
mHandleLength = -1;
// prepare some static colors
QPalette p = palette();
......@@ -109,6 +109,10 @@ SeekSlider::SeekSlider( Qt::Orientation q, QWidget *_parent, bool _static )
setTracking( true );
setFocusPolicy( Qt::NoFocus );
/* Use the new/classic style */
if( !b_classic )
setStyle( new SeekStyle );
/* Init to 0 */
setPosition( -1.0, 0, 0 );
secstotimestr( psz_length, 0 );
......@@ -253,7 +257,8 @@ void SeekSlider::mousePressEvent( QMouseEvent* event )
}
isSliding = true ;
setValue( QStyle::sliderValueFromPosition( MINIMUM, MAXIMUM, event->x(), width(), false ) );
setValue( QStyle::sliderValueFromPosition( MINIMUM, MAXIMUM, event->x() - handleLength() / 2, width() - handleLength(), false ) );
emit sliderMoved( value() );
event->accept();
}
......@@ -262,14 +267,15 @@ void SeekSlider::mouseMoveEvent( QMouseEvent *event )
{
if( isSliding )
{
setValue( QStyle::sliderValueFromPosition( MINIMUM, MAXIMUM, event->x(), width(), false) );
setValue( QStyle::sliderValueFromPosition( MINIMUM, MAXIMUM, event->x() - handleLength() / 2, width() - handleLength(), false) );
emit sliderMoved( value() );
}
/* Tooltip */
if ( inputLength > 0 )
{
int posX = qMax( rect().left(), qMin( rect().right(), event->x() ) );
int margin = handleLength() / 2;
int posX = qMax( rect().left() + margin, qMin( rect().right() - margin, event->x() ) );
QString chapterLabel;
......@@ -292,7 +298,7 @@ void SeekSlider::mouseMoveEvent( QMouseEvent *event )
QPoint target( event->globalX() - ( event->x() - posX ),
QWidget::mapToGlobal( pos() ).y() );
secstotimestr( psz_length, ( posX * inputLength ) / size().width() );
secstotimestr( psz_length, ( ( posX - margin ) * inputLength ) / ( size().width() - handleLength() ) );
mTimeTooltip->setTip( target, psz_length, chapterLabel );
}
event->accept();
......@@ -368,178 +374,12 @@ bool SeekSlider::eventFilter( QObject *obj, QEvent *event )
QSize SeekSlider::sizeHint() const
{
if ( b_classic )
return QSlider::sizeHint();
return ( orientation() == Qt::Horizontal ) ? QSize( 100, 18 )
: QSize( 18, 100 );
}
QSize SeekSlider::handleSize() const
{
const int size = ( orientation() == Qt::Horizontal ? height() : width() );
return QSize( size, size );
}
void SeekSlider::paintEvent( QPaintEvent *event )
{
if( b_classic )
return QSlider::paintEvent( event );
QStyleOptionSlider option;
initStyleOption( &option );
/* */
QPainter painter( this );
painter.setRenderHints( QPainter::Antialiasing );
// draw bar
const int barCorner = 3;
qreal sliderPos = -1;
int range = MAXIMUM;
QRect barRect = rect();
// adjust positions based on the current orientation
if ( option.sliderPosition != 0 )
{
switch ( orientation() )
{
case Qt::Horizontal:
sliderPos = ( ( (qreal)width() ) / (qreal)range )
* (qreal)option.sliderPosition;
break;
case Qt::Vertical:
sliderPos = ( ( (qreal)height() ) / (qreal)range )
* (qreal)option.sliderPosition;
break;
}
}
switch ( orientation() )
{
case Qt::Horizontal:
barRect.setHeight( height() /2 );
break;
case Qt::Vertical:
barRect.setWidth( width() /2 );
break;
}
barRect.moveCenter( rect().center() );
QSize hSize( handleSize() - QSize( 6, 6 ) );
QSize sSize( handleSize() - QSize( 2, 2 ) );
if ( gradientsTargetSize != size() )
{
/* Need to fix gradients */
gradientsTargetSize = size();
backgroundGradient.setFinalStop( 0, height() );
foregroundGradient.setFinalStop( 0, height() );
handleGradient.setFinalStop( 0, hSize.height() );
}
// draw a slight 3d effect on the bottom
painter.setPen( QColor( 230, 230, 230 ) );
painter.setBrush( Qt::NoBrush );
painter.drawRoundedRect( barRect.adjusted( 0, 2, 0, 0 ), barCorner, barCorner );
// draw background
painter.setPen( Qt::NoPen );
painter.setBrush( backgroundGradient );
painter.drawRoundedRect( barRect, barCorner, barCorner );
// adjusted foreground rectangle
QRect valueRect = barRect.adjusted( 1, 1, -1, 0 );
switch ( orientation() )
{
case Qt::Horizontal:
valueRect.setWidth( qMin( width(), int( sliderPos ) ) );
break;
case Qt::Vertical:
valueRect.setHeight( qMin( height(), int( sliderPos ) ) );
valueRect.moveBottom( rect().bottom() );
break;
}
if ( option.sliderPosition > minimum() && option.sliderPosition <= maximum() )
{
// draw foreground
painter.setPen( Qt::NoPen );
painter.setBrush( foregroundGradient );
painter.drawRoundedRect( valueRect, barCorner, barCorner );
}
// draw buffering overlay
if ( f_buffering < 1.0 )
{
QRect innerRect = barRect.adjusted( 1, 1,
barRect.width() * ( -1.0 + f_buffering ) - 1, 0 );
QColor overlayColor = QColor( "Orange" );
overlayColor.setAlpha( 128 );
painter.setBrush( overlayColor );
painter.drawRoundedRect( innerRect, barCorner, barCorner );
}
if ( option.state & QStyle::State_MouseOver || isAnimationRunning() )
{
/* draw chapters tickpoints */
if ( chapters && inputLength && size().width() )
{
if ( orientation() == Qt::Horizontal ) /* TODO: vertical */
{
QList<SeekPoint> points = chapters->getPoints();
painter.setPen( tickpointForeground );
painter.setBrush( Qt::NoBrush );
foreach( SeekPoint point, points )
{
int x = point.time / 1000000.0 / inputLength * size().width();
painter.drawLine( x, height(), x, height() - CHAPTERSSPOTSIZE );
}
}
}
// draw handle
if ( sliderPos != -1 )
{
const int margin = 0;
QPoint pos;
switch ( orientation() )
{
case Qt::Horizontal:
pos = QPoint( sliderPos - ( hSize.width() / 2 ), 2 );
pos.rx() = qMax( margin, pos.x() );
pos.rx() = qMin( width() - hSize.width() - margin, pos.x() );
break;
case Qt::Vertical:
pos = QPoint( 2, height() - ( sliderPos + ( hSize.height() / 2 ) ) );
pos.ry() = qMax( margin, pos.y() );
pos.ry() = qMin( height() - hSize.height() - margin, pos.y() );
break;
}
QPoint shadowPos( pos - QPoint( 2, 2 ) );
/* FIXME: precompute gradient. Anim Compatible ? */
QRadialGradient shadowGradient( shadowPos.x() + ( sSize.width() / 2 ),
shadowPos.y() + ( sSize.height() / 2 ),
qMax( sSize.width(), sSize.height() ) / 2 );
shadowGradient.setColorAt( 0.4, shadowDark );
shadowGradient.setColorAt( 1.0, shadowLight );
painter.setPen( Qt::NoPen );
painter.setOpacity( mHandleOpacity );
// draw the handle's shadow
painter.setBrush( shadowGradient );
painter.drawEllipse( shadowPos.x(), shadowPos.y() + 1, sSize.width(), sSize.height() );
// finally draw the handle
painter.setBrush( handleGradient );
painter.drawEllipse( pos.x(), pos.y(), hSize.width(), hSize.height() );
}
}
}
qreal SeekSlider::handleOpacity() const
{
return mHandleOpacity;
......@@ -552,6 +392,18 @@ void SeekSlider::setHandleOpacity(qreal opacity)
update();
}
inline int SeekSlider::handleLength()
{
if ( mHandleLength > 0 )
return mHandleLength;
/* Ask for the length of the handle to the underlying style */
QStyleOptionSlider option;
initStyleOption( &option );
mHandleLength = style()->pixelMetric( QStyle::PM_SliderLength, &option );
return mHandleLength;
}
void SeekSlider::hideHandle()
{
/* If pause is called while not running Qt will complain */
......@@ -568,6 +420,7 @@ bool SeekSlider::isAnimationRunning() const
|| hideHandleTimer->isActive();
}
/* This work is derived from Amarok's work under GPLv2+
- Mark Kretschmann
- Gábor Lehel
......
......@@ -32,6 +32,7 @@
#include <vlc_common.h>
#include "timetooltip.hpp"
#include "styles/seekstyle.hpp"
#include <QSlider>
#include <QPainter>
......@@ -44,6 +45,7 @@ class QHideEvent;
class QTimer;
class SeekPoints;
class QPropertyAnimation;
class QStyleOption;
/* Input Slider derived from QSlider */
class SeekSlider : public QSlider
......@@ -64,15 +66,14 @@ protected:
virtual void leaveEvent( QEvent * );
virtual void hideEvent( QHideEvent * );
virtual void paintEvent( QPaintEvent* event );
virtual bool eventFilter( QObject *obj, QEvent *event );
QSize handleSize() const;
virtual QSize sizeHint() const;
bool isAnimationRunning() const;
qreal handleOpacity() const;
void setHandleOpacity( qreal opacity );
int handleLength();
private:
bool isSliding; /* Whether we are currently sliding by user action */
......@@ -84,6 +85,7 @@ private:
float f_buffering;
SeekPoints* chapters;
bool b_classic;
int mHandleLength;
/* Colors & gradients */
QSize gradientsTargetSize;
......@@ -109,9 +111,11 @@ private slots:
signals:
void sliderDragged( float );
};
friend class SeekStyle;
};
/* Sound Slider inherited directly from QAbstractSlider */
class QPaintEvent;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment