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