Zeus
Zeus

Reputation: 3890

QT QSlider stylesheet causes the rounded border to disappear

I set the QSlider in QT using the qss style, which I set at the beginning

QSlider::groove: horizontal {
    background: rgb(217,221,227);
    height: 14px;
    border-radius: 7px;
}

QSlider::handle: horizontal {
    background: #0078D7;
    border: 2px solid white;
    width: 10px;
    height: 10px;
    border-radius: 7px;
}

This all worked fine, but I wanted to add color to the sliding process, so I added it

QSlider::sub-page:horizontal {
    background: rgb(56, 121, 216);
    height: 8px;
    border-radius: 7px;
}
QSlider::add-page:horizontal {
    background: rgb(217,221,227);
    height: 8px;
    border-radius: 7px;
}

But this causes the rounded border to disappear at the start and end positions, how do I display the rounded border properly in this case?

Upvotes: 1

Views: 79

Answers (1)

musicamante
musicamante

Reputation: 48509

I'm not 100% sure, but I'm afraid that this is not immediately possible by using QSS only.

The problem comes from two aspects:

  • the add-page and sub-page rectangles are created based on the groove rectangle and the center of the handle (for instance, for the sub-page it's the rectangle of the groove "clipped" to the middle of the handle);
  • QSS invalidates any border radius that is smaller than half of the dimensions it's based on, resulting in drawing a plain rectangle instead;

This is how the QSS implementation draws add-page and sub-page subcontrols of QSlider (see the PseudoElement_SliderSubPage usage in the source code), and there's no way to override that behavior.

The only possible solution would be to use a carefully crafted QSlider subclass that updates its stylesheet based on the value and handle position, considering its size.
For instance, if it's near the minimum and the radius is smaller than half the slider height, we then override the ::groove background by forcing it to the color of the sub-page, while setting the sub-page background to transparent.

The OP doesn't specify the language, and I can't really write proper C++ code, so I'll provide a Python based example that should work as pseudo-code too.

class RoundedSlider(QSlider):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._updateQSS()

    def _updateQSS(self):
        halfHeight = self.height() // 2
        grooveWidth = self.width() - halfHeight * 2
        p = self.style().sliderPositionFromValue(
            self.minimum(), self.maximum(), 
            self.value(), grooveWidth
        )
        if p < halfHeight:
            qss = '''
                QSlider::groove {
                    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                        stop: 0.0 rgb(56, 121, 216), 
                        stop: 0.5 rgb(56, 121, 216), 
                        stop: 0.5 transparent
                    );
                }
                QSlider::sub-page {
                    background: transparent;
                }
                QSlider::add-page {
                    border-top-left-radius: 0;
                    border-bottom-left-radius: 0;
                }
            '''
        elif p > grooveWidth - halfHeight:
            qss = '''
                QSlider::groove {
                    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                        stop: 0.5 transparent,
                        stop: 0.5 rgb(217, 221, 227), 
                        stop: 1.0 rgb(217, 221, 227)
                    );
                }
                QSlider::add-page {
                    background: transparent;
                }
                QaSlider::sub-page {
                    border-top-right-radius: 0;
                    border-bottom-right-radius: 0;
                }
            '''
        else:
            qss = ''
        self.setStyleSheet(qss)

    def sliderChange(self, change):
        if change == self.SliderValueChange:
            self.ensurePolished()
            self._updateQSS()
        super().sliderChange(change)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self._updateQSS()


app = QApplication([])
app.setStyleSheet('''
    QSlider::groove:horizontal {
        background: rgb(217, 221, 227);
        height: 14px;
        border-radius: 7px;
    }

    QSlider::handle:horizontal {
        background: #0078D7;
        border: 2px solid white;
        width: 10px;
        height: 10px;
        border-radius: 7px;
    }

    QSlider::sub-page:horizontal {
        background: rgb(56, 121, 216);
        border-radius: 7px;
    }

    QSlider::add-page:horizontal {
        background: rgb(217, 221, 227);
        border-radius: 7px;
    }
''')

test = QWidget()
layout = QVBoxLayout(test)
for i in range(4):
    layout.addWidget(RoundedSlider(Qt.Horizontal, maximum=50, value=i * 2))
test.show()
app.exec()

Note that the above implementation is only acceptable for horizontal and not inverted sliders. If you need to work with vertical sliders or inverted values, you will have to fix it up accordingly. Moreover, note that the usage of linear gradients is to avoid small artifacts caused by antialiasing at the rounded borders.

In any case, remember that QSS should always be used with caution and awareness. If you need a specially styled widget, it's probably better to completely implement it on your own by subclassing, overriding paintEvent() and all other actions that are based on QStyle functions (eg.: sizeHint(), mouse event handlers, etc.).

Finally, be aware that your initial QSS has an important syntax error, since you added a space within QSlider::groove: horizontal and QSlider::handle: horizontal. I didn't check if the latest Qt version is able to work around that, but it's still an invalid syntax that should be avoided in any case, since spaces in style sheet selectors are used as separators for descendants.

Upvotes: 0

Related Questions