Files

307 lines
9.2 KiB
Python
Raw Permalink Normal View History

2025-08-14 18:45:16 +08:00
# coding:utf-8
from enum import Enum
from PySide6.QtCore import Qt, QSize, QRectF, QPoint
from PySide6.QtGui import QPainter, QPainterPath, QColor
from PySide6.QtWidgets import (QSpinBox, QDoubleSpinBox, QToolButton, QHBoxLayout,
QDateEdit, QDateTimeEdit, QTimeEdit, QVBoxLayout, QApplication)
from ...common.style_sheet import FluentStyleSheet, themeColor, isDarkTheme
from ...common.icon import FluentIconBase, Theme, getIconColor
from ...common.font import setFont
from ...common.color import FluentSystemColor, autoFallbackThemeColor
from .button import TransparentToolButton
from .line_edit import LineEditMenu
from .flyout import Flyout, FlyoutViewBase, FlyoutAnimationType
class SpinIcon(FluentIconBase, Enum):
""" Spin icon """
UP = "Up"
DOWN = "Down"
def path(self, theme=Theme.AUTO):
return f':/qfluentwidgets/images/spin_box/{self.value}_{getIconColor(theme)}.svg'
class SpinButton(QToolButton):
def __init__(self, icon: SpinIcon, parent=None):
super().__init__(parent=parent)
self.isPressed = False
self._icon = icon
self.setFixedSize(31, 23)
self.setIconSize(QSize(10, 10))
FluentStyleSheet.SPIN_BOX.apply(self)
def mousePressEvent(self, e):
self.isPressed = True
super().mousePressEvent(e)
def mouseReleaseEvent(self, e):
self.isPressed = False
super().mouseReleaseEvent(e)
def paintEvent(self, e):
super().paintEvent(e)
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
if not self.isEnabled():
painter.setOpacity(0.36)
elif self.isPressed:
painter.setOpacity(0.7)
self._icon.render(painter, QRectF(10, 6.5, 11, 11))
class CompactSpinButton(QToolButton):
""" Compact spin button """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setFixedSize(26, 33)
self.setCursor(Qt.IBeamCursor)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
x = (self.width() - 10) / 2
s = 9
SpinIcon.UP.render(painter, QRectF(x, self.height() / 2 - s + 1, s, s))
SpinIcon.DOWN.render(painter, QRectF(x, self.height() / 2 , s, s))
class SpinFlyoutView(FlyoutViewBase):
""" Spin flyout view """
def __init__(self, parent=None):
super().__init__(parent)
self.upButton = TransparentToolButton(SpinIcon.UP, self)
self.downButton = TransparentToolButton(SpinIcon.DOWN, self)
self.vBoxLayout = QVBoxLayout(self)
self.upButton.setFixedSize(36, 36)
self.downButton.setFixedSize(36, 36)
self.upButton.setIconSize(QSize(13, 13))
self.downButton.setIconSize(QSize(13, 13))
self.vBoxLayout.setContentsMargins(6, 6, 6, 6)
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.addWidget(self.upButton)
self.vBoxLayout.addWidget(self.downButton)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
painter.setBrush(
QColor(46, 46, 46) if isDarkTheme() else QColor(249, 249, 249))
painter.setPen(
QColor(0, 0, 0, 51) if isDarkTheme() else QColor(0, 0, 0, 15))
rect = self.rect().adjusted(1, 1, -1, -1)
painter.drawRoundedRect(rect, 8, 8)
class SpinBoxBase:
""" Spin box ui """
def __init__(self, parent=None):
super().__init__(parent=parent)
self._isError = False
self.lightFocusedBorderColor = QColor()
self.darkFocusedBorderColor = QColor()
self.hBoxLayout = QHBoxLayout(self)
self.setProperty('transparent', True)
FluentStyleSheet.SPIN_BOX.apply(self)
self.setButtonSymbols(QSpinBox.NoButtons)
self.setFixedHeight(33)
setFont(self)
self.setAttribute(Qt.WA_MacShowFocusRect, False)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._showContextMenu)
def isError(self):
return self._isError
def setError(self, isError: bool):
""" set the error status """
if isError == self.isError():
return
self._isError = isError
self.update()
def setReadOnly(self, isReadOnly: bool):
super().setReadOnly(isReadOnly)
self.setSymbolVisible(not isReadOnly)
def setSymbolVisible(self, isVisible: bool):
""" set whether the spin symbol is visible """
self.setProperty("symbolVisible", isVisible)
self.setStyle(QApplication.style())
def setCustomFocusedBorderColor(self, light, dark):
""" set the border color in focused status
Parameters
----------
light, dark: str | QColor | Qt.GlobalColor
border color in light/dark theme mode
"""
self.lightFocusedBorderColor = QColor(light)
self.darkFocusedBorderColor = QColor(dark)
self.update()
def focusedBorderColor(self):
if self.isError():
return FluentSystemColor.CRITICAL_FOREGROUND.color()
return autoFallbackThemeColor(self.lightFocusedBorderColor, self.darkFocusedBorderColor)
def _showContextMenu(self, pos):
menu = LineEditMenu(self.lineEdit())
menu.exec_(self.mapToGlobal(pos))
def _drawBorderBottom(self):
if not self.hasFocus():
return
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
path = QPainterPath()
w, h = self.width(), self.height()
path.addRoundedRect(QRectF(0, h-10, w, 10), 5, 5)
rectPath = QPainterPath()
rectPath.addRect(0, h-10, w, 8)
path = path.subtracted(rectPath)
painter.fillPath(path, self.focusedBorderColor())
def paintEvent(self, e):
super().paintEvent(e)
self._drawBorderBottom()
class InlineSpinBoxBase(SpinBoxBase):
""" Inline spin box base """
def __init__(self, parent=None):
super().__init__(parent)
self.upButton = SpinButton(SpinIcon.UP, self)
self.downButton = SpinButton(SpinIcon.DOWN, self)
self.hBoxLayout.setContentsMargins(0, 4, 4, 4)
self.hBoxLayout.setSpacing(5)
self.hBoxLayout.addWidget(self.upButton, 0, Qt.AlignRight)
self.hBoxLayout.addWidget(self.downButton, 0, Qt.AlignRight)
self.hBoxLayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.upButton.clicked.connect(self.stepUp)
self.downButton.clicked.connect(self.stepDown)
def setSymbolVisible(self, isVisible: bool):
super().setSymbolVisible(isVisible)
self.upButton.setVisible(isVisible)
self.downButton.setVisible(isVisible)
def setAccelerated(self, on: bool):
super().setAccelerated(on)
self.upButton.setAutoRepeat(on)
self.downButton.setAutoRepeat(on)
class CompactSpinBoxBase(SpinBoxBase):
""" Compact spin box base """
def __init__(self, parent=None):
super().__init__(parent)
self.compactSpinButton = CompactSpinButton(self)
self.spinFlyoutView = SpinFlyoutView(self)
self.spinFlyout = Flyout(self.spinFlyoutView, self, False)
self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
self.hBoxLayout.addWidget(self.compactSpinButton, 0, Qt.AlignRight)
self.hBoxLayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.compactSpinButton.clicked.connect(self._showFlyout)
self.spinFlyoutView.upButton.clicked.connect(self.stepUp)
self.spinFlyoutView.downButton.clicked.connect(self.stepDown)
self.spinFlyout.hide()
def setAccelerated(self, on: bool):
super().setAccelerated(on)
self.spinFlyoutView.upButton.setAutoRepeat(on)
self.spinFlyoutView.downButton.setAutoRepeat(on)
def focusInEvent(self, e):
super().focusInEvent(e)
self._showFlyout()
def setSymbolVisible(self, isVisible: bool):
super().setSymbolVisible(isVisible)
self.compactSpinButton.setVisible(isVisible)
def _showFlyout(self):
if self.spinFlyout.isVisible() or self.isReadOnly():
return
y = int(self.compactSpinButton.height() / 2 - 46)
pos = self.compactSpinButton.mapToGlobal(QPoint(-12, y))
self.spinFlyout.exec(pos, FlyoutAnimationType.FADE_IN)
class SpinBox(InlineSpinBoxBase, QSpinBox):
""" Spin box """
class CompactSpinBox(CompactSpinBoxBase, QSpinBox):
""" Compact spin box """
class DoubleSpinBox(InlineSpinBoxBase, QDoubleSpinBox):
""" Double spin box """
class CompactDoubleSpinBox(CompactSpinBoxBase, QDoubleSpinBox):
""" Compact double spin box """
class TimeEdit(InlineSpinBoxBase, QTimeEdit):
""" Time edit """
class CompactTimeEdit(CompactSpinBoxBase, QTimeEdit):
""" Compact time edit """
class DateTimeEdit(InlineSpinBoxBase, QDateTimeEdit):
""" Date time edit """
class CompactDateTimeEdit(CompactSpinBoxBase, QDateTimeEdit):
""" Compact date time edit """
class DateEdit(InlineSpinBoxBase, QDateEdit):
""" Date edit """
class CompactDateEdit(CompactSpinBoxBase, QDateEdit):
""" Compact date edit """