Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to change the color of a QSlider's handle according to its position?

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:

                                              enter image description here

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?

like image 860
IAmInPLS Avatar asked Oct 19 '16 13:10

IAmInPLS


2 Answers

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.

enter image description here

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:

enter image description here

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.

like image 59
dtech Avatar answered Sep 27 '22 21:09

dtech


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:

enter image description here enter image description here enter image description here

like image 45
jpo38 Avatar answered Sep 27 '22 21:09

jpo38