Skip to content

Instantly share code, notes, and snippets.

@dridk
Last active February 1, 2024 08:59
Show Gist options
  • Save dridk/ff9c2e8dbf333fb6b808cf9873555045 to your computer and use it in GitHub Desktop.
Save dridk/ff9c2e8dbf333fb6b808cf9873555045 to your computer and use it in GitHub Desktop.
The following code creates a range slider as a Qt widget with a native looks and feel
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
import sys
class RangeSlider(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.first_position = 1
self.second_position = 8
self.opt = QStyleOptionSlider()
self.opt.minimum = 0
self.opt.maximum = 10
self.setTickPosition(QSlider.TicksAbove)
self.setTickInterval(1)
self.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed, QSizePolicy.Slider)
)
def setRangeLimit(self, minimum: int, maximum: int):
self.opt.minimum = minimum
self.opt.maximum = maximum
def setRange(self, start: int, end: int):
self.first_position = start
self.second_position = end
def getRange(self):
return (self.first_position, self.second_position)
def setTickPosition(self, position: QSlider.TickPosition):
self.opt.tickPosition = position
def setTickInterval(self, ti: int):
self.opt.tickInterval = ti
def paintEvent(self, event: QPaintEvent):
painter = QPainter(self)
# Draw rule
self.opt.initFrom(self)
self.opt.rect = self.rect()
self.opt.sliderPosition = 0
self.opt.subControls = QStyle.SC_SliderGroove | QStyle.SC_SliderTickmarks
#   Draw GROOVE
self.style().drawComplexControl(QStyle.CC_Slider, self.opt, painter)
#  Draw INTERVAL
color = self.palette().color(QPalette.Highlight)
color.setAlpha(160)
painter.setBrush(QBrush(color))
painter.setPen(Qt.NoPen)
self.opt.sliderPosition = self.first_position
x_left_handle = (
self.style()
.subControlRect(QStyle.CC_Slider, self.opt, QStyle.SC_SliderHandle)
.right()
)
self.opt.sliderPosition = self.second_position
x_right_handle = (
self.style()
.subControlRect(QStyle.CC_Slider, self.opt, QStyle.SC_SliderHandle)
.left()
)
groove_rect = self.style().subControlRect(
QStyle.CC_Slider, self.opt, QStyle.SC_SliderGroove
)
selection = QRect(
x_left_handle,
groove_rect.y(),
x_right_handle - x_left_handle,
groove_rect.height(),
).adjusted(-1, 1, 1, -1)
painter.drawRect(selection)
# Draw first handle
self.opt.subControls = QStyle.SC_SliderHandle
self.opt.sliderPosition = self.first_position
self.style().drawComplexControl(QStyle.CC_Slider, self.opt, painter)
# Draw second handle
self.opt.sliderPosition = self.second_position
self.style().drawComplexControl(QStyle.CC_Slider, self.opt, painter)
def mousePressEvent(self, event: QMouseEvent):
self.opt.sliderPosition = self.first_position
self._first_sc = self.style().hitTestComplexControl(
QStyle.CC_Slider, self.opt, event.pos(), self
)
self.opt.sliderPosition = self.second_position
self._second_sc = self.style().hitTestComplexControl(
QStyle.CC_Slider, self.opt, event.pos(), self
)
def mouseMoveEvent(self, event: QMouseEvent):
distance = self.opt.maximum - self.opt.minimum
pos = self.style().sliderValueFromPosition(
0, distance, event.pos().x(), self.rect().width()
)
if self._first_sc == QStyle.SC_SliderHandle:
if pos <= self.second_position:
self.first_position = pos
self.update()
return
if self._second_sc == QStyle.SC_SliderHandle:
if pos >= self.first_position:
self.second_position = pos
self.update()
def sizeHint(self):
""" override """
SliderLength = 84
TickSpace = 5
w = SliderLength
h = self.style().pixelMetric(QStyle.PM_SliderThickness, self.opt, self)
if (
self.opt.tickPosition & QSlider.TicksAbove
or self.opt.tickPosition & QSlider.TicksBelow
):
h += TickSpace
return (
self.style()
.sizeFromContents(QStyle.CT_Slider, self.opt, QSize(w, h), self)
.expandedTo(QApplication.globalStrut())
)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = RangeSlider()
w.show()
# q = QSlider()
# q.show()
app.exec_()
@cglukas
Copy link

cglukas commented Apr 4, 2022

Thank you for this amazing tool : ) I really enjoy using it! I noticed, that if you set a positive value for opt.minimum the right slider won't be able to get to the maximum value anymore. It seems that the distance in the mouseMoveEvent is not necessary. To fix this 'bug' I just used:

pos = self.style().sliderValueFromPosition(
    self.opt.minimum, self.opt.maximum, event.pos().x(), self.rect().width()
) 

@dridk
Copy link
Author

dridk commented Apr 4, 2022

Hi ! Thanks !
You can suggest an update of the full code here .

@JotaRata
Copy link

JotaRata commented Feb 1, 2024

Does it support stylesheets?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment