diff --git a/examples/Gallery for siui/components/page_refactor/__init__.py b/examples/Gallery for siui/components/page_refactor/__init__.py
new file mode 100644
index 0000000..6d71f51
--- /dev/null
+++ b/examples/Gallery for siui/components/page_refactor/__init__.py
@@ -0,0 +1 @@
+from .page_refactor import RefactoredWidgets
\ No newline at end of file
diff --git a/examples/Gallery for siui/components/page_refactor/page_refactor.py b/examples/Gallery for siui/components/page_refactor/page_refactor.py
new file mode 100644
index 0000000..b82fbd0
--- /dev/null
+++ b/examples/Gallery for siui/components/page_refactor/page_refactor.py
@@ -0,0 +1,375 @@
+import random
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QIcon
+from PyQt5.QtWidgets import QLabel
+
+from siui.components import SiDenseHContainer, SiTitledWidgetGroup, SiLongPressButton, SiDenseVContainer
+from siui.components.button import SiPushButtonRefactor, SiProgressPushButton, SiLongPressButtonRefactor, SiFlatButton, \
+ SiToggleButtonRefactor, SiSwitchRefactor, SiRadioButtonWithAvatar, SiRadioButtonWithDescription, \
+ SiRadioButtonRefactor
+from siui.components.page import SiPage
+from siui.components.slider_ import SiSlider, SiCoordinatePicker2D, SiCoordinatePicker3D
+from siui.core import SiGlobal
+from siui.gui import SiFont
+
+from ..option_card import OptionCardPlaneForWidgetDemos
+
+
+class RefactoredWidgets(SiPage):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.setPadding(64)
+ self.setScrollMaximumWidth(1000)
+ self.setScrollAlignment(Qt.AlignLeft)
+ self.setTitle("重构控件")
+
+ # 创建控件组
+ self.titled_widgets_group = SiTitledWidgetGroup(self)
+ self.titled_widgets_group.setSpacing(32)
+ self.titled_widgets_group.setAdjustWidgetsSize(True) # 禁用调整宽度
+
+ # 按钮
+ with self.titled_widgets_group as group:
+ group.addTitle("按钮")
+
+ # 按压按钮
+ self.push_buttons = OptionCardPlaneForWidgetDemos(self)
+ self.push_buttons.setTitle("按压按钮")
+
+ container = SiDenseHContainer(self)
+
+ self.demo_push_button_text = SiPushButtonRefactor(self)
+ self.demo_push_button_text.setText("按压按钮")
+ self.demo_push_button_text.adjustSize()
+
+ self.demo_push_button_text_icon = SiPushButtonRefactor(self)
+ self.demo_push_button_text_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_location_filled"))
+ self.demo_push_button_text_icon.setText("获取定位")
+ self.demo_push_button_text_icon.setToolTip("包括经纬度、朝向信息")
+ self.demo_push_button_text_icon.adjustSize()
+
+ self.demo_push_button_icon = SiPushButtonRefactor(self)
+ self.demo_push_button_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_location_filled"))
+ self.demo_push_button_icon.setToolTip("获取定位")
+ self.demo_push_button_icon.adjustSize()
+
+ container.addWidget(self.demo_push_button_text)
+ container.addWidget(self.demo_push_button_text_icon)
+ container.addWidget(self.demo_push_button_icon)
+
+ self.push_buttons.body().addWidget(container)
+ self.push_buttons.body().addPlaceholder(12)
+ self.push_buttons.adjustSize()
+
+ # 进度按钮
+ self.progress_buttons = OptionCardPlaneForWidgetDemos(self)
+ self.progress_buttons.setTitle("进度按钮")
+
+ container = SiDenseHContainer(self)
+
+ self.demo_progress_button_text = SiProgressPushButton(self)
+ self.demo_progress_button_text.setText("进度按钮")
+ self.demo_progress_button_text.setToolTip("点击以设置随机进度")
+ self.demo_progress_button_text.clicked.connect(lambda: self.demo_progress_button_text.setProgress(random.random() * 1.3))
+ self.demo_progress_button_text.clicked.connect(lambda: self.demo_progress_button_text_icon.setProgress(random.random() * 1.3))
+ self.demo_progress_button_text.clicked.connect(lambda: self.demo_progress_button_icon.setProgress(random.random() * 1.3))
+ self.demo_progress_button_text.adjustSize()
+
+ self.demo_progress_button_text_icon = SiProgressPushButton(self)
+ self.demo_progress_button_text_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_arrow_download_filled"))
+ self.demo_progress_button_text_icon.setText("下载中")
+ self.demo_progress_button_text_icon.adjustSize()
+
+ self.demo_progress_button_icon = SiProgressPushButton(self)
+ self.demo_progress_button_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_arrow_download_filled"))
+ self.demo_progress_button_icon.setToolTip("下载中")
+ self.demo_progress_button_icon.adjustSize()
+
+ container.addWidget(self.demo_progress_button_text)
+ container.addWidget(self.demo_progress_button_text_icon)
+ container.addWidget(self.demo_progress_button_icon)
+
+ self.progress_buttons.body().addWidget(container)
+ self.progress_buttons.body().addPlaceholder(12)
+ self.progress_buttons.adjustSize()
+
+ # 长按确定按钮
+ self.long_press_buttons = OptionCardPlaneForWidgetDemos(self)
+ self.long_press_buttons.setTitle("长按确定按钮")
+
+ container = SiDenseHContainer(self)
+
+ self.demo_long_press_button_text = SiLongPressButtonRefactor(self)
+ self.demo_long_press_button_text.setText("格式化磁盘")
+ self.demo_long_press_button_text.setToolTip("长按以确认")
+ self.demo_long_press_button_text.adjustSize()
+
+ self.demo_long_press_button_text_icon = SiLongPressButtonRefactor(self)
+ self.demo_long_press_button_text_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_delete_filled"))
+ self.demo_long_press_button_text_icon.setText("删除备份")
+ self.demo_long_press_button_text_icon.setToolTip("长按以确认")
+ self.demo_long_press_button_text_icon.adjustSize()
+
+ self.demo_long_press_button_icon = SiLongPressButtonRefactor(self)
+ self.demo_long_press_button_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_delete_filled"))
+ self.demo_long_press_button_icon.setToolTip("长按以删除备份
警告: 此操作将无法撤销")
+ self.demo_long_press_button_icon.adjustSize()
+
+ container.addWidget(self.demo_long_press_button_text)
+ container.addWidget(self.demo_long_press_button_text_icon)
+ container.addWidget(self.demo_long_press_button_icon)
+
+ self.long_press_buttons.body().addWidget(container)
+ self.long_press_buttons.body().addPlaceholder(12)
+ self.long_press_buttons.adjustSize()
+
+ # 扁平按钮
+ self.flat_buttons = OptionCardPlaneForWidgetDemos(self)
+ self.flat_buttons.setTitle("扁平按钮")
+
+ container = SiDenseHContainer(self)
+
+ self.demo_flat_button_text = SiFlatButton(self)
+ self.demo_flat_button_text.setText("扁平按钮")
+ self.demo_flat_button_text.adjustSize()
+
+ self.demo_flat_button_text_icon = SiFlatButton(self)
+ self.demo_flat_button_text_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_zoom_in_filled"))
+ self.demo_flat_button_text_icon.setText("放大")
+ self.demo_flat_button_text_icon.adjustSize()
+
+ self.demo_flat_button_icon = SiFlatButton(self)
+ self.demo_flat_button_icon.resize(32, 32)
+ self.demo_flat_button_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_zoom_in_filled"))
+ self.demo_flat_button_icon.setToolTip("放大")
+
+ container.addWidget(self.demo_flat_button_text)
+ container.addWidget(self.demo_flat_button_text_icon)
+ container.addWidget(self.demo_flat_button_icon)
+
+ self.flat_buttons.body().addWidget(container)
+ self.flat_buttons.body().addPlaceholder(12)
+ self.flat_buttons.adjustSize()
+
+ # 状态切换按钮
+ self.toggle_buttons = OptionCardPlaneForWidgetDemos(self)
+ self.toggle_buttons.setTitle("状态切换按钮")
+
+ container = SiDenseHContainer(self)
+
+ self.demo_toggle_button_text = SiToggleButtonRefactor(self)
+ self.demo_toggle_button_text.setText("自动保存")
+ self.demo_toggle_button_text.adjustSize()
+
+ self.demo_toggle_button_text_icon = SiToggleButtonRefactor(self)
+ self.demo_toggle_button_text_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_save_filled"))
+ self.demo_toggle_button_text_icon.setText("自动保存")
+ self.demo_toggle_button_text_icon.adjustSize()
+
+ self.demo_toggle_button_icon = SiToggleButtonRefactor(self)
+ self.demo_toggle_button_icon.resize(32, 32)
+ self.demo_toggle_button_icon.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_save_filled"))
+ self.demo_toggle_button_icon.setToolTip("自动保存")
+
+ container.addWidget(self.demo_toggle_button_text)
+ container.addWidget(self.demo_toggle_button_text_icon)
+ container.addWidget(self.demo_toggle_button_icon)
+
+ self.toggle_buttons.body().addWidget(container)
+ self.toggle_buttons.body().addPlaceholder(12)
+ self.toggle_buttons.adjustSize()
+
+ # 开关
+ self.switches = OptionCardPlaneForWidgetDemos(self)
+ self.switches.setTitle("开关")
+
+ self.demo_switch = SiSwitchRefactor(self)
+
+ self.switches.body().addWidget(self.demo_switch)
+ self.switches.body().addPlaceholder(12)
+ self.switches.adjustSize()
+
+ group.addWidget(self.push_buttons)
+ group.addWidget(self.progress_buttons)
+ group.addWidget(self.long_press_buttons)
+ group.addWidget(self.flat_buttons)
+ group.addWidget(self.toggle_buttons)
+ group.addWidget(self.switches)
+
+ # 单选框
+ with self.titled_widgets_group as group:
+ group.addTitle("单选框")
+
+ self.refactor_radiobuttons = OptionCardPlaneForWidgetDemos(self)
+ self.refactor_radiobuttons.setTitle("单行单选框")
+
+ 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_button.setChecked(True)
+
+ 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()
+
+ self.refactor_radio_button4 = SiRadioButtonRefactor(self)
+ self.refactor_radio_button4.setText("不是哥们我真的要困死了让我睡觉吧")
+ self.refactor_radio_button4.adjustSize()
+
+ self.refactor_radio_button5 = SiRadioButtonRefactor(self)
+ self.refactor_radio_button5.setText("nihao")
+ self.refactor_radio_button5.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.addWidget(self.refactor_radio_button4)
+ radio_button_container.addWidget(self.refactor_radio_button5)
+ radio_button_container.adjustSize()
+
+ self.refactor_radiobuttons.body().addWidget(radio_button_container)
+ self.refactor_radiobuttons.body().addPlaceholder(12)
+ self.refactor_radiobuttons.adjustSize()
+
+ self.refactor_radiobuttons_desc = OptionCardPlaneForWidgetDemos(self)
+ self.refactor_radiobuttons_desc.setTitle("带解释的单选框")
+
+ radio_button_container = SiDenseVContainer(self)
+ radio_button_container.setSpacing(6)
+
+ self.refactor_radio_button = SiRadioButtonWithDescription(self)
+ self.refactor_radio_button.setText("Hello World")
+ self.refactor_radio_button.setDescription("This is the description of Item1, which is very long.")
+ self.refactor_radio_button.setDescriptionWidth(180)
+ self.refactor_radio_button.adjustSize()
+ self.refactor_radio_button.setChecked(True)
+
+ self.refactor_radio_button2 = SiRadioButtonWithDescription(self)
+ self.refactor_radio_button2.setText("我吃你牛魔")
+ self.refactor_radio_button2.setDescription("这是第二个选项的解释,短一些")
+ self.refactor_radio_button2.setDescriptionWidth(180)
+ self.refactor_radio_button2.adjustSize()
+
+ self.refactor_radio_button3 = SiRadioButtonWithDescription(self)
+ self.refactor_radio_button3.setText("诗人我吃")
+ self.refactor_radio_button3.setDescription("你干嘛嗨嗨呦~")
+ self.refactor_radio_button3.setDescriptionWidth(180)
+ 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_radiobuttons_desc.body().addWidget(radio_button_container)
+ self.refactor_radiobuttons_desc.body().addPlaceholder(12)
+ self.refactor_radiobuttons_desc.adjustSize()
+
+ self.refactor_radiobuttons_avatar = OptionCardPlaneForWidgetDemos(self)
+ self.refactor_radiobuttons_avatar.setTitle("带头像的单选框")
+
+ radio_button_container = SiDenseVContainer(self)
+ radio_button_container.setSpacing(16)
+
+ self.refactor_radio_button = SiRadioButtonWithAvatar(self)
+ self.refactor_radio_button.setText("霏泠Ice")
+ self.refactor_radio_button.setDescription("114514@nigamna.com")
+ self.refactor_radio_button.setIcon(QIcon("./img/avatar1.png"))
+ self.refactor_radio_button.adjustSize()
+ self.refactor_radio_button.setChecked(True)
+
+ self.refactor_radio_button2 = SiRadioButtonWithAvatar(self)
+ self.refactor_radio_button2.setText("我家鸽鸽")
+ self.refactor_radio_button2.setDescription("zhiyin@qq.com")
+ self.refactor_radio_button2.setIcon(QIcon("./img/avatar2.png"))
+ self.refactor_radio_button2.adjustSize()
+
+ self.refactor_radio_button3 = SiRadioButtonWithAvatar(self)
+ self.refactor_radio_button3.setText("你干嘛嗨嗨呦")
+ self.refactor_radio_button3.setDescription("1231524232@qq.com")
+ self.refactor_radio_button3.setIcon(QIcon("./img/avatar1.png"))
+ 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_radiobuttons_avatar.body().addWidget(radio_button_container)
+ self.refactor_radiobuttons_avatar.body().addPlaceholder(12)
+ self.refactor_radiobuttons_avatar.adjustSize()
+
+ group.addWidget(self.refactor_radiobuttons)
+ group.addWidget(self.refactor_radiobuttons_desc)
+ group.addWidget(self.refactor_radiobuttons_avatar)
+
+ # 滑动条
+ with self.titled_widgets_group as group:
+ group.addTitle("滑动条")
+
+ self.sliders_horizontal = OptionCardPlaneForWidgetDemos(self)
+ self.sliders_horizontal.setTitle("横向滑动条")
+
+ self.demo_slider_horizontal = SiSlider(self)
+ self.demo_slider_horizontal.resize(512, 48)
+
+ self.sliders_horizontal.body().addWidget(self.demo_slider_horizontal)
+ self.sliders_horizontal.body().addPlaceholder(12)
+ self.sliders_horizontal.adjustSize()
+
+ self.sliders_vertical = OptionCardPlaneForWidgetDemos(self)
+ self.sliders_vertical.setTitle("纵向滑动条")
+
+ self.demo_slider_vertical = SiSlider(self)
+ self.demo_slider_vertical.resize(48, 140)
+ self.demo_slider_vertical.setOrientation(Qt.Orientation.Vertical)
+
+ self.sliders_vertical.body().addWidget(self.demo_slider_vertical)
+ self.sliders_vertical.body().addPlaceholder(12)
+ self.sliders_vertical.adjustSize()
+
+ self.coordinate_picker_2ds = OptionCardPlaneForWidgetDemos(self)
+ self.coordinate_picker_2ds.setTitle("二维坐标选取器")
+
+ self.demo_coordinate_picker_2d = SiCoordinatePicker2D(self)
+ self.demo_coordinate_picker_2d.resize(384, 256)
+ self.demo_coordinate_picker_2d.slider_x.setValue(72)
+ self.demo_coordinate_picker_2d.slider_y.setValue(63)
+
+ self.coordinate_picker_2ds.body().addWidget(self.demo_coordinate_picker_2d)
+ self.coordinate_picker_2ds.body().addPlaceholder(6)
+ self.coordinate_picker_2ds.adjustSize()
+
+ self.coordinate_picker_3ds = OptionCardPlaneForWidgetDemos(self)
+ self.coordinate_picker_3ds.setTitle("三维坐标选取器")
+
+ self.demo_coordinate_picker_3d = SiCoordinatePicker3D(self)
+ self.demo_coordinate_picker_3d.resize(384, 256)
+ self.demo_coordinate_picker_3d.slider_x.setValue(72)
+ self.demo_coordinate_picker_3d.slider_y.setValue(63)
+ self.demo_coordinate_picker_3d.slider_z.setMaximum(6)
+ self.demo_coordinate_picker_3d.slider_z.setValue(6)
+
+ self.coordinate_picker_3ds.body().addWidget(self.demo_coordinate_picker_3d)
+ self.coordinate_picker_3ds.body().addPlaceholder(6)
+ self.coordinate_picker_3ds.adjustSize()
+
+ group.addWidget(self.sliders_horizontal)
+ group.addWidget(self.sliders_vertical)
+ group.addWidget(self.coordinate_picker_2ds)
+ group.addWidget(self.coordinate_picker_3ds)
+
+ # 添加页脚的空白以增加美观性
+ self.titled_widgets_group.addPlaceholder(64)
+
+ # 设置控件组为页面对象
+ self.setAttachment(self.titled_widgets_group)
diff --git a/examples/Gallery for siui/components/page_widgets/page_widgets.py b/examples/Gallery for siui/components/page_widgets/page_widgets.py
index bb39175..3d0734e 100644
--- a/examples/Gallery for siui/components/page_widgets/page_widgets.py
+++ b/examples/Gallery for siui/components/page_widgets/page_widgets.py
@@ -3,10 +3,12 @@
import numpy
from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QCursor
+from PyQt5.QtGui import QCursor, QIcon
+from PyQt5.QtWidgets import QGraphicsBlurEffect
from siui.components import (
SiCircularProgressBar,
+ SiDenseVContainer,
SiLineEdit,
SiLineEditWithDeletionButton,
SiLineEditWithItemName,
@@ -14,12 +16,23 @@
SiTitledWidgetGroup,
SiWidget,
)
-from siui.components.button import SiPushButtonRefactor
+from siui.components.button import (
+ SiFlatButton,
+ SiLongPressButtonRefactor,
+ SiProgressPushButton,
+ SiPushButtonRefactor,
+ SiRadioButtonRefactor,
+ SiRadioButtonWithAvatar,
+ SiRadioButtonWithDescription,
+ SiSwitchRefactor,
+ SiToggleButtonRefactor,
+)
from siui.components.combobox import SiComboBox
from siui.components.menu import SiMenu
from siui.components.page import SiPage
from siui.components.progress_bar import SiProgressBar
from siui.components.slider import SiSliderH
+from siui.components.slider_ import SiCoordinatePicker2D, SiCoordinatePicker3D, SiSlider
from siui.components.spinbox.spinbox import SiDoubleSpinBox, SiIntSpinBox
from siui.components.widgets import (
SiCheckBox,
@@ -250,6 +263,189 @@ def __init__(self, *args, **kwargs):
with self.titled_widgets_group as group:
group.addTitle("按钮")
+ # 按钮
+ self.refactor_buttons = OptionCardPlaneForWidgetDemos(self)
+ self.refactor_buttons.setSourceCodeURL("https://github.com/ChinaIceF/PyQt-SiliconUI/blob/main/siui/components"
+ "/widgets/button.py")
+ self.refactor_buttons.setTitle("重构的按钮")
+
+ self.refactor_pushbutton = SiPushButtonRefactor(self)
+ self.refactor_pushbutton.setText("Confirm")
+ self.refactor_pushbutton.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_mail_checkmark_filled"))
+ self.refactor_pushbutton.adjustSize()
+
+ self.refactor_progress_button = SiProgressPushButton(self)
+ self.refactor_progress_button.setText("Downloading")
+ self.refactor_progress_button.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_arrow_download_filled"))
+ self.refactor_progress_button.setToolTip("Click me to set a random progress value.")
+ self.refactor_progress_button.clicked.connect(lambda: self.refactor_progress_button.setProgress(random.random() * 1.3))
+ self.refactor_progress_button.adjustSize()
+
+ self.refactor_long_press_button = SiLongPressButtonRefactor(self)
+ self.refactor_long_press_button.setText("Delete Files")
+ self.refactor_long_press_button.setToolTip("Hold me to confirm.
Your files will be lost forever!")
+ self.refactor_long_press_button.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_delete_filled"))
+ self.refactor_long_press_button.longPressed.connect(lambda: self.refactor_long_press_button.setToolTip("Deleted!"))
+ self.refactor_long_press_button.adjustSize()
+
+ self.refactor_flat_button = SiFlatButton(self)
+ self.refactor_flat_button.setText("Flat Button")
+ self.refactor_flat_button.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_wrench_settings_filled"))
+ self.refactor_flat_button.adjustSize()
+
+ self.refactor_toggle_button = SiToggleButtonRefactor(self)
+ self.refactor_toggle_button.setText("Auto Save")
+ self.refactor_toggle_button.setSvgIcon(SiGlobal.siui.iconpack.get("ic_fluent_save_filled"))
+ self.refactor_toggle_button.adjustSize()
+
+ self.refactor_slider = SiSlider(self)
+ self.refactor_slider.resize(512, 32)
+ self.refactor_slider.setValue(5)
+ self.refactor_slider.setMinimum(-50)
+ self.refactor_slider.setMaximum(50)
+ self.refactor_slider.setToolTipConvertionFunc(lambda x: f"{x} ms")
+
+ self.refactor_slider2 = SiSlider(self)
+ self.refactor_slider2.resize(32, 256)
+ self.refactor_slider2.setOrientation(Qt.Orientation.Vertical)
+ self.refactor_slider2.setValue(0)
+ self.refactor_slider2.setMinimum(-30)
+ self.refactor_slider2.setMaximum(12)
+ self.refactor_slider2.setToolTipConvertionFunc(lambda x: f"{x} dB")
+
+ self.coordinate_picker_2d = SiCoordinatePicker3D(self)
+ self.coordinate_picker_2d.resize(384, 256)
+ self.coordinate_picker_2d.slider_z.setMaximum(6)
+
+ self.refactor_switch = SiSwitchRefactor(self)
+
+ 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(self.refactor_slider)
+ self.refactor_buttons.body().addWidget(self.refactor_slider2)
+ self.refactor_buttons.body().addWidget(self.coordinate_picker_2d)
+
+ self.refactor_buttons.body().addPlaceholder(12)
+ self.refactor_buttons.adjustSize()
+
+ self.refactor_radiobuttons = OptionCardPlaneForWidgetDemos(self)
+ self.refactor_radiobuttons.setSourceCodeURL("https://github.com/ChinaIceF/PyQt-SiliconUI/blob/main/siui/components"
+ "/widgets/button.py")
+ self.refactor_radiobuttons.setTitle("单行单选框")
+
+ 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_button.setChecked(True)
+
+ 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()
+
+ self.refactor_radio_button4 = SiRadioButtonRefactor(self)
+ self.refactor_radio_button4.setText("不是哥们我真的要困死了让我睡觉吧")
+ self.refactor_radio_button4.adjustSize()
+
+ self.refactor_radio_button5 = SiRadioButtonRefactor(self)
+ self.refactor_radio_button5.setText("nihao")
+ self.refactor_radio_button5.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.addWidget(self.refactor_radio_button4)
+ radio_button_container.addWidget(self.refactor_radio_button5)
+ radio_button_container.adjustSize()
+
+ self.refactor_radiobuttons.body().addWidget(radio_button_container)
+ self.refactor_radiobuttons.body().addPlaceholder(12)
+ self.refactor_radiobuttons.adjustSize()
+
+ self.refactor_radiobuttons_desc = OptionCardPlaneForWidgetDemos(self)
+ self.refactor_radiobuttons_desc.setSourceCodeURL("https://github.com/ChinaIceF/PyQt-SiliconUI/blob/main/siui/components"
+ "/widgets/button.py")
+ self.refactor_radiobuttons_desc.setTitle("带解释的单选框")
+
+ radio_button_container = SiDenseVContainer(self)
+ radio_button_container.setSpacing(6)
+
+ self.refactor_radio_button = SiRadioButtonWithDescription(self)
+ self.refactor_radio_button.setText("Hello World")
+ self.refactor_radio_button.setDescription("This is the description of Item1, which is very long.")
+ self.refactor_radio_button.setDescriptionWidth(180)
+ self.refactor_radio_button.adjustSize()
+ self.refactor_radio_button.setChecked(True)
+
+ self.refactor_radio_button2 = SiRadioButtonWithDescription(self)
+ self.refactor_radio_button2.setText("我吃你牛魔")
+ self.refactor_radio_button2.setDescription("这是第二个选项的解释,短一些")
+ self.refactor_radio_button2.setDescriptionWidth(180)
+ self.refactor_radio_button2.adjustSize()
+
+ self.refactor_radio_button3 = SiRadioButtonWithDescription(self)
+ self.refactor_radio_button3.setText("诗人我吃")
+ self.refactor_radio_button3.setDescription("你干嘛嗨嗨呦~")
+ self.refactor_radio_button3.setDescriptionWidth(180)
+ 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_radiobuttons_desc.body().addWidget(radio_button_container)
+ self.refactor_radiobuttons_desc.body().addPlaceholder(12)
+ self.refactor_radiobuttons_desc.adjustSize()
+
+
+ self.refactor_radiobuttons_avatar = OptionCardPlaneForWidgetDemos(self)
+ self.refactor_radiobuttons_avatar.setSourceCodeURL("https://github.com/ChinaIceF/PyQt-SiliconUI/blob/main/siui/components"
+ "/widgets/button.py")
+ self.refactor_radiobuttons_avatar.setTitle("带头像的单选框")
+
+ radio_button_container = SiDenseVContainer(self)
+ radio_button_container.setSpacing(16)
+
+ self.refactor_radio_button = SiRadioButtonWithAvatar(self)
+ self.refactor_radio_button.setText("霏泠Ice")
+ self.refactor_radio_button.setDescription("114514@nigamna.com")
+ self.refactor_radio_button.setIcon(QIcon("./img/avatar1.png"))
+ self.refactor_radio_button.adjustSize()
+ self.refactor_radio_button.setChecked(True)
+
+ self.refactor_radio_button2 = SiRadioButtonWithAvatar(self)
+ self.refactor_radio_button2.setText("我家鸽鸽")
+ self.refactor_radio_button2.setDescription("zhiyin@qq.com")
+ self.refactor_radio_button2.setIcon(QIcon("./img/avatar2.png"))
+ self.refactor_radio_button2.adjustSize()
+
+ self.refactor_radio_button3 = SiRadioButtonWithAvatar(self)
+ self.refactor_radio_button3.setText("你干嘛嗨嗨呦")
+ self.refactor_radio_button3.setDescription("1231524232@qq.com")
+ self.refactor_radio_button3.setIcon(QIcon("./img/avatar1.png"))
+ 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_radiobuttons_avatar.body().addWidget(radio_button_container)
+ self.refactor_radiobuttons_avatar.body().addPlaceholder(12)
+ self.refactor_radiobuttons_avatar.adjustSize()
+
+
# 按钮
self.push_buttons = OptionCardPlaneForWidgetDemos(self)
self.push_buttons.setSourceCodeURL("https://github.com/ChinaIceF/PyQt-SiliconUI/blob/main/siui/components"
@@ -259,11 +455,6 @@ def __init__(self, *args, **kwargs):
container_push_buttons = SiDenseHContainer(self)
container_push_buttons.setFixedHeight(32)
- self.debug_new_button = SiPushButtonRefactor(self)
- self.debug_new_button.resize(128, 32)
- self.debug_new_button.setText("新按钮")
- self.debug_new_button.setToolTip("我是工具提示")
-
self.demo_push_button_normal = SiPushButton(self)
self.demo_push_button_normal.resize(128, 32)
self.demo_push_button_normal.attachment().setText("普通按钮")
@@ -277,7 +468,6 @@ def __init__(self, *args, **kwargs):
self.demo_push_button_long_press.resize(128, 32)
self.demo_push_button_long_press.attachment().setText("长按按钮")
- container_push_buttons.addWidget(self.debug_new_button)
container_push_buttons.addWidget(self.demo_push_button_normal)
container_push_buttons.addWidget(self.demo_push_button_transition)
container_push_buttons.addWidget(self.demo_push_button_long_press)
@@ -387,6 +577,10 @@ def __init__(self, *args, **kwargs):
self.checkboxes.body().addPlaceholder(12)
self.checkboxes.adjustSize()
+ group.addWidget(self.refactor_buttons)
+ group.addWidget(self.refactor_radiobuttons)
+ group.addWidget(self.refactor_radiobuttons_desc)
+ group.addWidget(self.refactor_radiobuttons_avatar)
group.addWidget(self.push_buttons)
group.addWidget(self.flat_buttons)
group.addWidget(self.switches)
diff --git a/examples/Gallery for siui/img/avatar2.png b/examples/Gallery for siui/img/avatar2.png
new file mode 100644
index 0000000..97874a3
Binary files /dev/null and b/examples/Gallery for siui/img/avatar2.png differ
diff --git a/examples/Gallery for siui/start.py b/examples/Gallery for siui/start.py
index bd37163..aca2f40 100644
--- a/examples/Gallery for siui/start.py
+++ b/examples/Gallery for siui/start.py
@@ -8,7 +8,7 @@
import siui
from siui.core import SiGlobal
-#siui.gui.set_scale_factor(1)
+# siui.gui.set_scale_factor(1)
def show_version_message(window):
diff --git a/examples/Gallery for siui/ui.py b/examples/Gallery for siui/ui.py
index 4a722a5..a091404 100644
--- a/examples/Gallery for siui/ui.py
+++ b/examples/Gallery for siui/ui.py
@@ -7,6 +7,7 @@
from components.page_icons import ExampleIcons
from components.page_option_cards import ExampleOptionCards
from components.page_page_control import ExamplePageControl
+from components.page_refactor import RefactoredWidgets
from components.page_widgets import ExampleWidgets
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QDesktopWidget
@@ -39,6 +40,9 @@ def __init__(self, *args, **kwargs):
self.layerMain().addPage(ExampleIcons(self),
icon=SiGlobal.siui.iconpack.get("ic_fluent_diversity_filled"),
hint="图标包", side="top")
+ self.layerMain().addPage(RefactoredWidgets(self),
+ icon=SiGlobal.siui.iconpack.get("ic_fluent_box_arrow_up_filled"),
+ hint="重构控件", side="top")
self.layerMain().addPage(ExampleWidgets(self),
icon=SiGlobal.siui.iconpack.get("ic_fluent_box_multiple_filled"),
hint="控件", side="top")
diff --git a/siui/components/__init__.py b/siui/components/__init__.py
index 2e95728..1e6a15a 100644
--- a/siui/components/__init__.py
+++ b/siui/components/__init__.py
@@ -1,5 +1,5 @@
from siui.components.option_card import *
from siui.components.progress_bar import *
-from siui.components.slider import *
+# from siui.components.slider import *
from siui.components.titled_widget_group import *
from siui.components.widgets import *
diff --git a/siui/components/button.py b/siui/components/button.py
index 3f3e854..bfccf52 100644
--- a/siui/components/button.py
+++ b/siui/components/button.py
@@ -3,73 +3,386 @@
from __future__ import annotations
from dataclasses import dataclass
+from typing import TYPE_CHECKING
-from PyQt5.QtCore import QEvent, QRect, QRectF, Qt
-from PyQt5.QtGui import QColor, QIcon, QPainter, QPainterPath, QPaintEvent
-from PyQt5.QtWidgets import QPushButton, QWidget
+from PyQt5.QtCore import (
+ QEvent,
+ QObject,
+ QPointF,
+ QRect,
+ QRectF,
+ QSize,
+ Qt,
+ QTimer,
+ pyqtProperty,
+ pyqtSignal,
+)
+from PyQt5.QtGui import (
+ QColor,
+ QFont,
+ QFontMetrics,
+ QIcon,
+ QLinearGradient,
+ QPainter,
+ QPainterPath,
+ QPaintEvent,
+ QPen,
+ QPixmap,
+)
+from PyQt5.QtSvg import QSvgRenderer
+from PyQt5.QtWidgets import QLabel, QPushButton, QRadioButton
+from typing_extensions import Self
-from siui.core import GlobalFont, SiColor, SiExpAnimation, SiGlobal
+from siui.core import GlobalFont, SiGlobal, createPainter
+from siui.core.animation import SiExpAnimationRefactor
from siui.gui import SiFont
+if TYPE_CHECKING:
+ from siui.typing import T_WidgetParent
+
+
+class SiStyleAttr:
+ Font = "font"
+ TextColor = "text_color"
+ IdleColor = "idle_color"
+ HoverColor = "hover_color"
+ ClickColor = "click_color"
+ ButtonColor = "button_color"
+ ProgressColor = "progress_color"
+ CompleteColor = "complete_color"
+ BackgroundColor = "background_color"
+ ToggledTextColor = "toggled_text_color"
+ ToggledButtonColor = "toggled_button_color"
+ BorderInnerRadius = "border_inner_radius"
+ BorderRadius = "border_radius"
+ BorderHeight = "border_height"
+ IconTextGap = "icon_text_gap"
+
+
+SA = SiStyleAttr
+
+
+class GlobalStyleManager:
+ class Theme:
+ Bright = 0
+ Dark = 1
+
+ style_dark = {
+ "Button": {
+ "default": {
+ SA.Font: SiFont.tokenized(GlobalFont.S_NORMAL),
+ SA.TextColor: QColor("#DFDFDF"),
+ SA.IdleColor: QColor("#00baadc7"),
+ SA.HoverColor: QColor("#1abaadc7"),
+ SA.ClickColor: QColor("#50baadc7"),
+ SA.ButtonColor: QColor("#4C4554"),
+ SA.ProgressColor: QColor("#806799"),
+ SA.CompleteColor: QColor("#519868"),
+ SA.BackgroundColor: QColor("#2d2932"),
+ SA.ToggledTextColor: QColor("#DFDFDF"),
+ SA.ToggledButtonColor: QColor("#519868"),
+ SA.BorderInnerRadius: 5,
+ SA.BorderRadius: 7,
+ SA.BorderHeight: 3,
+ SA.IconTextGap: 4,
+ },
+ "LongPressButtonStyleData": {
+ SA.ProgressColor: QColor("#DA3462"),
+ SA.ButtonColor: QColor("#932a48"),
+ SA.BackgroundColor: QColor("#642d41"),
+ SA.ClickColor: QColor("#40FFFFFF"),
+ },
+ "FlatButtonStyleData": {SA.ButtonColor: QColor("#004C4554")},
+ "ToggleButtonStyleData": {
+ SA.ButtonColor: QColor("#004C4554"),
+ },
+ "PushButtonStyleData": {},
+ "ProgressPushButtonStyleData": {},
+ }
+ }
+
+ def updateWidgetStyleData(self, widget: QObject) -> None:
+ try:
+ instance = widget.style_data
+ except NameError:
+ return
+ self.updateStyleData(instance)
+
+ def updateStyleData(self, instance: QObject) -> None:
+ style_dict = self.style_dark
+ instance_class_name = instance.__class__.__name__
+ for type_name, type_dict in zip(style_dict.keys(), style_dict.values()):
+ if type_name not in instance.STYLE_TYPES:
+ continue
+
+ for class_name, class_dict in zip(type_dict.keys(), type_dict.values()):
+ if instance_class_name == class_name or class_name == "default":
+ self._setAttribute(instance, class_dict)
+
+ @staticmethod
+ def _setAttribute(instance, class_dict: dict) -> None:
+ for attr_name, value in zip(class_dict.keys(), class_dict.values()):
+ setattr(instance, attr_name, value)
+
@dataclass
-class PushButtonStyleData:
- idle_color = SiColor.toArray("#00FFFFFF")
- hover_color = SiColor.toArray("#10FFFFFF")
- click_color = SiColor.toArray("#40FFFFFF")
- background_color = SiColor.toArray("#2d2932", "rgba")
- button_color = SiColor.toArray("#4C4554", "rgba")
- border_radius: int = 4
- border_inner_radius: int = 3
- border_height: int = 3
-
-
-class SiPushButtonRefactor(QPushButton):
- def __init__(self, parent: QWidget | None = None) -> None:
+class ButtonStyleData(QObject):
+ STYLE_TYPES = ["Button"]
+
+ font: QFont
+
+ text_color: QColor
+ idle_color: QColor
+ hover_color: QColor
+ click_color: QColor
+ button_color: QColor
+ progress_color: QColor
+ complete_color: QColor
+ background_color: QColor
+ toggled_text_color: QColor
+ toggled_button_color: QColor
+
+ border_inner_radius: int
+ border_radius: int
+ border_height: int
+ icon_text_gap: int
+
+ def __init__(self):
+ super().__init__()
+ GlobalStyleManager().updateStyleData(self)
+
+
+@dataclass(init=False)
+class FlatButtonStyleData(ButtonStyleData):
+ pass
+
+
+@dataclass(init=False)
+class PushButtonStyleData(ButtonStyleData):
+ pass
+
+
+@dataclass(init=False)
+class ProgressPushButtonStyleData(PushButtonStyleData):
+ pass
+
+
+@dataclass(init=False)
+class LongPressButtonStyleData(PushButtonStyleData):
+ pass
+
+
+@dataclass(init=False)
+class ToggleButtonStyleData(ButtonStyleData):
+ pass
+
+
+class ABCButton(QPushButton):
+ class Property:
+ ScaleFactor = "scaleFactor"
+ TextColor = "textColor"
+ ButtonRectColor = "buttonRectColor"
+ ProgressRectColor = "progressRectColor"
+ HighlightRectColor = "highlightRectColor"
+ BackgroundRectColor = "backgroundRectColor"
+ Progress = "progress"
+
+ def __init__(self, parent: T_WidgetParent = None) -> None:
super().__init__(parent)
- self.style_data = PushButtonStyleData()
- self._initStyle()
+ self.style_data = None
+ self._progress = 0
+ self._scale_factor = 1
+ self._highlight_rect_color = QColor("#00FFFFFF")
+ self._progress_rect_color = None
+ self._background_rect_color = None
+ self._button_rect_color = None
+ self._text_color = None
- self.animation = SiExpAnimation(self)
- self.animation.setFactor(1/8)
- self.animation.setBias(0.2)
- self.animation.setTarget(self.style_data.idle_color)
- self.animation.setCurrent(self.style_data.idle_color)
- self.animation.ticked.connect(self.animate)
+ self.highlight_ani = SiExpAnimationRefactor(self, self.Property.HighlightRectColor)
+ self.highlight_ani.init(1 / 8, 0.2, self._highlight_rect_color, self._highlight_rect_color)
self.clicked.connect(self._onButtonClicked)
- def _initStyle(self):
- self.setFont(SiFont.tokenized(GlobalFont.S_NORMAL))
- self.setStyleSheet("color: #DFDFDF;")
+ @pyqtProperty(float)
+ def scaleFactor(self):
+ return self._scale_factor
+
+ @scaleFactor.setter
+ def scaleFactor(self, value: float):
+ self._scale_factor = value
+ self.update()
+
+ @pyqtProperty(QColor)
+ def highlightRectColor(self):
+ return self._highlight_rect_color
+
+ @highlightRectColor.setter
+ def highlightRectColor(self, value: QColor):
+ self._highlight_rect_color = value
+ self.update()
+
+ @pyqtProperty(QColor)
+ def buttonRectColor(self):
+ return self._button_rect_color
+
+ @buttonRectColor.setter
+ def buttonRectColor(self, color: QColor):
+ self._button_rect_color = color
+ self.update()
+
+ @pyqtProperty(QColor)
+ def backgroundRectColor(self):
+ return self._background_rect_color
+
+ @backgroundRectColor.setter
+ def backgroundRectColor(self, color: QColor):
+ self._background_rect_color = color
+ self.update()
+
+ @pyqtProperty(QColor)
+ def textColor(self):
+ return self._text_color
+
+ @textColor.setter
+ def textColor(self, color: QColor):
+ self._text_color = color
+ self.update()
+
+ @pyqtProperty(float)
+ def progress(self):
+ return self._progress
+
+ @progress.setter
+ def progress(self, value: float):
+ self._progress = max(0.0, min(value, 1.0))
+ self.update()
+
+ @pyqtProperty(QColor)
+ def progressRectColor(self):
+ return self._progress_rect_color
+
+ @progressRectColor.setter
+ def progressRectColor(self, value: QColor):
+ self._progress_rect_color = value
+ self.update()
+
+ def styleData(self) -> PushButtonStyleData:
+ return self.style_data
+
+ def reloadStyleData(self):
+ raise NotImplementedError()
+
+ def _onButtonClicked(self) -> None:
+ raise NotImplementedError()
+
+ def flash(self) -> None:
+ self.highlight_ani.setCurrentValue(self.style_data.click_color)
+ self.highlight_ani.start()
+
+ def setToolTip(self, tooltip: str) -> None:
+ super().setToolTip(tooltip)
+ self._updateToolTip()
+
+ def setIconTextGap(self, gap: int) -> None:
+ self.style_data.icon_text_gap = gap
+ self.update()
+
+ def setSvgIcon(self, svg_data: bytes) -> None:
+ pixmap = QPixmap(64, 64)
+ pixmap.fill(Qt.transparent)
+ painter = QPainter(pixmap)
+ painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
+ svg_renderer = QSvgRenderer(svg_data)
+ svg_renderer.render(painter)
+ painter.end()
+ self.setIcon(QIcon(pixmap))
+ self.update()
+
+ def sizeHint(self) -> QSize:
+ font_metrics = QFontMetrics(self.font())
+ text_width = font_metrics.width(self.text())
+ text_height = font_metrics.height()
+ icon_width = self.iconSize().width() if not self.icon().isNull() else 0
+ icon_height = self.iconSize().height() if not self.icon().isNull() else 0
+ gap = self.style_data.icon_text_gap if text_width > 0 and icon_width > 0 else 0
+
+ preferred_width = text_width + icon_width + gap + 32
+ preferred_height = max(32, text_height, icon_height)
+ return QSize(preferred_width, preferred_height)
+
+ def _showToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(self)
+ tool_tip_window.show_()
+
+ def _hideToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(None)
+ tool_tip_window.hide_()
+
+ def _updateToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and tool_tip_window.nowInsideOf() == self:
+ tool_tip_window.setText(self.toolTip())
+
+ def event(self, event):
+ if event.type() == QEvent.ToolTip:
+ return True # 忽略工具提示事件
+ return super().event(event)
+
+ def enterEvent(self, a0):
+ super().enterEvent(a0)
+ self._showToolTip()
+ self._updateToolTip()
+
+ def leaveEvent(self, a0):
+ super().leaveEvent(a0)
+ self._hideToolTip()
+
+
+class SiPushButtonRefactor(ABCButton):
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+
+ self.style_data = PushButtonStyleData()
+ self._initStyle()
+ self._scale_factor = 1
+
+ self.scale_factor_ani = SiExpAnimationRefactor(self, self.Property.ScaleFactor)
+ self.scale_factor_ani.init(1 / 16, 0, 1, 1)
@classmethod
- def withText(cls, text: str, parent: QWidget | None = None) -> "SiPushButton":
+ def withText(cls, text: str, parent: T_WidgetParent = None) -> Self:
obj = cls(parent)
obj.setText(text)
return obj
@classmethod
- def withIcon(cls, icon: QIcon, parent: QWidget | None = None) -> "SiPushButton":
+ def withIcon(cls, icon: QIcon, parent: T_WidgetParent = None) -> Self:
obj = cls(parent)
obj.setIcon(icon)
return obj
@classmethod
- def withTextAndIcon(cls, text: str, icon: str, parent: QWidget | None = None) -> "SiPushButton":
+ def withTextAndIcon(cls, text: str, icon: str, parent: T_WidgetParent = None) -> Self:
obj = cls(parent)
obj.setText(text)
obj.setIcon(QIcon(icon))
return obj
+ def _initStyle(self):
+ self.setFont(self.style_data.font)
+ self.setIconSize(QSize(20, 20))
+
@property
def bottomBorderHeight(self) -> int:
return self.style_data.border_height
- @property
- def styleData(self) -> PushButtonStyleData:
- return self.style_data
+ def reloadStyleData(self) -> None:
+ self.update()
def _drawBackgroundPath(self, rect: QRect) -> QPainterPath:
radius = self.style_data.border_radius
@@ -78,7 +391,7 @@ def _drawBackgroundPath(self, rect: QRect) -> QPainterPath:
return path
def _drawBackgroundRect(self, painter: QPainter, rect: QRect) -> None:
- painter.setBrush(QColor(*self.style_data.background_color))
+ painter.setBrush(self.style_data.background_color)
painter.drawPath(self._drawBackgroundPath(rect))
def _drawButtonPath(self, rect: QRect) -> QPainterPath:
@@ -88,97 +401,849 @@ def _drawButtonPath(self, rect: QRect) -> QPainterPath:
return path
def _drawButtonRect(self, painter: QPainter, rect: QRect) -> None:
- painter.setBrush(QColor(*self.style_data.button_color))
+ painter.setBrush(self.style_data.button_color)
painter.drawPath(self._drawButtonPath(rect))
def _drawHighLightRect(self, painter: QPainter, rect: QRect) -> None:
- painter.setBrush(QColor(SiColor.toCode(self.animation.current_)))
+ painter.setBrush(self._highlight_rect_color) # use property variable
painter.drawPath(self._drawButtonPath(rect))
def _drawTextRect(self, painter: QPainter, rect: QRect) -> None:
- painter.setPen(self.palette().text().color())
+ painter.setPen(self.style_data.text_color)
painter.setFont(self.font())
painter.drawText(rect, Qt.AlignCenter, self.text())
+ def _drawPixmapRect(self, painter: QPainter, rect: QRectF) -> None:
+ painter.drawPixmap(rect, self.icon().pixmap(64, 64))
+
def _onButtonClicked(self) -> None:
- self.animation.setCurrent(self.style_data.click_color)
- self.animation.start()
+ self.flash()
- def _showToolTip(self) -> None:
- tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
- if tool_tip_window is not None and self.toolTip() != "":
- tool_tip_window.setNowInsideOf(self)
- tool_tip_window.show_()
+ def textRectAndIconRect(self) -> (QRectF, QRect):
+ font_metrics = QFontMetrics(self.font())
+ text_width = font_metrics.width(self.text())
+ icon_width = self.iconSize().width() if not self.icon().isNull() else 0
+ icon_height = self.iconSize().height() if not self.icon().isNull() else 0
+ gap = self.style_data.icon_text_gap if text_width > 0 and icon_width > 0 else 0
- def _hideToolTip(self) -> None:
- tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
- if tool_tip_window is not None and self.toolTip() != "":
- tool_tip_window.setNowInsideOf(None)
- tool_tip_window.hide_()
+ text_rect = QRectF(
+ (self.width() - icon_width - text_width - gap) / 2 + icon_width + gap,
+ 0,
+ text_width,
+ self.height() - self.style_data.border_height - 1,
+ )
+ pixmap_rect = QRect(
+ (self.width() - icon_width - text_width - gap) // 2,
+ ((self.height() - self.style_data.border_height) - icon_height) // 2,
+ icon_width,
+ icon_height,
+ )
- def _updateToolTip(self) -> None:
- tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
- if tool_tip_window is not None and tool_tip_window.nowInsideOf() == self:
- tool_tip_window.setText(self.toolTip())
+ return text_rect, pixmap_rect
- def animate(self, _) -> None:
- self.update()
+ def mousePressEvent(self, e) -> None:
+ super().mousePressEvent(e)
+ self.scale_factor_ani.setFactor(1 / 16)
+ self.scale_factor_ani.setBias(0)
+ self.scale_factor_ani.setEndValue(0.9)
+ self.scale_factor_ani.start()
- def setToolTip(self, tooltip) -> None:
- super().setToolTip(tooltip)
+ def mouseReleaseEvent(self, e) -> None:
+ super().mouseReleaseEvent(e)
+ self.scale_factor_ani.setFactor(1 / 4)
+ self.scale_factor_ani.setBias(0.001)
+ self.scale_factor_ani.setEndValue(1)
+ self.scale_factor_ani.start()
+
+ def enterEvent(self, event) -> None:
+ super().enterEvent(event)
+ self.highlight_ani.setEndValue(self.style_data.hover_color)
+ self.highlight_ani.start()
+ self._showToolTip()
self._updateToolTip()
- def setButtonColor(self, code: str) -> None:
- self.style_data.button_color = SiColor.toArray(code, "rgba")
- self.update()
+ def leaveEvent(self, event) -> None:
+ super().leaveEvent(event)
+ self.highlight_ani.setEndValue(self.style_data.idle_color)
+ self.highlight_ani.start()
+ self._hideToolTip()
+
+ def paintEvent(self, event: QPaintEvent) -> None:
+ rect = self.rect()
+ text_rect, icon_rect = self.textRectAndIconRect()
+ device_pixel_ratio = self.devicePixelRatioF()
+ renderHints = (
+ QPainter.RenderHint.SmoothPixmapTransform
+ | QPainter.RenderHint.TextAntialiasing
+ | QPainter.RenderHint.Antialiasing
+ )
+
+ 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)
- def setBackgroundColor(self, code: str) -> None:
- self.style_data.background_color = SiColor.toArray(code, "rgba")
+ a = self._scale_factor
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
+ painter.translate(QPointF(rect.width() * (1 - a) / 2, rect.height() * (1 - a) / 2))
+ painter.scale(a, a)
+
+ painter.drawPixmap(0, 0, buffer)
+
+
+class SiProgressPushButton(SiPushButtonRefactor):
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+
+ self.style_data = ProgressPushButtonStyleData()
+ self._progress_rect_color = self.style_data.progress_color
+ self._progress = 0
+
+ self.progress_ani = SiExpAnimationRefactor(self, self.Property.Progress)
+ self.progress_ani.init(1 / 6, 0.005, 0, 0)
+
+ self.progress_color_ani = SiExpAnimationRefactor(self, self.Property.ProgressRectColor)
+ self.progress_color_ani.init(1 / 8, 0.01, self._progress_rect_color, self._progress_rect_color)
+
+ def setProgress(self, p: float, ani: bool = True) -> None:
+ self._progress = max(0.0, min(p, 1.0))
+ self._updateProgress(ani)
+ self._updateCompleteState()
+
+ def reloadStyleData(self) -> None:
+ self._updateCompleteState()
self.update()
- def setBorderRadius(self, r: int) -> None:
- self.style_data.border_radius = r
+ def _updateProgress(self, ani: bool = True) -> None:
+ if ani is True:
+ self.progress_ani.setEndValue(self._progress)
+ self.progress_ani.start()
+ else:
+ self.progress_ani.setEndValue(self._progress)
+ self.progress_ani.setCurrentValue(self._progress)
+ self.progress_ani.stop()
+
+ def _updateCompleteState(self) -> None:
+ if self.progress_ani.endValue() == 1.0:
+ self.progress_color_ani.setEndValue(self.style_data.complete_color)
+ self.progress_color_ani.start()
+ else:
+ self.progress_color_ani.setEndValue(self.style_data.progress_color)
+ self.progress_color_ani.start()
+
+ def _drawButtonRect(self, painter: QPainter, rect: QRect) -> None:
+ p = min(self._progress, 1) # prevent progress exceeding caused by using animation.
+ gradient = QLinearGradient(rect.left(), rect.top(), rect.right(), rect.top())
+ gradient.setColorAt(p - 0.0001, self._progress_rect_color) # use property variable
+ gradient.setColorAt(p, self.style_data.button_color)
+ painter.setBrush(gradient)
+ painter.drawPath(self._drawButtonPath(rect))
+
+
+class SiLongPressButtonRefactor(SiPushButtonRefactor):
+ longPressed = pyqtSignal()
+
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+
+ self.style_data = LongPressButtonStyleData()
+ self._progress = 0
+
+ self.progress_ani = SiExpAnimationRefactor(self, self.Property.Progress)
+ self.progress_ani.init(-1 / 16, 0.12, 0, 0)
+
+ self.go_backwards_timer = QTimer(self)
+ self.go_backwards_timer.setSingleShot(True)
+ self.go_backwards_timer.setInterval(500)
+ self.go_backwards_timer.timeout.connect(self._goBackwards)
+
+ self.mouse_pressed_timer = QTimer(self)
+ self.mouse_pressed_timer.setInterval(1000 // 60)
+ self.mouse_pressed_timer.timeout.connect(self._onMousePressed)
+
+ def setProgress(self, p: float, ani: bool = True) -> None:
+ self._progress = max(0.0, min(p, 1.0))
+ self._updateProgress(ani)
+ self.progress_ani.update()
self.update()
- def setBorderInnerRadius(self, r: int) -> None:
- self.style_data.border_inner_radius = r
+ def reloadStyleData(self) -> None:
self.update()
- def setBorderHeight(self, h: int) -> None:
- self.style_data.border_height = h
+ def _stepLength(self) -> float:
+ return (1 - self._progress) / 16 + 0.001
+
+ def _onMousePressed(self) -> None:
+ self.setProgress(self._progress + self._stepLength(), ani=False)
+
+ def _onButtonClicked(self) -> None:
+ pass # disable flashes on mouse click
+
+ def _updateProgress(self, ani: bool) -> None:
+ if ani is True:
+ self.progress_ani.setEndValue(self._progress)
+ self.progress_ani.start()
+ else:
+ self.progress_ani.setEndValue(self._progress)
+ self.progress_ani.setCurrentValue(self._progress)
+ self.progress_ani.stop()
+
+ if self._progress == 1.0:
+ self.mouse_pressed_timer.stop()
+ self.go_backwards_timer.stop()
+ self.longPressed.emit()
+ self._onLongPressed()
+ self._goBackwards(200)
+
+ def _onLongPressed(self) -> None:
+ self.highlight_ani.setCurrentValue(self.style_data.click_color)
+ self.highlight_ani.start()
+
+ def _goBackwards(self, delay: int = 0) -> None:
+ self.progress_ani.setEndValue(0)
+ self.progress_ani.startAfter(delay)
+
+ def _drawButtonRect(self, painter: QPainter, rect: QRect) -> None:
+ p = min(self._progress, 1) # prevent progress exceeding caused by using animation.
+ gradient = QLinearGradient(rect.left(), rect.top(), rect.right(), rect.top())
+ gradient.setColorAt(p - 0.0001, self.style_data.progress_color)
+ gradient.setColorAt(p, self.style_data.button_color)
+ painter.setBrush(gradient)
+ painter.drawPath(self._drawButtonPath(rect))
+
+ def mousePressEvent(self, e) -> None:
+ super().mousePressEvent(e)
+ if self.progress_ani.state() != self.progress_ani.State.Running and not self.mouse_pressed_timer.isActive():
+ self.mouse_pressed_timer.start()
+ self.go_backwards_timer.stop()
+
+ def mouseReleaseEvent(self, e) -> None:
+ super().mouseReleaseEvent(e)
+ self.mouse_pressed_timer.stop()
+ self.go_backwards_timer.start()
+
+
+class SiFlatButton(ABCButton):
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+
+ self.style_data = FlatButtonStyleData()
+ self._initStyle()
+ self._scale_factor = 1
+
+ self.scale_factor_ani = SiExpAnimationRefactor(self, self.Property.ScaleFactor)
+ self.scale_factor_ani.init(1 / 16, 0, 1, 1)
+
+ def _initStyle(self):
+ self.setFont(self.style_data.font)
+ self.setIconSize(QSize(20, 20))
+
+ def reloadStyleData(self) -> None:
self.update()
- def event(self, event):
- if event.type() == QEvent.ToolTip:
- return True # 忽略工具提示事件
- return super().event(event)
+ def _drawButtonPath(self, rect: QRect) -> QPainterPath:
+ radius = self.style_data.border_radius
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(0, 0, rect.width(), rect.height()), radius, radius)
+ return path
+
+ def _drawButtonRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self.style_data.button_color)
+ painter.drawPath(self._drawButtonPath(rect))
+
+ def _drawHighLightRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self._highlight_rect_color) # use property variable
+ painter.drawPath(self._drawButtonPath(rect))
+
+ def _drawTextRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setPen(self.style_data.text_color)
+ painter.setFont(self.font())
+ painter.drawText(rect, Qt.AlignCenter, self.text())
+
+ def _drawPixmapRect(self, painter: QPainter, rect: QRectF) -> None:
+ painter.drawPixmap(rect, self.icon().pixmap(64, 64))
+
+ def textRectAndIconRect(self) -> (QRectF, QRect):
+ font_metrics = QFontMetrics(self.font())
+ text_width = font_metrics.width(self.text())
+ icon_width = self.iconSize().width() if not self.icon().isNull() else 0
+ icon_height = self.iconSize().height() if not self.icon().isNull() else 0
+ gap = self.style_data.icon_text_gap if text_width > 0 and icon_width > 0 else 0
+
+ text_rect = QRectF(
+ (self.width() - icon_width - text_width - gap) / 2 + icon_width + gap, 0, text_width, self.height()
+ )
+ pixmap_rect = QRect(
+ (self.width() - icon_width - text_width - gap) // 2,
+ (self.height() - icon_height) // 2,
+ icon_width,
+ icon_height,
+ )
+
+ return text_rect, pixmap_rect
+
+ def _onButtonClicked(self) -> None:
+ self.highlight_ani.setCurrentValue(self.style_data.click_color)
+ self.highlight_ani.start()
+
+ def paintEvent(self, event: QPaintEvent) -> None:
+ rect = self.rect()
+ text_rect, icon_rect = self.textRectAndIconRect()
+ 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)
+
+ self._drawButtonRect(buffer_painter, rect)
+ self._drawHighLightRect(buffer_painter, rect)
+ self._drawPixmapRect(buffer_painter, icon_rect)
+ self._drawTextRect(buffer_painter, text_rect)
+ buffer_painter.end()
+
+ a = self._scale_factor
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
+ painter.translate(QPointF(rect.width() * (1 - a) / 2, rect.height() * (1 - a) / 2))
+ painter.scale(a, a)
+
+ painter.drawPixmap(0, 0, buffer)
def enterEvent(self, event) -> None:
super().enterEvent(event)
- self.animation.setTarget(self.style_data.hover_color)
- self.animation.start()
+ self.highlight_ani.setEndValue(self.style_data.hover_color)
+ self.highlight_ani.start()
self._showToolTip()
self._updateToolTip()
def leaveEvent(self, event) -> None:
super().leaveEvent(event)
- self.animation.setTarget(self.style_data.idle_color)
- self.animation.start()
+ self.highlight_ani.setEndValue(self.style_data.idle_color)
+ self.highlight_ani.start()
self._hideToolTip()
- def resizeEvent(self, event) -> None:
- super().resizeEvent(event)
+ def mousePressEvent(self, e):
+ super().mousePressEvent(e)
+ self.scale_factor_ani.setFactor(1 / 16)
+ self.scale_factor_ani.setBias(0)
+ self.scale_factor_ani.setEndValue(0.9)
+ self.scale_factor_ani.start()
- def paintEvent(self, event: QPaintEvent) -> None:
+ def mouseReleaseEvent(self, e):
+ super().mouseReleaseEvent(e)
+ self.scale_factor_ani.setFactor(1 / 4)
+ self.scale_factor_ani.setBias(0.001)
+ self.scale_factor_ani.setEndValue(1)
+ self.scale_factor_ani.start()
+
+
+class SiToggleButtonRefactor(SiFlatButton):
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+ self.setCheckable(True)
+
+ self.style_data = ToggleButtonStyleData()
+ self._button_rect_color = self.style_data.button_color
+ self._text_color = self.style_data.text_color
+
+ self.toggle_btn_color_ani = SiExpAnimationRefactor(self, self.Property.ButtonRectColor)
+ self.toggle_btn_color_ani.init(1 / 8, 0.01, self._button_rect_color, self._button_rect_color)
+
+ self.toggle_text_color_ani = SiExpAnimationRefactor(self, self.Property.TextColor)
+ self.toggle_text_color_ani.init(1 / 8, 0.01, self._text_color, self._text_color)
+
+ self.toggled.connect(self._onButtonToggled)
+
+ def reloadStyleData(self) -> None:
+ self._onButtonToggled(self.isChecked())
+ self.update()
+
+ def _onButtonToggled(self, state: bool) -> None:
+ if state:
+ self.toggle_btn_color_ani.setEndValue(self.style_data.toggled_button_color)
+ self.toggle_text_color_ani.setEndValue(self.style_data.toggled_text_color)
+ self.toggle_btn_color_ani.start()
+ self.toggle_text_color_ani.start()
+ else:
+ self.toggle_btn_color_ani.setEndValue(self.style_data.button_color)
+ self.toggle_text_color_ani.setEndValue(self.style_data.text_color)
+ self.toggle_btn_color_ani.start()
+ self.toggle_text_color_ani.start()
+
+ def _drawButtonRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self._button_rect_color) # use property variable
+ painter.drawPath(self._drawButtonPath(rect))
+
+ def _drawTextRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setPen(self._text_color) # use property variable
+ painter.setFont(self.font())
+ painter.drawText(rect, Qt.AlignCenter, self.text())
+
+
+@dataclass
+class SwitchStyleData(QObject):
+ STYLE_TYPES = ["Switch"]
+
+ background_color_starting: QColor = QColor("#a681bf")
+ background_color_ending: QColor = QColor("#a681bf")
+ frame_color: QColor = QColor("#D2D2D2")
+ thumb_color_checked: QColor = QColor("#0f0912")
+ thumb_color_unchecked: QColor = QColor("#D2D2D2")
+
+
+class SiSwitchRefactor(QPushButton):
+ class Property:
+ Progress = "progress"
+ ScaleFactor = "scaleFactor"
+
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+ self.setCheckable(True)
+
+ self.style_data = SwitchStyleData()
+ self._progress = 0
+ self._scale_factor = 1
+
+ self._initStyle()
+
+ self.scale_factor_ani = SiExpAnimationRefactor(self, self.Property.ScaleFactor)
+ self.scale_factor_ani.init(1 / 16, 0, 1, 1)
+
+ self.progress_ani = SiExpAnimationRefactor(self, self.Property.Progress)
+ self.progress_ani.init(1 / 4, 0.01, 0, 0)
+
+ self.clicked.connect(self._onClicked)
+
+ def _initStyle(self) -> None:
+ self.setFixedSize(40, 20)
+
+ @pyqtProperty(float)
+ def scaleFactor(self):
+ return self._scale_factor
+
+ @scaleFactor.setter
+ def scaleFactor(self, value: float):
+ self._scale_factor = value
+ self.update()
+
+ @pyqtProperty(float)
+ def progress(self) -> float:
+ return self._progress
+
+ @progress.setter
+ def progress(self, value: float) -> None:
+ self._progress = value
+ self.update()
+
+ def _onClicked(self):
+ if self.isChecked():
+ self.progress_ani.setEndValue(1)
+ self.progress_ani.start()
+ else:
+ self.progress_ani.setEndValue(0)
+ self.progress_ani.start()
+
+ def _drawBackgroundPath(self, rect: QRect) -> QPainterPath:
+ radius = rect.height() / 2
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(0, 0, rect.width(), rect.height()), radius, radius)
+ return path
+
+ def _drawFramePath(self, rect: QRect) -> QPainterPath:
+ width = 0.5
+ radius = rect.height() / 2
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(width, width, rect.width() - 2 * width, rect.height() - 2 * width), radius, radius)
+ return path
+
+ def _drawThumbPath(self, rect: QRect) -> QPainterPath:
+ p = self._progress
+ radius = rect.height() / 2 - 3
+ width = radius * 2 + (p * (1 - p)) ** 1 * 16
+ height = radius * 2
+ track_length = rect.width() - width - 6
+ x = 3 + track_length * p
+ y = 3
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(x, y, width, height), radius, radius)
+ return path
+
+ def _drawBackgroundRect(self, painter: QPainter, rect: QRect) -> None:
+ if self._progress > 0.5:
+ gradient = QLinearGradient(0, 0, rect.width(), rect.height())
+ gradient.setColorAt(0, self.style_data.background_color_starting)
+ gradient.setColorAt(1, self.style_data.background_color_ending)
+
+ painter.setBrush(gradient)
+ painter.drawPath(self._drawBackgroundPath(rect))
+
+ def _drawFrameRect(self, painter: QPainter, rect: QRect) -> None:
+ if self._progress <= 0.5:
+ pen = QPen(self.style_data.frame_color)
+ pen.setWidth(1)
+ painter.setPen(pen)
+ painter.drawPath(self._drawFramePath(rect))
+ painter.setPen(Qt.NoPen)
+
+ def _drawThumbRect(self, painter: QPainter, rect: QRect) -> None:
+ color = self.style_data.thumb_color_checked if self._progress > 0.5 else self.style_data.thumb_color_unchecked
+ painter.setBrush(color)
+ painter.drawPath(self._drawThumbPath(rect))
+
+ def paintEvent(self, a0) -> None:
+ rect = self.rect()
+ 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)
+
+ self._drawBackgroundRect(buffer_painter, rect)
+ self._drawFrameRect(buffer_painter, rect)
+ self._drawThumbRect(buffer_painter, rect)
+ buffer_painter.end()
+
+ a = self._scale_factor
painter = QPainter(self)
+ painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
- painter.setRenderHint(QPainter.TextAntialiasing)
+ painter.translate(QPointF(rect.width() * (1 - a) / 2, rect.height() * (1 - a) / 2))
+ painter.scale(a, a)
+
+ painter.drawPixmap(0, 0, buffer)
+
+ def mousePressEvent(self, e):
+ super().mousePressEvent(e)
+ self.scale_factor_ani.setFactor(1 / 16)
+ self.scale_factor_ani.setBias(0)
+ self.scale_factor_ani.setEndValue(0.9)
+ self.scale_factor_ani.start()
+
+ def mouseReleaseEvent(self, e):
+ super().mouseReleaseEvent(e)
+ self.scale_factor_ani.setFactor(1 / 4)
+ 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 = 2
+ 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("#90baadc7")
+ highlight_hover_color: QColor = QColor("#40baadc7")
+
+ unchecked_indicator_color: QColor = QColor("#25222A")
+ unchecked_indicator_width: float = 33
+
+ checked_indicator_color: QColor = QColor("#a681bf")
+ 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 * ((self._indi_width_prog + 1) / 2))
+ 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))
- painter.setPen(Qt.PenStyle.NoPen)
+ 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()
- text_rect = QRect(0, 0, self.width(), self.height() - self.style_data.border_height - 1)
- self._drawBackgroundRect(painter, rect)
- self._drawButtonRect(painter, rect)
- self._drawHighLightRect(painter, rect)
- self._drawTextRect(painter, text_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)
diff --git a/siui/components/label.py b/siui/components/label.py
new file mode 100644
index 0000000..d69f8fc
--- /dev/null
+++ b/siui/components/label.py
@@ -0,0 +1,100 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import numpy
+from PyQt5.QtCore import QEvent
+from PyQt5.QtWidgets import QLabel, QWidget
+
+from siui.core import SiColor, SiGlobal
+
+
+@dataclass
+class SiLabelStyleData:
+ text_color = SiColor.toArray("#00FFFFFF")
+ background_color = SiColor.toArray("#00FFFFFF")
+ border_bottom_left_radius: int = 4
+ border_bottom_right_radius: int = 4
+ border_top_left_radius: int = 4
+ border_top_right_radius: int = 4
+
+
+class SiLabelRefactor(QLabel):
+ def __init__(self, parent: QWidget | None = None) -> None:
+ super().__init__(parent)
+
+ self.style_data = SiLabelStyleData()
+
+ @property
+ def textColor(self) -> numpy.ndarray:
+ return self.style_data.text_color
+
+ def backgroundColor(self) -> numpy.ndarray:
+ return self.style_data.background_color
+
+ def borderRadius(self) -> tuple:
+ return (self.style_data.border_bottom_left_radius, self.style_data.border_bottom_right_radius,
+ self.style_data.border_top_left_radius, self.style_data.border_top_right_radius)
+
+ def setTextColor(self, code: str | tuple) -> None:
+ self.style_data.text_color = SiColor.toArray(code)
+ self.update()
+
+ def setBackgroundColor(self, code: str | tuple) -> None:
+ self.style_data.background_color = SiColor.toArray(code)
+ self.update()
+
+ def setBorderRadius(self, *radius: int):
+ """
+ set the border radius of this label.
+ accepts 1 or 4 param(s).
+ """
+ if len(radius) == 1:
+ self.style_data.border_bottom_left_radius = radius[0]
+ self.style_data.border_bottom_right_radius = radius[0]
+ self.style_data.border_top_left_radius = radius[0]
+ self.style_data.border_top_right_radius = radius[0]
+ elif len(radius) == 4:
+ self.style_data.border_bottom_left_radius = radius[0]
+ self.style_data.border_bottom_right_radius = radius[1]
+ self.style_data.border_top_left_radius = radius[2]
+ self.style_data.border_top_right_radius = radius[3]
+ else:
+ raise ValueError(f"setBorderRadius expects 1 or 4 param, but {len(radius)} are given.")
+ self.update()
+
+ def event(self, event):
+ if event.type() == QEvent.ToolTip:
+ return True # 忽略工具提示事件
+ return super().event(event)
+
+ def _showToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(self)
+ tool_tip_window.show_()
+
+ def _hideToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(None)
+ tool_tip_window.hide_()
+
+ def _updateToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and tool_tip_window.nowInsideOf() == self:
+ tool_tip_window.setText(self.toolTip())
+
+ def setToolTip(self, tooltip) -> None:
+ super().setToolTip(tooltip)
+ self._updateToolTip()
+
+ def enterEvent(self, event) -> None:
+ super().enterEvent(event)
+ self._showToolTip()
+ self._updateToolTip()
+
+ def leaveEvent(self, event) -> None:
+ super().leaveEvent(event)
+ self._hideToolTip()
+
diff --git a/siui/components/slider_.py b/siui/components/slider_.py
new file mode 100644
index 0000000..05dca9a
--- /dev/null
+++ b/siui/components/slider_.py
@@ -0,0 +1,717 @@
+from dataclasses import dataclass
+
+from PyQt5.QtCore import QEvent, QPoint, QPointF, QRect, QRectF, Qt, pyqtProperty, QSize
+from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen, QPixmap
+from PyQt5.QtWidgets import QAbstractSlider, QWidget
+
+from siui.core import SiGlobal, createPainter
+from siui.core.animation import SiExpAnimationRefactor
+from siui.typing import T_WidgetParent
+
+
+@dataclass
+class SliderStyleData:
+ STYLE_TYPES = ["Slider"]
+
+ thumb_idle_color: QColor = QColor("#a681bf")
+ thumb_hover_color: QColor = QColor("#EDE1F4")
+ thumb_width: int = 52
+ thumb_height: int = 14
+
+ track_color: QColor = QColor("#77568d")
+ track_height: int = 5
+
+ background_color: QColor = QColor("#1C191F")
+
+
+class SiSlider(QAbstractSlider):
+ class Property:
+ ThumbColor = "thumbColor"
+ TrackProgress = "trackProgress"
+
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+
+ self.setMouseTracking(True)
+
+ self.style_data = SliderStyleData()
+ self._thumb_color = self.style_data.thumb_idle_color
+ self._track_progress = 0
+ self._is_dragging = False
+ self._is_dragging_thumb = False
+ self._dragging_start_pos = QPoint()
+ self._dragging_anchor_pos = QPoint()
+ self._value_to_tooltip_func = self._defaultValueToToolTip
+ self._is_draw_track = True
+
+ self.thumb_color_ani = SiExpAnimationRefactor(self, self.Property.ThumbColor)
+ self.thumb_color_ani.init(1/4, 0.01, self._thumb_color, self._thumb_color)
+
+ self.progress_ani = SiExpAnimationRefactor(self, self.Property.TrackProgress)
+ self.progress_ani.init(1/3.5, 0.00001, 0, 0)
+
+ self.valueChanged.connect(self._onValueChanged)
+ self.rangeChanged.connect(self._onRangeChanged)
+
+ @pyqtProperty(QColor)
+ def thumbColor(self):
+ return self._thumb_color
+
+ @thumbColor.setter
+ def thumbColor(self, value: QColor):
+ self._thumb_color = value
+ self.update()
+
+ @pyqtProperty(float)
+ def trackProgress(self):
+ return self._track_progress
+
+ @trackProgress.setter
+ def trackProgress(self, value: float):
+ self._track_progress = value
+ self.update()
+
+ def setDrawTrack(self, state: bool) -> None:
+ self._is_draw_track = state
+ self.update()
+
+ def isDrawTrack(self) -> bool:
+ return self._is_draw_track
+
+ @staticmethod
+ def _defaultValueToToolTip(value: int) -> str:
+ return str(value)
+
+ def setToolTipConvertionFunc(self, func) -> None:
+ self._value_to_tooltip_func = func
+ self.setToolTip(func(self.value()))
+
+ def _onValueChanged(self, value):
+ self.progress_ani.setEndValue((value - self.minimum()) / (self.maximum() - self.minimum()))
+ self.progress_ani.start()
+ self._updateToolTip(flash=False)
+
+ def _onRangeChanged(self, _, __):
+ p = (self.value() - self.minimum()) / (self.maximum() - self.minimum())
+ self.setProperty(self.Property.TrackProgress, p)
+ self.progress_ani.update()
+ self.progress_ani.setCurrentValue(p)
+ self.progress_ani.setEndValue(p)
+
+ def _drawBackgroundPath(self, rect) -> QPainterPath:
+ radius = min(rect.height() / 2, rect.width() / 2)
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(rect), radius, radius)
+ return path
+
+ def _drawTrackPath(self, rect: QRect) -> QPainterPath:
+ radius = min(rect.height() / 2, rect.width() / 2)
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(rect), radius, radius)
+ return path
+
+ def _drawThumbPath(self, rect: QRect) -> QPainterPath:
+ radius = min(rect.height() / 2, rect.width() / 2)
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(rect), radius, radius)
+ return path
+
+ def _drawBackgroundRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self.style_data.background_color)
+ painter.drawPath(self._drawBackgroundPath(rect))
+
+ def _drawTrackRect(self, painter: QPainter, rect: QRect) -> None:
+ if self._is_draw_track:
+ painter.setBrush(self.style_data.track_color)
+ painter.drawPath(self._drawTrackPath(rect))
+
+ def _drawThumbRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self._thumb_color)
+ painter.drawPath(self._drawThumbPath(rect))
+
+ def _isMouseInThumbRect(self, pos: QPoint) -> bool:
+ p = self._track_progress
+ thumb_w = self.style_data.thumb_width
+ thumb_h = self.style_data.thumb_height
+ if self.orientation() == Qt.Orientation.Horizontal:
+ thumb_rect = QRectF((self.width() - thumb_w) * p, (self.height() - thumb_h) / 2, thumb_w, thumb_h)
+ else:
+ thumb_rect = QRectF((self.width() - thumb_h) / 2, (self.height() - thumb_w) * (1 - p), thumb_h, thumb_w)
+ return thumb_rect.contains(pos)
+
+ def _setValueToMousePos(self, pos: QPoint) -> None:
+ thumb_width = self.style_data.thumb_width
+ region = self.maximum() - self.minimum()
+ if self.orientation() == Qt.Orientation.Horizontal:
+ p = min(1, max((pos.x() - thumb_width / 2) / (self.width() - thumb_width), 0))
+ else:
+ p = min(1, max(1 - (pos.y() - thumb_width / 2) / (self.height() - thumb_width), 0))
+ self.setValue(int(self.minimum() + region * p))
+
+ def _setThumbHovering(self, state: bool) -> None:
+ if state:
+ self.thumb_color_ani.setEndValue(self.style_data.thumb_hover_color)
+ self.thumb_color_ani.start()
+ else:
+ self.thumb_color_ani.setEndValue(self.style_data.thumb_idle_color)
+ self.thumb_color_ani.start()
+
+ def _updateDraggingAnchor(self):
+ p = (self.value() - self.minimum()) / (self.maximum() - self.minimum())
+ thumb_w = self.style_data.thumb_width
+ thumb_h = self.style_data.thumb_height
+ if self.orientation() == Qt.Orientation.Horizontal:
+ thumb_rect = QRectF((self.width() - thumb_w) * p, (self.height() - thumb_h) / 2, thumb_w, thumb_h)
+ else:
+ thumb_rect = QRectF((self.width() - thumb_h) / 2, (self.height() - thumb_w) * (1 - p), thumb_h, thumb_w)
+ self._dragging_anchor_pos = thumb_rect.center()
+
+ def _showToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(self)
+ tool_tip_window.show_()
+
+ def _hideToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(None)
+ tool_tip_window.hide_()
+
+ def _updateToolTip(self, flash: bool = True) -> None:
+ self.setToolTip(self._value_to_tooltip_func(self.value()))
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and tool_tip_window.nowInsideOf() == self:
+ tool_tip_window.setText(self.toolTip(), flash=flash)
+
+ def event(self, event):
+ if event.type() == QEvent.ToolTip:
+ return True # 忽略工具提示事件
+ return super().event(event)
+
+ def enterEvent(self, a0):
+ super().enterEvent(a0)
+ self._showToolTip()
+ self._updateToolTip()
+
+ def leaveEvent(self, a0):
+ super().leaveEvent(a0)
+ self._hideToolTip()
+
+ def mousePressEvent(self, a0):
+ super().mousePressEvent(a0)
+ self.sliderPressed.emit()
+ self._setThumbHovering(True) # force to change the outfit of thumb
+ self._is_dragging = True
+ if self._isMouseInThumbRect(a0.pos()):
+ self._is_dragging_thumb = True
+ self._updateDraggingAnchor()
+ self._dragging_start_pos = a0.pos()
+ else:
+ self._setValueToMousePos(a0.pos())
+
+ def mouseMoveEvent(self, a0):
+ super().mouseMoveEvent(a0)
+ if self._is_dragging:
+ if self._is_dragging_thumb:
+ pos = self._dragging_anchor_pos + (a0.pos() - self._dragging_start_pos)
+ self._setValueToMousePos(pos)
+ else:
+ self._setValueToMousePos(a0.pos())
+
+ else:
+ self._setThumbHovering(state=self._isMouseInThumbRect(a0.pos()))
+
+ def mouseReleaseEvent(self, a0):
+ super().mouseReleaseEvent(a0)
+ self.sliderReleased.emit()
+
+ self._setThumbHovering(False)
+ self._is_dragging_thumb = False
+ self._is_dragging = False
+
+ def paintEvent(self, event):
+ p = self._track_progress
+ thumb_w = self.style_data.thumb_width
+ thumb_h = self.style_data.thumb_height
+ track_w = self.style_data.track_height
+ track_h = self.style_data.track_height
+
+ if self.orientation() == Qt.Orientation.Horizontal:
+ background_rect = QRectF(0, (self.height() - track_h) / 2, self.width(), track_h)
+ track_rect = QRectF(0, (self.height() - track_h) / 2, thumb_w / 2 + (self.width() - thumb_w) * p, track_h)
+ thumb_rect = QRectF((self.width() - thumb_w) * p, (self.height() - thumb_h) / 2, thumb_w, thumb_h)
+ else:
+ background_rect = QRectF((self.width() - track_w) / 2, 0, track_w, self.height())
+ track_rect = QRectF((self.width() - track_w) / 2, self.height() * (1-p), track_w, self.height() * p)
+ thumb_rect = QRectF((self.width() - thumb_h) / 2, (self.height() - thumb_w) * (1-p), thumb_h, thumb_w)
+
+ renderHints = (
+ QPainter.RenderHint.SmoothPixmapTransform
+ | QPainter.RenderHint.TextAntialiasing
+ | QPainter.RenderHint.Antialiasing
+ )
+
+ with createPainter(self, renderHints) as painter:
+ self._drawBackgroundRect(painter, background_rect)
+ self._drawTrackRect(painter, track_rect)
+ self._drawThumbRect(painter, thumb_rect)
+
+
+@dataclass
+class CoordinatePickerStyleData:
+ STYLE_TYPES = ["Slider"]
+
+ slider_x_height: int = 64
+ slider_y_width: int = 64
+
+ indicator_size: int = 26
+ indicator_idle_color: QColor = QColor("#a681bf")
+ indicator_hover_color: QColor = QColor("#EDE1F4")
+ indicator_outline_weight: int = 10
+ indicator_stroke_weight: int = 6
+ indicator_background_color: QColor = QColor("#25222a")
+ indicator_stroke_color: QColor = QColor("#a681bf")
+
+ base_line_weight: int = 2
+ base_line_color: QColor = QColor("#3b3143")
+
+ xoy_plate_background_color: QColor = QColor("#571c191f")
+ deepest_background_color: QColor = QColor("#1c191f")
+
+ background_color: QColor = QColor("#25222a")
+ background_border_radius: int = 6
+
+
+class SiCoordinatePicker2D(QWidget):
+ class Property:
+ ProgressX = "progressX"
+ ProgressY = "progressY"
+ IndicatorRect = "indicatorRect"
+ ThumbColor = "thumbColor"
+
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+
+ self.setMouseTracking(True)
+
+ self.style_data = CoordinatePickerStyleData()
+ self._dragging_anchor_pos = QPoint()
+ self._dragging_start_pos = QPoint()
+ self._is_dragging_thumb = False
+ self._is_dragging = False
+ self._value_to_tooltip_func = self._defaultValueToToolTip
+
+ self._progress_x = 0
+ self._progress_y = 0
+ self._thumb_color = self.style_data.indicator_idle_color
+ self._indicator_rect = QRectF()
+
+ self.slider_x = SiSlider(self)
+ self.slider_y = SiSlider(self)
+
+ self.thumb_color_ani = SiExpAnimationRefactor(self, self.Property.ThumbColor)
+ self.thumb_color_ani.init(1/4, 0.01, self._thumb_color, self._thumb_color)
+
+ self.progress_x_ani = SiExpAnimationRefactor(self, self.Property.ProgressX)
+ self.progress_x_ani.init(1/3.5, 0.00001, 0, 0)
+
+ self.progress_y_ani = SiExpAnimationRefactor(self, self.Property.ProgressY)
+ self.progress_y_ani.init(1/3.5, 0.00001, 0, 0)
+
+ self._initStyle()
+
+ self.slider_x.valueChanged.connect(self._onSliderXValueChanged)
+ self.slider_y.valueChanged.connect(self._onSliderYValueChanged)
+
+ def _initStyle(self) -> None:
+ self.slider_x.setOrientation(Qt.Orientation.Horizontal)
+ self.slider_x.setFixedHeight(self.style_data.slider_x_height)
+ self.slider_y.setFixedWidth(self.style_data.slider_y_width)
+ self.slider_y.setOrientation(Qt.Orientation.Vertical)
+ self.slider_x.setDrawTrack(False)
+ self.slider_y.setDrawTrack(False)
+ self.slider_x.leaveEvent = self.enterEvent
+ self.slider_y.leaveEvent = self.enterEvent
+
+ # self.slider_x.style_data.thumb_idle_color = QColor("#b9e2e6")
+ # self.slider_x.style_data.track_color = QColor("#83b4b9")
+ #
+ # self.slider_y.style_data.thumb_idle_color = QColor("#eaa9c4")
+ # self.slider_y.style_data.track_color = QColor("#b96f98")
+
+ @pyqtProperty(QColor)
+ def thumbColor(self):
+ return self._thumb_color
+
+ @thumbColor.setter
+ def thumbColor(self, value: QColor):
+ self._thumb_color = value
+ self.update()
+
+ @pyqtProperty(float)
+ def progressX(self):
+ return self._progress_x
+
+ @progressX.setter
+ def progressX(self, value: float):
+ self._progress_x = value
+ self.update()
+
+ @pyqtProperty(float)
+ def progressY(self):
+ return self._progress_y
+
+ @progressY.setter
+ def progressY(self, value: float):
+ self._progress_y = value
+ self.update()
+
+ @pyqtProperty(QRectF)
+ def indicatorRect(self):
+ return self._indicator_rect
+
+ @indicatorRect.setter
+ def indicatorRect(self, value: QRectF):
+ self._indicator_rect = value
+
+ @staticmethod
+ def _defaultValueToToolTip(*args):
+ return f"x = {args[0]}\ny = {args[1]}"
+
+ def setToolTipConvertionFunc(self, func) -> None:
+ self._value_to_tooltip_func = func
+ self.setToolTip(func(self.value()))
+
+ def _isMouseInThumbRect(self, pos: QPoint) -> bool:
+ rect: QRect = self.property(self.Property.IndicatorRect)
+ return rect.contains(pos)
+
+ def _isMousePosValid(self, pos: QPoint) -> bool:
+ slider_x_height = self.style_data.slider_y_width
+ slider_y_width = self.style_data.slider_x_height
+ background_rect = QRectF(slider_y_width, 0, self.width() - slider_y_width, self.height() - slider_x_height)
+ return background_rect.contains(pos)
+
+ def _setThumbHovering(self, state: bool) -> None:
+ if state:
+ self.thumb_color_ani.setEndValue(self.style_data.indicator_hover_color)
+ self.thumb_color_ani.start()
+ else:
+ self.thumb_color_ani.setEndValue(self.style_data.indicator_idle_color)
+ self.thumb_color_ani.start()
+
+ def _updateDraggingAnchor(self) -> None:
+ indicator_outline_rect = self.property(self.Property.IndicatorRect)
+ self._dragging_anchor_pos = indicator_outline_rect.center()
+
+ def _setValueToMousePos(self, pos: QPoint) -> None:
+ margin = self.slider_x.style_data.thumb_width / 2
+ slider_y_width = self.style_data.slider_y_width
+ slider_x_height = self.style_data.slider_x_height
+ progress_x = (pos.x() - slider_y_width - margin) / (self.width() - slider_y_width - margin * 2)
+ progress_y = 1 - (pos.y() - margin) / (self.height() - margin * 2 - slider_x_height)
+
+ self.slider_x.setValue(int(self.slider_x.minimum() +
+ (self.slider_x.maximum() - self.slider_x.minimum()) * progress_x))
+ self.slider_y.setValue(int(self.slider_y.minimum() +
+ (self.slider_y.maximum() - self.slider_y.minimum()) * progress_y))
+
+ def _onSliderXValueChanged(self, _) -> None:
+ self.progress_x_ani.setEndValue(self._progressSliderX())
+ self.progress_x_ani.start()
+ self._updateToolTip(flash=False)
+
+ def _onSliderYValueChanged(self, _) -> None:
+ self.progress_y_ani.setEndValue(self._progressSliderY())
+ self.progress_y_ani.start()
+ self._updateToolTip(flash=False)
+
+ def _progressSliderX(self) -> float:
+ return (self.slider_x.value() - self.slider_x.minimum()) / (self.slider_x.maximum() - self.slider_x.minimum())
+
+ def _progressSliderY(self) -> float:
+ return (self.slider_y.value() - self.slider_y.minimum()) / (self.slider_y.maximum() - self.slider_y.minimum())
+
+ def _drawBackgroundPath(self, rect: QRect) -> QPainterPath:
+ radius = self.style_data.background_border_radius
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(rect), radius, radius)
+ return path
+
+ def _drawIndicatorPath(self, rect: QRect) -> QPainterPath:
+ indicator_size = self.style_data.indicator_size
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(rect), indicator_size / 2, indicator_size / 2)
+ return path
+
+ def _drawBackgroundRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self.style_data.background_color)
+ painter.drawPath(self._drawBackgroundPath(rect))
+
+ def _drawIndicatorRect(self, painter: QPainter, rect: QRect) -> None: # the rect should be the rect of background.
+ margin = self.slider_x.style_data.thumb_width / 2
+ indicator_size = self.style_data.indicator_size - self.style_data.indicator_stroke_weight
+ available_w = rect.width() - margin * 2
+ available_h = rect.height() - margin * 2
+
+ x = margin + available_w * self._progress_x - indicator_size / 2 + self.style_data.slider_y_width
+ y = margin + available_h * (1 - self._progress_y) - indicator_size / 2
+ d = self.style_data.indicator_outline_weight
+ indicator_rect = QRectF(x, y, indicator_size, indicator_size)
+ indicator_outline_rect = QRectF(x - d, y - d, indicator_size + d * 2, indicator_size + d * 2)
+ self.setProperty(self.Property.IndicatorRect, indicator_outline_rect)
+
+ painter.setBrush(self.style_data.background_color)
+ painter.drawPath(self._drawIndicatorPath(indicator_outline_rect))
+
+ painter.setBrush(self.style_data.indicator_background_color)
+ painter.setPen(QPen(self._thumb_color, self.style_data.indicator_stroke_weight))
+ painter.drawPath(self._drawIndicatorPath(indicator_rect))
+ painter.setPen(Qt.NoPen)
+
+ def _drawBaseLine(self, painter: QPainter, rect: QRect) -> None: # the rect should be the rect of background.
+ margin = self.slider_x.style_data.thumb_width / 2
+ available_w = rect.width() - margin * 2
+ available_h = rect.height() - margin * 2
+
+ x = margin + available_w * self._progress_x + self.style_data.slider_y_width
+ y = margin + available_h * (1 - self._progress_y)
+ d = 12
+
+ painter.setPen(QPen(self.style_data.base_line_color, self.style_data.base_line_weight))
+ painter.drawLine(QPointF(x, margin - d), QPointF(x, margin + d + available_h))
+ painter.drawLine(QPointF(margin + rect.x() - d, y), QPointF(margin + available_w + rect.x() + d, y))
+ painter.setPen(Qt.NoPen)
+
+ def resizeEvent(self, a0):
+ super().resizeEvent(a0)
+ self.slider_y.setGeometry(0, 0, self.slider_y.width(), self.height() - self.slider_x.height())
+ self.slider_x.setGeometry(self.slider_y.width(), self.height() - self.slider_x.height(),
+ self.width() - self.slider_y.width(), self.slider_x.height())
+
+ def mousePressEvent(self, a0):
+ super().mousePressEvent(a0)
+ if self._isMousePosValid(a0.pos()):
+ self._setThumbHovering(True) # force to change the outfit of thumb
+ self._is_dragging = True
+ if self._isMouseInThumbRect(a0.pos()):
+ self._is_dragging_thumb = True
+ self._updateDraggingAnchor()
+ self._dragging_start_pos = a0.pos()
+ else:
+ self._setValueToMousePos(a0.pos())
+
+ def mouseMoveEvent(self, a0):
+ super().mouseMoveEvent(a0)
+ if self._is_dragging:
+ if self._is_dragging_thumb:
+ pos = self._dragging_anchor_pos + (a0.pos() - self._dragging_start_pos)
+ self._setValueToMousePos(pos)
+ else:
+ self._setValueToMousePos(a0.pos())
+
+ else:
+ self._setThumbHovering(state=self._isMouseInThumbRect(a0.pos()))
+
+ def mouseReleaseEvent(self, a0):
+ super().mouseReleaseEvent(a0)
+
+ self._setThumbHovering(False)
+ self._is_dragging_thumb = False
+ self._is_dragging = False
+
+ def _showToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(self)
+ tool_tip_window.show_()
+
+ def _hideToolTip(self) -> None:
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and self.toolTip() != "":
+ tool_tip_window.setNowInsideOf(None)
+ tool_tip_window.hide_()
+
+ def _updateToolTip(self, flash: bool = True) -> None:
+ self.setToolTip(self._value_to_tooltip_func(self.slider_x.value(), self.slider_y.value()))
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and tool_tip_window.nowInsideOf() == self:
+ tool_tip_window.setText(self.toolTip(), flash=flash)
+
+ def event(self, event):
+ if event.type() == QEvent.ToolTip:
+ return True # 忽略工具提示事件
+ return super().event(event)
+
+ def enterEvent(self, a0):
+ super().enterEvent(a0)
+ self._showToolTip()
+ self._updateToolTip()
+
+ def leaveEvent(self, a0):
+ super().leaveEvent(a0)
+ self._hideToolTip()
+
+ def paintEvent(self, a0):
+ slider_x_height = self.style_data.slider_y_width
+ slider_y_width = self.style_data.slider_x_height
+ background_rect = QRectF(slider_y_width, 0, self.width() - slider_y_width, self.height() - slider_x_height)
+
+ renderHints = (
+ QPainter.RenderHint.SmoothPixmapTransform
+ | QPainter.RenderHint.TextAntialiasing
+ | QPainter.RenderHint.Antialiasing
+ )
+
+ with createPainter(self, renderHints) as painter:
+ self._drawBackgroundRect(painter, background_rect)
+ self._drawBaseLine(painter, background_rect)
+ self._drawIndicatorRect(painter, background_rect)
+
+
+class SiCoordinatePicker3D(SiCoordinatePicker2D):
+ class Property:
+ ProgressX = "progressX"
+ ProgressY = "progressY"
+ ProgressZ = "progressZ"
+ IndicatorRect = "indicatorRect"
+ ThumbColor = "thumbColor"
+
+ def __init__(self, parent: T_WidgetParent = None) -> None:
+ super().__init__(parent)
+ self._value_to_tooltip_func = lambda *args: f"x = {args[0]}\ny = {args[1]}\nz = {args[2]}"
+ self._progress_z = 0
+ self._min_scale_factor = 0.75
+
+ self.slider_z = SiSlider(self)
+ self.slider_z.setVisible(False)
+
+ self.progress_z_ani = SiExpAnimationRefactor(self, self.Property.ProgressZ)
+ self.progress_z_ani.init(1/4, 0.00001, 0, 0)
+
+ self.slider_z.valueChanged.connect(self._onSliderZValueChanged)
+
+ @pyqtProperty(float)
+ def progressZ(self):
+ return self._progress_z
+
+ @progressZ.setter
+ def progressZ(self, value: float):
+ self._progress_z = value
+ self.update()
+
+ def _updateToolTip(self, flash: bool = True) -> None:
+ self.setToolTip(
+ self._value_to_tooltip_func(self.slider_x.value(), self.slider_y.value(), self.slider_z.value()))
+
+ tool_tip_window = SiGlobal.siui.windows.get("TOOL_TIP")
+ if tool_tip_window is not None and tool_tip_window.nowInsideOf() == self:
+ tool_tip_window.setText(self.toolTip(), flash=flash)
+
+ def _progressSliderZ(self) -> float:
+ return (self.slider_z.value() - self.slider_z.minimum()) / (self.slider_z.maximum() - self.slider_z.minimum())
+
+ def _onSliderZValueChanged(self, _) -> None:
+ self.progress_z_ani.setEndValue(self._progressSliderZ())
+ self.progress_z_ani.start()
+
+ def _drawXOYPlatePath(self, rect: QRect) -> QPainterPath:
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(rect), 8, 8)
+ return path
+
+ def _drawXOYPlateRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self.style_data.xoy_plate_background_color)
+ painter.drawPath(self._drawXOYPlatePath(rect))
+
+ def _drawBackgroundRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self.style_data.background_color)
+ painter.drawPath(self._drawBackgroundPath(rect))
+
+ def _drawDeepestBackgroundRect(self, painter: QPainter, rect: QRect) -> None:
+ painter.setBrush(self.style_data.deepest_background_color)
+ painter.drawPath(self._drawBackgroundPath(rect))
+
+ def _drawBaseLine(self, painter: QPainter, rect: QRect) -> None: # the rect should be the rect of background.
+ margin = self.slider_x.style_data.thumb_width / 2
+ available_w = rect.width() - margin * 2
+ available_h = rect.height() - margin * 2
+
+ x = margin + available_w * self._progress_x
+ y = margin + available_h * (1 - self._progress_y)
+ d = 12
+
+ painter.setPen(QPen(self.style_data.base_line_color, self.style_data.base_line_weight))
+ painter.drawLine(QPointF(x, margin - d), QPointF(x, margin + d + available_h))
+ painter.drawLine(QPointF(margin + rect.x() - d, y), QPointF(margin + available_w + rect.x() + d, y))
+ painter.setPen(Qt.NoPen)
+
+ def _drawIndicatorRect(self, painter: QPainter, rect: QRect) -> None: # the rect should be the rect of background.
+ margin = self.slider_x.style_data.thumb_width / 2
+ indicator_size = self.style_data.indicator_size - self.style_data.indicator_stroke_weight
+ available_w = rect.width() - margin * 2
+ available_h = rect.height() - margin * 2
+
+ x = margin + available_w * self._progress_x - indicator_size / 2
+ y = margin + available_h * (1 - self._progress_y) - indicator_size / 2
+ d = self.style_data.indicator_outline_weight
+ indicator_rect = QRectF(x, y, indicator_size, indicator_size)
+ indicator_outline_rect = QRectF(x - d, y - d, indicator_size + d * 2, indicator_size + d * 2)
+ self.setProperty(self.Property.IndicatorRect, indicator_outline_rect)
+
+ # painter.setBrush(self.style_data.background_color)
+ # painter.drawPath(self._drawIndicatorPath(indicator_outline_rect))
+
+ painter.setBrush(self.style_data.indicator_background_color)
+ painter.setPen(QPen(self._thumb_color, self.style_data.indicator_stroke_weight))
+ painter.drawPath(self._drawIndicatorPath(indicator_rect))
+ painter.setPen(Qt.NoPen)
+
+ def wheelEvent(self, a0):
+ super().wheelEvent(a0)
+ direction = 1 if a0.angleDelta().y() > 0 else -1
+ self.slider_z.setValue(self.slider_z.value() + self.slider_z.singleStep() * direction) # noqa: E501
+ self._updateToolTip(flash=False)
+ a0.accept()
+
+ def paintEvent(self, a0):
+ slider_x_height = self.style_data.slider_y_width
+ slider_y_width = self.style_data.slider_x_height
+ background_rect = QRect(slider_y_width, 0, self.width() - slider_y_width, self.height() - slider_x_height)
+ buffer_rect = QRect(0, 0, self.width() - slider_y_width, self.height() - slider_x_height)
+
+ device_pixel_ratio = self.devicePixelRatioF()
+ min_scale_factor = self._min_scale_factor
+
+ buffer = QPixmap(background_rect.size() * device_pixel_ratio)
+ buffer.setDevicePixelRatio(device_pixel_ratio)
+ buffer.fill(Qt.transparent)
+
+ renderHints = (
+ QPainter.RenderHint.SmoothPixmapTransform
+ | QPainter.RenderHint.TextAntialiasing
+ | QPainter.RenderHint.Antialiasing
+ )
+
+ with createPainter(buffer, renderHints) as painter:
+ self._drawXOYPlateRect(painter, buffer_rect)
+ self._drawBaseLine(painter, buffer_rect)
+ self._drawIndicatorRect(painter, buffer_rect)
+
+ a = self._progress_z * (1 - min_scale_factor) + min_scale_factor
+ b = min_scale_factor
+ with createPainter(self, renderHints) as painter:
+ self._drawBackgroundRect(painter, background_rect)
+
+ painter.save()
+ painter.translate(QPointF(background_rect.width() * (1 - b) / 2 + self.style_data.slider_y_width,
+ background_rect.height() * (1 - b) / 2))
+ painter.scale(b, b)
+ self._drawDeepestBackgroundRect(painter, buffer_rect)
+
+ painter.restore()
+ painter.translate(QPointF(background_rect.width() * (1 - a) / 2 + self.style_data.slider_y_width,
+ background_rect.height() * (1 - a) / 2))
+ painter.scale(a, a)
+ painter.drawPixmap(0, 0, buffer)
diff --git a/siui/components/tooltip/tooltip.py b/siui/components/tooltip/tooltip.py
index 4767e81..b051392 100644
--- a/siui/components/tooltip/tooltip.py
+++ b/siui/components/tooltip/tooltip.py
@@ -81,7 +81,7 @@ def hide_(self):
def _completely_hid_signal_handler(self, target):
if target == 0:
self.completely_hid = True
- self.resize(2 * self.margin, 36 + 2 * self.margin) # 变单行内容的高度,宽度不足以显示任何内容
+ self.resize(2 * self.margin, 36 + 2 * self.margin) # 变单行内容的高度,宽度不足以显示任何内容 # 2024.11.1 宽度设0解决幽灵窗口
self.text_label.setText("") # 清空文本内容
def setNowInsideOf(self, widget):
diff --git a/siui/core/animation.py b/siui/core/animation.py
index fdb5e82..81a7624 100644
--- a/siui/core/animation.py
+++ b/siui/core/animation.py
@@ -1,5 +1,19 @@
+from typing import Any
+
import numpy
-from PyQt5.QtCore import QObject, QTimer, pyqtSignal
+from PyQt5.QtCore import (
+ QAbstractAnimation,
+ QObject,
+ QPoint,
+ QPointF,
+ QRect,
+ QRectF,
+ QSize,
+ QSizeF,
+ QTimer,
+ pyqtSignal,
+)
+from PyQt5.QtGui import QColor
global_fps = 60
@@ -154,6 +168,13 @@ def __init__(self, parent=None):
self.factor = 1/2
self.bias = 1
+ def init(self, factor: float, bias: float, current: Any, target: Any, fps: int = 60):
+ self.setFactor(factor)
+ self.setBias(bias)
+ self.setCurrent(current)
+ self.setTarget(target)
+ self.setFPS(fps)
+
def setFactor(self, factor: float):
"""
Set the factor of the animation.
@@ -368,3 +389,154 @@ def fromToken(self, aim_token: str) -> ABCSiAnimation:
if token == aim_token:
return ani
raise ValueError(f"未在代号组中找到传入的代号:{aim_token}")
+
+
+class TypeConversionFuncs:
+ functions = {
+ QPoint.__name__: [
+ lambda x: numpy.array((x.x(), x.y()), dtype="float32"),
+ lambda x: QPoint(int(x[0]), int(x[1]))
+ ],
+ QPointF.__name__: [
+ lambda x: numpy.array((x.x(), x.y()), dtype="float32"),
+ lambda x: QPointF(float(x[0]), float(x[1])),
+ ],
+ QSize.__name__: [
+ lambda x: numpy.array((x.width(), x.height()), dtype="float32"),
+ lambda x: QSize(int(x[0]), int(x[1])),
+ ],
+ QSizeF.__name__: [
+ lambda x: numpy.array((x.width(), x.height()), dtype="float32"),
+ lambda x: QSizeF(float(x[0]), float(x[1])),
+ ],
+ QRect.__name__: [
+ lambda x: numpy.array((x.x(), x.y(), x.width(), x.height()), dtype="float32"),
+ lambda x: QRect(int(x[0]), int(x[1]), int(x[2]), int(x[3]))
+ ],
+ QRectF.__name__: [
+ lambda x: numpy.array((x.x(), x.y(), x.width(), x.height()), dtype="float32"),
+ lambda x: QRect(float(x[0]), float(x[1]), float(x[2]), float(x[3]))
+ ],
+ QColor.__name__: [
+ lambda x: numpy.array(x.getRgb(), dtype="float32"),
+ lambda x: QColor(int(x[0]), int(x[1]), int(x[2]), int(x[3]))
+ ]
+ }
+
+
+class SiExpAnimationRefactor(QAbstractAnimation):
+ valueChanged = pyqtSignal(object)
+
+ def __init__(self, target: QObject, property_name=None, parent=None) -> None:
+ super().__init__(parent)
+ self.start_after_timer = QTimer(self)
+
+ self._target = target
+ self._property_name = None
+ self._property_type = None
+ self._in_func = None
+ self._out_func = None
+ self._end_value = None
+ self._current_value = None
+ self.factor = 1/4
+ self.bias = 0.5
+
+ if property_name is not None:
+ self.setPropertyName(property_name)
+
+ def init(self, factor: float, bias: float, current_value, end_value) -> None:
+ self.factor = factor
+ self.bias = bias
+ self.setCurrentValue(current_value)
+ self.setEndValue(end_value)
+
+ def setFactor(self, factor: float):
+ self.factor = factor
+
+ def setBias(self, bias: float):
+ self.bias = bias
+
+ def target(self) -> QObject:
+ return self._target
+
+ def propertyName(self) -> str:
+ return self._property_name
+
+ def endValue(self, raw=False) -> Any:
+ if raw is True:
+ return self._end_value
+ else:
+ return self._out_func(self._end_value)
+
+ def currentValue(self, raw=False) -> Any:
+ if raw is True:
+ return self._current_value
+ else:
+ return self._out_func(self._current_value)
+
+ def distance(self) -> numpy.array:
+ return self._end_value - self._current_value
+
+ def duration(self) -> int:
+ return -1
+
+ def start(self, *args, **kwargs):
+ if self.state() != QAbstractAnimation.State.Running:
+ super().start(*args, **kwargs)
+
+ def startAfter(self, msec: int):
+ self.start_after_timer.singleShot(msec, self.start)
+
+ def update(self):
+ self.setCurrentValue(self._target.property(self._property_name))
+
+ def updateAndStart(self):
+ self.update()
+ self.start()
+
+ def setPropertyName(self, name: str) -> None:
+ self._property_name = name
+ self._property_type = type(self._target.property(name))
+ self._loadConversionFuncs()
+ self._end_value = self._in_func(self._target.property(name))
+ self._current_value = self._in_func(self._target.property(name))
+
+ def setEndValue(self, value: Any) -> None:
+ if isinstance(value, self._property_type):
+ self._end_value = self._in_func(value)
+ else:
+ self._end_value = numpy.array(value)
+
+ def setCurrentValue(self, value: Any) -> None:
+ if isinstance(value, self._property_type):
+ self._current_value = self._in_func(value)
+ else:
+ self._current_value = numpy.array(value)
+ self.valueChanged.emit(self._current_value)
+
+ def updateCurrentTime(self, _) -> None:
+ # print(self.distance())
+ if (self.distance() == 0).all():
+ self.stop()
+ return
+
+ distance = self._end_value - self._current_value
+ flag = numpy.array(abs(distance) <= self.bias, dtype="int8")
+ step = abs(distance) * self.factor + self.bias # 基本指数动画运算
+ step = step * (numpy.array(distance > 0, dtype="int8") * 2 - 1) # 确定动画方向
+ step = step * (1 - flag) + distance * flag # 差距小于偏置的项,返回差距
+
+ self._current_value = self._current_value + step
+ self.valueChanged.emit(self._current_value)
+ try:
+ self._target.setProperty(self._property_name, self._out_func(self._current_value))
+ except RuntimeError:
+ pass
+
+ def _loadConversionFuncs(self) -> None:
+ if self._property_type.__name__ in TypeConversionFuncs.functions.keys():
+ self._in_func = TypeConversionFuncs.functions.get(self._property_type.__name__)[0]
+ self._out_func = TypeConversionFuncs.functions.get(self._property_type.__name__)[1]
+ else:
+ self._in_func = lambda x: numpy.array(x)
+ self._out_func = lambda x: self._property_type(numpy.array(x, dtype="float32"))
diff --git a/siui/core/color.py b/siui/core/color.py
index bc2d352..5143cc7 100644
--- a/siui/core/color.py
+++ b/siui/core/color.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
from enum import Enum, auto
from typing import Union
@@ -103,23 +104,29 @@ def RGB_to_RGBA(code: str):
@classmethod
def toArray(cls,
- code: str,
- c_format: str = "argb"):
+ code: str | tuple,
+ to_format: str = "argb"):
"""
transform `#AARRGGBB` or `#RRGGBB` into `array(A, R, G, B, dtype=int16)`
+ if code is already be a list / tuple / ndarray, the method returns ndarray.
"""
+ if isinstance(code, numpy.ndarray):
+ return code
+ if isinstance(code, (list, tuple)):
+ return numpy.array(code)
+
code = cls.RGB_to_RGBA(code)
code = code.lstrip("#")
a, r, g, b = int(code[0:2], 16), int(code[2:4], 16), int(code[4:6], 16), int(code[6:8], 16)
- c_format = c_format.lower()
- if c_format not in ["rgba", "argb", "rgb"]:
- raise ValueError(f"{c_format} is not a valid format (rgba, argb, rgb)")
- if c_format == "rgba":
+ to_format = to_format.lower()
+ if to_format not in ["rgba", "argb", "rgb"]:
+ raise ValueError(f"{to_format} is not a valid format (rgba, argb, rgb)")
+ if to_format == "rgba":
return numpy.array([r, g, b, a], dtype=numpy.int16)
- if c_format == "argb":
+ if to_format == "argb":
return numpy.array([a, r, g, b], dtype=numpy.int16)
- if c_format == "rgb":
+ if to_format == "rgb":
return numpy.array([r, g, b], dtype=numpy.int16)
@staticmethod
diff --git a/siui/core/painter.py b/siui/core/painter.py
index ee876fa..7f49c36 100644
--- a/siui/core/painter.py
+++ b/siui/core/painter.py
@@ -7,13 +7,12 @@
if TYPE_CHECKING:
from PyQt5.QtGui import QFont, QPaintDevice
-
- from siui.typing import T_Brush, T_PenStyle
+ from siui.typing import T_Brush, T_PenStyle, T_RenderHint
def createPainter(
paintDevice: QPaintDevice,
- renderHint: Optional[QPainter.RenderHint] = QPainter.RenderHint.Antialiasing,
+ renderHint: T_RenderHint = QPainter.RenderHint.Antialiasing,
penStyle: T_PenStyle = Qt.PenStyle.NoPen,
brush: T_Brush = None,
font: Optional[QFont] = None,
@@ -33,7 +32,7 @@ def createPainter(
"""
painter = QPainter(paintDevice)
if renderHint is not None:
- painter.setRenderHint(renderHint)
+ painter.setRenderHints(renderHint)
if penStyle is not None:
painter.setPen(penStyle)
diff --git a/siui/gui/icons/parser.py b/siui/gui/icons/parser.py
index fb1f9b5..ec136ae 100644
--- a/siui/gui/icons/parser.py
+++ b/siui/gui/icons/parser.py
@@ -1,6 +1,8 @@
import os
-from PyQt5.QtCore import QByteArray
+from PyQt5.QtCore import QByteArray, QSize, Qt
+from PyQt5.QtGui import QPainter, QPixmap
+from PyQt5.QtSvg import QSvgRenderer
class GlobalIconPack:
@@ -79,3 +81,14 @@ def getDict(self, class_name=None) -> dict:
def getClassNames(self) -> dict.keys:
return self.icons_classified.keys()
+
+ def toPixmap(self, name: str, size: QSize = QSize(64, 64), color_code: str = None):
+ svg_bytes = self.get(name, color_code)
+ pixmap = QPixmap(size)
+ pixmap.fill(Qt.transparent)
+ painter = QPainter(pixmap)
+ painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
+ svg_renderer = QSvgRenderer(svg_bytes)
+ svg_renderer.render(painter)
+ painter.end()
+ return pixmap
diff --git a/siui/templates/application/components/layer/layer_left_global_drawer/layer_left_global_drawer.py b/siui/templates/application/components/layer/layer_left_global_drawer/layer_left_global_drawer.py
index 3ad4e43..277771c 100644
--- a/siui/templates/application/components/layer/layer_left_global_drawer/layer_left_global_drawer.py
+++ b/siui/templates/application/components/layer/layer_left_global_drawer/layer_left_global_drawer.py
@@ -1,6 +1,7 @@
from PyQt5.QtCore import Qt
-from siui.components import SiLabel, SiSliderH, SiTitledWidgetGroup, SiWidget
+from siui.components import SiLabel, SiTitledWidgetGroup, SiWidget
+from siui.components.slider.slider import SiSliderH
from siui.components.combobox import SiComboBox
from siui.core import SiColor
from siui.core import SiGlobal
diff --git a/siui/typing.py b/siui/typing.py
index db76463..ee1596a 100644
--- a/siui/typing.py
+++ b/siui/typing.py
@@ -10,7 +10,7 @@
from typing import Optional, Union
from PyQt5.QtCore import QObject, Qt
-from PyQt5.QtGui import QColor, QGradient, QPen
+from PyQt5.QtGui import QColor, QGradient, QPainter, QPen
from PyQt5.QtWidgets import QWidget
from typing_extensions import TypeAlias
@@ -25,3 +25,6 @@
T_Brush: TypeAlias = Optional[Union[QGradient, QColor, Qt.GlobalColor]]
"""Type of QBrush"""
+
+T_RenderHint: TypeAlias = Optional[Union[QPainter.RenderHint, int]]
+"""Type of QPainter.RenderHint"""
\ No newline at end of file