c++qtqtstylesheetsqslider

How to change the color of a QSlider's handle according to its position


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 style sheets, how? And if not, how can I implement that in the paintEvent of a subclass of QSlider?


Solution

  • 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.