提交 65b38311 编写于 作者: 之一Yo's avatar 之一Yo

添加时间拾取器组件

上级 e8be9174
...@@ -100,7 +100,7 @@ class StatusInfoInterface(GalleryInterface): ...@@ -100,7 +100,7 @@ class StatusInfoInterface(GalleryInterface):
parent=self parent=self
) )
infoBar.addWidget(PushButton(self.tr('Action'))) infoBar.addWidget(PushButton(self.tr('Action')))
infoBar.setCustomBackgroundColor("white", "#202020") infoBar.setCustomBackgroundColor("white", "#2a2a2a")
self.addExampleCard( self.addExampleCard(
self.tr('An InfoBar with custom icon, background color and widget.'), self.tr('An InfoBar with custom icon, background color and widget.'),
infoBar, infoBar,
......
# coding:utf-8
import sys
from PyQt5.QtCore import Qt, QTime
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
from qfluentwidgets import TimePicker, AMTimePicker
class Demo(QWidget):
def __init__(self):
super().__init__()
self.vBoxLayout = QVBoxLayout(self)
self.picker1 = AMTimePicker(self)
self.picker2 = TimePicker(self)
self.picker1.timeChanged.connect(lambda t: print(t.toString()))
self.picker2.timeChanged.connect(lambda t: print(t.toString()))
# set current time
# self.picker1.setTime(QTime(13, 15))
# self.picker2.setTime(QTime(13, 15))
self.resize(500, 500)
self.vBoxLayout.addWidget(self.picker1, 0, Qt.AlignHCenter)
self.vBoxLayout.addWidget(self.picker2, 0, Qt.AlignHCenter)
if __name__ == '__main__':
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
w = Demo()
w.show()
app.exec_()
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve"><path fill="#000000" transform="translate(0, 350)" d="M0 704 q0 -26 19 -45 q19 -19 45 -19 q26 0 45 19 l595 594 l1235 -1234 q19 -19 45 -19 q26 0 45 19 q19 19 19 45 q0 26 -19 45 l-1280 1280 q-19 19 -45 19 q-26 0 -45 -19 l-640 -640 q-19 -19 -19 -45 Z"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve"><path fill="#FFFFFF" transform="translate(0, 350)" d="M0 704 q0 -26 19 -45 q19 -19 45 -19 q26 0 45 19 l595 594 l1235 -1234 q19 -19 45 -19 q26 0 45 19 q19 19 19 45 q0 26 -19 45 l-1280 1280 q-19 19 -45 19 q-26 0 -45 -19 l-640 -640 q-19 -19 -19 -45 Z"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve"><path fill="#5E5E5E" transform="translate(0, 400)" d="M0 184 q0 -37.71 13.71 -70.86 q13.71 -33.14 38.28 -58.29 q24.57 -25.15 57.14 -40 q32.57 -14.86 70.29 -14.86 l1689.14 0 q37.72 0 70.29 14.86 q32.57 14.86 57.14 40 q24.57 25.14 38.28 58.29 q13.72 33.14 13.72 70.86 q0 68.57 -44.57 120 l-790.86 924.57 q-36.57 42.29 -84.57 65.14 q-48 22.86 -104 22.86 q-56 0 -104 -22.86 q-48 -22.86 -84.57 -65.14 l-790.86 -924.57 q-44.57 -51.43 -44.57 -120 Z"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve"><path fill="#FFFFFF" transform="translate(0, 400)" d="M0 184 q0 -37.71 13.71 -70.86 q13.71 -33.14 38.28 -58.29 q24.57 -25.15 57.14 -40 q32.57 -14.86 70.29 -14.86 l1689.14 0 q37.72 0 70.29 14.86 q32.57 14.86 57.14 40 q24.57 25.14 38.28 58.29 q13.72 33.14 13.72 70.86 q0 68.57 -44.57 120 l-790.86 924.57 q-36.57 42.29 -84.57 65.14 q-48 22.86 -104 22.86 q-56 0 -104 -22.86 q-48 -22.86 -84.57 -65.14 l-790.86 -924.57 q-44.57 -51.43 -44.57 -120 Z"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve"><path fill="#5E5E5E" transform="translate(0, 350)" d="M179.43 1316.57 q-37.72 0 -70.29 -14.86 q-32.57 -14.86 -57.14 -40 q-24.57 -25.14 -38.28 -58.29 q-13.71 -33.14 -13.71 -70.86 q0 -68.57 44.57 -120 l790.86 -924.57 q36.57 -42.29 84.57 -65.14 q48 -22.86 104 -22.86 q113.14 0 188.57 88 l790.86 924.57 q44.57 51.43 44.57 120 q0 37.72 -13.72 70.86 q-13.71 33.14 -38.28 58.29 q-24.57 25.14 -57.14 40 q-32.57 14.86 -70.29 14.86 l-1689.14 0 Z"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve"><path fill="#FFFFFF" transform="translate(0, 350)" d="M179.43 1316.57 q-37.72 0 -70.29 -14.86 q-32.57 -14.86 -57.14 -40 q-24.57 -25.14 -38.28 -58.29 q-13.71 -33.14 -13.71 -70.86 q0 -68.57 44.57 -120 l790.86 -924.57 q36.57 -42.29 84.57 -65.14 q48 -22.86 104 -22.86 q113.14 0 188.57 88 l790.86 924.57 q44.57 51.43 44.57 120 q0 37.72 -13.72 70.86 q-13.71 33.14 -38.28 58.29 q-24.57 25.14 -57.14 40 q-32.57 14.86 -70.29 14.86 l-1689.14 0 Z"/></svg>
\ No newline at end of file
ScrollButton {
background-color: rgb(44, 44, 44);
border: none;
border-radius: 7px;
}
CycleListWidget {
background-color: transparent;
border: none;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
outline: none;
font: 14px 'Segoe UI', 'Microsoft YaHei';
}
CycleListWidget::item {
color: white;
background-color: transparent;
border: none;
border-radius: 5px;
margin: 0 4px;
padding-left: 11px;
padding-right: 11px;
}
CycleListWidget::item:hover {
background-color: rgba(255, 255, 255, 9);
}
CycleListWidget::item:selected {
background-color: rgba(255, 255, 255, 9);
}
CycleListWidget::item:selected:active {
background-color: rgba(255, 255, 255, 6);
}
PickerPanel > #view {
background-color: rgb(44, 44, 44);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 7px;
}
SeparatorWidget {
background-color: rgb(61, 61, 61);
}
ItemMaskWidget {
font: 14px 'Segoe UI', 'Microsoft YaHei';
}
PickerBase {
background: rgba(255, 255, 255, 0.0605);
border: 1px solid rgba(255, 255, 255, 0.053);
border-top: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 5px;
}
PickerBase:hover {
background: rgba(255, 255, 255, 0.0837);
}
PickerBase:pressed {
color: rgba(255, 255, 255, 0.786);
background: rgba(255, 255, 255, 0.0326);
border-top: 1px solid rgba(255, 255, 255, 0.053);
}
#pickerButton {
font: 14px 'Segoe UI', 'Microsoft YaHei';
color: rgba(255, 255, 255, 0.786);
background-color: transparent;
border: none;
outline: none;
}
#pickerButton[hasBorder=true] {
border-right: 1px solid rgba(255, 255, 255, 0.053);
}
#pickerButton[hasBorder=false] {
border-right: transparent;
}
#pickerButton[enter=true],
#pickerButton[hasValue=true] {
color: rgba(255, 255, 255, 1);
}
#pickerButton[pressed=true] {
color: rgba(255, 255, 255, 0.786);
}
#pickerButton[align="left"] {
text-align: left;
padding-left: 10px;
}
#pickerButton[align="right"] {
text-align: right;
padding-right: 10px;
}
\ No newline at end of file
ScrollButton {
background-color: rgb(249, 249, 249);
border: none;
border-radius: 7px;
}
CycleListWidget {
background-color: transparent;
border: none;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
outline: none;
font: 14px 'Segoe UI', 'Microsoft YaHei';
}
CycleListWidget::item {
color: black;
background-color: transparent;
border: none;
border-radius: 5px;
margin: 0 4px;
padding-left: 11px;
padding-right: 11px;
}
CycleListWidget::item:hover {
background-color: rgba(0, 0, 0, 9);
}
CycleListWidget::item:selected {
background-color: rgba(0, 0, 0, 9);
}
CycleListWidget::item:selected:active {
background-color: rgba(0, 0, 0, 6);
}
PickerPanel > #view {
background-color: rgb(249, 249, 249);
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 7px;
}
SeparatorWidget {
background-color: rgb(234, 234, 234);
}
ItemMaskWidget {
font: 14px 'Segoe UI', 'Microsoft YaHei';
}
PickerBase {
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.073);
border-bottom: 1px solid rgba(0, 0, 0, 0.183);
border-radius: 5px;
}
PickerBase:hover {
background: rgba(249, 249, 249, 0.5);
}
PickerBase:pressed {
background: rgba(249, 249, 249, 0.3);
border-bottom: 1px solid rgba(0, 0, 0, 0.073);
}
#pickerButton {
font: 14px 'Segoe UI', 'Microsoft YaHei';
color: rgba(0, 0, 0, 0.6);
background-color: transparent;
border: none;
outline: none;
}
#pickerButton[hasBorder=true] {
border-right: 1px solid rgba(0, 0, 0, 0.073);
}
#pickerButton[hasBorder=false] {
border-right: transparent;
}
#pickerButton[enter=true],
#pickerButton[hasValue=true] {
color: rgba(0, 0, 0, 0.896);
}
#pickerButton[pressed=true] {
color: rgba(0, 0, 0, 0.6);
}
#pickerButton[align="left"] {
text-align: left;
padding-left: 10px;
}
#pickerButton[align="right"] {
text-align: right;
padding-right: 10px;
}
此差异已折叠。
...@@ -159,6 +159,8 @@ ...@@ -159,6 +159,8 @@
<file>images/icons/Hide_white.svg</file> <file>images/icons/Hide_white.svg</file>
<file>images/icons/Delete_black.svg</file> <file>images/icons/Delete_black.svg</file>
<file>images/icons/Delete_white.svg</file> <file>images/icons/Delete_white.svg</file>
<file>images/icons/Accept_black.svg</file>
<file>images/icons/Accept_white.svg</file>
<file>images/acrylic/noise.png</file> <file>images/acrylic/noise.png</file>
<file>images/folder_list_dialog/Close_white.png</file> <file>images/folder_list_dialog/Close_white.png</file>
<file>images/folder_list_dialog/Close_black.png</file> <file>images/folder_list_dialog/Close_black.png</file>
...@@ -184,6 +186,10 @@ ...@@ -184,6 +186,10 @@
<file>images/tree_view/TreeViewOpen_black.svg</file> <file>images/tree_view/TreeViewOpen_black.svg</file>
<file>images/tree_view/TreeViewClose_white.svg</file> <file>images/tree_view/TreeViewClose_white.svg</file>
<file>images/tree_view/TreeViewOpen_white.svg</file> <file>images/tree_view/TreeViewOpen_white.svg</file>
<file>images/time_picker/Up_white.svg</file>
<file>images/time_picker/Up_black.svg</file>
<file>images/time_picker/Down_white.svg</file>
<file>images/time_picker/Down_black.svg</file>
<file>qss/dark/color_dialog.qss</file> <file>qss/dark/color_dialog.qss</file>
<file>qss/dark/dialog.qss</file> <file>qss/dark/dialog.qss</file>
...@@ -205,6 +211,7 @@ ...@@ -205,6 +211,7 @@
<file>qss/dark/info_bar.qss</file> <file>qss/dark/info_bar.qss</file>
<file>qss/dark/spin_box.qss</file> <file>qss/dark/spin_box.qss</file>
<file>qss/dark/tree_view.qss</file> <file>qss/dark/tree_view.qss</file>
<file>qss/dark/time_picker.qss</file>
<file>qss/light/color_dialog.qss</file> <file>qss/light/color_dialog.qss</file>
<file>qss/light/dialog.qss</file> <file>qss/light/dialog.qss</file>
...@@ -226,5 +233,6 @@ ...@@ -226,5 +233,6 @@
<file>qss/light/info_bar.qss</file> <file>qss/light/info_bar.qss</file>
<file>qss/light/spin_box.qss</file> <file>qss/light/spin_box.qss</file>
<file>qss/light/tree_view.qss</file> <file>qss/light/tree_view.qss</file>
<file>qss/light/time_picker.qss</file>
</qresource> </qresource>
</RCC> </RCC>
\ No newline at end of file
...@@ -240,6 +240,7 @@ class FluentIcon(FluentIconBase, Enum): ...@@ -240,6 +240,7 @@ class FluentIcon(FluentIconBase, Enum):
SHARE = "Share" SHARE = "Share"
UNPIN = "Unpin" UNPIN = "Unpin"
VIDEO = "Video" VIDEO = "Video"
ACCEPT = "Accept"
CAMERA = "Camera" CAMERA = "Camera"
CANCEL = "Cancel" CANCEL = "Cancel"
DELETE = "Delete" DELETE = "Delete"
......
...@@ -97,6 +97,7 @@ class FluentStyleSheet(StyleSheetBase, Enum): ...@@ -97,6 +97,7 @@ class FluentStyleSheet(StyleSheetBase, Enum):
COMBO_BOX = "combo_box" COMBO_BOX = "combo_box"
LINE_EDIT = "line_edit" LINE_EDIT = "line_edit"
TREE_VIEW = "tree_view" TREE_VIEW = "tree_view"
TIME_PICKER = "time_picker"
SETTING_CARD = "setting_card" SETTING_CARD = "setting_card"
COLOR_DIALOG = "color_dialog" COLOR_DIALOG = "color_dialog"
SWITCH_BUTTON = "switch_button" SWITCH_BUTTON = "switch_button"
......
...@@ -2,4 +2,5 @@ from .dialog_box import * ...@@ -2,4 +2,5 @@ from .dialog_box import *
from .layout import * from .layout import *
from .settings import * from .settings import *
from .widgets import * from .widgets import *
from .navigation import * from .navigation import *
\ No newline at end of file from .date_time import *
\ No newline at end of file
from .time_picker import TimePicker, AMTimePicker
from .picker_base import PickerBase, PickerPanel
\ No newline at end of file
# coding:utf-8
from typing import Iterable, List
from PyQt5.QtCore import QEvent, Qt, pyqtSignal, QSize, QRectF, QPoint, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QColor, QPainter, QCursor, QRegion
from PyQt5.QtWidgets import (QApplication, QWidget, QFrame, QVBoxLayout, QHBoxLayout,
QGraphicsDropShadowEffect, QSizePolicy, QPushButton, QListWidgetItem)
from ..widgets.cycle_list_widget import CycleListWidget
from ..widgets.button import TransparentToolButton
from ...common.icon import FluentIcon
from ...common.style_sheet import FluentStyleSheet, themeColor, isDarkTheme
class SeparatorWidget(QWidget):
""" Separator widget """
def __init__(self, orient: Qt.Orientation, parent=None):
super().__init__(parent=parent)
if orient == Qt.Horizontal:
self.setFixedHeight(1)
else:
self.setFixedWidth(1)
self.setAttribute(Qt.WA_StyledBackground)
FluentStyleSheet.TIME_PICKER.apply(self)
class ItemMaskWidget(QWidget):
""" Item mask widget """
def __init__(self, listWidgets: List[CycleListWidget], parent=None):
super().__init__(parent=parent)
self.listWidgets = listWidgets
self.setFixedHeight(37)
FluentStyleSheet.TIME_PICKER.apply(self)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.TextAntialiasing)
# draw background
painter.setPen(Qt.NoPen)
painter.setBrush(themeColor())
painter.drawRoundedRect(self.rect().adjusted(4, 0, -3, 0), 5, 5)
# draw text
painter.setPen(Qt.black if isDarkTheme() else Qt.white)
painter.setFont(self.font())
w, h = 0, self.height()
for i, p in enumerate(self.listWidgets):
painter.save()
# draw first item's text
x = p.itemSize.width()//2 + 4 + self.x()
item1 = p.itemAt(QPoint(x, self.y() + 6))
if not item1:
painter.restore()
continue
iw = item1.sizeHint().width()
y = p.visualItemRect(item1).y()
painter.translate(w, y - self.y() + 7)
self._drawText(item1, painter, 0)
# draw second item's text
item2 = p.itemAt(self.pos() + QPoint(x, h - 6))
self._drawText(item2, painter, h)
painter.restore()
w += (iw + 8) # margin: 0 4px;
def _drawText(self, item: QListWidgetItem, painter: QPainter, y: int):
align = item.textAlignment()
w, h = item.sizeHint().width(), item.sizeHint().height()
if align & Qt.AlignLeft:
rect = QRectF(15, y, w, h) # padding-left: 11px
elif align & Qt.AlignRight:
rect = QRectF(4, y, w-15, h) # padding-right: 11px
elif align & Qt.AlignCenter:
rect = QRectF(4, y, w, h)
painter.drawText(rect, align, item.text())
class PickerColumn:
""" Picker column """
def __init__(self, name: str, items: list, width: int, align=Qt.AlignLeft):
self.name = name
self.items = items
self.width = width
self.align = align
self.value = None # type: str
class PickerBase(QPushButton):
""" Picker base class """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.columns = [] # type: List[PickerColumn]
self.columnMap = {}
self.buttons = [] # type: List[QPushButton]
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.setSpacing(0)
self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
self.hBoxLayout.setSizeConstraint(QHBoxLayout.SetFixedSize)
FluentStyleSheet.TIME_PICKER.apply(self)
self.clicked.connect(self._showPanel)
def addColumn(self, name: str, items: Iterable, width: int, align=Qt.AlignCenter):
""" add column
Parameters
----------
name: str
the name of column
items: Iterable
the items of column
width: int
the width of column
align: Qt.AlignmentFlag
the text alignment of button
"""
if name in self.columnMap:
return
# create column
column = PickerColumn(name, list(items), width, align)
self.columns.append(column)
self.columnMap[name] = column
# create button
button = QPushButton(name, self)
button.setFixedSize(width, 30)
button.setObjectName('pickerButton')
button.setProperty('hasBorder', False)
button.setAttribute(Qt.WA_TransparentForMouseEvents)
self.hBoxLayout.addWidget(button, 0, Qt.AlignLeft)
self.buttons.append(button)
if align == Qt.AlignLeft:
button.setProperty('align', 'left')
elif align == Qt.AlignRight:
button.setProperty('align', 'right')
# update the style of buttons
for btn in self.buttons[:-1]:
btn.setProperty('hasBorder', True)
btn.setStyle(QApplication.style())
def value(self):
return [c.value for c in self.columns]
def setColumnValue(self, index: int, value):
if not 0 <= index < len(self.columns):
return
value = str(value)
self.columns[index].value = value
self.buttons[index].setText(value)
self._setButtonProperty('hasValue', True)
def enterEvent(self, e):
self._setButtonProperty('enter', True)
def leaveEvent(self, e):
self._setButtonProperty('enter', False)
def mousePressEvent(self, e):
self._setButtonProperty('pressed', True)
super().mousePressEvent(e)
def mouseReleaseEvent(self, e):
self._setButtonProperty('pressed', False)
super().mouseReleaseEvent(e)
def _setButtonProperty(self, name, value):
""" send event to picker buttons """
for button in self.buttons:
button.setProperty(name, value)
button.setStyle(QApplication.style())
def _showPanel(self):
""" show panel """
panel = PickerPanel(self)
for column in self.columns:
panel.addColumn(column.items, column.width, column.align)
panel.setValue(self.value())
panel.confirmed.connect(self._onConfirmed)
panel.exec(self.mapToGlobal(QPoint(0, -37*4)))
def _onConfirmed(self, value: list):
for i, v in enumerate(value):
self.setColumnValue(i, v)
class PickerPanel(QWidget):
""" picker panel """
confirmed = pyqtSignal(list)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.itemHeight = 37
self.listWidgets = [] # type: List[CycleListWidget]
self.view = QFrame(self)
self.itemMaskWidget = ItemMaskWidget(self.listWidgets, self)
self.hSeparatorWidget = SeparatorWidget(Qt.Horizontal, self.view)
self.yesButton = TransparentToolButton(FluentIcon.ACCEPT, self.view)
self.cancelButton = TransparentToolButton(FluentIcon.CLOSE, self.view)
self.hBoxLayout = QHBoxLayout(self)
self.listLayout = QHBoxLayout()
self.buttonLayout = QHBoxLayout()
self.vBoxLayout = QVBoxLayout(self.view)
self.__initWidget()
def __initWidget(self):
self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint |
Qt.NoDropShadowWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setShadowEffect()
self.yesButton.setIconSize(QSize(16, 16))
self.cancelButton.setIconSize(QSize(13, 13))
self.yesButton.setFixedHeight(33)
self.cancelButton.setFixedHeight(33)
self.hBoxLayout.setContentsMargins(12, 8, 12, 20)
self.hBoxLayout.addWidget(self.view, 1, Qt.AlignCenter)
self.hBoxLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize)
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.addLayout(self.listLayout, 1)
self.vBoxLayout.addWidget(self.hSeparatorWidget)
self.vBoxLayout.addLayout(self.buttonLayout, 1)
self.vBoxLayout.setSizeConstraint(QVBoxLayout.SetMinimumSize)
self.buttonLayout.setSpacing(6)
self.buttonLayout.setContentsMargins(3, 3, 3, 3)
self.buttonLayout.addWidget(self.yesButton)
self.buttonLayout.addWidget(self.cancelButton)
self.yesButton.setSizePolicy(
QSizePolicy.Expanding, QSizePolicy.Expanding)
self.cancelButton.setSizePolicy(
QSizePolicy.Expanding, QSizePolicy.Expanding)
self.yesButton.clicked.connect(self._fadeOut)
self.yesButton.clicked.connect(
lambda: self.confirmed.emit(self.value()))
self.cancelButton.clicked.connect(self._fadeOut)
self.view.setObjectName('view')
FluentStyleSheet.TIME_PICKER.apply(self)
def setShadowEffect(self, blurRadius=30, offset=(0, 8), color=QColor(0, 0, 0, 30)):
""" add shadow to dialog """
self.shadowEffect = QGraphicsDropShadowEffect(self.view)
self.shadowEffect.setBlurRadius(blurRadius)
self.shadowEffect.setOffset(*offset)
self.shadowEffect.setColor(color)
self.view.setGraphicsEffect(None)
self.view.setGraphicsEffect(self.shadowEffect)
def addColumn(self, items: Iterable, width: int, align=Qt.AlignCenter):
""" add one column to view
Parameters
----------
items: Iterable[Any]
the items to be added
width: int
the width of item
align: Qt.AlignmentFlag
the text alignment of item
"""
if self.listWidgets:
self.listLayout.addWidget(SeparatorWidget(Qt.Vertical))
w = CycleListWidget(items, QSize(width, self.itemHeight), align, self)
w.vScrollBar.valueChanged.connect(self.itemMaskWidget.update)
self.listWidgets.append(w)
self.listLayout.addWidget(w)
def resizeEvent(self, e):
self.itemMaskWidget.resize(self.view.width()-3, self.itemHeight)
m = self.hBoxLayout.contentsMargins()
self.itemMaskWidget.move(m.left()+2, m.top() + 148)
def value(self):
return [i.currentItem().text() for i in self.listWidgets]
def setValue(self, value: list):
for v, w in zip(value, self.listWidgets):
w.setSelectedItem(v)
def exec(self, pos, ani=True):
""" show menu
Parameters
----------
pos: QPoint
pop-up position
ani: bool
Whether to show pop-up animation
"""
if self.isVisible():
return
rect = QApplication.screenAt(QCursor.pos()).availableGeometry()
w, h = self.width() + 5, self.height() + 5
pos.setX(
min(pos.x() - self.layout().contentsMargins().left(), rect.right() - w))
pos.setY(max(rect.top(), min(pos.y() - 4, rect.bottom() - h)))
self.move(pos)
# show before running animation, or the height calculation will be wrong
self.show()
if not ani:
return
self.isExpanded = False
self.ani = QPropertyAnimation(self.view, b'windowOpacity', self)
self.ani.valueChanged.connect(self._onAniValueChanged)
self.ani.setStartValue(0)
self.ani.setEndValue(1)
self.ani.setDuration(150)
self.ani.setEasingCurve(QEasingCurve.OutQuad)
self.ani.start()
def _onAniValueChanged(self, opacity):
m = self.layout().contentsMargins()
w = self.view.width() + m.left() + m.right() + 120
h = self.view.height() + m.top() + m.bottom() + 12
if not self.isExpanded:
y = int(h / 2 * (1 - opacity))
self.setMask(QRegion(0, y, w, h-y*2))
else:
y = int(h / 3 * (1 - opacity))
self.setMask(QRegion(0, y, w, h-y*2))
def _fadeOut(self):
self.isExpanded = True
self.ani = QPropertyAnimation(self, b'windowOpacity', self)
self.ani.valueChanged.connect(self._onAniValueChanged)
self.ani.finished.connect(self.deleteLater)
self.ani.setStartValue(1)
self.ani.setEndValue(0)
self.ani.setDuration(150)
self.ani.setEasingCurve(QEasingCurve.OutQuad)
self.ani.start()
# coding:utf-8
from enum import Enum
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QTime
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QWidget
from .picker_base import PickerBase
class TimePickerBase(PickerBase):
""" Time picker base class """
timeChanged = pyqtSignal(QTime)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.time = QTime()
self._addColumns()
def _addColumns(self):
""" add column to time picker """
raise NotImplementedError
def setTime(self, time: QTime):
""" set current time
Parameters
----------
time: QTime
current time
"""
raise NotImplementedError
class TimePicker(TimePickerBase):
""" 24 hours time picker """
def _addColumns(self):
self.addColumn(self.tr('hour'), range(0, 24), 120)
minute = [str(i).zfill(2) for i in range(0, 60)]
self.addColumn(self.tr('minute'), minute, 120)
def setTime(self, time):
if not time.isValid() or time.isNull():
return
self.time = time
self.setColumnValue(0, time.hour())
self.setColumnValue(1, str(time.minute()).zfill(2))
def _onConfirmed(self, value: list):
super()._onConfirmed(value)
h, m = int(value[0]), int(value[1])
time = QTime(h, m)
ot = self.time
self.setTime(time)
if ot != time:
self.timeChanged.emit(time)
class AMTimePicker(TimePickerBase):
""" AM/PM time picker """
def __init__(self, parent=None):
super().__init__(parent)
def _addColumns(self):
self.addColumn(self.tr('hour'), range(1, 13), 80)
minute = [str(i).zfill(2) for i in range(0, 60)]
self.addColumn(self.tr('minute'), minute, 80)
self.addColumn(self.tr('AM'), [self.tr('AM'), self.tr('PM')], 80)
def setTime(self, time):
if not time.isValid() or time.isNull():
return
self.time = time
h = time.hour()
if h in [0, 12]:
self.setColumnValue(0, 12)
else:
self.setColumnValue(0, h % 12)
self.setColumnValue(1, str(time.minute()).zfill(2))
self.setColumnValue(2, self.tr('AM') if h < 12 else self.tr('PM'))
def _onConfirmed(self, value: list):
super()._onConfirmed(value)
h, m, p = value
h, m = int(h), int(m)
if p == self.tr('AM'):
h = 0 if h == 12 else h
elif p == self.tr('PM'):
h = h if h == 12 else h + 12
time = QTime(h, m)
ot = self.time
self.setTime(time)
if ot != time:
self.timeChanged.emit(time)
from .button import PrimaryPushButton, PushButton, RadioButton, HyperlinkButton, ToolButton from .button import PrimaryPushButton, PushButton, RadioButton, HyperlinkButton, ToolButton, TransparentToolButton
from .check_box import CheckBox from .check_box import CheckBox
from .combo_box import ComboBox, EditableComboBox from .combo_box import ComboBox, EditableComboBox
from .line_edit import LineEdit, TextEdit, PlainTextEdit, LineEditButton, SearchLineEdit from .line_edit import LineEdit, TextEdit, PlainTextEdit, LineEditButton, SearchLineEdit
...@@ -13,4 +13,5 @@ from .stacked_widget import PopUpAniStackedWidget, OpacityAniStackedWidget ...@@ -13,4 +13,5 @@ from .stacked_widget import PopUpAniStackedWidget, OpacityAniStackedWidget
from .state_tool_tip import StateToolTip from .state_tool_tip import StateToolTip
from .switch_button import SwitchButton, IndicatorPosition from .switch_button import SwitchButton, IndicatorPosition
from .tool_tip import ToolTip, ToolTipFilter from .tool_tip import ToolTip, ToolTipFilter
from .tree_view import TreeWidget, TreeView from .tree_view import TreeWidget, TreeView
\ No newline at end of file from .cycle_list_widget import CycleListWidget
\ No newline at end of file
# coding:utf-8
from enum import Enum
from typing import Iterable
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QEvent, QRectF, QPoint
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget, QListWidget, QListWidgetItem, QToolButton, QStyledItemDelegate
from .scroll_area import SmoothScrollBar
from ...common.icon import FluentIconBase, Theme, getIconColor
from ...common.style_sheet import themeColor, isDarkTheme
class ScrollIcon(FluentIconBase, Enum):
""" Scroll icon """
UP = "Up"
DOWN = "Down"
def path(self, theme=Theme.AUTO):
if theme == Theme.AUTO:
c = getIconColor()
else:
c = "white" if theme == Theme.DARK else "black"
return f':/qfluentwidgets/images/time_picker/{self.value}_{c}.svg'
class ScrollButton(QToolButton):
""" Scroll button """
def __init__(self, icon: ScrollIcon, parent=None):
super().__init__(parent=parent)
self._icon = icon
self.isPressed = False
self.installEventFilter(self)
def eventFilter(self, obj, e: QEvent):
if obj is self:
if e.type() == QEvent.MouseButtonPress:
self.isPressed = True
self.update()
elif e.type() == QEvent.MouseButtonRelease:
self.isPressed = False
self.update()
return super().eventFilter(obj, e)
def paintEvent(self, e):
super().paintEvent(e)
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
if not self.isPressed:
w, h = 10, 10
else:
w, h = 8, 8
x = (self.width() - w) / 2
y = (self.height() - h) / 2
self._icon.render(painter, QRectF(x, y, w, h))
class CycleListWidget(QListWidget):
""" Cycle list widget """
def __init__(self, items: Iterable, itemSize: QSize, align=Qt.AlignCenter, parent=None):
"""
Parameters
----------
items: Iterable[Any]
the items to be added
itemSize: QSize
the size of item
align: Qt.AlignmentFlag
the text alignment of item
parent: QWidget
parent widget
"""
super().__init__(parent=parent)
self.itemSize = itemSize
self.upButton = ScrollButton(ScrollIcon.UP, self)
self.downButton = ScrollButton(ScrollIcon.DOWN, self)
self.scrollDuration = 250
self.originItems = list(items)
self.vScrollBar = SmoothScrollBar(self)
self.visibleNumber = 9
# repeat adding items to achieve circular scrolling
self._createItems(items, itemSize, align)
self.setVerticalScrollMode(self.ScrollPerPixel)
self.setVerticalScrollBar(self.vScrollBar)
self.vScrollBar.setScrollAnimation(self.scrollDuration)
self.setViewportMargins(0, 0, 0, 0)
self.setFixedSize(itemSize.width()+8, itemSize.height()*self.visibleNumber)
# hide scroll bar
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.upButton.hide()
self.downButton.hide()
self.upButton.clicked.connect(self.scrollUp)
self.downButton.clicked.connect(self.scrollDown)
self.itemClicked.connect(self._onItemClicked)
self.installEventFilter(self)
def _createItems(self, items: list, itemSize: QSize, align=Qt.AlignCenter):
N = len(items)
self.isCycle = N > self.visibleNumber
if self.isCycle:
for _ in range(2):
self._addColumnItems(items, itemSize, align)
self._currentIndex = len(items)
super().scrollToItem(
self.item(self.currentIndex()-self.visibleNumber//2), QListWidget.PositionAtTop)
else:
n = self.visibleNumber // 2 # add empty items to enable scrolling
self._addColumnItems(['']*n, itemSize, align, True)
self._addColumnItems(items, itemSize, align)
self._addColumnItems(['']*n, itemSize, align, True)
self._currentIndex = n
def _addColumnItems(self, items, itemSize, align, disabled=False):
for i in items:
item = QListWidgetItem(str(i), self)
item.setSizeHint(itemSize)
item.setTextAlignment(align | Qt.AlignVCenter)
if disabled:
item.setFlags(Qt.NoItemFlags)
self.addItem(item)
def _onItemClicked(self, item):
self.setCurrentIndex(self.row(item))
self.scrollToItem(self.currentItem())
def setSelectedItem(self, text: str):
""" set the selected item """
if text is None:
return
items = self.findItems(str(text), Qt.MatchExactly)
if not items:
return
if len(items) >= 2:
self.setCurrentIndex(self.row(items[1]))
else:
self.setCurrentIndex(self.row(items[0]))
super().scrollToItem(self.currentItem(), QListWidget.PositionAtCenter)
def scrollToItem(self, item: QListWidgetItem, hint=QListWidget.PositionAtCenter):
""" scroll to item """
# scroll to center position
index = self.row(item)
y = item.sizeHint().height() * (index - self.visibleNumber // 2)
self.vScrollBar.scrollTo(y)
# clear selection
self.clearSelection()
item.setSelected(False)
def wheelEvent(self, e):
if e.angleDelta().y() < 0:
self.scrollDown()
else:
self.scrollUp()
def scrollDown(self):
""" scroll down an item """
self.setCurrentIndex(self.currentIndex() + 1)
self.scrollToItem(self.currentItem())
def scrollUp(self):
""" scroll up an item """
self.setCurrentIndex(self.currentIndex() - 1)
self.scrollToItem(self.currentItem())
def enterEvent(self, e):
self.upButton.show()
self.downButton.show()
def leaveEvent(self, e):
self.upButton.hide()
self.downButton.hide()
def resizeEvent(self, e):
self.upButton.resize(self.width(), 34)
self.downButton.resize(self.width(), 34)
self.downButton.move(0, self.height() - 34)
def eventFilter(self, obj, e: QEvent):
if obj is not self or e.type() != QEvent.KeyPress:
return super().eventFilter(obj, e)
if e.key() == Qt.Key_Down:
self.scrollDown()
return True
elif e.key() == Qt.Key_Up:
self.scrollUp()
return True
return super().eventFilter(obj, e)
def currentItem(self):
return self.item(self.currentIndex())
def currentIndex(self):
return self._currentIndex
def setCurrentIndex(self, index: int):
if not self.isCycle:
n = self.visibleNumber // 2
self._currentIndex = max(n, min(n + len(self.originItems) - 1, index))
else:
N = self.count() // 2
m = (self.visibleNumber + 1) // 2
self._currentIndex = index
# scroll to center to achieve circular scrolling
if index >= self.count() - m:
self._currentIndex = N + index - self.count()
super().scrollToItem(self.item(self.currentIndex() - 1), self.PositionAtCenter)
elif index <= m - 1:
self._currentIndex = N + index
super().scrollToItem(self.item(N + index + 1), self.PositionAtCenter)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册