initial fluent-widgets ui
This commit is contained in:
315
qfluentwidgets/components/widgets/slider.py
Normal file
315
qfluentwidgets/components/widgets/slider.py
Normal file
@ -0,0 +1,315 @@
|
||||
# coding:utf-8
|
||||
from PySide6.QtCore import QSize, Qt, Signal, QPoint, QRectF, QPropertyAnimation, Property, QEasingCurve
|
||||
from PySide6.QtGui import QColor, QMouseEvent, QPainter, QPainterPath
|
||||
from PySide6.QtWidgets import QProxyStyle, QSlider, QStyle, QStyleOptionSlider, QWidget
|
||||
|
||||
from ...common.style_sheet import FluentStyleSheet, themeColor, isDarkTheme
|
||||
from ...common.color import autoFallbackThemeColor
|
||||
from ...common.overload import singledispatchmethod
|
||||
|
||||
|
||||
|
||||
class SliderHandle(QWidget):
|
||||
""" Slider handle """
|
||||
|
||||
pressed = Signal()
|
||||
released = Signal()
|
||||
|
||||
def __init__(self, parent: QSlider):
|
||||
super().__init__(parent=parent)
|
||||
self.setFixedSize(22, 22)
|
||||
self._radius = 5
|
||||
self.lightHandleColor = QColor()
|
||||
self.darkHandleColor = QColor()
|
||||
self.radiusAni = QPropertyAnimation(self, b'radius', self)
|
||||
self.radiusAni.setDuration(100)
|
||||
|
||||
@Property(int)
|
||||
def radius(self):
|
||||
return self._radius
|
||||
|
||||
@radius.setter
|
||||
def radius(self, r):
|
||||
self._radius = r
|
||||
self.update()
|
||||
|
||||
def setHandleColor(self, light, dark):
|
||||
self.lightHandleColor = QColor(light)
|
||||
self.darkHandleColor = QColor(dark)
|
||||
self.update()
|
||||
|
||||
def enterEvent(self, e):
|
||||
self._startAni(6)
|
||||
|
||||
def leaveEvent(self, e):
|
||||
self._startAni(5)
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
self._startAni(4)
|
||||
self.pressed.emit()
|
||||
|
||||
def mouseReleaseEvent(self, e):
|
||||
self._startAni(6)
|
||||
self.released.emit()
|
||||
|
||||
def _startAni(self, radius):
|
||||
self.radiusAni.stop()
|
||||
self.radiusAni.setStartValue(self.radius)
|
||||
self.radiusAni.setEndValue(radius)
|
||||
self.radiusAni.start()
|
||||
|
||||
def paintEvent(self, e):
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHints(QPainter.RenderHint.Antialiasing)
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
|
||||
# draw outer circle
|
||||
isDark = isDarkTheme()
|
||||
painter.setPen(QColor(0, 0, 0, 90 if isDark else 25))
|
||||
painter.setBrush(QColor(69, 69, 69) if isDark else Qt.GlobalColor.white)
|
||||
painter.drawEllipse(self.rect().adjusted(1, 1, -1, -1))
|
||||
|
||||
# draw innert circle
|
||||
painter.setBrush(autoFallbackThemeColor(self.lightHandleColor, self.darkHandleColor))
|
||||
painter.drawEllipse(QPoint(11, 11), self.radius, self.radius)
|
||||
|
||||
|
||||
|
||||
class Slider(QSlider):
|
||||
""" A slider can be clicked
|
||||
|
||||
Constructors
|
||||
------------
|
||||
* Slider(`parent`: QWidget = None)
|
||||
* Slider(`orient`: Qt.Orientation, `parent`: QWidget = None)
|
||||
"""
|
||||
|
||||
clicked = Signal(int)
|
||||
|
||||
@singledispatchmethod
|
||||
def __init__(self, parent: QWidget = None):
|
||||
super().__init__(parent)
|
||||
self._postInit()
|
||||
|
||||
@__init__.register
|
||||
def _(self, orientation: Qt.Orientation, parent: QWidget = None):
|
||||
super().__init__(orientation, parent=parent)
|
||||
self._postInit()
|
||||
|
||||
def _postInit(self):
|
||||
self.handle = SliderHandle(self)
|
||||
self._pressedPos = QPoint()
|
||||
self.lightGrooveColor = QColor()
|
||||
self.darkGrooveColor = QColor()
|
||||
self.setOrientation(self.orientation())
|
||||
|
||||
self.handle.pressed.connect(self.sliderPressed)
|
||||
self.handle.released.connect(self.sliderReleased)
|
||||
self.valueChanged.connect(self._adjustHandlePos)
|
||||
|
||||
def setThemeColor(self, light, dark):
|
||||
self.lightGrooveColor = QColor(light)
|
||||
self.darkGrooveColor = QColor(dark)
|
||||
self.handle.setHandleColor(light, dark)
|
||||
self.update()
|
||||
|
||||
def setOrientation(self, orientation: Qt.Orientation) -> None:
|
||||
super().setOrientation(orientation)
|
||||
if orientation == Qt.Orientation.Horizontal:
|
||||
self.setMinimumHeight(22)
|
||||
else:
|
||||
self.setMinimumWidth(22)
|
||||
|
||||
def mousePressEvent(self, e: QMouseEvent):
|
||||
self._pressedPos = e.pos()
|
||||
self.setValue(self._posToValue(e.pos()))
|
||||
self.clicked.emit(self.value())
|
||||
|
||||
def mouseMoveEvent(self, e: QMouseEvent):
|
||||
self.setValue(self._posToValue(e.pos()))
|
||||
self._pressedPos = e.pos()
|
||||
self.sliderMoved.emit(self.value())
|
||||
|
||||
@property
|
||||
def grooveLength(self):
|
||||
l = self.width() if self.orientation() == Qt.Orientation.Horizontal else self.height()
|
||||
return l - self.handle.width()
|
||||
|
||||
def _adjustHandlePos(self):
|
||||
total = max(self.maximum() - self.minimum(), 1)
|
||||
delta = int((self.value() - self.minimum()) / total * self.grooveLength)
|
||||
|
||||
if self.orientation() == Qt.Orientation.Vertical:
|
||||
self.handle.move(0, delta)
|
||||
else:
|
||||
self.handle.move(delta, 0)
|
||||
|
||||
def _posToValue(self, pos: QPoint):
|
||||
pd = self.handle.width() / 2
|
||||
gs = max(self.grooveLength, 1)
|
||||
v = pos.x() if self.orientation() == Qt.Orientation.Horizontal else pos.y()
|
||||
return int((v - pd) / gs * (self.maximum() - self.minimum()) + self.minimum())
|
||||
|
||||
def paintEvent(self, e):
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHints(QPainter.RenderHint.Antialiasing)
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
painter.setBrush(QColor(255, 255, 255, 115) if isDarkTheme() else QColor(0, 0, 0, 100))
|
||||
|
||||
if self.orientation() == Qt.Orientation.Horizontal:
|
||||
self._drawHorizonGroove(painter)
|
||||
self._drawHorizonTick(painter)
|
||||
else:
|
||||
self._drawVerticalGroove(painter)
|
||||
self._drawVerticalTick(painter)
|
||||
|
||||
def _drawHorizonTick(self, painter: QPainter):
|
||||
pass
|
||||
|
||||
def _drawVerticalTick(self, painter: QPainter):
|
||||
pass
|
||||
|
||||
def _drawHorizonGroove(self, painter: QPainter):
|
||||
w, r = self.width(), self.handle.width() / 2
|
||||
painter.drawRoundedRect(QRectF(r, r-2, w-r*2, 4), 2, 2)
|
||||
|
||||
if self.maximum() - self.minimum() == 0:
|
||||
return
|
||||
|
||||
painter.setBrush(autoFallbackThemeColor(self.lightGrooveColor, self.darkGrooveColor))
|
||||
aw = (self.value() - self.minimum()) / (self.maximum() - self.minimum()) * (w - r*2)
|
||||
painter.drawRoundedRect(QRectF(r, r-2, aw, 4), 2, 2)
|
||||
|
||||
def _drawVerticalGroove(self, painter: QPainter):
|
||||
h, r = self.height(), self.handle.width() / 2
|
||||
painter.drawRoundedRect(QRectF(r-2, r, 4, h-2*r), 2, 2)
|
||||
|
||||
if self.maximum() - self.minimum() == 0:
|
||||
return
|
||||
|
||||
painter.setBrush(autoFallbackThemeColor(self.lightGrooveColor, self.darkGrooveColor))
|
||||
ah = (self.value() - self.minimum()) / (self.maximum() - self.minimum()) * (h - r*2)
|
||||
painter.drawRoundedRect(QRectF(r-2, r, 4, ah), 2, 2)
|
||||
|
||||
def resizeEvent(self, e):
|
||||
self._adjustHandlePos()
|
||||
|
||||
|
||||
class ClickableSlider(QSlider):
|
||||
""" A slider can be clicked """
|
||||
|
||||
clicked = Signal(int)
|
||||
|
||||
def mousePressEvent(self, e: QMouseEvent):
|
||||
super().mousePressEvent(e)
|
||||
|
||||
if self.orientation() == Qt.Horizontal:
|
||||
value = int(e.pos().x() / self.width() * self.maximum())
|
||||
else:
|
||||
value = int((self.height()-e.pos().y()) /
|
||||
self.height() * self.maximum())
|
||||
|
||||
self.setValue(value)
|
||||
self.clicked.emit(self.value())
|
||||
|
||||
|
||||
|
||||
class HollowHandleStyle(QProxyStyle):
|
||||
""" Hollow handle style """
|
||||
|
||||
def __init__(self, config: dict = None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
config: dict
|
||||
style config
|
||||
"""
|
||||
super().__init__()
|
||||
self.config = {
|
||||
"groove.height": 3,
|
||||
"sub-page.color": QColor(255, 255, 255),
|
||||
"add-page.color": QColor(255, 255, 255, 64),
|
||||
"handle.color": QColor(255, 255, 255),
|
||||
"handle.ring-width": 4,
|
||||
"handle.hollow-radius": 6,
|
||||
"handle.margin": 4
|
||||
}
|
||||
config = config if config else {}
|
||||
self.config.update(config)
|
||||
|
||||
# get handle size
|
||||
w = self.config["handle.margin"]+self.config["handle.ring-width"] + \
|
||||
self.config["handle.hollow-radius"]
|
||||
self.config["handle.size"] = QSize(2*w, 2*w)
|
||||
|
||||
def subControlRect(self, cc: QStyle.ComplexControl, opt: QStyleOptionSlider, sc: QStyle.SubControl, widget: QSlider):
|
||||
""" get the rectangular area occupied by the sub control """
|
||||
if cc != self.ComplexControl.CC_Slider or widget.orientation() != Qt.Horizontal \
|
||||
or sc == self.SubControl.SC_SliderTickmarks:
|
||||
return super().subControlRect(cc, opt, sc, widget)
|
||||
|
||||
rect = widget.rect()
|
||||
|
||||
if sc == self.SubControl.SC_SliderGroove:
|
||||
h = self.config["groove.height"]
|
||||
grooveRect = QRectF(0, (rect.height()-h)//2, rect.width(), h)
|
||||
return grooveRect.toRect()
|
||||
|
||||
elif sc == self.SubControl.SC_SliderHandle:
|
||||
size = self.config["handle.size"]
|
||||
x = self.sliderPositionFromValue(
|
||||
widget.minimum(), widget.maximum(), widget.value(), rect.width())
|
||||
|
||||
# solve the situation that the handle runs out of slider
|
||||
x *= (rect.width()-size.width())/rect.width()
|
||||
sliderRect = QRectF(x, 0, size.width(), size.height())
|
||||
return sliderRect.toRect()
|
||||
|
||||
def drawComplexControl(self, cc: QStyle.ComplexControl, opt: QStyleOptionSlider, painter: QPainter, widget: QSlider):
|
||||
""" draw sub control """
|
||||
if cc != self.ComplexControl.CC_Slider or widget.orientation() != Qt.Horizontal:
|
||||
return super().drawComplexControl(cc, opt, painter, widget)
|
||||
|
||||
grooveRect = self.subControlRect(cc, opt, self.SubControl.SC_SliderGroove, widget)
|
||||
handleRect = self.subControlRect(cc, opt, self.SubControl.SC_SliderHandle, widget)
|
||||
painter.setRenderHints(QPainter.Antialiasing)
|
||||
painter.setPen(Qt.NoPen)
|
||||
|
||||
# paint groove
|
||||
painter.save()
|
||||
painter.translate(grooveRect.topLeft())
|
||||
|
||||
# paint the crossed part
|
||||
w = handleRect.x()-grooveRect.x()
|
||||
h = self.config['groove.height']
|
||||
painter.setBrush(self.config["sub-page.color"])
|
||||
painter.drawRect(0, 0, w, h)
|
||||
|
||||
# paint the uncrossed part
|
||||
x = w+self.config['handle.size'].width()
|
||||
painter.setBrush(self.config["add-page.color"])
|
||||
painter.drawRect(x, 0, grooveRect.width()-w, h)
|
||||
painter.restore()
|
||||
|
||||
# paint handle
|
||||
ringWidth = self.config["handle.ring-width"]
|
||||
hollowRadius = self.config["handle.hollow-radius"]
|
||||
radius = ringWidth + hollowRadius
|
||||
|
||||
path = QPainterPath()
|
||||
path.moveTo(0, 0)
|
||||
center = handleRect.center() + QPoint(1, 1)
|
||||
path.addEllipse(center, radius, radius)
|
||||
path.addEllipse(center, hollowRadius, hollowRadius)
|
||||
|
||||
handleColor = self.config["handle.color"] # type:QColor
|
||||
handleColor.setAlpha(255 if opt.activeSubControls !=
|
||||
self.SubControl.SC_SliderHandle else 153)
|
||||
painter.setBrush(handleColor)
|
||||
painter.drawPath(path)
|
||||
|
||||
# press handle
|
||||
if widget.isSliderDown():
|
||||
handleColor.setAlpha(255)
|
||||
painter.setBrush(handleColor)
|
||||
painter.drawEllipse(handleRect)
|
||||
Reference in New Issue
Block a user