Skip to content

Commit

Permalink
Merge pull request #194 from ChinaIceF/refactor-newbtns
Browse files Browse the repository at this point in the history
Refactor SiRadioButtons
  • Loading branch information
ChinaIceF authored Nov 21, 2024
2 parents 237223c + b9bbaaa commit e25cc40
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 8 deletions.
25 changes: 23 additions & 2 deletions examples/Gallery for siui/components/page_widgets/page_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
SiLineEditWithItemName,
SiOptionCardLinear,
SiTitledWidgetGroup,
SiWidget,
SiWidget, SiDenseVContainer,
)
from siui.components.button import (
SiFlatButton,
SiLongPressButtonRefactor,
SiProgressPushButton,
SiPushButtonRefactor,
SiToggleButtonRefactor, SiSwitchRefactor,
SiToggleButtonRefactor, SiSwitchRefactor, SiRadioButtonRefactor,
)
from siui.components.combobox import SiComboBox
from siui.components.menu import SiMenu
Expand Down Expand Up @@ -293,12 +293,33 @@ def __init__(self, *args, **kwargs):

self.refactor_switch = SiSwitchRefactor(self)

radio_button_container = SiDenseVContainer(self)
radio_button_container.setSpacing(6)

self.refactor_radio_button = SiRadioButtonRefactor(self)
self.refactor_radio_button.setText("I want to go sleep now")
self.refactor_radio_button.adjustSize()

self.refactor_radio_button2 = SiRadioButtonRefactor(self)
self.refactor_radio_button2.setText("你干嘛嗨嗨呦")
self.refactor_radio_button2.adjustSize()

self.refactor_radio_button3 = SiRadioButtonRefactor(self)
self.refactor_radio_button3.setText("唱跳 Rap 篮球")
self.refactor_radio_button3.adjustSize()

radio_button_container.addWidget(self.refactor_radio_button)
radio_button_container.addWidget(self.refactor_radio_button2)
radio_button_container.addWidget(self.refactor_radio_button3)
radio_button_container.adjustSize()

self.refactor_buttons.body().addWidget(self.refactor_pushbutton)
self.refactor_buttons.body().addWidget(self.refactor_progress_button)
self.refactor_buttons.body().addWidget(self.refactor_long_press_button)
self.refactor_buttons.body().addWidget(self.refactor_flat_button)
self.refactor_buttons.body().addWidget(self.refactor_toggle_button)
self.refactor_buttons.body().addWidget(self.refactor_switch)
self.refactor_buttons.body().addWidget(radio_button_container)
self.refactor_buttons.body().addPlaceholder(12)
self.refactor_buttons.adjustSize()

Expand Down
Binary file added examples/Gallery for siui/img/avatar2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
314 changes: 308 additions & 6 deletions siui/components/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
QPixmap,
)
from PyQt5.QtSvg import QSvgRenderer
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QPushButton, QRadioButton, QLabel
from typing_extensions import Self

from siui.core import GlobalFont, SiGlobal, createPainter
Expand Down Expand Up @@ -477,17 +477,18 @@ def paintEvent(self, event: QPaintEvent) -> None:
| QPainter.RenderHint.TextAntialiasing
| QPainter.RenderHint.Antialiasing
)
with createPainter(self, renderHints) as bufferPainter:

buffer = QPixmap(rect.size() * device_pixel_ratio)
buffer.setDevicePixelRatio(device_pixel_ratio)
buffer.fill(Qt.transparent)

with createPainter(buffer, renderHints) as bufferPainter:
self._drawBackgroundRect(bufferPainter, rect)
self._drawButtonRect(bufferPainter, rect)
self._drawHighLightRect(bufferPainter, rect)
self._drawPixmapRect(bufferPainter, icon_rect)
self._drawTextRect(bufferPainter, text_rect)

buffer = QPixmap(rect.size() * device_pixel_ratio)
buffer.setDevicePixelRatio(device_pixel_ratio)
buffer.fill(Qt.transparent)

a = self._scale_factor
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
Expand Down Expand Up @@ -945,3 +946,304 @@ def mouseReleaseEvent(self, e):
self.scale_factor_ani.setBias(0.001)
self.scale_factor_ani.setEndValue(1)
self.scale_factor_ani.start()


@dataclass
class RadioButtonStyleData(QObject):
STYLE_TYPES = ["Button"]

text_color = QColor("#D1CBD4")
description_color = QColor("#918497")

indicator_border_radius: float = 9.5
indicator_allocated_width: int = 60
indicator_hover_additional_width: int = 4
indicator_height: int = 19

avatar_width: int = 36
avatar_height: int = 36
avatar_border_radius: int = 18

highlight_idle_color: QColor = QColor("#00baadc7")
highlight_flash_color: QColor = QColor("#70baadc7")
highlight_hover_color: QColor = QColor("#40baadc7")

unchecked_indicator_color: QColor = QColor("#25222A")
unchecked_indicator_width: float = 33

checked_indicator_color: QColor = QColor("#9F89AA")
checked_indicator_width: float = 51


class SiRadioButtonRefactor(QRadioButton):
class Property:
IndicatorWidthProg = "indicatorWidthProg"
IndicatorHoverWidth = "indicatorHoverWidth"
IndicatorColor = "indicatorColor"
HighlightRectColor = "highlightRectColor"

def __init__(self, parent: T_WidgetParent = None) -> None:
super().__init__(parent)

self.style_data = RadioButtonStyleData()
self._description = ""
self._indi_hover_width = 0
self._indi_width_prog = 0
self._indi_color = self.style_data.unchecked_indicator_color
self._hl_color = self.style_data.highlight_idle_color

self.indi_width_ani = SiExpAnimationRefactor(self, self.Property.IndicatorWidthProg)
self.indi_width_ani.init(1/6, 0.015, 0, 0)

self.indi_hover_width_ani = SiExpAnimationRefactor(self, self.Property.IndicatorHoverWidth)
self.indi_hover_width_ani.init(1/4, 0.01, 0, 0)

self.indi_color_ani = SiExpAnimationRefactor(self, self.Property.IndicatorColor)
self.indi_color_ani.init(1/3, 1, self._indi_color, self._indi_color)

self.highlight_color_ani = SiExpAnimationRefactor(self, self.Property.HighlightRectColor)
self.highlight_color_ani.init(1/8, 0.1, self._hl_color, self._hl_color)

self.toggled.connect(self._onButtonToggled)

self._initStyle()

def _initStyle(self) -> None:
self.setFont(SiFont.getFont(size=13))

@pyqtProperty(float)
def indicatorWidthProg(self):
return self._indi_width_prog

@indicatorWidthProg.setter
def indicatorWidthProg(self, value: float):
self._indi_width_prog = value
self.update()

@pyqtProperty(float)
def indicatorHoverWidth(self):
return self._indi_hover_width

@indicatorHoverWidth.setter
def indicatorHoverWidth(self, value: float):
self._indi_hover_width = value
self.update()

@pyqtProperty(QColor)
def indicatorColor(self):
return self._indi_color

@indicatorColor.setter
def indicatorColor(self, value: QColor):
self._indi_color = value
self.update()

@pyqtProperty(QColor)
def highlightRectColor(self):
return self._hl_color

@highlightRectColor.setter
def highlightRectColor(self, value: QColor):
self._hl_color = value
self.update()

def _indicatorWidthInterpolation(self, p: float) -> float:
start = self.style_data.unchecked_indicator_width
end = self.style_data.checked_indicator_width
return start + (end - start) * 3 * p / (2 * p ** 2 + 1)

def _drawIndicatorPath(self, rect: QRect) -> QPainterPath:
alloc_width = self.style_data.indicator_allocated_width
radius = self.style_data.indicator_border_radius
width = (self._indicatorWidthInterpolation(self._indi_width_prog) +
self._indi_hover_width * (1 - self._indi_width_prog * 0.6))
path = QPainterPath()
path.addRoundedRect(QRectF(alloc_width - width, rect.y(), width, rect.height()), radius, radius)
return path

def _drawIndicatorRect(self, painter: QPainter, rect: QRect) -> None:
painter.setBrush(self._indi_color)
painter.drawPath(self._drawIndicatorPath(rect))

def _drawHighlightRect(self, painter: QPainter, rect: QRect) -> None:
painter.setCompositionMode(QPainter.CompositionMode_Plus)
painter.setBrush(self._hl_color)
painter.drawPath(self._drawIndicatorPath(rect))
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)

def _drawIndicatorInnerPath(self, rect: QRect) -> QPainterPath:
path = QPainterPath()
path.addRoundedRect(QRectF(28.5, 8, self.style_data.unchecked_indicator_width - 6, rect.height() - 8), 6, 6)
return path

def _drawIndicatorInnerRect(self, painter: QPainter, rect: QRect) -> None:
if self.isChecked():
painter.setBrush(self.style_data.unchecked_indicator_color)
painter.drawPath(self._drawIndicatorInnerPath(rect))

def _drawNameTextRect(self, painter: QPainter, rect: QRect) -> None:
painter.setPen(self.style_data.text_color)
painter.setFont(self.font())
painter.drawText(rect, Qt.AlignVCenter | Qt.AlignLeft, self.text())
painter.setPen(Qt.NoPen)

def _onButtonToggled(self) -> None:
if self.isChecked():
self.indi_width_ani.setEndValue(1)
self.indi_color_ani.setEndValue(self.style_data.checked_indicator_color)
self.indi_color_ani.setCurrentValue(self.style_data.checked_indicator_color)
self.setProperty(self.Property.IndicatorColor, self.style_data.checked_indicator_color)
else:
self.indi_width_ani.setEndValue(0)
self.indi_width_ani.setCurrentValue(0.5)
self.indi_color_ani.setEndValue(self.style_data.unchecked_indicator_color)

self.indi_width_ani.start()
self.indi_color_ani.start()

def sizeHint(self) -> QSize:
return QSize(super().sizeHint().width() + self.style_data.indicator_allocated_width, 24)

def paintEvent(self, a0) -> None:
rect = self.rect()
indi_rect = QRect(0, 4, self.style_data.indicator_allocated_width, self.style_data.indicator_height)
text_rect = QRect(indi_rect.width() + 22, 0, rect.width() - indi_rect.width() - 22, 26)

renderHints = (
QPainter.RenderHint.SmoothPixmapTransform
| QPainter.RenderHint.TextAntialiasing
| QPainter.RenderHint.Antialiasing
)

with createPainter(self, renderHints) as painter:
self._drawIndicatorRect(painter, indi_rect)
self._drawHighlightRect(painter, indi_rect)
self._drawIndicatorInnerRect(painter, indi_rect)
self._drawNameTextRect(painter, text_rect)

def enterEvent(self, a0) -> None:
super().enterEvent(a0)
self.indi_hover_width_ani.setEndValue(self.style_data.indicator_hover_additional_width)
self.indi_hover_width_ani.start()
self.highlight_color_ani.setCurrentValue(self.style_data.highlight_flash_color)
self.highlight_color_ani.setEndValue(self.style_data.highlight_hover_color)
self.highlight_color_ani.start()

def leaveEvent(self, a0) -> None:
super().leaveEvent(a0)
self.indi_hover_width_ani.setEndValue(0)
self.indi_hover_width_ani.start()
self.highlight_color_ani.setEndValue(self.style_data.highlight_idle_color)
self.highlight_color_ani.start()

def mousePressEvent(self, e):
super().mousePressEvent(e)
self.click()


class SiRadioButtonWithDescription(SiRadioButtonRefactor):
def __init__(self, parent: T_WidgetParent = None) -> None:
super().__init__(parent)

self.desc_label = QLabel(self)
self.desc_label.setStyleSheet("color: #918497")
self.desc_label.setFont(SiFont.getFont(size=12))
self.desc_label.setWordWrap(True)
self.desc_label.setFixedWidth(180)
self.desc_label.move(self.style_data.indicator_allocated_width + 22, 24)

def setDescription(self, desc: str) -> None:
self.desc_label.setText(desc)

def setDescriptionWidth(self, width: int) -> None:
self.desc_label.setFixedWidth(width)

def sizeHint(self) -> QSize:
width = max(self.desc_label.width(), super().sizeHint().width()) + self.style_data.indicator_allocated_width +22
height = self.desc_label.sizeHint().height() + 24
return QSize(width, height)

def adjustSize(self) -> None:
super().adjustSize()
self.desc_label.adjustSize()


class SiRadioButtonWithAvatar(SiRadioButtonRefactor):
def __init__(self, parent: T_WidgetParent = None) -> None:
super().__init__(parent)
self._description = ""
self._description_font = SiFont.getFont(size=12)

def _initStyle(self) -> None:
self.setFont(SiFont.getFont(size=14))

def setDescription(self, text: str) -> None:
self._description = text
self.update()

def sizeHint(self) -> QSize:
return QSize(super().sizeHint().width() + self.style_data.avatar_width + 12, 36)

def _drawIndicatorInnerPath(self, rect: QRect) -> QPainterPath:
path = QPainterPath()
path.addRoundedRect(QRectF(28.5, 12, self.style_data.unchecked_indicator_width - 6, rect.height() - 8), 6, 6)
return path

def _drawDescriptionTextRect(self, painter: QPainter, rect: QRect) -> None:
painter.setPen(self.style_data.description_color)
painter.setFont(self._description_font)
painter.drawText(rect, Qt.AlignTop | Qt.AlignLeft, self._description)
painter.setPen(Qt.NoPen)

def _drawAvatarIcon(self, painter: QPainter, rect: QRect) -> None:
device_pixel_ratio = self.devicePixelRatioF()

buffer = QPixmap(rect.size() * device_pixel_ratio)
buffer.setDevicePixelRatio(device_pixel_ratio)
buffer.fill(Qt.transparent)

buffer_painter = QPainter(buffer)
buffer_painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
buffer_painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
buffer_painter.setRenderHint(QPainter.RenderHint.Antialiasing)
buffer_painter.setPen(Qt.PenStyle.NoPen)

width = self.style_data.avatar_width
height = self.style_data.avatar_height
border_radius = self.style_data.avatar_border_radius
x = (rect.width() - width) // 2
y = (rect.height() - height) // 2
target_rect = QRect(x, y, width, height)
size = QSize(width, height) * device_pixel_ratio

path = QPainterPath()
path.addRoundedRect(x, y,
width, height,
border_radius, border_radius)

buffer_painter.setClipPath(path)
buffer_painter.drawPixmap(target_rect, self.icon().pixmap(size))
buffer_painter.end()

painter.drawPixmap(rect, buffer)

def paintEvent(self, a0) -> None:
rect = self.rect()
indi_rect = QRect(0, 8, self.style_data.indicator_allocated_width, self.style_data.indicator_height)
avatar_rect = QRect(indi_rect.width() + 22, 0, self.style_data.avatar_width, self.style_data.avatar_height)
text_rect = QRect(indi_rect.width() + 22 + self.style_data.avatar_width + 12, 3, rect.width() - indi_rect.width() - 22, 14)
desc_rect = QRect(indi_rect.width() + 22 + self.style_data.avatar_width + 12, 19, rect.width() - indi_rect.width() - 22, 18)

renderHints = (
QPainter.RenderHint.SmoothPixmapTransform
| QPainter.RenderHint.TextAntialiasing
| QPainter.RenderHint.Antialiasing
)

with createPainter(self, renderHints) as painter:
self._drawIndicatorRect(painter, indi_rect)
self._drawHighlightRect(painter, indi_rect)
self._drawIndicatorInnerRect(painter, indi_rect)
self._drawAvatarIcon(painter, avatar_rect)
self._drawNameTextRect(painter, text_rect)
self._drawDescriptionTextRect(painter, desc_rect)

0 comments on commit e25cc40

Please sign in to comment.