From 90a5ddd8185692e3f60a8b791e1294a83ff76b81 Mon Sep 17 00:00:00 2001 From: MidnightCrowing <110297461+MidnightCrowing@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:57:23 +0800 Subject: [PATCH 1/6] feat(SiSwitch): Add mouse dragging feature --- siui/components/widgets/button.py | 79 +++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/siui/components/widgets/button.py b/siui/components/widgets/button.py index c64debf..ec563d7 100644 --- a/siui/components/widgets/button.py +++ b/siui/components/widgets/button.py @@ -1,4 +1,4 @@ -from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtCore import QPoint, Qt, pyqtSignal from PyQt5.QtWidgets import QAbstractButton from siui.components.widgets.abstracts import ABCButton, ABCPushButton, ABCToggleButton, LongPressThread @@ -406,7 +406,9 @@ class SiSwitch(QAbstractButton): """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.setCheckable(True) + + # 自定义状态属性,初始值为 False + self._checked = False # 颜色组 self.color_group = SiColorGroup(reference=SiGlobal.siui.colors) @@ -414,9 +416,6 @@ def __init__(self, *args, **kwargs): # 设置自身固定大小 self.setFixedSize(40, 20) - # 绑定切换事件 - self.toggled.connect(self._toggle_handler) - # 开关框架 self.switch_frame = SiLabel(self) self.switch_frame.setGeometry(0, 0, 40, 20) @@ -434,6 +433,10 @@ def __init__(self, *args, **kwargs): self.toggle_animation.setCurrent(3) self.toggle_animation.ticked.connect(self._lever_move_animation_handler) + # 记录拉杆与鼠标偏移量 + self._initial_pos: QPoint = QPoint(0, 0) + self._drag_offset = 0 + def reloadStyleSheet(self): """ 重载样式表 @@ -455,7 +458,7 @@ def _lever_move_animation_handler(self, x): self.switch_lever.move(int(x), self.switch_lever.y()) # 检测拉杆的位置,如果过了半程,则改变边框样式 - if (x - 3) / 20 >= 0.5: + if self._process_lever_position(): self.switch_frame.setStyleSheet( f""" background-color: qlineargradient(x1:0, y1:0, x2:1, y2:1, @@ -470,14 +473,70 @@ def _lever_move_animation_handler(self, x): self.switch_lever.setStyleSheet(f"background-color:{self.getColor(SiColor.SWITCH_DEACTIVATE)}") # noqa: E501 def _set_animation_target(self, is_checked): - if is_checked is True: - self.toggle_animation.setTarget(23) - else: - self.toggle_animation.setTarget(3) + self.toggle_animation.setCurrent(self.switch_lever.x()) + self.toggle_animation.setTarget(23 if is_checked else 3) + + def _process_lever_position(self) -> bool: + """ + 根据滑杆位置决定开关的选中状态。 + """ + lever_position = self.switch_lever.x() + return (lever_position - 3) / 20 >= 0.5 def paintEvent(self, e): pass + def isChecked(self): + return self._checked # 返回自定义状态 + + def setChecked(self, checked): + if self._checked != checked: + self._checked = checked + self.toggled.emit(self._checked) # 发射信号 + self._toggle_handler(self._checked) # 更新动画 + + def mousePressEvent(self, event): + """ + 处理鼠标按下事件,记录鼠标点击位置与滑杆的相对位置。 + """ + if event.button() == Qt.LeftButton: + self._drag_offset = event.pos().x() - self.switch_lever.x() # 记录偏移量 + self._initial_pos = event.pos() # 记录初始点击位置 + super().mousePressEvent(event) + + def mouseMoveEvent(self, event): + """ + 处理滑条的鼠标移动事件,拖动时移动滑杆。 + """ + if event.buttons() & Qt.LeftButton: # 检查鼠标左键是否按下 + # 获取鼠标在 slider 上的位置,并使用之前记录的偏移量来移动滑杆 + mouse_pos = event.pos() + target_pos = mouse_pos.x() - self._drag_offset # 保持相对位置 + self._lever_move_animation_handler(min(max(target_pos, 3), 23)) + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + """ + 处理鼠标松开事件,区分点击和拖动操作。 + """ + if event.button() == Qt.LeftButton: + release_pos = event.pos() + drag_distance = abs(release_pos.x() - self._initial_pos.x()) + + if drag_distance < 3: # 点击操作 + self._checked = not self._checked # 切换状态 + self.toggled.emit(self._checked) # 手动发射toggled信号 + else: # 拖动操作 + new_checked_state = self._process_lever_position() + if self._checked != new_checked_state: + self._checked = new_checked_state + self.toggled.emit(self._checked) # 手动发射toggled信号 + + # 更新动画 + self._toggle_handler(self._checked) + + super().mouseReleaseEvent(event) + def _toggle_handler(self, is_checked): self._set_animation_target(is_checked) self.toggle_animation.try_to_start() From 447c6057e460f297b8c31119bc3ed314f81ca2f4 Mon Sep 17 00:00:00 2001 From: rainzee Date: Sun, 3 Nov 2024 00:19:02 +0800 Subject: [PATCH 2/6] feat: respect application level font family component now supports native Qt's global font configuration method. You can specify the application's global font family at the entry. --- siui/gui/font.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/siui/gui/font.py b/siui/gui/font.py index 4ac2ed8..a5f61d4 100644 --- a/siui/gui/font.py +++ b/siui/gui/font.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import qApp from siui.core.token import FontStyle, GlobalFont, GlobalFontSize, GlobalFontWeight @@ -13,7 +14,8 @@ class SiFont: @staticmethod def getFont( - families: Sequence[str] = ["Segoe UI", "Microsoft YaHei", "San Francisco Fonts", "PingFang SC"], + families: Sequence[str] = qApp.font().families() + or ["Segoe UI", "Microsoft YaHei", "San Francisco Fonts", "PingFang SC"], size: int = 14, weight: QFont.Weight = QFont.Weight.Normal, italic: bool = False, From 213c40261add0411f13e2dc1910b9102cb75d898 Mon Sep 17 00:00:00 2001 From: rainzee Date: Sun, 3 Nov 2024 00:42:10 +0800 Subject: [PATCH 3/6] docs: add docstring for `SiFont` --- siui/gui/font.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/siui/gui/font.py b/siui/gui/font.py index a5f61d4..5d9ab79 100644 --- a/siui/gui/font.py +++ b/siui/gui/font.py @@ -12,6 +12,12 @@ class SiFont: + """SiUI Font Class + + You can use low-level API to customize fonts details, + or use tokenized global fonts to quickly create fonts. + """ + @staticmethod def getFont( families: Sequence[str] = qApp.font().families() @@ -20,6 +26,21 @@ def getFont( weight: QFont.Weight = QFont.Weight.Normal, italic: bool = False, ) -> QFont: + """Low-level API for creating font instance + + Application-level configuration takes the highest priority, + and it is recommended to use the tokenized Hier-level API. + + Args: + - families: 字体族列表,如果指定了应用程序级别的全局配置,则会覆盖默认字体家族 + - size: 字体大小 + - weight: 字体粗细 + - italic: 是否斜体 + + Returns: + - QFont: 字体实例 + + """ font = QFont() font.setFamilies(families) font.setPixelSize(size) From 4077f4466bd2fa86ae950d5e59ec414414373e11 Mon Sep 17 00:00:00 2001 From: IceF Date: Tue, 5 Nov 2024 10:24:19 +0800 Subject: [PATCH 4/6] fix: add `python-dateutil` as a dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a61208d..42669e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "A powerful and artistic UI library based on PyQt5 / PySide6" readme = "README.md" license = { file = "LICENSE" } requires-python = ">=3.8" -dependencies = ["PyQt5>=5.15.10", "typing-extensions>=4.12.2"] +dependencies = ["PyQt5>=5.15.10", "typing-extensions>=4.12.2", "python-dateutil>=2.9.0"] [project.urls] Repository = "https://github.com/ChinaIceF/PyQt-SiliconUI" From 46dd2c1bac73b766784de0cb2de0e4b29321c166 Mon Sep 17 00:00:00 2001 From: rainzee Date: Sun, 17 Nov 2024 17:59:59 +0800 Subject: [PATCH 5/6] feat(typing): add `T_WidgetParent` and `T_ObjectParent` --- siui/typing.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/siui/typing.py b/siui/typing.py index 80f8325..e033445 100644 --- a/siui/typing.py +++ b/siui/typing.py @@ -7,4 +7,14 @@ - [`PEP 526`](https://www.python.org/dev/peps/pep-0526/) """ +from typing import Optional + +from PyQt5.QtCore import QObject +from PyQt5.QtWidgets import QWidget from typing_extensions import TypeAlias + +T_WidgetParent: TypeAlias = Optional[QWidget] +"""Type of widget parent""" + +T_ObjectParent: TypeAlias = Optional[QObject] +"""Type of object parent""" From de6fa95fd163f157440f9ac1178b9f6d2d00e948 Mon Sep 17 00:00:00 2001 From: rainzee Date: Sun, 17 Nov 2024 18:27:12 +0800 Subject: [PATCH 6/6] feat(core): impl `createPainter` --- siui/core/__init__.py | 30 ++++++++++++++++++--------- siui/core/painter.py | 47 +++++++++++++++++++++++++++++++++++++++++++ siui/typing.py | 11 ++++++++-- 3 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 siui/core/painter.py diff --git a/siui/core/__init__.py b/siui/core/__init__.py index 47f349b..8fbd86d 100644 --- a/siui/core/__init__.py +++ b/siui/core/__init__.py @@ -1,4 +1,3 @@ - from .alignment import SiQuickAlignmentManager from .animation import ( ABCSiAnimation, @@ -13,17 +12,28 @@ from .effect import SiQuickEffect from .enumrates import Si from .globals import NewGlobal, SiGlobal +from .painter import createPainter as createPainter from .token import FontStyle as FontStyle from .token import GlobalFont as GlobalFont from .token import GlobalFontSize as GlobalFontSize from .token import GlobalFontWeight as GlobalFontWeight -__all__ = ("SiQuickAlignmentManager", - "ABCSiAnimation", "Curve", "SiAnimationGroup", "SiCounterAnimation", - "SiExpAccelerateAnimation", "SiExpAnimation", "SiSqrExpAnimation", - "SiColor", - "SiQuickEffect", - "Si", - "NewGlobal", "SiGlobal", - "FontStyle", "GlobalFont", "GlobalFontSize", "GlobalFontWeight") - +__all__ = ( + "SiQuickAlignmentManager", + "ABCSiAnimation", + "Curve", + "SiAnimationGroup", + "SiCounterAnimation", + "SiExpAccelerateAnimation", + "SiExpAnimation", + "SiSqrExpAnimation", + "SiColor", + "SiQuickEffect", + "Si", + "NewGlobal", + "SiGlobal", + "FontStyle", + "GlobalFont", + "GlobalFontSize", + "GlobalFontWeight", +) diff --git a/siui/core/painter.py b/siui/core/painter.py new file mode 100644 index 0000000..ee876fa --- /dev/null +++ b/siui/core/painter.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QPainter + +if TYPE_CHECKING: + from PyQt5.QtGui import QFont, QPaintDevice + + from siui.typing import T_Brush, T_PenStyle + + +def createPainter( + paintDevice: QPaintDevice, + renderHint: Optional[QPainter.RenderHint] = QPainter.RenderHint.Antialiasing, + penStyle: T_PenStyle = Qt.PenStyle.NoPen, + brush: T_Brush = None, + font: Optional[QFont] = None, +) -> QPainter: + """构造并初始化 QPainter 对象 + 应该使用 with 关键字来创建和关闭 QPainter 对象 + + 参数: + - parent: QPaintDevice 的子类实例,通常是 QWidget 或 QImage + - renderHint: 指定渲染提示,默认为 QPainter.RenderHint.Antialiasing 标准抗锯齿 + - penStyle: Qt.PenStyle 类型,指定画笔样式,默认为 Qt.PenStyle.NoPen + - brushColor: 字符串或 QColor 对象,指定画刷颜色,默认不指定 + - font: QFont 对象,指定字体,默认不指定 + + 返回: + QPainter 对象实例 + """ + painter = QPainter(paintDevice) + if renderHint is not None: + painter.setRenderHint(renderHint) + + if penStyle is not None: + painter.setPen(penStyle) + + if brush is not None: + painter.setBrush(brush) + + if font is not None: + painter.setFont(font) + + return painter diff --git a/siui/typing.py b/siui/typing.py index e033445..db76463 100644 --- a/siui/typing.py +++ b/siui/typing.py @@ -7,9 +7,10 @@ - [`PEP 526`](https://www.python.org/dev/peps/pep-0526/) """ -from typing import Optional +from typing import Optional, Union -from PyQt5.QtCore import QObject +from PyQt5.QtCore import QObject, Qt +from PyQt5.QtGui import QColor, QGradient, QPen from PyQt5.QtWidgets import QWidget from typing_extensions import TypeAlias @@ -18,3 +19,9 @@ T_ObjectParent: TypeAlias = Optional[QObject] """Type of object parent""" + +T_PenStyle: TypeAlias = Union[QPen, Qt.PenStyle, QColor, Qt.GlobalColor] +"""Type of QPen style""" + +T_Brush: TypeAlias = Optional[Union[QGradient, QColor, Qt.GlobalColor]] +"""Type of QBrush"""