mirror of
https://github.com/pyapp-kit/superqt.git
synced 2026-01-04 19:31:24 +01:00
breakout styles
This commit is contained in:
@@ -111,7 +111,8 @@ These screenshots show `QRangeSlider` (multiple handles) next to the native `QSl
|
|||||||
style of `QSlider` – with or without tick marks. When styles have been applied
|
style of `QSlider` – with or without tick marks. When styles have been applied
|
||||||
using [Qt Style Sheets](https://doc.qt.io/qt-5/stylesheet-reference.html), then
|
using [Qt Style Sheets](https://doc.qt.io/qt-5/stylesheet-reference.html), then
|
||||||
`QRangeSlider` will inherit any styles applied to `QSlider` (since it inherits
|
`QRangeSlider` will inherit any styles applied to `QSlider` (since it inherits
|
||||||
from QSlider).
|
from QSlider). If you'd like to style `QRangeSlider` differently than `QSlider`,
|
||||||
|
then you can also target it directly in your style sheet.
|
||||||
|
|
||||||
> The code for these example widgets is [here](examples/demo_widget.py)
|
> The code for these example widgets is [here](examples/demo_widget.py)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import re
|
|
||||||
from typing import List, Sequence, Tuple
|
from typing import List, Sequence, Tuple
|
||||||
|
|
||||||
|
from ._style import RangeSliderStyle, update_styles_from_stylesheet
|
||||||
from .qtcompat import QtGui
|
from .qtcompat import QtGui
|
||||||
from .qtcompat.QtCore import QEvent, QPoint, QPointF, QRect, QRectF, Qt, Signal
|
from .qtcompat.QtCore import QEvent, QPoint, QPointF, QRect, QRectF, Qt, Signal
|
||||||
from .qtcompat.QtWidgets import (
|
from .qtcompat.QtWidgets import (
|
||||||
@@ -12,7 +12,7 @@ from .qtcompat.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
Control = Tuple[str, int]
|
ControlType = Tuple[str, int]
|
||||||
|
|
||||||
|
|
||||||
class QRangeSlider(QSlider):
|
class QRangeSlider(QSlider):
|
||||||
@@ -23,9 +23,11 @@ class QRangeSlider(QSlider):
|
|||||||
# The value is the positions of *all* handles.
|
# The value is the positions of *all* handles.
|
||||||
sliderMoved = Signal(tuple)
|
sliderMoved = Signal(tuple)
|
||||||
|
|
||||||
NULL_CTRL = ("None", -1)
|
_NULL_CTRL = ("None", -1)
|
||||||
|
|
||||||
def __init__(self, orientation=Qt.Horizontal, parent: QWidget = None):
|
def __init__(
|
||||||
|
self, orientation: Qt.Orientation = Qt.Horizontal, parent: QWidget = None
|
||||||
|
):
|
||||||
super().__init__(orientation, parent)
|
super().__init__(orientation, parent)
|
||||||
|
|
||||||
# list of values
|
# list of values
|
||||||
@@ -33,29 +35,23 @@ class QRangeSlider(QSlider):
|
|||||||
# list of current positions of each handle. Must be same length as _value
|
# list of current positions of each handle. Must be same length as _value
|
||||||
# If tracking is enabled (the default) this will be identical to _value
|
# If tracking is enabled (the default) this will be identical to _value
|
||||||
self._position: List[int] = [20, 80]
|
self._position: List[int] = [20, 80]
|
||||||
self._pressedControl: Control = self.NULL_CTRL
|
self._pressedControl: ControlType = self._NULL_CTRL
|
||||||
self._hoverControl: Control = self.NULL_CTRL
|
self._hoverControl: ControlType = self._NULL_CTRL
|
||||||
|
|
||||||
# whether bar length is constant when dragging the bar
|
# whether bar length is constant when dragging the bar
|
||||||
# if False, the bar can shorten when dragged beyond min/max
|
# if False, the bar can shorten when dragged beyond min/max
|
||||||
self._bar_is_stiff = True
|
self._bar_is_stiff = True
|
||||||
# whether clicking on the bar moves all handles, or just the nearest handle.
|
# whether clicking on the bar moves all handles, or just the nearest handle.
|
||||||
self._bar_moves_all = True
|
self._bar_moves_all = True
|
||||||
|
self._should_draw_bar = True
|
||||||
|
|
||||||
# for keyboard nav
|
# for keyboard nav
|
||||||
self._repeatMultiplier = 1 # TODO
|
self._repeatMultiplier = 1 # TODO
|
||||||
# for wheel nav
|
# for wheel nav
|
||||||
self._offset_accum = 0
|
self._tick__accum = 0
|
||||||
|
|
||||||
# color
|
# color
|
||||||
self._bar_color_active = QtGui.QColor("#3B88FD")
|
self._style = RangeSliderStyle()
|
||||||
self._bar_color_inactive = QtGui.QColor("#8F8F8F")
|
|
||||||
self._bar_color_disabled = QtGui.QColor("#BBBBBB")
|
|
||||||
self._bar_height = 3
|
|
||||||
self._bar_width = 3
|
|
||||||
|
|
||||||
# try to parse QSS for the bar height and color
|
|
||||||
self._parse_stylesheet = True
|
|
||||||
|
|
||||||
def value(self) -> Tuple[int, ...]:
|
def value(self) -> Tuple[int, ...]:
|
||||||
return tuple(self._value)
|
return tuple(self._value)
|
||||||
@@ -103,6 +99,19 @@ class QRangeSlider(QSlider):
|
|||||||
opt.sliderPosition = 0
|
opt.sliderPosition = 0
|
||||||
return opt
|
return opt
|
||||||
|
|
||||||
|
def _drawBar(self, painter: QStylePainter, opt: QStyleOptionSlider):
|
||||||
|
|
||||||
|
brush = self._style.brush(self.palette().currentColorGroup())
|
||||||
|
|
||||||
|
r_bar = self._barRect(opt)
|
||||||
|
if isinstance(brush, QtGui.QGradient):
|
||||||
|
brush.setStart(r_bar.topLeft())
|
||||||
|
brush.setFinalStop(r_bar.bottomRight())
|
||||||
|
|
||||||
|
painter.setPen(Qt.NoPen)
|
||||||
|
painter.setBrush(brush)
|
||||||
|
painter.drawRect(r_bar)
|
||||||
|
|
||||||
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
|
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
|
||||||
"""Paint the slider."""
|
"""Paint the slider."""
|
||||||
# initialize painter and options
|
# initialize painter and options
|
||||||
@@ -113,21 +122,8 @@ class QRangeSlider(QSlider):
|
|||||||
opt.subControls = QStyle.SC_SliderGroove | QStyle.SC_SliderTickmarks
|
opt.subControls = QStyle.SC_SliderGroove | QStyle.SC_SliderTickmarks
|
||||||
painter.drawComplexControl(QStyle.CC_Slider, opt)
|
painter.drawComplexControl(QStyle.CC_Slider, opt)
|
||||||
|
|
||||||
# draw bar
|
if self._should_draw_bar:
|
||||||
r_bar = self._barRect(opt)
|
self._drawBar(painter, opt)
|
||||||
if self._bar_color_active is not None: # FIXME
|
|
||||||
if self.palette().currentColorGroup() == QtGui.QPalette.Active:
|
|
||||||
color = self._bar_color_active
|
|
||||||
elif self.palette().currentColorGroup() == QtGui.QPalette.Inactive:
|
|
||||||
color = self._bar_color_inactive
|
|
||||||
else:
|
|
||||||
color = self._bar_color_disabled
|
|
||||||
else:
|
|
||||||
color = self.palette().color(QtGui.QPalette.Highlight)
|
|
||||||
if isinstance(color, QtGui.QGradient):
|
|
||||||
color.setStart(r_bar.topLeft())
|
|
||||||
color.setFinalStop(r_bar.bottomRight())
|
|
||||||
painter.fillRect(r_bar, color)
|
|
||||||
|
|
||||||
# draw handles
|
# draw handles
|
||||||
opt.subControls = QStyle.SC_SliderHandle
|
opt.subControls = QStyle.SC_SliderHandle
|
||||||
@@ -151,7 +147,7 @@ class QRangeSlider(QSlider):
|
|||||||
|
|
||||||
def event(self, ev: QEvent) -> bool:
|
def event(self, ev: QEvent) -> bool:
|
||||||
if ev.type() == QEvent.StyleChange:
|
if ev.type() == QEvent.StyleChange:
|
||||||
self._traverseStyleSheet()
|
update_styles_from_stylesheet(self)
|
||||||
if ev.type() in (QEvent.HoverEnter, QEvent.HoverLeave, QEvent.HoverMove):
|
if ev.type() in (QEvent.HoverEnter, QEvent.HoverLeave, QEvent.HoverMove):
|
||||||
old_hover = self._hoverControl
|
old_hover = self._hoverControl
|
||||||
self._hoverControl = self._getControlAtPos(ev.pos())
|
self._hoverControl = self._getControlAtPos(ev.pos())
|
||||||
@@ -229,7 +225,7 @@ class QRangeSlider(QSlider):
|
|||||||
return
|
return
|
||||||
ev.accept()
|
ev.accept()
|
||||||
old_pressed = self._pressedControl
|
old_pressed = self._pressedControl
|
||||||
self._pressedControl = self.NULL_CTRL
|
self._pressedControl = self._NULL_CTRL
|
||||||
self.setRepeatAction(QSlider.SliderNoAction)
|
self.setRepeatAction(QSlider.SliderNoAction)
|
||||||
if old_pressed[0] in ("handle", "bar"):
|
if old_pressed[0] in ("handle", "bar"):
|
||||||
self.setSliderDown(False)
|
self.setSliderDown(False)
|
||||||
@@ -274,21 +270,17 @@ class QRangeSlider(QSlider):
|
|||||||
r_bar = QRectF(r_groove)
|
r_bar = QRectF(r_groove)
|
||||||
hdl_low, *_, hdl_high = self._handleRects(opt)
|
hdl_low, *_, hdl_high = self._handleRects(opt)
|
||||||
|
|
||||||
tp = self.tickPosition()
|
thickness = self._style.thickness(opt.orientation)
|
||||||
if tp & QSlider.TicksAbove:
|
tick_offset = self._style.offset(self.tickPosition())
|
||||||
displace = 4
|
|
||||||
elif tp & QSlider.TicksBelow:
|
|
||||||
displace = -4
|
|
||||||
else:
|
|
||||||
displace = 0
|
|
||||||
if opt.orientation == Qt.Horizontal:
|
if opt.orientation == Qt.Horizontal:
|
||||||
r_bar.setTop(r_bar.center().y() - self._bar_height / 2 + displace)
|
r_bar.setTop(r_bar.center().y() - thickness / 2 + tick_offset)
|
||||||
r_bar.setHeight(self._bar_height)
|
r_bar.setHeight(thickness)
|
||||||
r_bar.setLeft(hdl_low.center().x())
|
r_bar.setLeft(hdl_low.center().x())
|
||||||
r_bar.setRight(hdl_high.center().x())
|
r_bar.setRight(hdl_high.center().x())
|
||||||
else:
|
else:
|
||||||
r_bar.setLeft(r_bar.center().x() - self._bar_width / 2 + displace)
|
r_bar.setLeft(r_bar.center().x() - thickness / 2 + tick_offset)
|
||||||
r_bar.setWidth(self._bar_width)
|
r_bar.setWidth(thickness)
|
||||||
r_bar.setBottom(hdl_low.center().y())
|
r_bar.setBottom(hdl_low.center().y())
|
||||||
r_bar.setTop(hdl_high.center().y())
|
r_bar.setTop(hdl_high.center().y())
|
||||||
|
|
||||||
@@ -296,7 +288,7 @@ class QRangeSlider(QSlider):
|
|||||||
|
|
||||||
def _getControlAtPos(
|
def _getControlAtPos(
|
||||||
self, pos: QPoint, opt: QStyleOptionSlider = None, closest_handle=False
|
self, pos: QPoint, opt: QStyleOptionSlider = None, closest_handle=False
|
||||||
) -> Control:
|
) -> ControlType:
|
||||||
"""Update self._pressedControl based on ev.pos()."""
|
"""Update self._pressedControl based on ev.pos()."""
|
||||||
if not opt:
|
if not opt:
|
||||||
opt = self._getStyleOption()
|
opt = self._getStyleOption()
|
||||||
@@ -333,7 +325,7 @@ class QRangeSlider(QSlider):
|
|||||||
elif closest_handle:
|
elif closest_handle:
|
||||||
return ("handle", hdl_idx)
|
return ("handle", hdl_idx)
|
||||||
|
|
||||||
return self.NULL_CTRL
|
return self._NULL_CTRL
|
||||||
|
|
||||||
def _handle_offset(self, opt: QStyleOptionSlider) -> QPoint:
|
def _handle_offset(self, opt: QStyleOptionSlider) -> QPoint:
|
||||||
# to take half of the slider off for the setSliderPosition call we use the
|
# to take half of the slider off for the setSliderPosition call we use the
|
||||||
@@ -454,98 +446,7 @@ class QRangeSlider(QSlider):
|
|||||||
def keyPressEvent(self, ev: QtGui.QKeyEvent) -> None:
|
def keyPressEvent(self, ev: QtGui.QKeyEvent) -> None:
|
||||||
return # TODO
|
return # TODO
|
||||||
|
|
||||||
def _traverseStyleSheet(self):
|
|
||||||
|
|
||||||
qss = self.styleSheet()
|
|
||||||
p = self
|
|
||||||
while p.parent():
|
|
||||||
qss = p.styleSheet() + qss
|
|
||||||
p = p.parent()
|
|
||||||
qss = QApplication.instance().styleSheet() + qss
|
|
||||||
|
|
||||||
# Find bar color
|
|
||||||
# TODO: optional horizontal or vertical
|
|
||||||
match = re.search(r"Slider::sub-page:?([^{\s]*)?\s*{\s*([^}]+)}", qss, re.S)
|
|
||||||
if match:
|
|
||||||
orientation, content = match.groups()
|
|
||||||
for line in reversed(content.splitlines()):
|
|
||||||
bgrd = re.search(r"background(-color)?:\s*([^;]+)", line)
|
|
||||||
if bgrd:
|
|
||||||
self._bar_color_active = parse_color(bgrd.groups()[-1])
|
|
||||||
# TODO: bar color inactive?
|
|
||||||
# TODO: bar color disabled?
|
|
||||||
class_name = type(self).__name__
|
|
||||||
_ss = f"\n{class_name}::sub-page:{orientation}{{background: none}}"
|
|
||||||
# TODO: block double event
|
|
||||||
self.setStyleSheet(qss + _ss)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Find bar height/width
|
|
||||||
for orient, dim in (("horizontal", "height"), ("vertical", "width")):
|
|
||||||
match = re.search(rf"Slider::groove:{orient}\s*{{\s*([^}}]+)}}", qss, re.S)
|
|
||||||
if match:
|
|
||||||
for line in reversed(match.groups()[0].splitlines()):
|
|
||||||
bgrd = re.search(rf"{dim}\s*:\s*(\d+)", line)
|
|
||||||
if bgrd:
|
|
||||||
setattr(self, f"_bar_{dim}", float(bgrd.groups()[-1]))
|
|
||||||
|
|
||||||
|
|
||||||
def _bound(min_: int, max_: int, value: int) -> int:
|
def _bound(min_: int, max_: int, value: int) -> int:
|
||||||
"""Return value bounded by min_ and max_."""
|
"""Return value bounded by min_ and max_."""
|
||||||
return max(min_, min(max_, value))
|
return max(min_, min(max_, value))
|
||||||
|
|
||||||
|
|
||||||
# Styles Parsing ##############
|
|
||||||
|
|
||||||
qlineargrad_pattern = re.compile(
|
|
||||||
r"""
|
|
||||||
qlineargradient\(
|
|
||||||
x1:\s*(?P<x1>\d*\.?\d+),\s*
|
|
||||||
y1:\s*(?P<y1>\d*\.?\d+),\s*
|
|
||||||
x2:\s*(?P<x2>\d*\.?\d+),\s*
|
|
||||||
y2:\s*(?P<y2>\d*\.?\d+),\s*
|
|
||||||
stop:0\s*(?P<stop0>\S+),.*
|
|
||||||
stop:1\s*(?P<stop1>\S+)
|
|
||||||
\)""",
|
|
||||||
re.X,
|
|
||||||
)
|
|
||||||
|
|
||||||
qradial_pattern = re.compile(
|
|
||||||
r"""
|
|
||||||
qradialgradient\(
|
|
||||||
cx:\s*(?P<cx>\d*\.?\d+),\s*
|
|
||||||
cy:\s*(?P<cy>\d*\.?\d+),\s*
|
|
||||||
radius:\s*(?P<radius>\d*\.?\d+),\s*
|
|
||||||
fx:\s*(?P<fx>\d*\.?\d+),\s*
|
|
||||||
fy:\s*(?P<fy>\d*\.?\d+),\s*
|
|
||||||
stop:0\s*(?P<stop0>\S+),.*
|
|
||||||
stop:1\s*(?P<stop1>\S+)
|
|
||||||
\)""",
|
|
||||||
re.X,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_color(color: str):
|
|
||||||
qc = QtGui.QColor(color)
|
|
||||||
if qc.isValid():
|
|
||||||
return qc
|
|
||||||
|
|
||||||
# try linear gradient:
|
|
||||||
match = qlineargrad_pattern.match(color)
|
|
||||||
if match:
|
|
||||||
grad = QtGui.QLinearGradient(*[float(i) for i in match.groups()[:4]])
|
|
||||||
grad.setColorAt(0, QtGui.QColor(match.groupdict()["stop0"]))
|
|
||||||
grad.setColorAt(1, QtGui.QColor(match.groupdict()["stop1"]))
|
|
||||||
return grad
|
|
||||||
|
|
||||||
# try linear gradient:
|
|
||||||
match = qradial_pattern.match(color)
|
|
||||||
print("match", match.groupdict())
|
|
||||||
if match:
|
|
||||||
grad = QtGui.QRadialGradient(*[float(i) for i in match.groups()[:5]])
|
|
||||||
grad.setColorAt(0, QtGui.QColor(match.groupdict()["stop0"]))
|
|
||||||
grad.setColorAt(1, QtGui.QColor(match.groupdict()["stop1"]))
|
|
||||||
return grad
|
|
||||||
|
|
||||||
# fallback to dark gray
|
|
||||||
return QtGui.QColor("#333")
|
|
||||||
|
|||||||
173
qtrangeslider/_style.py
Normal file
173
qtrangeslider/_style.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
import platform
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass, replace
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from .qtcompat.QtCore import Qt
|
||||||
|
from .qtcompat.QtGui import (
|
||||||
|
QColor,
|
||||||
|
QGradient,
|
||||||
|
QLinearGradient,
|
||||||
|
QPalette,
|
||||||
|
QRadialGradient,
|
||||||
|
)
|
||||||
|
from .qtcompat.QtWidgets import QApplication, QSlider
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RangeSliderStyle:
|
||||||
|
brush_active: str = None
|
||||||
|
brush_inactive: str = None
|
||||||
|
brush_disabled: str = None
|
||||||
|
pen_active: str = None
|
||||||
|
pen_inactive: str = None
|
||||||
|
pen_disabled: str = None
|
||||||
|
vertical_thickness: float = None
|
||||||
|
horizontal_thickness: float = None
|
||||||
|
tick_offest: float = None
|
||||||
|
|
||||||
|
def brush(self, cg: QPalette.ColorGroup) -> Union[QGradient, QColor]:
|
||||||
|
attr = {
|
||||||
|
QPalette.Active: "brush_active", # 0
|
||||||
|
QPalette.Disabled: "brush_disabled", # 1
|
||||||
|
QPalette.Inactive: "brush_inactive", # 2
|
||||||
|
}[cg]
|
||||||
|
val = getattr(self, attr) or getattr(SYSTEM_STYLE, attr)
|
||||||
|
if isinstance(val, str):
|
||||||
|
val = QColor(val)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def offset(self, tp: QSlider.TickPosition) -> int:
|
||||||
|
val = self.tick_offest or SYSTEM_STYLE.tick_offest
|
||||||
|
if tp & QSlider.TicksAbove:
|
||||||
|
return val
|
||||||
|
elif tp & QSlider.TicksBelow:
|
||||||
|
return -val
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def thickness(self, orientation: Qt.Orientation) -> float:
|
||||||
|
if orientation == Qt.Horizontal:
|
||||||
|
return self.horizontal_thickness or SYSTEM_STYLE.horizontal_thickness
|
||||||
|
else:
|
||||||
|
return self.vertical_thickness or SYSTEM_STYLE.vertical_thickness
|
||||||
|
|
||||||
|
|
||||||
|
# ########## System-specific default styles ############
|
||||||
|
|
||||||
|
CATALINA_STYLE = RangeSliderStyle(
|
||||||
|
brush_active="#3B88FD",
|
||||||
|
brush_inactive="#8F8F8F",
|
||||||
|
brush_disabled="#BBBBBB",
|
||||||
|
horizontal_thickness=3,
|
||||||
|
vertical_thickness=3,
|
||||||
|
tick_offest=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
BIG_SUR_STYLE = replace(CATALINA_STYLE)
|
||||||
|
|
||||||
|
SYSTEM = platform.system()
|
||||||
|
if SYSTEM == "Darwin":
|
||||||
|
if int(platform.mac_ver()[0].split(".", maxsplit=1)[0]) >= 11:
|
||||||
|
SYSTEM_STYLE = BIG_SUR_STYLE
|
||||||
|
else:
|
||||||
|
SYSTEM_STYLE = CATALINA_STYLE
|
||||||
|
elif SYSTEM == "Windows":
|
||||||
|
SYSTEM_STYLE = RangeSliderStyle()
|
||||||
|
elif SYSTEM == "Linux":
|
||||||
|
LINUX = True
|
||||||
|
SYSTEM_STYLE = RangeSliderStyle()
|
||||||
|
else:
|
||||||
|
SYSTEM_STYLE = RangeSliderStyle()
|
||||||
|
|
||||||
|
|
||||||
|
# ################ Stylesheet parsing logic ########################
|
||||||
|
|
||||||
|
qlineargrad_pattern = re.compile(
|
||||||
|
r"""
|
||||||
|
qlineargradient\(
|
||||||
|
x1:\s*(?P<x1>\d*\.?\d+),\s*
|
||||||
|
y1:\s*(?P<y1>\d*\.?\d+),\s*
|
||||||
|
x2:\s*(?P<x2>\d*\.?\d+),\s*
|
||||||
|
y2:\s*(?P<y2>\d*\.?\d+),\s*
|
||||||
|
stop:0\s*(?P<stop0>\S+),.*
|
||||||
|
stop:1\s*(?P<stop1>\S+)
|
||||||
|
\)""",
|
||||||
|
re.X,
|
||||||
|
)
|
||||||
|
|
||||||
|
qradial_pattern = re.compile(
|
||||||
|
r"""
|
||||||
|
qradialgradient\(
|
||||||
|
cx:\s*(?P<cx>\d*\.?\d+),\s*
|
||||||
|
cy:\s*(?P<cy>\d*\.?\d+),\s*
|
||||||
|
radius:\s*(?P<radius>\d*\.?\d+),\s*
|
||||||
|
fx:\s*(?P<fx>\d*\.?\d+),\s*
|
||||||
|
fy:\s*(?P<fy>\d*\.?\d+),\s*
|
||||||
|
stop:0\s*(?P<stop0>\S+),.*
|
||||||
|
stop:1\s*(?P<stop1>\S+)
|
||||||
|
\)""",
|
||||||
|
re.X,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_color(color: str) -> Union[str, QGradient]:
|
||||||
|
qc = QColor(color)
|
||||||
|
if qc.isValid():
|
||||||
|
return qc
|
||||||
|
|
||||||
|
# try linear gradient:
|
||||||
|
match = qlineargrad_pattern.match(color)
|
||||||
|
if match:
|
||||||
|
grad = QLinearGradient(*[float(i) for i in match.groups()[:4]])
|
||||||
|
grad.setColorAt(0, QColor(match.groupdict()["stop0"]))
|
||||||
|
grad.setColorAt(1, QColor(match.groupdict()["stop1"]))
|
||||||
|
return grad
|
||||||
|
|
||||||
|
# try linear gradient:
|
||||||
|
match = qradial_pattern.match(color)
|
||||||
|
print("match", match.groupdict())
|
||||||
|
if match:
|
||||||
|
grad = QRadialGradient(*[float(i) for i in match.groups()[:5]])
|
||||||
|
grad.setColorAt(0, QColor(match.groupdict()["stop0"]))
|
||||||
|
grad.setColorAt(1, QColor(match.groupdict()["stop1"]))
|
||||||
|
return grad
|
||||||
|
|
||||||
|
# fallback to dark gray
|
||||||
|
return "#333"
|
||||||
|
|
||||||
|
|
||||||
|
def update_styles_from_stylesheet(obj):
|
||||||
|
qss = obj.styleSheet()
|
||||||
|
p = obj
|
||||||
|
while p.parent():
|
||||||
|
qss = p.styleSheet() + qss
|
||||||
|
p = p.parent()
|
||||||
|
qss = QApplication.instance().styleSheet() + qss
|
||||||
|
|
||||||
|
# Find bar color
|
||||||
|
# TODO: optional horizontal or vertical
|
||||||
|
match = re.search(r"Slider::sub-page:?([^{\s]*)?\s*{\s*([^}]+)}", qss, re.S)
|
||||||
|
if match:
|
||||||
|
orientation, content = match.groups()
|
||||||
|
for line in reversed(content.splitlines()):
|
||||||
|
bgrd = re.search(r"background(-color)?:\s*([^;]+)", line)
|
||||||
|
if bgrd:
|
||||||
|
obj._style.brush_active = parse_color(bgrd.groups()[-1])
|
||||||
|
# TODO: bar color inactive?
|
||||||
|
# TODO: bar color disabled?
|
||||||
|
class_name = type(obj).__name__
|
||||||
|
_ss = f"\n{class_name}::sub-page:{orientation}{{background: none}}"
|
||||||
|
# TODO: block double event
|
||||||
|
obj.setStyleSheet(qss + _ss)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Find bar height/width
|
||||||
|
for orient, dim in (("horizontal", "height"), ("vertical", "width")):
|
||||||
|
match = re.search(rf"Slider::groove:{orient}\s*{{\s*([^}}]+)}}", qss, re.S)
|
||||||
|
if match:
|
||||||
|
for line in reversed(match.groups()[0].splitlines()):
|
||||||
|
bgrd = re.search(rf"{dim}\s*:\s*(\d+)", line)
|
||||||
|
if bgrd:
|
||||||
|
thickness = float(bgrd.groups()[-1])
|
||||||
|
setattr(obj._style, f"{orient}_thickness", thickness)
|
||||||
Reference in New Issue
Block a user