I pretty well understood how to customize a QSlider through style sheets, but I am wondering if it is possible to do the following thing:
I'd like the handle of the slider to go from blue to yellow. When set on the left, it's blue; and when you move it to the right, it will have a gradient from blue to yellow.
If it is possible through the style sheets, how? And if not, how can I implement that in the paintEvent of a subclass of a QSlider?
Actually you don't really have to do anything fancy, the stock QSlider
already has the valueChanged(int)
signal, so you can connect that to a function which mixes between the two colors based on the position and sets the style color. Here is a minimal example:
static QColor operator+(const QColor & a, const QColor & b) {
return QColor(a.red() + b.red(), a.green() + b.green(), a.blue() + b.blue());
}
static QColor operator*(const QColor & c, const qreal r) {
return QColor(c.red() * r, c.green() * r, c.blue() * r);
}
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = 0) : QWidget(parent), from(248, 181, 20), to(64, 150, 214) {
auto l = new QHBoxLayout(this);
setLayout(l);
s = new QSlider(Qt::Horizontal, this);
s->setMinimum(0);
s->setMaximum(100);
l->addWidget(s);
connect(s, &QSlider::valueChanged, this, &Widget::colorize);
colorize(s->value());
}
private:
void colorize(int v) {
int d = s->maximum() - s->minimum();
v = v - s->minimum();
qreal rv = qreal(v) / d;
QColor c = from * rv + to * (1.0 - rv);
s->setStyleSheet(QString("QSlider::handle:horizontal {background-color: %1;}").arg(c.name()));
}
QSlider * s;
QColor from, to;
};
This will work for any slider range and orientation, the code basically finds the relative handle position in range 0.0 to 1.0 and uses that to mix the from
and to
colors to set the handle color to the respective value. Oddly enough, QColor
didn't have the operators to multiply and add, which can come quite in handy.
Additionally, instead of mixing between two colors you can construct a color in HSL format, which will give you a slightly different gradient. Changing from/to
from QColor
to hues 42 and 202 respectively, you can then:
QColor c = QColor::fromHsl(205 - (205 - 42) * rv, 200, 135);
This will give you a color sweep for the hue rather than mix between two fixed colors, which may be more applicable in the context of temperature:
Note that now in the middle you get a cyan-ish color rather than "zombie" green and you get through clean green before you get to orange.
I don't believe you can do this using a simple style sheet. But that's easily doable by specializing QSlider
class and applying an appropriate stylesheet when user moves the cursor (i.e: when valueChanged is emited).
Here is a class I wrote that does the trick. It works for horizontal and vertical cursor and can be customized to use any color. It create a QImage from a QLinearGradient to store the gradient color map, then, when slider value changes, it extracts the appropriate color from the image based on slider's position and applies it through a stylesheet.
Tried to make the class generic for reusability, but it could be simplified if you don't need to customize colors and only use horizontal sliders.
gradientslider.h:
#include <QSlider>
#include <QImage>
#include <QColor>
class GradientSlider : public QSlider
{
Q_OBJECT
public:
GradientSlider( QColor from, QColor to, Qt::Orientation orientation, QWidget* parent );
private slots:
void changeColor( int );
private:
QImage gradient;
};
gradientslider.cpp:
#include "gradientslider.h"
#include <QLinearGradient>
#include <QPainter>
GradientSlider::GradientSlider( QColor from, QColor to, Qt::Orientation orientation, QWidget* parent ) :
QSlider( orientation, parent ),
gradient( QSize(100,100), QImage::Format_RGB32 )
{
// create linear gradient
QLinearGradient linearGrad( QPointF(0, 0), (orientation==Qt::Horizontal) ? QPointF(100, 0) : QPointF(0, 100) );
linearGrad.setColorAt(0, from);
linearGrad.setColorAt(1, to);
// paint gradient in a QImage:
QPainter p(&gradient);
p.fillRect(gradient.rect(), linearGrad);
connect( this, SIGNAL(valueChanged(int)), this, SLOT(changeColor(int)) );
// initialize
changeColor( value() );
}
void GradientSlider::changeColor( int pos )
{
QColor color;
if ( orientation() == Qt::Horizontal )
{
// retrieve color index based on cursor position
int posIndex = gradient.size().width() * ( pos - minimum() ) / (maximum() - minimum());
posIndex = std::min( posIndex, gradient.width() - 1 );
// pickup appropriate color
color = gradient.pixel( posIndex, gradient.size().height()/2 );
}
else
{
// retrieve color index based on cursor position
int posIndex = gradient.size().height() * ( pos - minimum() ) / (maximum() - minimum());
posIndex = std::min( posIndex, gradient.height() - 1 );
// pickup appropriate color
color = gradient.pixel( gradient.size().width()/2, posIndex );
}
// create and apply stylesheet!
// can be customized to change background and handle border!
setStyleSheet( "QSlider::handle:" + (( orientation() == Qt::Horizontal ) ? QString("horizontal"):QString("vertical")) + "{ \
border-radius: 5px; \
border: 2px solid #FFFFFF; \
width: 20px; \
margin: -5px 0; \
background: " + color.name() + "}" );
}
Now just do:
QHBoxLayout* layout = new QHBoxLayout( this );
// horizontal slider:
layout->addWidget( new GradientSlider( QColor(79,174,231), QColor(251,192,22), Qt::Horizontal, this ) );
// or, vertical slider:
layout->addWidget( new GradientSlider( QColor(79,174,231), QColor(251,192,22), Qt::Vertical, this ) );
Colors QColor(79,174,231)
(~blue) and QColor(251,192,22)
(~yellow) were picked up from the image in the original question post and can be replaced by Qt::blue
, Qt::yellow
(ending up with slightly different coloration).
This will do it:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With