├── .gitignore
├── .settings
└── org.eclipse.core.resources.prefs
├── CustomWidgets
├── CAvatar.py
├── CColorPicker
│ ├── CColorControl.py
│ ├── CColorInfos.py
│ ├── CColorItems.py
│ ├── CColorPalettes.py
│ ├── CColorPanel.py
│ ├── CColorPicker.py
│ ├── CColorSlider.py
│ ├── CColorStraw.py
│ ├── README.md
│ └── __init__.py
├── CCountUp.py
├── CDrawer.py
├── CFontIcon
│ ├── CFontIcon.py
│ ├── Fonts
│ │ ├── fontawesome-webfont.json
│ │ ├── fontawesome-webfont.ttf
│ │ ├── materialdesignicons-webfont.json
│ │ └── materialdesignicons-webfont.ttf
│ └── __init__.py
├── CFramelessWidget.py
├── CLoadingBar.py
├── CPaginationBar.py
├── CSlider.py
├── CTitleBar.py
├── README.md
└── __init__.py
├── LICENSE
├── README.md
├── ScreenShot
├── CAvatar.gif
├── CColorPicker.gif
├── CCountUp.gif
├── CDrawer.gif
├── CFramelessWidget.gif
├── CLoadingBar.gif
├── CPaginationBar.gif
└── CTitleBar.gif
├── TestCAvatar.py
├── TestCColorPicker.py
├── TestCCountUp.py
├── TestCDrawer.py
├── TestCFontIcon.py
├── TestCFramelessWidget.py
├── TestCLoadingBar.py
├── TestCPaginationBar.py
├── TestCSlider.py
├── TestCTitleBar.py
└── TestData
├── example-1.gif
├── example-1.jpg
├── example-2.jpg
└── example-3.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | .pydevproject
3 | .idea
4 |
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[co]
8 | *$py.class
9 |
10 | # C extensions
11 |
12 | # Distribution / packaging
13 | .Python
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | .hypothesis/
51 | .pytest_cache/
52 |
53 | # Translations
54 | *.mo
55 | *.pot
56 |
57 | # Django stuff:
58 | *.log
59 | local_settings.py
60 | db.sqlite3
61 |
62 | # Flask stuff:
63 | instance/
64 | .webassets-cache
65 |
66 | # Scrapy stuff:
67 | .scrapy
68 |
69 | # Sphinx documentation
70 | docs/_build/
71 |
72 | # PyBuilder
73 | target/
74 |
75 | # Jupyter Notebook
76 | .ipynb_checkpoints
77 |
78 | # pyenv
79 | .python-version
80 |
81 | # celery beat schedule file
82 | celerybeat-schedule
83 |
84 | # SageMath parsed files
85 | *.sage.py
86 |
87 | # Environments
88 | .env
89 | .venv
90 | env/
91 | venv/
92 | ENV/
93 | env.bak/
94 | venv.bak/
95 |
96 | # Spyder project settings
97 | .spyderproject
98 | .spyproject
99 |
100 | # Rope project settings
101 | .ropeproject
102 |
103 | # mkdocs documentation
104 | /site
105 |
106 | # mypy
107 | .mypy_cache/
108 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding//CustomWidgets/CAvatar.py=utf-8
3 | encoding//CustomWidgets/CColorPicker/CColorControl.py=utf-8
4 | encoding//CustomWidgets/CColorPicker/CColorInfos.py=utf-8
5 | encoding//CustomWidgets/CColorPicker/CColorItems.py=utf-8
6 | encoding//CustomWidgets/CColorPicker/CColorPalettes.py=utf-8
7 | encoding//CustomWidgets/CColorPicker/CColorPanel.py=utf-8
8 | encoding//CustomWidgets/CColorPicker/CColorPicker.py=utf-8
9 | encoding//CustomWidgets/CColorPicker/CColorSlider.py=utf-8
10 | encoding//CustomWidgets/CColorPicker/CColorStraw.py=utf-8
11 | encoding//CustomWidgets/CCountUp.py=utf-8
12 | encoding//CustomWidgets/CDrawer.py=utf-8
13 | encoding//CustomWidgets/CFontIcon/CFontIcon.py=utf-8
14 | encoding//CustomWidgets/CFramelessWidget.py=utf-8
15 | encoding//CustomWidgets/CLoadingBar.py=utf-8
16 | encoding//CustomWidgets/CPaginationBar.py=utf-8
17 | encoding//CustomWidgets/CSlider.py=utf-8
18 | encoding//CustomWidgets/CTitleBar.py=utf-8
19 | encoding/TestCAvatar.py=utf-8
20 | encoding/TestCColorPicker.py=utf-8
21 | encoding/TestCCountUp.py=utf-8
22 | encoding/TestCDrawer.py=utf-8
23 | encoding/TestCFontIcon.py=utf-8
24 | encoding/TestCFramelessWidget.py=utf-8
25 | encoding/TestCLoadingBar.py=utf-8
26 | encoding/TestCPaginationBar.py=utf-8
27 | encoding/TestCSlider.py=utf-8
28 | encoding/TestCTitleBar.py=utf-8
29 |
--------------------------------------------------------------------------------
/CustomWidgets/CAvatar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月26日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CAvatar
10 | @description: 头像
11 | """
12 | import os
13 |
14 | from PyQt5.QtCore import QUrl, QRectF, Qt, QSize, QTimer, QPropertyAnimation,\
15 | QPointF, pyqtProperty
16 | from PyQt5.QtGui import QPixmap, QColor, QPainter, QPainterPath, QMovie
17 | from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkDiskCache,\
18 | QNetworkRequest
19 | from PyQt5.QtWidgets import QWidget, qApp
20 |
21 |
22 | __Author__ = 'Irony'
23 | __Copyright__ = 'Copyright (c) 2019 Irony'
24 | __Version__ = 1.0
25 |
26 |
27 | class CAvatar(QWidget):
28 |
29 | Circle = 0 # 圆圈
30 | Rectangle = 1 # 圆角矩形
31 | SizeLarge = QSize(128, 128)
32 | SizeMedium = QSize(64, 64)
33 | SizeSmall = QSize(32, 32)
34 | StartAngle = 0 # 起始旋转角度
35 | EndAngle = 360 # 结束旋转角度
36 |
37 | def __init__(self, *args, shape=0, url='', cacheDir=False, size=QSize(64, 64), animation=False, **kwargs):
38 | super(CAvatar, self).__init__(*args, **kwargs)
39 | self.url = ''
40 | self._angle = 0 # 角度
41 | self.pradius = 0 # 加载进度条半径
42 | self.animation = animation # 是否使用动画
43 | self._movie = None # 动态图
44 | self._pixmap = QPixmap() # 图片对象
45 | self.pixmap = QPixmap() # 被绘制的对象
46 | self.isGif = url.endswith('.gif')
47 | # 进度动画定时器
48 | self.loadingTimer = QTimer(self, timeout=self.onLoading)
49 | # 旋转动画
50 | self.rotateAnimation = QPropertyAnimation(
51 | self, b'angle', self, loopCount=1)
52 | self.setShape(shape)
53 | self.setCacheDir(cacheDir)
54 | self.setSize(size)
55 | self.setUrl(url)
56 |
57 | def paintEvent(self, event):
58 | super(CAvatar, self).paintEvent(event)
59 | # 画笔
60 | painter = QPainter(self)
61 | painter.setRenderHint(QPainter.Antialiasing, True)
62 | painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
63 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
64 | # 绘制
65 | path = QPainterPath()
66 | diameter = min(self.width(), self.height())
67 | if self.shape == self.Circle:
68 | radius = int(diameter / 2)
69 | elif self.shape == self.Rectangle:
70 | radius = 4
71 | halfW = self.width() / 2
72 | halfH = self.height() / 2
73 | painter.translate(halfW, halfH)
74 | path.addRoundedRect(
75 | QRectF(-halfW, -halfH, diameter, diameter), radius, radius)
76 | painter.setClipPath(path)
77 | # 如果是动画效果
78 | if self.rotateAnimation.state() == QPropertyAnimation.Running:
79 | painter.rotate(self._angle) # 旋转
80 | painter.drawPixmap(
81 | QPointF(-self.pixmap.width() / 2, -self.pixmap.height() / 2), self.pixmap)
82 | else:
83 | painter.drawPixmap(-int(halfW), -int(halfH), self.pixmap)
84 | # 如果在加载
85 | if self.loadingTimer.isActive():
86 | diameter = 2 * self.pradius
87 | painter.setBrush(
88 | QColor(45, 140, 240, (1 - self.pradius / 10) * 255))
89 | painter.setPen(Qt.NoPen)
90 | painter.drawRoundedRect(
91 | QRectF(-self.pradius, -self.pradius, diameter, diameter), self.pradius, self.pradius)
92 |
93 | def enterEvent(self, event):
94 | """鼠标进入动画
95 | :param event:
96 | """
97 | if not (self.animation and not self.isGif):
98 | return
99 | self.rotateAnimation.stop()
100 | cv = self.rotateAnimation.currentValue() or self.StartAngle
101 | self.rotateAnimation.setDuration(
102 | 540 if cv == 0 else int(cv / self.EndAngle * 540))
103 | self.rotateAnimation.setStartValue(cv)
104 | self.rotateAnimation.setEndValue(self.EndAngle)
105 | self.rotateAnimation.start()
106 |
107 | def leaveEvent(self, event):
108 | """鼠标离开动画
109 | :param event:
110 | """
111 | if not (self.animation and not self.isGif):
112 | return
113 | self.rotateAnimation.stop()
114 | cv = self.rotateAnimation.currentValue() or self.EndAngle
115 | self.rotateAnimation.setDuration(int(cv / self.EndAngle * 540))
116 | self.rotateAnimation.setStartValue(cv)
117 | self.rotateAnimation.setEndValue(self.StartAngle)
118 | self.rotateAnimation.start()
119 |
120 | def onLoading(self):
121 | """更新进度动画
122 | """
123 | if self.loadingTimer.isActive():
124 | if self.pradius > 9:
125 | self.pradius = 0
126 | self.pradius += 1
127 | else:
128 | self.pradius = 0
129 | self.update()
130 |
131 | def onFinished(self):
132 | """图片下载完成
133 | """
134 | self.loadingTimer.stop()
135 | self.pradius = 0
136 | reply = self.sender()
137 |
138 | if self.isGif:
139 | self._movie = QMovie(reply, b'gif', self)
140 | if self._movie.isValid():
141 | self._movie.frameChanged.connect(self._resizeGifPixmap)
142 | self._movie.start()
143 | else:
144 | data = reply.readAll().data()
145 | reply.deleteLater()
146 | del reply
147 | self._pixmap.loadFromData(data)
148 | if self._pixmap.isNull():
149 | self._pixmap = QPixmap(self.size())
150 | self._pixmap.fill(QColor(204, 204, 204))
151 | self._resizePixmap()
152 |
153 | def onError(self, code):
154 | """下载出错了
155 | :param code:
156 | """
157 | self._pixmap = QPixmap(self.size())
158 | self._pixmap.fill(QColor(204, 204, 204))
159 | self._resizePixmap()
160 |
161 | def refresh(self):
162 | """强制刷新
163 | """
164 | self._get(self.url)
165 |
166 | def isLoading(self):
167 | """判断是否正在加载
168 | """
169 | return self.loadingTimer.isActive()
170 |
171 | def setShape(self, shape):
172 | """设置形状
173 | :param shape: 0=圆形, 1=圆角矩形
174 | """
175 | self.shape = shape
176 |
177 | def setUrl(self, url):
178 | """设置url,可以是本地路径,也可以是网络地址
179 | :param url:
180 | """
181 | self.url = url
182 | self._get(url)
183 |
184 | def setCacheDir(self, cacheDir=''):
185 | """设置本地缓存路径
186 | :param cacheDir:
187 | """
188 | self.cacheDir = cacheDir
189 | self._initNetWork()
190 |
191 | def setSize(self, size):
192 | """设置固定尺寸
193 | :param size:
194 | """
195 | if not isinstance(size, QSize):
196 | size = self.SizeMedium
197 | self.setMinimumSize(size)
198 | self.setMaximumSize(size)
199 | self._resizePixmap()
200 |
201 | @pyqtProperty(int)
202 | def angle(self):
203 | return self._angle
204 |
205 | @angle.setter
206 | def angle(self, value):
207 | self._angle = value
208 | self.update()
209 |
210 | def _resizePixmap(self):
211 | """缩放图片
212 | """
213 | if not self._pixmap.isNull():
214 | self.pixmap = self._pixmap.scaled(
215 | self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
216 | self.update()
217 |
218 | def _resizeGifPixmap(self, _):
219 | """缩放动画图片
220 | """
221 | if self._movie:
222 | self.pixmap = self._movie.currentPixmap().scaled(
223 | self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
224 | self.update()
225 |
226 | def _initNetWork(self):
227 | """初始化异步网络库
228 | """
229 | if not hasattr(qApp, '_network'):
230 | network = QNetworkAccessManager(self.window())
231 | setattr(qApp, '_network', network)
232 | # 是否需要设置缓存
233 | if self.cacheDir and not qApp._network.cache():
234 | cache = QNetworkDiskCache(self.window())
235 | cache.setCacheDirectory(self.cacheDir)
236 | qApp._network.setCache(cache)
237 |
238 | def _get(self, url):
239 | """设置图片或者请求网络图片
240 | :param url:
241 | """
242 | if not url:
243 | self.onError('')
244 | return
245 | if url.startswith('http') and not self.loadingTimer.isActive():
246 | url = QUrl(url)
247 | request = QNetworkRequest(url)
248 | request.setHeader(QNetworkRequest.UserAgentHeader, b'CAvatar')
249 | request.setRawHeader(b'Author', b'Irony')
250 | request.setAttribute(
251 | QNetworkRequest.FollowRedirectsAttribute, True)
252 | if qApp._network.cache():
253 | request.setAttribute(
254 | QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.PreferNetwork)
255 | request.setAttribute(
256 | QNetworkRequest.CacheSaveControlAttribute, True)
257 | reply = qApp._network.get(request)
258 | self.pradius = 0
259 | self.loadingTimer.start(50) # 显示进度动画
260 | reply.finished.connect(self.onFinished)
261 | reply.error.connect(self.onError)
262 | return
263 | self.pradius = 0
264 | if os.path.exists(url) and os.path.isfile(url):
265 | if self.isGif:
266 | self._movie = QMovie(url, parent=self)
267 | if self._movie.isValid():
268 | self._movie.frameChanged.connect(self._resizeGifPixmap)
269 | self._movie.start()
270 | else:
271 | self._pixmap = QPixmap(url)
272 | self._resizePixmap()
273 | else:
274 | self.onError('')
275 |
276 |
277 | if __name__ == '__main__':
278 | import sys
279 | from PyQt5.QtWidgets import QApplication
280 | app = QApplication(sys.argv)
281 | w = CAvatar()
282 | w.show()
283 | sys.exit(app.exec_())
284 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorControl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月21日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/PyQt5
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorControl
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt, pyqtSignal
13 | from PyQt5.QtGui import QPainter, QPainterPath, QColor
14 | from PyQt5.QtWidgets import QWidget
15 |
16 |
17 | __Author__ = "Irony"
18 | __Copyright__ = "Copyright (c) 2019 "
19 | __Version__ = "Version 1.0"
20 |
21 |
22 | class CColorControl(QWidget):
23 |
24 | colorChanged = pyqtSignal(QColor)
25 |
26 | def __init__(self, *args, color=Qt.red, **kwargs):
27 | super(CColorControl, self).__init__(*args, **kwargs)
28 | self._alpha = 255
29 | self._color = QColor(color)
30 | self.colorChanged.emit(self._color)
31 |
32 | def updateColor(self, color, alpha=255):
33 | self._color = QColor(color)
34 | self.colorChanged.emit(self._color)
35 | self._color.setAlpha(alpha)
36 | self.update()
37 |
38 | def reset(self):
39 | self.updateColor(Qt.red)
40 |
41 | def paintEvent(self, event):
42 | super(CColorControl, self).paintEvent(event)
43 | painter = QPainter(self)
44 | painter.setRenderHint(QPainter.Antialiasing, True)
45 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
46 | painter.setPen(Qt.NoPen)
47 |
48 | # 变换圆心
49 | painter.translate(self.rect().center())
50 |
51 | # 画背景方格图
52 | painter.save()
53 | # 保证方格在前景圆内部
54 | diameter = min(self.width(), self.height()) - 8
55 | radius = diameter / 2
56 | path = QPainterPath()
57 | path.addRoundedRect(-radius, -radius, diameter,
58 | diameter, radius, radius)
59 | painter.setClipPath(path)
60 |
61 | pixSize = 5
62 | for x in range(int(self.width() / pixSize)):
63 | for y in range(int(self.height() / pixSize)):
64 | _x, _y = x * pixSize, y * pixSize
65 | painter.fillRect(_x - radius, _y - radius, pixSize, pixSize,
66 | Qt.white if x % 2 != y % 2 else Qt.darkGray)
67 | painter.restore()
68 |
69 | # 画前景颜色
70 | diameter = min(self.width(), self.height()) - 4
71 | radius = diameter / 2
72 | path = QPainterPath()
73 | path.addRoundedRect(-radius, -radius, diameter,
74 | diameter, radius, radius)
75 | painter.setClipPath(path)
76 |
77 | painter.setBrush(self._color)
78 | painter.drawRoundedRect(-radius, -radius,
79 | diameter, diameter, radius, radius)
80 |
81 |
82 | if __name__ == '__main__':
83 | import sys
84 | from PyQt5.QtWidgets import QApplication
85 | app = QApplication(sys.argv)
86 | w = CColorControl()
87 | w.resize(200, 200)
88 | w.show()
89 | sys.exit(app.exec_())
90 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorInfos.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月21日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/PyQt5
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorInfos
10 | @description:
11 | """
12 | from PyQt5.QtCore import QSize, Qt, QRegExp, pyqtSignal
13 | from PyQt5.QtGui import QRegExpValidator, QColor
14 | from PyQt5.QtWidgets import QWidget, QGridLayout, QLineEdit, QLabel, QSpinBox,\
15 | QSpacerItem, QSizePolicy, QPushButton
16 |
17 |
18 | __Author__ = "Irony"
19 | __Copyright__ = "Copyright (c) 2019 "
20 | __Version__ = "Version 1.0"
21 |
22 |
23 | class CColorInfos(QWidget):
24 |
25 | colorChanged = pyqtSignal(QColor, int)
26 | colorAdded = pyqtSignal(QColor)
27 |
28 | def __init__(self, *args, **kwargs):
29 | super(CColorInfos, self).__init__(*args, **kwargs)
30 | layout = QGridLayout(self)
31 | layout.setContentsMargins(11, 2, 11, 2)
32 | layout.setSpacing(8)
33 |
34 | self.editHex = QLineEdit(
35 | '#FF0000', self, alignment=Qt.AlignCenter,
36 | objectName='editHex',
37 | textChanged=self.onHexChanged)
38 | self.editHex.setValidator(
39 | QRegExpValidator(QRegExp('#[0-9a-fA-F]{6}$'), self.editHex))
40 | self.labelHex = QLabel('HEX', self, alignment=Qt.AlignCenter)
41 | layout.addWidget(self.editHex, 0, 0)
42 | layout.addWidget(self.labelHex, 1, 0)
43 |
44 | layout.addItem(QSpacerItem(
45 | 10, 20, QSizePolicy.Fixed, QSizePolicy.Minimum), 0, 1)
46 |
47 | self.editRed = QSpinBox(
48 | self, buttonSymbols=QSpinBox.NoButtons,
49 | alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged)
50 | self.editRed.setRange(0, 255)
51 | self.labelRed = QLabel('R', self, alignment=Qt.AlignCenter)
52 | layout.addWidget(self.editRed, 0, 2)
53 | layout.addWidget(self.labelRed, 1, 2)
54 |
55 | self.editGreen = QSpinBox(
56 | self, buttonSymbols=QSpinBox.NoButtons,
57 | alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged)
58 | self.editGreen.setRange(0, 255)
59 | self.labelGreen = QLabel('G', self, alignment=Qt.AlignCenter)
60 | layout.addWidget(self.editGreen, 0, 3)
61 | layout.addWidget(self.labelGreen, 1, 3)
62 |
63 | self.editBlue = QSpinBox(
64 | self, buttonSymbols=QSpinBox.NoButtons,
65 | alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged)
66 | self.editBlue.setRange(0, 255)
67 | self.labelBlue = QLabel('B', self, alignment=Qt.AlignCenter)
68 | layout.addWidget(self.editBlue, 0, 4)
69 | layout.addWidget(self.labelBlue, 1, 4)
70 |
71 | self.editAlpha = QSpinBox(
72 | self, buttonSymbols=QSpinBox.NoButtons,
73 | alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged)
74 | self.editAlpha.setRange(0, 255)
75 | self.labelAlpha = QLabel('A', self, alignment=Qt.AlignCenter)
76 | layout.addWidget(self.editAlpha, 0, 5)
77 | layout.addWidget(self.labelAlpha, 1, 5)
78 |
79 | layout.addItem(QSpacerItem(
80 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 6)
81 | layout.addWidget(QPushButton(
82 | '+', self, cursor=Qt.PointingHandCursor,
83 | toolTip='添加自定义颜色',
84 | clicked=self.onColorAdd), 0, 7)
85 |
86 | layout.setColumnStretch(0, 3)
87 | layout.setColumnStretch(1, 1)
88 | layout.setColumnStretch(2, 1)
89 | layout.setColumnStretch(3, 1)
90 | layout.setColumnStretch(4, 1)
91 | layout.setColumnStretch(5, 1)
92 | layout.setColumnStretch(6, 2)
93 | layout.setColumnStretch(7, 1)
94 | self.setFocus()
95 | self.editRed.setValue(255)
96 | self.editAlpha.setValue(255)
97 |
98 | def reset(self):
99 | pass
100 |
101 | def onColorAdd(self):
102 | self.colorAdded.emit(QColor(
103 | self.editRed.value(),
104 | self.editGreen.value(),
105 | self.editBlue.value()
106 | ))
107 |
108 | def setHex(self, code):
109 | self.editHex.setText(str(code))
110 |
111 | def updateColor(self, color):
112 | self.editRed.setValue(color.red())
113 | self.editGreen.setValue(color.green())
114 | self.editBlue.setValue(color.blue())
115 |
116 | def updateAlpha(self, _, alpha):
117 | self.editAlpha.setValue(alpha)
118 |
119 | def onHexChanged(self, code):
120 | if len(code) != 7:
121 | return
122 | color = QColor(code)
123 | if color.isValid():
124 | self.blockRgbaSignals(True)
125 | self.editHex.blockSignals(True)
126 | self.editRed.setValue(color.red())
127 | self.editGreen.setValue(color.green())
128 | self.editBlue.setValue(color.blue())
129 | self.editAlpha.setValue(color.alpha())
130 | self.colorChanged.emit(color, color.alpha())
131 | self.editHex.blockSignals(False)
132 | self.blockRgbaSignals(False)
133 |
134 | def onRgbaChanged(self, _):
135 | self.editHex.blockSignals(True)
136 | self.blockRgbaSignals(True)
137 | color = QColor(
138 | self.editRed.value(),
139 | self.editGreen.value(),
140 | self.editBlue.value(),
141 | self.editAlpha.value()
142 | )
143 | self.editHex.setText(color.name())
144 | self.colorChanged.emit(color, self.editAlpha.value())
145 | self.blockRgbaSignals(False)
146 | self.editHex.blockSignals(False)
147 |
148 | def blockRgbaSignals(self, block=True):
149 | self.editRed.blockSignals(block)
150 | self.editGreen.blockSignals(block)
151 | self.editBlue.blockSignals(block)
152 | self.editAlpha.blockSignals(block)
153 |
154 | def sizeHint(self):
155 | return QSize(280, 48)
156 |
157 |
158 | if __name__ == '__main__':
159 | import sys
160 | from PyQt5.QtWidgets import QApplication
161 | app = QApplication(sys.argv)
162 | w = CColorInfos()
163 | w.show()
164 | sys.exit(app.exec_())
165 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorItems.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月21日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/PyQt5
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorItems
10 | @description: 小方块列表
11 | """
12 | from PyQt5.QtCore import Qt, QSize
13 | from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem
14 | from PyQt5.QtWidgets import QListView, QStyledItemDelegate, QStyle
15 |
16 |
17 | __Author__ = "Irony"
18 | __Copyright__ = "Copyright (c) 2019 "
19 | __Version__ = "Version 1.0"
20 |
21 |
22 | class StyledItemDelegate(QStyledItemDelegate):
23 |
24 | def paint(self, painter, option, index):
25 | if option.state & QStyle.State_HasFocus:
26 | # 取消虚线框
27 | option.state = option.state ^ QStyle.State_HasFocus
28 |
29 | # 取出颜色
30 | item = index.model().itemFromIndex(index)
31 | color = item.data()
32 |
33 | # 绘制矩形区域
34 | rect = option.rect
35 | # 是否鼠标悬停
36 | _in = option.state & QStyle.State_MouseOver
37 |
38 | painter.save()
39 | painter.setPen(color.darker(150) if _in else Qt.NoPen)
40 | painter.setBrush(color)
41 | rect = rect if _in else rect.adjusted(1, 1, -1, -1)
42 | painter.drawRoundedRect(rect, 2, 2)
43 | painter.restore()
44 |
45 |
46 | class CColorItems(QListView):
47 |
48 | def __init__(self, colors, *args, **kwargs):
49 | super(CColorItems, self).__init__(*args, **kwargs)
50 | self.setItemDelegate(StyledItemDelegate(self))
51 | self.setEditTriggers(self.NoEditTriggers)
52 | self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
53 | self.setFlow(self.LeftToRight)
54 | self.setWrapping(True)
55 | self.setResizeMode(self.Adjust)
56 | self.setSpacing(6)
57 | self.setCursor(Qt.PointingHandCursor)
58 | self.setFrameShape(self.NoFrame)
59 | self._model = QStandardItemModel(self)
60 | self.setModel(self._model)
61 |
62 | for color in colors:
63 | self.addColor(color)
64 |
65 | def addColor(self, color):
66 | item = QStandardItem('')
67 | item.setData(QColor(color))
68 | item.setSizeHint(QSize(20, 20))
69 | item.setToolTip(color)
70 | self._model.appendRow(item)
71 |
72 |
73 | if __name__ == '__main__':
74 | import sys
75 | from PyQt5.QtWidgets import QApplication
76 | app = QApplication(sys.argv)
77 | w = CColorItems(['#A4C400', '#60A917', '#008A00', '#00ABA9', ])
78 | w.colorChanged.connect(lambda c: print('color: ', c.name()))
79 | w.show()
80 | sys.exit(app.exec_())
81 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorPalettes.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月21日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorPalettes
10 | @description:
11 | """
12 | from PyQt5.QtCore import QSettings, pyqtSignal
13 | from PyQt5.QtGui import QColor
14 | from PyQt5.QtWidgets import QTabWidget
15 |
16 | from CustomWidgets.CColorPicker.CColorItems import CColorItems
17 |
18 |
19 | __Author__ = 'Irony'
20 | __Copyright__ = 'Copyright (c) 2019'
21 |
22 | MaterialColors = [
23 | '#F44336', '#E91E63', '#9C27B0', '#673AB7',
24 | '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4',
25 | '#009688', '#4CAF50', '#8BC34A', '#CDDC39',
26 | '#FFEB3B', '#FFC107', '#FF9800', '#FF5722',
27 | '#795548', '#9E9E9E', '#607D8B',
28 | ]
29 |
30 | FlatColors = [
31 | '#1ABC9C', '#2ECC71', '#3498DB', '#9B59B6',
32 | '#34495E', '#16A085', '#27AE60', '#2980B9',
33 | '#8E44AD', '#2C3E50', '#F1C40F', '#E67E22',
34 | '#E74C3C', '#ECF0F1', '#95A5A6', '#F39C12',
35 | '#D35400', '#C0392B', '#BDC3C7', '#7F8C8D',
36 | ]
37 |
38 | FluentColors = [
39 | '#FFB900', '#E74856', '#0078D7', '#0099BC',
40 | '#7A7574', '#767676', '#FF8C00', '#E81123',
41 | '#0063B1', '#2D7D9A', '#5D5A58', '#4C4A48',
42 | '#F7630C', '#EA005E', '#8E8CD8', '#00B7C3',
43 | '#68768A', '#69797E', '#CA5010', '#C30052',
44 | '#6B69D6', '#038387', '#515C6B', '#4A5459',
45 | '#DA3B01', '#E3008C', '#8764B8', '#00B294',
46 | '#567C73', '#647C64', '#EF6950', '#BF0077',
47 | '#744DA9', '#018574', '#486860', '#525E54',
48 | '#D13438', '#C239B3', '#B146C2', '#00CC6A',
49 | '#498205', '#847545', '#FF4343', '#9A0089',
50 | '#881798', '#10893E', '#107C10', '#7E735F',
51 | ]
52 |
53 | SocialColors = [
54 | '#3B5999', '#0084FF', '#55ACEE', '#0077B5',
55 | '#00AFF0', '#007EE5', '#21759B', '#1AB7EA',
56 | '#0077B5', '#4C75A3', '#34465D', '#410093',
57 | '#DD4B39', '#BD081C', '#CD201F', '#EB4924',
58 | '#FF5700', '#B92B27', '#AF0606', '#DF2029',
59 | '#DA552F', '#FF6600', '#FF3300', '#F57D00',
60 | '#25D366', '#09B83E', '#00C300', '#02B875',
61 | '#00B489', '#3AAF85', '#E4405F', '#EA4C89',
62 | '#FF0084', '#F94877', '#131418', '#FFFC00',
63 | ]
64 |
65 | MetroColors = [
66 | '#A4C400', '#60A917', '#008A00', '#00ABA9',
67 | '#1BA1E2', '#0050EF', '#6A00FF', '#AA00FF',
68 | '#F472D0', '#D80073', '#A20025', '#E51400',
69 | '#FA6800', '#F0A30A', '#E3C800', '#825A2C',
70 | '#6D8764', '#647687', '#76608A', '#A0522D',
71 | ]
72 |
73 |
74 | class CColorPalettes(QTabWidget):
75 |
76 | colorChanged = pyqtSignal(QColor)
77 |
78 | def __init__(self, *args, **kwargs):
79 | super(CColorPalettes, self).__init__(*args, **kwargs)
80 |
81 | self._setting = QSettings('CColorPicker', QSettings.NativeFormat, self)
82 | self.addTab(CColorItems(MaterialColors, self,
83 | clicked=self.onColorChanged), 'Material')
84 | self.addTab(CColorItems(FlatColors, self,
85 | clicked=self.onColorChanged), 'Flat')
86 | self.addTab(CColorItems(FluentColors, self,
87 | clicked=self.onColorChanged), 'Fluent')
88 | self.addTab(CColorItems(SocialColors, self,
89 | clicked=self.onColorChanged), 'Social')
90 | self.addTab(CColorItems(MetroColors, self,
91 | clicked=self.onColorChanged), 'Metro')
92 |
93 | self.customColors = self._setting.value('colors', [])
94 | self.customItems = CColorItems(
95 | self.customColors, self, clicked=self.onColorChanged)
96 | self.addTab(self.customItems, 'Custom')
97 |
98 | def reset(self):
99 | self.setCurrentIndex(0)
100 |
101 | def onColorChanged(self, index):
102 | self.colorChanged.emit(
103 | self.sender().model().itemFromIndex(index).data())
104 |
105 | def addColor(self, color):
106 | name = color.name().upper()
107 | if not name in self.customColors:
108 | self.customColors.append(name)
109 | self.customItems.addColor(name)
110 | self.setCurrentWidget(self.customItems)
111 | self._setting.setValue('colors', self.customColors)
112 |
113 |
114 | if __name__ == '__main__':
115 | import sys
116 | from PyQt5.QtWidgets import QApplication
117 | app = QApplication(sys.argv)
118 | w = CColorPalettes()
119 | w.show()
120 | w.colorChanged.connect(lambda c: print(c.name()))
121 | sys.exit(app.exec_())
122 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorPanel.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月20日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/PyQt5
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorPanel
10 | @description: 饱和度面板
11 | """
12 | from PyQt5.QtCore import Qt, QPoint, pyqtSignal
13 | from PyQt5.QtGui import QPainter, QLinearGradient, QColor, QImage, QPen,\
14 | QPainterPath
15 | from PyQt5.QtWidgets import QWidget
16 |
17 |
18 | __Author__ = "Irony"
19 | __Copyright__ = "Copyright (c) 2019 "
20 | __Version__ = "Version 1.0"
21 |
22 |
23 | class CColorPanel(QWidget):
24 |
25 | colorChanged = pyqtSignal(QColor)
26 |
27 | def __init__(self, *args, color=Qt.red, **kwargs):
28 | super(CColorPanel, self).__init__(*args, **kwargs)
29 | self._color = QColor(color)
30 | self._image = None
31 | self._imagePointer = None # 小圆环
32 | self._pointerPos = None # 小圆环位置
33 | self._createPointer()
34 |
35 | def reset(self):
36 | self.blockSignals(True)
37 | self._color = QColor(Qt.red)
38 | self._pointerPos = self.rect().topRight() - QPoint(6, 6)
39 | self.update()
40 | self.blockSignals(False)
41 |
42 | def _createPointer(self):
43 | # 绘制一个小圆环
44 | self._imagePointer = QImage(12, 12, QImage.Format_ARGB32)
45 | self._imagePointer.fill(Qt.transparent)
46 | painter = QPainter()
47 | painter.begin(self._imagePointer)
48 | painter.setRenderHint(QPainter.Antialiasing, True)
49 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
50 | painter.setPen(QPen(Qt.white, 2))
51 | painter.setBrush(Qt.NoBrush)
52 | path = QPainterPath()
53 | path.addRoundedRect(0, 0, 12, 12, 6.0, 6.0)
54 | painter.setClipPath(path)
55 | painter.drawRoundedRect(0, 0, 12, 12, 6.0, 6.0)
56 | painter.end()
57 |
58 | def mousePressEvent(self, event):
59 | # 鼠标按下更新小圆环位置
60 | super(CColorPanel, self).mousePressEvent(event)
61 | self._pointerPos = event.pos()
62 | if self._image:
63 | self.colorChanged.emit(self._image.pixelColor(
64 | max(min(self._pointerPos.x(), self.width() - 1), 0),
65 | max(min(self._pointerPos.y(), self.height() - 1), 0)
66 | ))
67 | self._pointerPos -= QPoint(6, 6)
68 | self.update()
69 |
70 | def mouseMoveEvent(self, event):
71 | # 小圆环随鼠标移动
72 | super(CColorPanel, self).mouseMoveEvent(event)
73 | self._pointerPos = event.pos()
74 | self._pointerPos.setX(max(min(self._pointerPos.x(), self.width()), 0))
75 | self._pointerPos.setY(max(min(self._pointerPos.y(), self.height()), 0))
76 | if self._image:
77 | self.colorChanged.emit(self._image.pixelColor(
78 | max(min(self._pointerPos.x(), self.width() - 1), 0),
79 | max(min(self._pointerPos.y(), self.height() - 1), 0)
80 | ))
81 | self._pointerPos -= QPoint(6, 6)
82 | self.update()
83 |
84 | def paintEvent(self, event):
85 | super(CColorPanel, self).paintEvent(event)
86 | if self._image:
87 | painter = QPainter(self)
88 | painter.setRenderHint(QPainter.Antialiasing, True)
89 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
90 | painter.drawImage(self.rect(), self._image)
91 | painter.setPen(QColor(240, 240, 240))
92 | painter.drawRect(self.rect())
93 | if self._pointerPos:
94 | painter.setPen(Qt.NoPen)
95 | painter.drawImage(self._pointerPos, self._imagePointer)
96 |
97 | def showEvent(self, event):
98 | super(CColorPanel, self).showEvent(event)
99 | # 右上角
100 | if not self._pointerPos:
101 | self._pointerPos = self.rect().topRight() - QPoint(6, 6)
102 |
103 | def resizeEvent(self, event):
104 | super(CColorPanel, self).resizeEvent(event)
105 | self.createImage(self._color)
106 |
107 | def createImage(self, color, alpha=255):
108 | self._color = QColor(color)
109 | self._color.setAlpha(255)
110 | self._image = QImage(self.size(), QImage.Format_ARGB32)
111 |
112 | painter = QPainter()
113 | painter.begin(self._image)
114 | painter.setRenderHint(QPainter.Antialiasing, True)
115 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
116 | # 背景色
117 | painter.fillRect(self.rect(), color)
118 | # 白色渐变
119 | gradient = QLinearGradient(0, 0, self.width(), 0)
120 | gradient.setColorAt(0, Qt.white)
121 | gradient.setColorAt(1, QColor.fromHslF(0.055, 0.42, 0.65, 0))
122 | painter.fillRect(self.rect(), gradient)
123 | # 黑色渐变
124 | gradient = QLinearGradient(0, self.height(), 0, 0)
125 | gradient.setColorAt(1, QColor.fromHslF(0.055, 0.42, 0.65, 0))
126 | gradient.setColorAt(0, Qt.black)
127 | painter.fillRect(self.rect(), gradient)
128 | painter.end()
129 |
130 | self.update()
131 |
132 | if self._image and self._pointerPos:
133 | self.colorChanged.emit(self._image.pixelColor(
134 | max(min(self._pointerPos.x(), self.width() - 1), 0),
135 | max(min(self._pointerPos.y(), self.height() - 1), 0)
136 | ))
137 |
138 |
139 | if __name__ == '__main__':
140 | import sys
141 | from PyQt5.QtWidgets import QApplication
142 | app = QApplication(sys.argv)
143 | w = CColorPanel(color=Qt.blue)
144 | w.resize(224, 120)
145 | w.colorChanged.connect(lambda c: print('color:', c.name()))
146 | w.show()
147 | sys.exit(app.exec_())
148 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorPicker.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月19日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorPicker
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtGui import QColor
14 | from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget,\
15 | QGraphicsDropShadowEffect, QSpacerItem, QSizePolicy,\
16 | QHBoxLayout, QPushButton
17 |
18 | from CustomWidgets.CColorPicker.CColorControl import CColorControl
19 | from CustomWidgets.CColorPicker.CColorInfos import CColorInfos
20 | from CustomWidgets.CColorPicker.CColorPalettes import CColorPalettes
21 | from CustomWidgets.CColorPicker.CColorPanel import CColorPanel
22 | from CustomWidgets.CColorPicker.CColorSlider import CColorSlider
23 | from CustomWidgets.CColorPicker.CColorStraw import CColorStraw
24 |
25 |
26 | __Author__ = "Irony"
27 | __Copyright__ = 'Copyright (c) 2019 Irony'
28 | __Version__ = 1.0
29 |
30 | Stylesheet = """
31 | QLineEdit, QLabel, QTabWidget {
32 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
33 | }
34 | #Custom_Color_View {
35 | background: white;
36 | border-radius: 3px;
37 | }
38 | CColorPalettes {
39 | min-width: 322px;
40 | max-width: 322px;
41 | max-height: 120px;
42 | }
43 | CColorPanel {
44 | min-height: 160px;
45 | max-height: 160px;
46 | }
47 | CColorControl {
48 | min-width: 50px;
49 | max-width: 50px;
50 | min-height: 50px;
51 | max-height: 50px;
52 | }
53 |
54 | #editHex {
55 | min-width: 75px;
56 | }
57 |
58 | #splitLine {
59 | min-height: 1px;
60 | max-height: 1px;
61 | background: #e2e2e2;
62 | }
63 |
64 | QLineEdit, QSpinBox {
65 | border: 1px solid #cbcbcb;
66 | border-radius: 2px;
67 | background: white;
68 | min-width: 31px;
69 | min-height: 21px;
70 | }
71 | QLineEdit:focus, QSpinBox:focus {
72 | border-color: rgb(139, 173, 228);
73 | }
74 | QLabel {
75 | color: #a9a9a9;
76 | }
77 | QPushButton {
78 | border: 1px solid #cbcbcb;
79 | border-radius: 2px;
80 | min-width: 21px;
81 | max-width: 21px;
82 | min-height: 21px;
83 | max-height: 21px;
84 | font-size: 14px;
85 | background: white;
86 | }
87 | QPushButton:hover {
88 | border-color: rgb(139, 173, 228);
89 | }
90 | QPushButton:pressed {
91 | border-color: #cbcbcb;
92 | }
93 |
94 | CColorStraw {
95 | border: none;
96 | font-size: 18px;
97 | border-radius: 0px;
98 | }
99 | QPushButton:hover {
100 | color: rgb(139, 173, 228);
101 | }
102 | QPushButton:pressed {
103 | color: #cbcbcb;
104 | }
105 |
106 | #confirmButton, #cancelButton {
107 | min-width: 70px;
108 | min-height: 30px;
109 | }
110 | #cancelButton:hover {
111 | border-color: rgb(255, 133, 0);
112 | }
113 |
114 | QTabWidget::pane {
115 | border: none;
116 | }
117 | QTabBar::tab {
118 | padding: 3px 6px;
119 | color: rgb(100, 100, 100);
120 | background: transparent;
121 | }
122 | QTabBar::tab:hover {
123 | color: black;
124 | }
125 | QTabBar::tab:selected {
126 | color: rgb(139, 173, 228);
127 | border-bottom: 2px solid rgb(139, 173, 228);
128 | }
129 |
130 | QTabBar::tab:!selected {
131 | border-bottom: 2px solid transparent;
132 | }
133 |
134 | QScrollBar:vertical {
135 | max-width: 10px;
136 | border: none;
137 | margin: 0px 0px 0px 0px;
138 | }
139 |
140 | QScrollBar::handle:vertical {
141 | background: rgb(220, 220, 220);
142 | border: 1px solid rgb(207, 207, 207);
143 | border-radius: 5px;
144 | }
145 | """
146 |
147 |
148 | class CColorPicker(QDialog):
149 |
150 | selectedColor = QColor()
151 |
152 | def __init__(self, *args, **kwargs):
153 | super(CColorPicker, self).__init__(*args, **kwargs)
154 | self.setObjectName('Custom_Color_Dialog')
155 | self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
156 | self.setAttribute(Qt.WA_TranslucentBackground, True)
157 | self.setStyleSheet(Stylesheet)
158 | self.mPos = None
159 | self.initUi()
160 | self.initSignals()
161 | # 添加阴影
162 | effect = QGraphicsDropShadowEffect(self)
163 | effect.setBlurRadius(10)
164 | effect.setOffset(0, 0)
165 | effect.setColor(Qt.gray)
166 | self.setGraphicsEffect(effect)
167 |
168 | def initUi(self):
169 | layout = QVBoxLayout(self)
170 | self.colorView = QWidget(self)
171 | self.colorView.setObjectName('Custom_Color_View')
172 | layout.addWidget(self.colorView)
173 |
174 | # 内部布局
175 | layout = QVBoxLayout(self.colorView)
176 | layout.setContentsMargins(1, 1, 1, 1)
177 |
178 | # 面板
179 | self.colorPanel = CColorPanel(self.colorView)
180 | layout.addWidget(self.colorPanel)
181 |
182 | self.controlWidget = QWidget(self.colorView)
183 | layout.addWidget(self.controlWidget)
184 | clayout = QHBoxLayout(self.controlWidget)
185 |
186 | # 取色器
187 | self.colorStraw = CColorStraw(self.colorView)
188 | clayout.addWidget(self.colorStraw)
189 | # 小圆
190 | self.colorControl = CColorControl(self.colorView)
191 | clayout.addWidget(self.colorControl)
192 |
193 | self.sliderWidget = QWidget(self.colorView)
194 | clayout.addWidget(self.sliderWidget)
195 | slayout = QVBoxLayout(self.sliderWidget)
196 | slayout.setContentsMargins(0, 0, 0, 0)
197 | # 滑动条
198 | self.rainbowSlider = CColorSlider(
199 | CColorSlider.TypeRainbow, self.colorView)
200 | slayout.addWidget(self.rainbowSlider)
201 | self.alphaSlider = CColorSlider(CColorSlider.TypeAlpha, self.colorView)
202 | slayout.addWidget(self.alphaSlider)
203 |
204 | # 信息
205 | self.colorInfos = CColorInfos(self.colorView)
206 | layout.addWidget(self.colorInfos)
207 |
208 | # 分割线
209 | layout.addWidget(QWidget(self.colorView, objectName='splitLine'))
210 |
211 | # 底部色板
212 | self.colorPalettes = CColorPalettes(self.colorView)
213 | layout.addWidget(self.colorPalettes)
214 |
215 | # 确定取消按钮
216 | self.confirmWidget = QWidget(self.colorView)
217 | clayout = QHBoxLayout(self.confirmWidget)
218 | clayout.addItem(QSpacerItem(
219 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
220 | clayout.addWidget(QPushButton(
221 | '确定', self, clicked=self.accept, objectName='confirmButton'))
222 | clayout.addWidget(QPushButton(
223 | '取消', self, clicked=self.reject, objectName='cancelButton'))
224 | layout.addWidget(self.confirmWidget)
225 |
226 | def initSignals(self):
227 | # 彩虹slider->面板->rgb文字->小圆
228 | self.rainbowSlider.colorChanged.connect(self.colorPanel.createImage)
229 | self.colorPanel.colorChanged.connect(self.colorInfos.updateColor)
230 | self.colorInfos.colorChanged.connect(self.colorControl.updateColor)
231 |
232 | # 透明slider->alpha文字->小圆
233 | self.alphaSlider.colorChanged.connect(self.colorInfos.updateAlpha)
234 |
235 | # alpha文字->透明slider
236 | # self.colorInfos.colorChanged.connect(self.alphaSlider.updateAlpha)
237 |
238 | # 底部多颜色卡
239 | self.colorPalettes.colorChanged.connect(self.colorInfos.updateColor)
240 | self.colorPalettes.colorChanged.connect(self.colorPanel.createImage)
241 | self.colorInfos.colorAdded.connect(self.colorPalettes.addColor)
242 |
243 | # 取色器
244 | self.colorStraw.colorChanged.connect(self.colorInfos.updateColor)
245 |
246 | # 颜色结果
247 | self.colorInfos.colorChanged.connect(self.setColor)
248 |
249 | def setColor(self, color, alpha):
250 | color = QColor(color)
251 | color.setAlpha(alpha)
252 | CColorPicker.selectedColor = color
253 |
254 | # def reset(self):
255 | # CColorPicker.selectedColor = QColor()
256 | # self.colorPalettes.reset()
257 | # self.colorPanel.reset()
258 | # self.colorControl.reset()
259 | # self.rainbowSlider.reset()
260 | # self.alphaSlider.reset()
261 | # self.colorInfos.reset()
262 |
263 | def mousePressEvent(self, event):
264 | """鼠标点击事件"""
265 | if event.button() == Qt.LeftButton:
266 | self.mPos = event.pos()
267 | event.accept()
268 |
269 | def mouseReleaseEvent(self, event):
270 | '''鼠标弹起事件'''
271 | self.mPos = None
272 | event.accept()
273 |
274 | def mouseMoveEvent(self, event):
275 | if event.buttons() == Qt.LeftButton and self.mPos:
276 | if not self.colorPanel.geometry().contains(self.mPos):
277 | self.move(self.mapToGlobal(event.pos() - self.mPos))
278 | event.accept()
279 |
280 | @classmethod
281 | def getColor(cls, parent=None):
282 | """获取选择的颜色
283 | :param cls:
284 | :param parent:
285 | """
286 | if not hasattr(cls, '_colorPicker'):
287 | cls._colorPicker = CColorPicker(parent)
288 | ret = cls._colorPicker.exec_()
289 | if ret != QDialog.Accepted:
290 | return ret, QColor()
291 | return ret, CColorPicker.selectedColor
292 |
293 |
294 | def test():
295 | import sys
296 | import cgitb
297 | sys.excepthook = cgitb.enable(1, None, 5, '')
298 | from PyQt5.QtWidgets import QApplication, QLabel
299 | app = QApplication(sys.argv)
300 |
301 | def getColor():
302 | ret, color = CColorPicker.getColor()
303 | if ret == QDialog.Accepted:
304 | r, g, b, a = color.red(), color.green(), color.blue(), color.alpha()
305 | label.setText('color: rgba(%d, %d, %d, %d)' % (r, g, b, a))
306 | label.setStyleSheet(
307 | 'background: rgba(%d, %d, %d, %d);' % (r, g, b, a))
308 |
309 | window = QWidget()
310 | window.resize(200, 200)
311 | layout = QVBoxLayout(window)
312 | label = QLabel('', window, alignment=Qt.AlignCenter)
313 | button = QPushButton('点击选择颜色', window, clicked=getColor)
314 | layout.addWidget(label)
315 | layout.addWidget(button)
316 | window.show()
317 |
318 | sys.exit(app.exec_())
319 |
320 |
321 | if __name__ == '__main__':
322 | test()
323 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorSlider.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月19日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorSlider
10 | @description: 颜色滑动条
11 | """
12 | from PyQt5.QtCore import Qt, pyqtSignal
13 | from PyQt5.QtGui import QLinearGradient, QColor, QImage, QPainter,\
14 | QRadialGradient
15 | from PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle
16 |
17 |
18 | __Author__ = "Irony"
19 | __Copyright__ = 'Copyright (c) 2019 Irony'
20 | __Version__ = 1.0
21 |
22 |
23 | class CColorSlider(QSlider):
24 |
25 | TypeAlpha = 0 # 透明颜色类型
26 | TypeRainbow = 1 # 彩虹色
27 |
28 | colorChanged = pyqtSignal(QColor, int) # 颜色, 透明度
29 |
30 | def __init__(self, types, parent=None, color=Qt.black):
31 | """
32 | :param types: 渐变类型(0-透明,1-彩虹)
33 | :param parent:
34 | """
35 | super(CColorSlider, self).__init__(Qt.Horizontal, parent)
36 | self.setObjectName('Custom_Color_Slider')
37 | self.setCursor(Qt.PointingHandCursor)
38 | self.valueChanged.connect(self.onValueChanged)
39 | self._types = types
40 | self._color = color
41 | self._isFirstShow = True
42 | self._imageRainbow = None # 彩虹背景图
43 | self._imageAlphaColor = None # 带颜色透明图
44 | self._imageAlphaTmp = None # 透明方格
45 | self._imageAlpha = None # 带颜色透明背景和方格合成图
46 | self._imageCircle = None # 圆形滑块图
47 | self._imageCircleHover = None # 圆形滑块悬停图
48 | self.setToolTip('彩虹色' if self._types == self.TypeRainbow else '透明度')
49 |
50 | def reset(self):
51 | self.setValue(0 if self._types == self.TypeRainbow else self.maximum())
52 |
53 | def updateAlpha(self, color, alpha):
54 | self.blockSignals(True)
55 | self.setValue(alpha)
56 | self.blockSignals(False)
57 |
58 | def showEvent(self, event):
59 | super(CColorSlider, self).showEvent(event)
60 | if self._isFirstShow:
61 | self._isFirstShow = False
62 | self.setRange(0, max(1, self.width() - 1))
63 | if self._types == self.TypeAlpha:
64 | self.blockSignals(True)
65 | self.setValue(self.maximum())
66 | self.blockSignals(False)
67 | self.gradientCirclePixmap()
68 | self.gradientPixmap(self._types, self._color)
69 |
70 | def pick(self, pt):
71 | return pt.x() if self.orientation() == Qt.Horizontal else pt.y()
72 |
73 | def pixelPosToRangeValue(self, pos):
74 | option = QStyleOptionSlider()
75 | self.initStyleOption(option)
76 | gr = self.style().subControlRect(QStyle.CC_Slider,
77 | option, QStyle.SC_SliderGroove, self)
78 | sr = self.style().subControlRect(QStyle.CC_Slider,
79 | option, QStyle.SC_SliderHandle, self)
80 | if self.orientation() == Qt.Horizontal:
81 | sliderLength = sr.width()
82 | sliderMin = gr.x()
83 | sliderMax = gr.right() - sliderLength + 1
84 | else:
85 | sliderLength = sr.height()
86 | sliderMin = gr.y()
87 | sliderMax = gr.bottom() - sliderLength + 1
88 | return QStyle.sliderValueFromPosition(
89 | self.minimum(), self.maximum(), pos - sliderMin,
90 | sliderMax - sliderMin, option.upsideDown)
91 |
92 | def mousePressEvent(self, event):
93 | # 获取上面的拉动块位置
94 | event.accept()
95 | option = QStyleOptionSlider()
96 | self.initStyleOption(option)
97 | rect = self.style().subControlRect(
98 | QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)
99 | rect.setX(max(min(rect.x(), self.width() - self.height()), 0))
100 | rect.setWidth(self.height())
101 | rect.setHeight(self.height())
102 | center = rect.center() - rect.topLeft()
103 | self.setSliderPosition(self.pixelPosToRangeValue(
104 | self.pick(event.pos() - center)))
105 | self.setSliderDown(True)
106 |
107 | def mouseMoveEvent(self, event):
108 | event.accept()
109 | self.setSliderPosition(
110 | self.pixelPosToRangeValue(self.pick(event.pos())))
111 |
112 | def paintEvent(self, event):
113 | if not (self._imageRainbow or self._imageAlpha):
114 | return super(CColorSlider, self).paintEvent(event)
115 |
116 | option = QStyleOptionSlider()
117 | self.initStyleOption(option)
118 | # 背景Rect
119 | groove = self.style().subControlRect(
120 | QStyle.CC_Slider, option, QStyle.SC_SliderGroove, self)
121 | groove.adjust(3, 5, -3, -5)
122 | # 滑块Rect
123 | handle = self.style().subControlRect(
124 | QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)
125 | handle.setX(max(min(handle.x(), self.width() - self.height()), 0))
126 | handle.setWidth(self.height())
127 | handle.setHeight(self.height())
128 | radius = self.height() / 2
129 | painter = QPainter(self)
130 | painter.setPen(Qt.NoPen)
131 | painter.drawImage(
132 | groove, self._imageRainbow if self._imageRainbow else self._imageAlpha)
133 |
134 | if not self._imageCircle or not self._imageCircleHover:
135 | painter.setBrush(QColor(245, 245, 245) if option.state &
136 | QStyle.State_MouseOver else QColor(254, 254, 254))
137 | painter.drawRoundedRect(handle, radius, radius)
138 | else:
139 | painter.drawImage(handle, self._imageCircleHover if option.state &
140 | QStyle.State_MouseOver else self._imageCircle)
141 |
142 | def gradientCirclePixmap(self):
143 | """白色带阴影
144 | """
145 | xy = self.height() / 2
146 | radius = self.height() * 0.8
147 |
148 | # 绘制普通状态下圆形的滑块
149 | circleColor = QRadialGradient(xy, xy, radius, xy, xy)
150 | circleColor.setColorAt(0.5, QColor(254, 254, 254))
151 | circleColor.setColorAt(0.7, QColor(0, 0, 0, 60))
152 | circleColor.setColorAt(0.7, QColor(0, 0, 0, 30))
153 | circleColor.setColorAt(0.9, QColor(0, 0, 0, 0))
154 | self._imageCircle = QImage(
155 | self.height(), self.height(), QImage.Format_ARGB32)
156 | self._imageCircle.fill(Qt.transparent)
157 | painter = QPainter()
158 | painter.begin(self._imageCircle)
159 | painter.setRenderHint(QPainter.Antialiasing, True)
160 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
161 | painter.setPen(Qt.NoPen)
162 | painter.setBrush(circleColor)
163 | painter.drawRoundedRect(0, 0, self.height(), self.height(), xy, xy)
164 | painter.end()
165 |
166 | # 绘制悬停状态下圆形的滑块
167 | circleColorHover = QRadialGradient(xy, xy, radius, xy, xy)
168 | circleColorHover.setColorAt(0.5, QColor(245, 245, 245))
169 | circleColorHover.setColorAt(0.7, QColor(0, 0, 0, 30))
170 | circleColorHover.setColorAt(0.9, QColor(0, 0, 0, 0))
171 | self._imageCircleHover = QImage(
172 | self.height(), self.height(), QImage.Format_ARGB32)
173 | self._imageCircleHover.fill(Qt.transparent)
174 | painter = QPainter()
175 | painter.begin(self._imageCircleHover)
176 | painter.setRenderHint(QPainter.Antialiasing, True)
177 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
178 | painter.setPen(Qt.NoPen)
179 | painter.setBrush(circleColorHover)
180 | painter.drawRoundedRect(0, 0, self.height(), self.height(), xy, xy)
181 | painter.end()
182 |
183 | def gradientPixmap(self, types, color):
184 | """生成渐变图片
185 | """
186 | pixSize = 5
187 | if types == self.TypeAlpha:
188 | # 生成黑边相间的模拟透明背景
189 | if not self._imageAlphaTmp:
190 | self._imageAlphaTmp = QImage(
191 | self.width(), self.height(), QImage.Format_ARGB32)
192 | painter = QPainter()
193 | painter.begin(self._imageAlphaTmp)
194 | painter.setRenderHint(QPainter.Antialiasing, True)
195 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
196 | for x in range(int(self.width() / pixSize)):
197 | for y in range(int(self.height() / pixSize)):
198 | _x, _y = x * pixSize, y * pixSize
199 | painter.fillRect(_x, _y, pixSize, pixSize,
200 | Qt.white if x % 2 != y % 2 else Qt.darkGray)
201 | painter.end()
202 | # 绘制透明渐变
203 | gradient = QLinearGradient(0, 0, self.width(), 0)
204 | gradient.setColorAt(0, QColor(0, 0, 0, 0))
205 | gradient.setColorAt(1, color)
206 | # 只画渐变颜色
207 | self._imageAlphaColor = QImage(
208 | self.width(), self.height(), QImage.Format_ARGB32)
209 | self._imageAlphaColor.fill(Qt.transparent)
210 | painter = QPainter()
211 | painter.begin(self._imageAlphaColor)
212 | painter.setRenderHint(QPainter.Antialiasing, True)
213 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
214 | painter.fillRect(0, 0, self.width(), self.height(), gradient)
215 | painter.end()
216 | # 合并方格图
217 | self._imageAlpha = self._imageAlphaColor.copy()
218 | painter = QPainter()
219 | painter.begin(self._imageAlpha)
220 | painter.setRenderHint(QPainter.Antialiasing, True)
221 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
222 | painter.drawImage(0, 0, self._imageAlphaTmp)
223 | painter.fillRect(0, 0, self.width(), self.height(), gradient)
224 | painter.end()
225 | else:
226 | gradient = QLinearGradient(0, 0, self.width(), 0)
227 | gradient.setColorAt(0, QColor('#ff0000'))
228 | gradient.setColorAt(0.17, QColor('#ffff00'))
229 | gradient.setColorAt(0.33, QColor('#00ff00'))
230 | gradient.setColorAt(0.5, QColor('#00ffff'))
231 | gradient.setColorAt(0.67, QColor('#0000ff'))
232 | gradient.setColorAt(0.83, QColor('#ff00ff'))
233 | gradient.setColorAt(1, QColor('#ff0000'))
234 | self._imageRainbow = QImage(
235 | self.width(), self.height(), QImage.Format_ARGB32)
236 | painter = QPainter()
237 | painter.begin(self._imageRainbow)
238 | painter.setRenderHint(QPainter.Antialiasing, True)
239 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
240 | painter.fillRect(0, 0, self.width(), self.height(), gradient)
241 | painter.end()
242 |
243 | def onValueChanged(self, value):
244 | hh = int(self.height() / 2)
245 | color = self.colorFromPoint(value, hh)
246 | alpha = self.alphaFromPoint(value, hh)
247 | self.colorChanged.emit(color, alpha)
248 |
249 | def colorFromPoint(self, x, y):
250 | if not self._imageRainbow:
251 | return QColor(Qt.red)
252 | return self._imageRainbow.pixelColor(x, y)
253 |
254 | def alphaFromPoint(self, x, y):
255 | if not self._imageAlphaColor:
256 | return 255
257 | return self._imageAlphaColor.pixelColor(x, y).alpha()
258 |
259 |
260 | if __name__ == '__main__':
261 | import sys
262 | import cgitb
263 | sys.excepthook = cgitb.enable(1, None, 5, '')
264 | from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
265 | app = QApplication(sys.argv)
266 | app.setStyleSheet('Window{background:white;}')
267 | w = QWidget()
268 | w.resize(250, 150)
269 | w.show()
270 |
271 | layout = QVBoxLayout(w)
272 |
273 | slider1 = CColorSlider(CColorSlider.TypeRainbow, w)
274 | slider1.colorChanged.connect(
275 | lambda c, a: print('TypeRainbow:', c.name(), a))
276 | layout.addWidget(slider1)
277 |
278 | slider2 = CColorSlider(CColorSlider.TypeAlpha, w, Qt.black)
279 | slider2.colorChanged.connect(lambda c, a: print('TypeAlpha:', c.name(), a))
280 | layout.addWidget(slider2)
281 |
282 | sys.exit(app.exec_())
283 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/CColorStraw.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月24日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CColorPicker.CColorStraw
10 | @description: 吸管
11 | """
12 | from PyQt5.QtCore import QByteArray, Qt, QRectF, QLineF, pyqtSignal
13 | from PyQt5.QtGui import QFontDatabase, QFont, QPainter,\
14 | QPainterPath, QColor, QPen
15 | from PyQt5.QtWidgets import QPushButton, QApplication, QWidget
16 |
17 |
18 | __Author__ = 'Irony'
19 | __Copyright__ = 'Copyright (c) 2019'
20 |
21 | FONT = b'AAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI9eEj0AAABfAAAAFZjbWFw6Cq4sAAAAdwAAAFwZ2x5ZhP0dwUAAANUAAAA8GhlYWQU7DSZAAAA4AAAADZoaGVhB94DgwAAALwAAAAkaG10eAgAAAAAAAHUAAAACGxvY2EAeAAAAAADTAAAAAZtYXhwAQ8AWAAAARgAAAAgbmFtZT5U/n0AAAREAAACbXBvc3Ta6Gh9AAAGtAAAADAAAQAAA4D/gABcBAAAAAAABAAAAQAAAAAAAAAAAAAAAAAAAAIAAQAAAAEAAAn6lORfDzz1AAsEAAAAAADY5nhOAAAAANjmeE4AAP/ABAADQAAAAAgAAgAAAAAAAAABAAAAAgBMAAMAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQAAZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5wLnAgOA/4AAXAOAAIAAAAABAAAAAAAABAAAAAQAAAAAAAAFAAAAAwAAACwAAAAEAAABVAABAAAAAABOAAMAAQAAACwAAwAKAAABVAAEACIAAAAEAAQAAQAA5wL//wAA5wL//wAAAAEABAAAAAEAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABwAAAAAAAAAAQAA5wIAAOcCAAAAAQAAAAAAeAAAAAMAAP/AA8EDQAApAEIASwAAAS4BIgYPAScmDgEfAQEOARcHBhQWMzEyPwEWNjcBFx4BPgE1NC8BNzY0AQ4BJyYPAgYjLgE/BTQnJjY3ARc3Byc3NjIXFhQDixlCSEEakjwOIwoNW/7DFxQFMhcvISAYMSE+GAE9WwcTEwoJPZI1/YIOJBMSDwM/BQUKBwc/AwMCAQIGCg0BPWTfkqKTIl0iIgMLGhsbGpI9DAkkDVz+xBg9ITIYQS8XMgUTGAE9XAcDBxALDQo8kjiP/YkNCgUHCwM/BAERBz8EBQUGCAcTJQ4BPGShkqKSISEjWwAAAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgECAQMABnhpZ3VhbgAA'
22 |
23 |
24 | class ScaleWindow(QWidget):
25 | # 放大图窗口
26 |
27 | def __init__(self, *args, **kwargs):
28 | super(ScaleWindow, self).__init__(*args, **kwargs)
29 | self.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint |
30 | Qt.WindowStaysOnTopHint)
31 | self.setAttribute(Qt.WA_TranslucentBackground, True)
32 | self.resize(1, 1)
33 | self.move(1, 1)
34 | self._image = None
35 |
36 | def updateImage(self, pos, image):
37 | self._image = image
38 | self.resize(image.size())
39 | self.move(pos.x() + 10, pos.y() + 10)
40 | self.show()
41 | self.update()
42 |
43 | def paintEvent(self, event):
44 | super(ScaleWindow, self).paintEvent(event)
45 | if self._image:
46 | painter = QPainter(self)
47 | painter.setRenderHint(QPainter.Antialiasing, True)
48 | path = QPainterPath()
49 | radius = min(self.width(), self.height()) / 2
50 | path.addRoundedRect(QRectF(self.rect()), radius, radius)
51 | painter.setClipPath(path)
52 | # 图片
53 | painter.drawImage(self.rect(), self._image)
54 | # 中间蓝色十字线
55 | painter.setPen(QPen(QColor(0, 174, 255), 3))
56 | hw = self.width() / 2
57 | hh = self.height() / 2
58 | painter.drawLines(
59 | QLineF(hw, 0, hw, self.height()),
60 | QLineF(0, hh, self.width(), hh)
61 | )
62 | # 边框
63 | painter.setPen(QPen(Qt.white, 3))
64 | painter.drawRoundedRect(self.rect(), radius, radius)
65 |
66 |
67 | class CColorStraw(QPushButton):
68 |
69 | colorChanged = pyqtSignal(QColor)
70 |
71 | def __init__(self, parent):
72 | super(CColorStraw, self).__init__(parent)
73 | QFontDatabase.addApplicationFontFromData(QByteArray.fromBase64(FONT))
74 | font = self.font() or QFont()
75 | font.setFamily('iconfont')
76 | self.setFont(font)
77 | self.setText('')
78 | self.setToolTip('吸取屏幕颜色')
79 | self._scaleWindow = ScaleWindow()
80 | # 一定要先显示再隐藏,否则QDialog情况下第一次会卡死
81 | self._scaleWindow.show()
82 | self._scaleWindow.hide()
83 |
84 | def closeEvent(self, event):
85 | self._scaleWindow.close()
86 | super(CColorStraw, self).closeEvent(event)
87 |
88 | def mousePressEvent(self, event):
89 | super(CColorStraw, self).mousePressEvent(event)
90 | # 设置鼠标样式为十字
91 | self.setCursor(Qt.CrossCursor)
92 |
93 | def mouseReleaseEvent(self, event):
94 | super(CColorStraw, self).mouseReleaseEvent(event)
95 | # 设置鼠标样式为普通
96 | self.setCursor(Qt.ArrowCursor)
97 | self._scaleWindow.hide()
98 |
99 | def mouseMoveEvent(self, event):
100 | super(CColorStraw, self).mouseMoveEvent(event)
101 | # 得到鼠标在屏幕中的位置
102 | pos = event.globalPos()
103 | # 截取一部分放大图效果
104 | image = QApplication.primaryScreen().grabWindow(
105 | int(QApplication.desktop().winId()),
106 | pos.x() - 6, pos.y() - 6, 13, 13).toImage()
107 | color = image.pixelColor(6, 6)
108 | if color.isValid():
109 | self.colorChanged.emit(color)
110 | self._scaleWindow.updateImage(pos, image.scaled(130, 130))
111 |
112 |
113 | if __name__ == '__main__':
114 | import sys
115 | import cgitb
116 | sys.excepthook = cgitb.enable(1, None, 5, '')
117 | from PyQt5.QtWidgets import QDialog, QVBoxLayout
118 | from PyQt5.QtCore import QTimer
119 | app = QApplication(sys.argv)
120 | w = QDialog()
121 | layout = QVBoxLayout(w)
122 | view = CColorStraw(w)
123 | view.colorChanged.connect(lambda c: print(c.name()))
124 | layout.addWidget(view)
125 | w.exec_()
126 | QTimer.singleShot(200, app.quit)
127 | sys.exit(app.exec_())
128 |
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/README.md:
--------------------------------------------------------------------------------
1 | # CColorPicker
2 |
3 | [使用方法](/TestCColorPicker.py)
4 |
5 | ```python
6 | from CColorPicker.CColorPicker import CColorPicker
7 |
8 | ret, color = CColorPicker.getColor()
9 | if ret == CColorPicker.Accepted:
10 | print(color.name())
11 | ```
12 |
13 | 
--------------------------------------------------------------------------------
/CustomWidgets/CColorPicker/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/CustomWidgets/CColorPicker/__init__.py
--------------------------------------------------------------------------------
/CustomWidgets/CCountUp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月31日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CCountUp
10 | @description: 数字动画
11 | """
12 | from PyQt5.QtCore import QTimeLine, QEasingCurve
13 | from PyQt5.QtGui import QFont
14 | from PyQt5.QtWidgets import QLabel
15 |
16 |
17 | __Author__ = 'Irony'
18 | __Copyright__ = 'Copyright (c) 2019 Irony'
19 | __Version__ = 1.0
20 |
21 |
22 | class CCountUp(QLabel):
23 |
24 | def __init__(self, *args, **kwargs):
25 | super(CCountUp, self).__init__(*args, **kwargs)
26 | self.isFloat = False # 是否是小数
27 | font = self.font() or QFont()
28 | font.setBold(True)
29 | self.setFont(font)
30 | self.timeline = QTimeLine(6000, self)
31 | self.timeline.setEasingCurve(QEasingCurve.OutExpo)
32 | self.timeline.frameChanged.connect(self.onFrameChanged)
33 |
34 | def pause(self):
35 | """暂停
36 | """
37 | self.timeline.setPaused(True)
38 |
39 | def resume(self):
40 | """继续
41 | """
42 | self.timeline.resume()
43 |
44 | def isPaused(self):
45 | """是否暂停
46 | """
47 | return self.timeline.state() == QTimeLine.Paused
48 |
49 | def reset(self):
50 | """重置
51 | """
52 | self.timeline.stop()
53 | self.isFloat = False # 是否是小数
54 | self.setText('0')
55 |
56 | def onFrameChanged(self, value):
57 | if self.isFloat:
58 | value = round(value / 100.0 + 0.00001, 2)
59 | value = str(format(value, ','))
60 | self.setText(value + '0' if value.endswith('.0') else value)
61 |
62 | def setDuration(self, duration):
63 | """设置动画持续时间
64 | :param duration:
65 | """
66 | self.timeline.setDuration(duration)
67 |
68 | def setNum(self, number):
69 | """设置数字
70 | :param number: int or float
71 | """
72 | if isinstance(number, int):
73 | self.isFloat = False
74 | self.timeline.setFrameRange(0, number)
75 | elif isinstance(number, float):
76 | self.isFloat = True
77 | self.timeline.setFrameRange(0, number * 100)
78 | self.timeline.stop()
79 | self.setText('0')
80 | self.timeline.start()
81 |
--------------------------------------------------------------------------------
/CustomWidgets/CDrawer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月24日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CDrawer
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt, QPropertyAnimation, QEasingCurve, QPoint, QPointF
13 | from PyQt5.QtGui import QMouseEvent
14 | from PyQt5.QtWidgets import QWidget, QApplication
15 |
16 |
17 | __Author__ = 'Irony'
18 | __Copyright__ = 'Copyright (c) 2019'
19 |
20 |
21 | class CDrawer(QWidget):
22 |
23 | LEFT, TOP, RIGHT, BOTTOM = range(4)
24 |
25 | def __init__(self, *args, stretch=1 / 3, direction=0, widget=None, **kwargs):
26 | super(CDrawer, self).__init__(*args, **kwargs)
27 | self.setWindowFlags(self.windowFlags(
28 | ) | Qt.FramelessWindowHint | Qt.Popup | Qt.NoDropShadowWindowHint)
29 | self.setAttribute(Qt.WA_StyledBackground, True)
30 | self.setAttribute(Qt.WA_TranslucentBackground, True)
31 | # 进入动画
32 | self.animIn = QPropertyAnimation(
33 | self, duration=500, easingCurve=QEasingCurve.OutCubic)
34 | self.animIn.setPropertyName(b'pos')
35 | # 离开动画
36 | self.animOut = QPropertyAnimation(
37 | self, duration=500, finished=self.onAnimOutEnd,
38 | easingCurve=QEasingCurve.OutCubic)
39 | self.animOut.setPropertyName(b'pos')
40 | self.animOut.setDuration(500)
41 | self.setStretch(stretch) # 占比
42 | self.direction = direction # 方向
43 | # 半透明背景
44 | self.alphaWidget = QWidget(
45 | self, objectName='CDrawer_alphaWidget',
46 | styleSheet='#CDrawer_alphaWidget{background:rgba(55,55,55,100);}')
47 | self.alphaWidget.setAttribute(Qt.WA_TransparentForMouseEvents, True)
48 | self.setWidget(widget) # 子控件
49 |
50 | def resizeEvent(self, event):
51 | self.alphaWidget.resize(self.size())
52 | super(CDrawer, self).resizeEvent(event)
53 |
54 | def mousePressEvent(self, event):
55 | pos = event.pos()
56 | if pos.x() >= 0 and pos.y() >= 0 and self.childAt(pos) == None and self.widget:
57 | if not self.widget.geometry().contains(pos):
58 | self.animationOut()
59 | return
60 | super(CDrawer, self).mousePressEvent(event)
61 |
62 | def show(self):
63 | super(CDrawer, self).show()
64 | parent = self.parent().window() if self.parent() else self.window()
65 | if not parent or not self.widget:
66 | return
67 | # 设置Drawer大小和主窗口一致
68 | self.setGeometry(parent.geometry())
69 | geometry = self.geometry()
70 | self.animationIn(geometry)
71 |
72 | def animationIn(self, geometry):
73 | """进入动画
74 | :param geometry:
75 | """
76 | if self.direction == self.LEFT:
77 | # 左侧抽屉
78 | self.widget.setGeometry(
79 | 0, 0, int(geometry.width() * self.stretch), geometry.height())
80 | self.widget.hide()
81 | self.animIn.setStartValue(QPoint(-self.widget.width(), 0))
82 | self.animIn.setEndValue(QPoint(0, 0))
83 | self.animIn.start()
84 | self.widget.show()
85 | elif self.direction == self.TOP:
86 | # 上方抽屉
87 | self.widget.setGeometry(
88 | 0, 0, geometry.width(), int(geometry.height() * self.stretch))
89 | self.widget.hide()
90 | self.animIn.setStartValue(QPoint(0, -self.widget.height()))
91 | self.animIn.setEndValue(QPoint(0, 0))
92 | self.animIn.start()
93 | self.widget.show()
94 | elif self.direction == self.RIGHT:
95 | # 右侧抽屉
96 | width = int(geometry.width() * self.stretch)
97 | self.widget.setGeometry(
98 | geometry.width() - width, 0, width, geometry.height())
99 | self.widget.hide()
100 | self.animIn.setStartValue(QPoint(self.width(), 0))
101 | self.animIn.setEndValue(
102 | QPoint(self.width() - self.widget.width(), 0))
103 | self.animIn.start()
104 | self.widget.show()
105 | elif self.direction == self.BOTTOM:
106 | # 下方抽屉
107 | height = int(geometry.height() * self.stretch)
108 | self.widget.setGeometry(
109 | 0, geometry.height() - height, geometry.width(), height)
110 | self.widget.hide()
111 | self.animIn.setStartValue(QPoint(0, self.height()))
112 | self.animIn.setEndValue(
113 | QPoint(0, self.height() - self.widget.height()))
114 | self.animIn.start()
115 | self.widget.show()
116 |
117 | def animationOut(self):
118 | """离开动画
119 | """
120 | self.animIn.stop() # 停止进入动画
121 | geometry = self.widget.geometry()
122 | if self.direction == self.LEFT:
123 | # 左侧抽屉
124 | self.animOut.setStartValue(geometry.topLeft())
125 | self.animOut.setEndValue(QPoint(-self.widget.width(), 0))
126 | self.animOut.start()
127 | elif self.direction == self.TOP:
128 | # 上方抽屉
129 | self.animOut.setStartValue(QPoint(0, geometry.y()))
130 | self.animOut.setEndValue(QPoint(0, -self.widget.height()))
131 | self.animOut.start()
132 | elif self.direction == self.RIGHT:
133 | # 右侧抽屉
134 | self.animOut.setStartValue(QPoint(geometry.x(), 0))
135 | self.animOut.setEndValue(QPoint(self.width(), 0))
136 | self.animOut.start()
137 | elif self.direction == self.BOTTOM:
138 | # 下方抽屉
139 | self.animOut.setStartValue(QPoint(0, geometry.y()))
140 | self.animOut.setEndValue(QPoint(0, self.height()))
141 | self.animOut.start()
142 |
143 | def onAnimOutEnd(self):
144 | """离开动画结束
145 | """
146 | # 模拟点击外侧关闭
147 | QApplication.sendEvent(self, QMouseEvent(
148 | QMouseEvent.MouseButtonPress, QPointF(-1, -1), Qt.LeftButton, Qt.NoButton, Qt.NoModifier))
149 |
150 | def setWidget(self, widget):
151 | """设置子控件
152 | :param widget:
153 | """
154 | self.widget = widget
155 | if widget:
156 | widget.setParent(self)
157 | self.animIn.setTargetObject(widget)
158 | self.animOut.setTargetObject(widget)
159 |
160 | def setEasingCurve(self, easingCurve):
161 | """设置动画曲线
162 | :param easingCurve:
163 | """
164 | self.animIn.setEasingCurve(easingCurve)
165 |
166 | def getStretch(self):
167 | """获取占比
168 | """
169 | return self.stretch
170 |
171 | def setStretch(self, stretch):
172 | """设置占比
173 | :param stretch:
174 | """
175 | self.stretch = max(0.1, min(stretch, 0.9))
176 |
177 | def getDirection(self):
178 | """获取方向
179 | """
180 | return self.direction
181 |
182 | def setDirection(self, direction):
183 | """设置方向
184 | :param direction:
185 | """
186 | direction = int(direction)
187 | if direction < 0 or direction > 3:
188 | direction = self.LEFT
189 | self.direction = direction
190 |
--------------------------------------------------------------------------------
/CustomWidgets/CFontIcon/CFontIcon.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月28日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/PyQt5
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CFontIcon
10 | @description: 字体图标
11 | """
12 | import json
13 | import os
14 |
15 | from PyQt5.QtCore import Qt, QPoint, QRect, QTimer
16 | from PyQt5.QtGui import QFontDatabase, QIcon, QIconEngine, QPixmap, QPainter,\
17 | QFont
18 |
19 |
20 | __Author__ = 'Irony'
21 | __Copyright__ = 'Copyright (c) 2019'
22 | __Version__ = 'Version 1.0'
23 |
24 |
25 | class CIconEngine(QIconEngine):
26 | """图标绘制引擎
27 | """
28 |
29 | def __init__(self, font, *args, **kwargs):
30 | super(CIconEngine, self).__init__(*args, **kwargs)
31 | self.font = font
32 | self.icon = None
33 |
34 | def setIcon(self, icon):
35 | self.icon = icon
36 |
37 | def paint(self, painter, rect, mode, state):
38 | painter.save()
39 | self.font.setPixelSize(round(0.875 * min(rect.width(), rect.height())))
40 | painter.setFont(self.font)
41 | if self.icon:
42 | if self.icon.animation:
43 | self.icon.animation.paint(painter, rect)
44 | ms = self.icon._getMode(mode) * self.icon._getState(state)
45 | text, color = self.icon.icons.get(ms, (None, None))
46 | if text == None and color == None:
47 | return
48 | painter.setPen(color)
49 | self.text = text if text else self.text
50 | painter.drawText(
51 | rect, int(Qt.AlignCenter | Qt.AlignVCenter), self.text)
52 | painter.restore()
53 |
54 | def pixmap(self, size, mode, state):
55 | pixmap = QPixmap(size)
56 | pixmap.fill(Qt.transparent)
57 | self.paint(QPainter(pixmap), QRect(QPoint(0, 0), size), mode, state)
58 | return pixmap
59 |
60 |
61 | class CIconAnimationSpin:
62 |
63 | def __init__(self, parent, interval=10, step=4):
64 | self.parent = parent
65 | self.angle = 0
66 | self.timer = None
67 | self.interval = interval
68 | self.step = step
69 |
70 | def update(self):
71 | if self.angle >= 360:
72 | self.angle = 0
73 | self.angle += self.step
74 | self.parent.update()
75 |
76 | def paint(self, painter, rect):
77 | if not self.timer:
78 | self.timer = QTimer(self.parent, timeout=self.update)
79 | self.timer.start(self.interval)
80 | else:
81 | x_center = rect.width() * 0.5
82 | y_center = rect.height() * 0.5
83 | painter.translate(x_center, y_center)
84 | painter.rotate(self.angle)
85 | painter.translate(-x_center, -y_center)
86 |
87 |
88 | class CIcon(QIcon):
89 |
90 | # New Mode
91 | Normal = int(QIcon.Normal) + 2
92 | Disabled = int(QIcon.Disabled) + 2
93 | Active = int(QIcon.Active) + 2
94 | Selected = int(QIcon.Selected) + 2
95 |
96 | # New State
97 | Off = int(QIcon.Off) + 6
98 | On = int(QIcon.On) + 6
99 |
100 | def __init__(self, engine, fontMap, animation=None):
101 | super(CIcon, self).__init__(engine)
102 | engine.setIcon(self)
103 | self.animation = animation
104 | self.fontMap = fontMap
105 | self.icons = {}
106 | self.modestate = [m * s for m in [
107 | self.Normal, self.Disabled, self.Active, self.Selected] for s in [self.Off, self.On]]
108 |
109 | def setAnimation(self, animation):
110 | self.animation = animation
111 |
112 | def add(self, name, color=Qt.black, mode=QIcon.Normal, state=QIcon.Off):
113 | """添加或者更新一个指定mode和state的字体和颜色
114 | :param name:
115 | :param color:
116 | :param mode:
117 | :param state:
118 | """
119 | ms = self._getMode(mode) * self._getState(state)
120 | self.icons[ms] = [self.fontMap.get(name, ''), color]
121 | return self
122 |
123 | def _getMode(self, mode):
124 | """修改QIcon::Mode值+2,比如
125 | QIcon::Normal 0 -> 2
126 | QIcon::Disabled 1 -> 3
127 | QIcon::Active 2 -> 4
128 | QIcon::Selected 3 -> 5
129 | :param mode:
130 | """
131 | return int(mode) + 2
132 |
133 | def _getState(self, state):
134 | """修改QIcon::State值+6,比如
135 | QIcon::Off 1 -> 7
136 | QIcon::On 0 -> 6
137 | :param state:
138 | """
139 | return int(state) + 6
140 |
141 |
142 | class CIconLoader:
143 | """字体图标加载器
144 | """
145 |
146 | def __init__(self, ttfFile, mapFile):
147 | """
148 | :param ttfFile: ttf字体文件路径
149 | :param mapFile: ttf字体文件对应的字符映射 json格式
150 | """
151 | fontId = QFontDatabase.addApplicationFont(ttfFile)
152 | fontFamilies = QFontDatabase.applicationFontFamilies(fontId)
153 | if fontFamilies:
154 | self._font = QFont(fontFamilies[0])
155 | self.fontMap = json.loads(open(mapFile, 'rb').read().decode(
156 | encoding='utf_8', errors='ignore'), encoding='utf_8', object_hook=self.object_hook)
157 | else:
158 | self._font = QFont()
159 | self.fontMap = {}
160 |
161 | @classmethod
162 | def fontAwesome(cls):
163 | """FontAwesome字体
164 | :param cls:
165 | """
166 | dirPath = os.path.dirname(__file__)
167 | return cls(
168 | os.path.join(dirPath, 'Fonts', 'fontawesome-webfont.ttf'),
169 | os.path.join(dirPath, 'Fonts', 'fontawesome-webfont.json'),
170 | )
171 |
172 | @classmethod
173 | def fontMaterial(cls):
174 | """material字体
175 | :param cls:
176 | """
177 | dirPath = os.path.dirname(__file__)
178 | return cls(
179 | os.path.join(dirPath, 'Fonts', 'materialdesignicons-webfont.ttf'),
180 | os.path.join(dirPath, 'Fonts', 'materialdesignicons-webfont.json'),
181 | )
182 |
183 | def icon(self, name, animation=None):
184 | """根据键值返回一个字体图标
185 | :param name:
186 | """
187 | return CIcon(CIconEngine(self._font), self.fontMap, animation).add(name)
188 |
189 | @property
190 | def font(self):
191 | return self._font
192 |
193 | def value(self, name):
194 | """返回对应的字符
195 | :param name:
196 | """
197 | return self.fontMap.get(name, '')
198 |
199 | def object_hook(self, obj):
200 | result = {}
201 | for key in obj:
202 | result[key] = chr(int(obj[key], 16))
203 | return result
204 |
--------------------------------------------------------------------------------
/CustomWidgets/CFontIcon/Fonts/fontawesome-webfont.json:
--------------------------------------------------------------------------------
1 | {
2 | "fa-500px": "0xf26e",
3 | "fa-address-book": "0xf2b9",
4 | "fa-address-book-o": "0xf2ba",
5 | "fa-address-card": "0xf2bb",
6 | "fa-address-card-o": "0xf2bc",
7 | "fa-adjust": "0xf042",
8 | "fa-adn": "0xf170",
9 | "fa-align-center": "0xf037",
10 | "fa-align-justify": "0xf039",
11 | "fa-align-left": "0xf036",
12 | "fa-align-right": "0xf038",
13 | "fa-amazon": "0xf270",
14 | "fa-ambulance": "0xf0f9",
15 | "fa-american-sign-language-interpreting": "0xf2a3",
16 | "fa-anchor": "0xf13d",
17 | "fa-android": "0xf17b",
18 | "fa-angellist": "0xf209",
19 | "fa-angle-double-down": "0xf103",
20 | "fa-angle-double-left": "0xf100",
21 | "fa-angle-double-right": "0xf101",
22 | "fa-angle-double-up": "0xf102",
23 | "fa-angle-down": "0xf107",
24 | "fa-angle-left": "0xf104",
25 | "fa-angle-right": "0xf105",
26 | "fa-angle-up": "0xf106",
27 | "fa-apple": "0xf179",
28 | "fa-archive": "0xf187",
29 | "fa-area-chart": "0xf1fe",
30 | "fa-arrow-circle-down": "0xf0ab",
31 | "fa-arrow-circle-left": "0xf0a8",
32 | "fa-arrow-circle-o-down": "0xf01a",
33 | "fa-arrow-circle-o-left": "0xf190",
34 | "fa-arrow-circle-o-right": "0xf18e",
35 | "fa-arrow-circle-o-up": "0xf01b",
36 | "fa-arrow-circle-right": "0xf0a9",
37 | "fa-arrow-circle-up": "0xf0aa",
38 | "fa-arrow-down": "0xf063",
39 | "fa-arrow-left": "0xf060",
40 | "fa-arrow-right": "0xf061",
41 | "fa-arrow-up": "0xf062",
42 | "fa-arrows": "0xf047",
43 | "fa-arrows-alt": "0xf0b2",
44 | "fa-arrows-h": "0xf07e",
45 | "fa-arrows-v": "0xf07d",
46 | "fa-asl-interpreting": "0xf2a3",
47 | "fa-assistive-listening-systems": "0xf2a2",
48 | "fa-asterisk": "0xf069",
49 | "fa-at": "0xf1fa",
50 | "fa-audio-description": "0xf29e",
51 | "fa-automobile": "0xf1b9",
52 | "fa-backward": "0xf04a",
53 | "fa-balance-scale": "0xf24e",
54 | "fa-ban": "0xf05e",
55 | "fa-bandcamp": "0xf2d5",
56 | "fa-bank": "0xf19c",
57 | "fa-bar-chart": "0xf080",
58 | "fa-bar-chart-o": "0xf080",
59 | "fa-barcode": "0xf02a",
60 | "fa-bars": "0xf0c9",
61 | "fa-bath": "0xf2cd",
62 | "fa-bathtub": "0xf2cd",
63 | "fa-battery": "0xf240",
64 | "fa-battery-0": "0xf244",
65 | "fa-battery-1": "0xf243",
66 | "fa-battery-2": "0xf242",
67 | "fa-battery-3": "0xf241",
68 | "fa-battery-4": "0xf240",
69 | "fa-battery-empty": "0xf244",
70 | "fa-battery-full": "0xf240",
71 | "fa-battery-half": "0xf242",
72 | "fa-battery-quarter": "0xf243",
73 | "fa-battery-three-quarters": "0xf241",
74 | "fa-bed": "0xf236",
75 | "fa-beer": "0xf0fc",
76 | "fa-behance": "0xf1b4",
77 | "fa-behance-square": "0xf1b5",
78 | "fa-bell": "0xf0f3",
79 | "fa-bell-o": "0xf0a2",
80 | "fa-bell-slash": "0xf1f6",
81 | "fa-bell-slash-o": "0xf1f7",
82 | "fa-bicycle": "0xf206",
83 | "fa-binoculars": "0xf1e5",
84 | "fa-birthday-cake": "0xf1fd",
85 | "fa-bitbucket": "0xf171",
86 | "fa-bitbucket-square": "0xf172",
87 | "fa-bitcoin": "0xf15a",
88 | "fa-black-tie": "0xf27e",
89 | "fa-blind": "0xf29d",
90 | "fa-bluetooth": "0xf293",
91 | "fa-bluetooth-b": "0xf294",
92 | "fa-bold": "0xf032",
93 | "fa-bolt": "0xf0e7",
94 | "fa-bomb": "0xf1e2",
95 | "fa-book": "0xf02d",
96 | "fa-bookmark": "0xf02e",
97 | "fa-bookmark-o": "0xf097",
98 | "fa-braille": "0xf2a1",
99 | "fa-briefcase": "0xf0b1",
100 | "fa-btc": "0xf15a",
101 | "fa-bug": "0xf188",
102 | "fa-building": "0xf1ad",
103 | "fa-building-o": "0xf0f7",
104 | "fa-bullhorn": "0xf0a1",
105 | "fa-bullseye": "0xf140",
106 | "fa-bus": "0xf207",
107 | "fa-buysellads": "0xf20d",
108 | "fa-cab": "0xf1ba",
109 | "fa-calculator": "0xf1ec",
110 | "fa-calendar": "0xf073",
111 | "fa-calendar-check-o": "0xf274",
112 | "fa-calendar-minus-o": "0xf272",
113 | "fa-calendar-o": "0xf133",
114 | "fa-calendar-plus-o": "0xf271",
115 | "fa-calendar-times-o": "0xf273",
116 | "fa-camera": "0xf030",
117 | "fa-camera-retro": "0xf083",
118 | "fa-car": "0xf1b9",
119 | "fa-caret-down": "0xf0d7",
120 | "fa-caret-left": "0xf0d9",
121 | "fa-caret-right": "0xf0da",
122 | "fa-caret-square-o-down": "0xf150",
123 | "fa-caret-square-o-left": "0xf191",
124 | "fa-caret-square-o-right": "0xf152",
125 | "fa-caret-square-o-up": "0xf151",
126 | "fa-caret-up": "0xf0d8",
127 | "fa-cart-arrow-down": "0xf218",
128 | "fa-cart-plus": "0xf217",
129 | "fa-cc": "0xf20a",
130 | "fa-cc-amex": "0xf1f3",
131 | "fa-cc-diners-club": "0xf24c",
132 | "fa-cc-discover": "0xf1f2",
133 | "fa-cc-jcb": "0xf24b",
134 | "fa-cc-mastercard": "0xf1f1",
135 | "fa-cc-paypal": "0xf1f4",
136 | "fa-cc-stripe": "0xf1f5",
137 | "fa-cc-visa": "0xf1f0",
138 | "fa-certificate": "0xf0a3",
139 | "fa-chain": "0xf0c1",
140 | "fa-chain-broken": "0xf127",
141 | "fa-check": "0xf00c",
142 | "fa-check-circle": "0xf058",
143 | "fa-check-circle-o": "0xf05d",
144 | "fa-check-square": "0xf14a",
145 | "fa-check-square-o": "0xf046",
146 | "fa-chevron-circle-down": "0xf13a",
147 | "fa-chevron-circle-left": "0xf137",
148 | "fa-chevron-circle-right": "0xf138",
149 | "fa-chevron-circle-up": "0xf139",
150 | "fa-chevron-down": "0xf078",
151 | "fa-chevron-left": "0xf053",
152 | "fa-chevron-right": "0xf054",
153 | "fa-chevron-up": "0xf077",
154 | "fa-child": "0xf1ae",
155 | "fa-chrome": "0xf268",
156 | "fa-circle": "0xf111",
157 | "fa-circle-o": "0xf10c",
158 | "fa-circle-o-notch": "0xf1ce",
159 | "fa-circle-thin": "0xf1db",
160 | "fa-clipboard": "0xf0ea",
161 | "fa-clock-o": "0xf017",
162 | "fa-clone": "0xf24d",
163 | "fa-close": "0xf00d",
164 | "fa-cloud": "0xf0c2",
165 | "fa-cloud-download": "0xf0ed",
166 | "fa-cloud-upload": "0xf0ee",
167 | "fa-cny": "0xf157",
168 | "fa-code": "0xf121",
169 | "fa-code-fork": "0xf126",
170 | "fa-codepen": "0xf1cb",
171 | "fa-codiepie": "0xf284",
172 | "fa-coffee": "0xf0f4",
173 | "fa-cog": "0xf013",
174 | "fa-cogs": "0xf085",
175 | "fa-columns": "0xf0db",
176 | "fa-comment": "0xf075",
177 | "fa-comment-o": "0xf0e5",
178 | "fa-commenting": "0xf27a",
179 | "fa-commenting-o": "0xf27b",
180 | "fa-comments": "0xf086",
181 | "fa-comments-o": "0xf0e6",
182 | "fa-compass": "0xf14e",
183 | "fa-compress": "0xf066",
184 | "fa-connectdevelop": "0xf20e",
185 | "fa-contao": "0xf26d",
186 | "fa-copy": "0xf0c5",
187 | "fa-copyright": "0xf1f9",
188 | "fa-creative-commons": "0xf25e",
189 | "fa-credit-card": "0xf09d",
190 | "fa-credit-card-alt": "0xf283",
191 | "fa-crop": "0xf125",
192 | "fa-crosshairs": "0xf05b",
193 | "fa-css3": "0xf13c",
194 | "fa-cube": "0xf1b2",
195 | "fa-cubes": "0xf1b3",
196 | "fa-cut": "0xf0c4",
197 | "fa-cutlery": "0xf0f5",
198 | "fa-dashboard": "0xf0e4",
199 | "fa-dashcube": "0xf210",
200 | "fa-database": "0xf1c0",
201 | "fa-deaf": "0xf2a4",
202 | "fa-deafness": "0xf2a4",
203 | "fa-dedent": "0xf03b",
204 | "fa-delicious": "0xf1a5",
205 | "fa-desktop": "0xf108",
206 | "fa-deviantart": "0xf1bd",
207 | "fa-diamond": "0xf219",
208 | "fa-digg": "0xf1a6",
209 | "fa-dollar": "0xf155",
210 | "fa-dot-circle-o": "0xf192",
211 | "fa-download": "0xf019",
212 | "fa-dribbble": "0xf17d",
213 | "fa-drivers-license": "0xf2c2",
214 | "fa-drivers-license-o": "0xf2c3",
215 | "fa-dropbox": "0xf16b",
216 | "fa-drupal": "0xf1a9",
217 | "fa-edge": "0xf282",
218 | "fa-edit": "0xf044",
219 | "fa-eercast": "0xf2da",
220 | "fa-eject": "0xf052",
221 | "fa-ellipsis-h": "0xf141",
222 | "fa-ellipsis-v": "0xf142",
223 | "fa-empire": "0xf1d1",
224 | "fa-envelope": "0xf0e0",
225 | "fa-envelope-o": "0xf003",
226 | "fa-envelope-open": "0xf2b6",
227 | "fa-envelope-open-o": "0xf2b7",
228 | "fa-envelope-square": "0xf199",
229 | "fa-envira": "0xf299",
230 | "fa-eraser": "0xf12d",
231 | "fa-etsy": "0xf2d7",
232 | "fa-eur": "0xf153",
233 | "fa-euro": "0xf153",
234 | "fa-exchange": "0xf0ec",
235 | "fa-exclamation": "0xf12a",
236 | "fa-exclamation-circle": "0xf06a",
237 | "fa-exclamation-triangle": "0xf071",
238 | "fa-expand": "0xf065",
239 | "fa-expeditedssl": "0xf23e",
240 | "fa-external-link": "0xf08e",
241 | "fa-external-link-square": "0xf14c",
242 | "fa-eye": "0xf06e",
243 | "fa-eye-slash": "0xf070",
244 | "fa-eyedropper": "0xf1fb",
245 | "fa-fa": "0xf2b4",
246 | "fa-facebook": "0xf09a",
247 | "fa-facebook-f": "0xf09a",
248 | "fa-facebook-official": "0xf230",
249 | "fa-facebook-square": "0xf082",
250 | "fa-fast-backward": "0xf049",
251 | "fa-fast-forward": "0xf050",
252 | "fa-fax": "0xf1ac",
253 | "fa-feed": "0xf09e",
254 | "fa-female": "0xf182",
255 | "fa-fighter-jet": "0xf0fb",
256 | "fa-file": "0xf15b",
257 | "fa-file-archive-o": "0xf1c6",
258 | "fa-file-audio-o": "0xf1c7",
259 | "fa-file-code-o": "0xf1c9",
260 | "fa-file-excel-o": "0xf1c3",
261 | "fa-file-image-o": "0xf1c5",
262 | "fa-file-movie-o": "0xf1c8",
263 | "fa-file-o": "0xf016",
264 | "fa-file-pdf-o": "0xf1c1",
265 | "fa-file-photo-o": "0xf1c5",
266 | "fa-file-picture-o": "0xf1c5",
267 | "fa-file-powerpoint-o": "0xf1c4",
268 | "fa-file-sound-o": "0xf1c7",
269 | "fa-file-text": "0xf15c",
270 | "fa-file-text-o": "0xf0f6",
271 | "fa-file-video-o": "0xf1c8",
272 | "fa-file-word-o": "0xf1c2",
273 | "fa-file-zip-o": "0xf1c6",
274 | "fa-files-o": "0xf0c5",
275 | "fa-film": "0xf008",
276 | "fa-filter": "0xf0b0",
277 | "fa-fire": "0xf06d",
278 | "fa-fire-extinguisher": "0xf134",
279 | "fa-firefox": "0xf269",
280 | "fa-first-order": "0xf2b0",
281 | "fa-flag": "0xf024",
282 | "fa-flag-checkered": "0xf11e",
283 | "fa-flag-o": "0xf11d",
284 | "fa-flash": "0xf0e7",
285 | "fa-flask": "0xf0c3",
286 | "fa-flickr": "0xf16e",
287 | "fa-floppy-o": "0xf0c7",
288 | "fa-folder": "0xf07b",
289 | "fa-folder-o": "0xf114",
290 | "fa-folder-open": "0xf07c",
291 | "fa-folder-open-o": "0xf115",
292 | "fa-font": "0xf031",
293 | "fa-font-awesome": "0xf2b4",
294 | "fa-fonticons": "0xf280",
295 | "fa-fort-awesome": "0xf286",
296 | "fa-forumbee": "0xf211",
297 | "fa-forward": "0xf04e",
298 | "fa-foursquare": "0xf180",
299 | "fa-free-code-camp": "0xf2c5",
300 | "fa-frown-o": "0xf119",
301 | "fa-futbol-o": "0xf1e3",
302 | "fa-gamepad": "0xf11b",
303 | "fa-gavel": "0xf0e3",
304 | "fa-gbp": "0xf154",
305 | "fa-ge": "0xf1d1",
306 | "fa-gear": "0xf013",
307 | "fa-gears": "0xf085",
308 | "fa-genderless": "0xf22d",
309 | "fa-get-pocket": "0xf265",
310 | "fa-gg": "0xf260",
311 | "fa-gg-circle": "0xf261",
312 | "fa-gift": "0xf06b",
313 | "fa-git": "0xf1d3",
314 | "fa-git-square": "0xf1d2",
315 | "fa-github": "0xf09b",
316 | "fa-github-alt": "0xf113",
317 | "fa-github-square": "0xf092",
318 | "fa-gitlab": "0xf296",
319 | "fa-gittip": "0xf184",
320 | "fa-glass": "0xf000",
321 | "fa-glide": "0xf2a5",
322 | "fa-glide-g": "0xf2a6",
323 | "fa-globe": "0xf0ac",
324 | "fa-google": "0xf1a0",
325 | "fa-google-plus": "0xf0d5",
326 | "fa-google-plus-circle": "0xf2b3",
327 | "fa-google-plus-official": "0xf2b3",
328 | "fa-google-plus-square": "0xf0d4",
329 | "fa-google-wallet": "0xf1ee",
330 | "fa-graduation-cap": "0xf19d",
331 | "fa-gratipay": "0xf184",
332 | "fa-grav": "0xf2d6",
333 | "fa-group": "0xf0c0",
334 | "fa-h-square": "0xf0fd",
335 | "fa-hacker-news": "0xf1d4",
336 | "fa-hand-grab-o": "0xf255",
337 | "fa-hand-lizard-o": "0xf258",
338 | "fa-hand-o-down": "0xf0a7",
339 | "fa-hand-o-left": "0xf0a5",
340 | "fa-hand-o-right": "0xf0a4",
341 | "fa-hand-o-up": "0xf0a6",
342 | "fa-hand-paper-o": "0xf256",
343 | "fa-hand-peace-o": "0xf25b",
344 | "fa-hand-pointer-o": "0xf25a",
345 | "fa-hand-rock-o": "0xf255",
346 | "fa-hand-scissors-o": "0xf257",
347 | "fa-hand-spock-o": "0xf259",
348 | "fa-hand-stop-o": "0xf256",
349 | "fa-handshake-o": "0xf2b5",
350 | "fa-hard-of-hearing": "0xf2a4",
351 | "fa-hashtag": "0xf292",
352 | "fa-hdd-o": "0xf0a0",
353 | "fa-header": "0xf1dc",
354 | "fa-headphones": "0xf025",
355 | "fa-heart": "0xf004",
356 | "fa-heart-o": "0xf08a",
357 | "fa-heartbeat": "0xf21e",
358 | "fa-history": "0xf1da",
359 | "fa-home": "0xf015",
360 | "fa-hospital-o": "0xf0f8",
361 | "fa-hotel": "0xf236",
362 | "fa-hourglass": "0xf254",
363 | "fa-hourglass-1": "0xf251",
364 | "fa-hourglass-2": "0xf252",
365 | "fa-hourglass-3": "0xf253",
366 | "fa-hourglass-end": "0xf253",
367 | "fa-hourglass-half": "0xf252",
368 | "fa-hourglass-o": "0xf250",
369 | "fa-hourglass-start": "0xf251",
370 | "fa-houzz": "0xf27c",
371 | "fa-html5": "0xf13b",
372 | "fa-i-cursor": "0xf246",
373 | "fa-id-badge": "0xf2c1",
374 | "fa-id-card": "0xf2c2",
375 | "fa-id-card-o": "0xf2c3",
376 | "fa-ils": "0xf20b",
377 | "fa-image": "0xf03e",
378 | "fa-imdb": "0xf2d8",
379 | "fa-inbox": "0xf01c",
380 | "fa-indent": "0xf03c",
381 | "fa-industry": "0xf275",
382 | "fa-info": "0xf129",
383 | "fa-info-circle": "0xf05a",
384 | "fa-inr": "0xf156",
385 | "fa-instagram": "0xf16d",
386 | "fa-institution": "0xf19c",
387 | "fa-internet-explorer": "0xf26b",
388 | "fa-intersex": "0xf224",
389 | "fa-ioxhost": "0xf208",
390 | "fa-italic": "0xf033",
391 | "fa-joomla": "0xf1aa",
392 | "fa-jpy": "0xf157",
393 | "fa-jsfiddle": "0xf1cc",
394 | "fa-key": "0xf084",
395 | "fa-keyboard-o": "0xf11c",
396 | "fa-krw": "0xf159",
397 | "fa-language": "0xf1ab",
398 | "fa-laptop": "0xf109",
399 | "fa-lastfm": "0xf202",
400 | "fa-lastfm-square": "0xf203",
401 | "fa-leaf": "0xf06c",
402 | "fa-leanpub": "0xf212",
403 | "fa-legal": "0xf0e3",
404 | "fa-lemon-o": "0xf094",
405 | "fa-level-down": "0xf149",
406 | "fa-level-up": "0xf148",
407 | "fa-life-bouy": "0xf1cd",
408 | "fa-life-buoy": "0xf1cd",
409 | "fa-life-ring": "0xf1cd",
410 | "fa-life-saver": "0xf1cd",
411 | "fa-lightbulb-o": "0xf0eb",
412 | "fa-line-chart": "0xf201",
413 | "fa-link": "0xf0c1",
414 | "fa-linkedin": "0xf0e1",
415 | "fa-linkedin-square": "0xf08c",
416 | "fa-linode": "0xf2b8",
417 | "fa-linux": "0xf17c",
418 | "fa-list": "0xf03a",
419 | "fa-list-alt": "0xf022",
420 | "fa-list-ol": "0xf0cb",
421 | "fa-list-ul": "0xf0ca",
422 | "fa-location-arrow": "0xf124",
423 | "fa-lock": "0xf023",
424 | "fa-long-arrow-down": "0xf175",
425 | "fa-long-arrow-left": "0xf177",
426 | "fa-long-arrow-right": "0xf178",
427 | "fa-long-arrow-up": "0xf176",
428 | "fa-low-vision": "0xf2a8",
429 | "fa-magic": "0xf0d0",
430 | "fa-magnet": "0xf076",
431 | "fa-mail-forward": "0xf064",
432 | "fa-mail-reply": "0xf112",
433 | "fa-mail-reply-all": "0xf122",
434 | "fa-male": "0xf183",
435 | "fa-map": "0xf279",
436 | "fa-map-marker": "0xf041",
437 | "fa-map-o": "0xf278",
438 | "fa-map-pin": "0xf276",
439 | "fa-map-signs": "0xf277",
440 | "fa-mars": "0xf222",
441 | "fa-mars-double": "0xf227",
442 | "fa-mars-stroke": "0xf229",
443 | "fa-mars-stroke-h": "0xf22b",
444 | "fa-mars-stroke-v": "0xf22a",
445 | "fa-maxcdn": "0xf136",
446 | "fa-meanpath": "0xf20c",
447 | "fa-medium": "0xf23a",
448 | "fa-medkit": "0xf0fa",
449 | "fa-meetup": "0xf2e0",
450 | "fa-meh-o": "0xf11a",
451 | "fa-mercury": "0xf223",
452 | "fa-microchip": "0xf2db",
453 | "fa-microphone": "0xf130",
454 | "fa-microphone-slash": "0xf131",
455 | "fa-minus": "0xf068",
456 | "fa-minus-circle": "0xf056",
457 | "fa-minus-square": "0xf146",
458 | "fa-minus-square-o": "0xf147",
459 | "fa-mixcloud": "0xf289",
460 | "fa-mobile": "0xf10b",
461 | "fa-mobile-phone": "0xf10b",
462 | "fa-modx": "0xf285",
463 | "fa-money": "0xf0d6",
464 | "fa-moon-o": "0xf186",
465 | "fa-mortar-board": "0xf19d",
466 | "fa-motorcycle": "0xf21c",
467 | "fa-mouse-pointer": "0xf245",
468 | "fa-music": "0xf001",
469 | "fa-navicon": "0xf0c9",
470 | "fa-neuter": "0xf22c",
471 | "fa-newspaper-o": "0xf1ea",
472 | "fa-object-group": "0xf247",
473 | "fa-object-ungroup": "0xf248",
474 | "fa-odnoklassniki": "0xf263",
475 | "fa-odnoklassniki-square": "0xf264",
476 | "fa-opencart": "0xf23d",
477 | "fa-openid": "0xf19b",
478 | "fa-opera": "0xf26a",
479 | "fa-optin-monster": "0xf23c",
480 | "fa-outdent": "0xf03b",
481 | "fa-pagelines": "0xf18c",
482 | "fa-paint-brush": "0xf1fc",
483 | "fa-paper-plane": "0xf1d8",
484 | "fa-paper-plane-o": "0xf1d9",
485 | "fa-paperclip": "0xf0c6",
486 | "fa-paragraph": "0xf1dd",
487 | "fa-paste": "0xf0ea",
488 | "fa-pause": "0xf04c",
489 | "fa-pause-circle": "0xf28b",
490 | "fa-pause-circle-o": "0xf28c",
491 | "fa-paw": "0xf1b0",
492 | "fa-paypal": "0xf1ed",
493 | "fa-pencil": "0xf040",
494 | "fa-pencil-square": "0xf14b",
495 | "fa-pencil-square-o": "0xf044",
496 | "fa-percent": "0xf295",
497 | "fa-phone": "0xf095",
498 | "fa-phone-square": "0xf098",
499 | "fa-photo": "0xf03e",
500 | "fa-picture-o": "0xf03e",
501 | "fa-pie-chart": "0xf200",
502 | "fa-pied-piper": "0xf2ae",
503 | "fa-pied-piper-alt": "0xf1a8",
504 | "fa-pied-piper-pp": "0xf1a7",
505 | "fa-pinterest": "0xf0d2",
506 | "fa-pinterest-p": "0xf231",
507 | "fa-pinterest-square": "0xf0d3",
508 | "fa-plane": "0xf072",
509 | "fa-play": "0xf04b",
510 | "fa-play-circle": "0xf144",
511 | "fa-play-circle-o": "0xf01d",
512 | "fa-plug": "0xf1e6",
513 | "fa-plus": "0xf067",
514 | "fa-plus-circle": "0xf055",
515 | "fa-plus-square": "0xf0fe",
516 | "fa-plus-square-o": "0xf196",
517 | "fa-podcast": "0xf2ce",
518 | "fa-power-off": "0xf011",
519 | "fa-print": "0xf02f",
520 | "fa-product-hunt": "0xf288",
521 | "fa-puzzle-piece": "0xf12e",
522 | "fa-qq": "0xf1d6",
523 | "fa-qrcode": "0xf029",
524 | "fa-question": "0xf128",
525 | "fa-question-circle": "0xf059",
526 | "fa-question-circle-o": "0xf29c",
527 | "fa-quora": "0xf2c4",
528 | "fa-quote-left": "0xf10d",
529 | "fa-quote-right": "0xf10e",
530 | "fa-ra": "0xf1d0",
531 | "fa-random": "0xf074",
532 | "fa-ravelry": "0xf2d9",
533 | "fa-rebel": "0xf1d0",
534 | "fa-recycle": "0xf1b8",
535 | "fa-reddit": "0xf1a1",
536 | "fa-reddit-alien": "0xf281",
537 | "fa-reddit-square": "0xf1a2",
538 | "fa-refresh": "0xf021",
539 | "fa-registered": "0xf25d",
540 | "fa-remove": "0xf00d",
541 | "fa-renren": "0xf18b",
542 | "fa-reorder": "0xf0c9",
543 | "fa-repeat": "0xf01e",
544 | "fa-reply": "0xf112",
545 | "fa-reply-all": "0xf122",
546 | "fa-resistance": "0xf1d0",
547 | "fa-retweet": "0xf079",
548 | "fa-rmb": "0xf157",
549 | "fa-road": "0xf018",
550 | "fa-rocket": "0xf135",
551 | "fa-rotate-left": "0xf0e2",
552 | "fa-rotate-right": "0xf01e",
553 | "fa-rouble": "0xf158",
554 | "fa-rss": "0xf09e",
555 | "fa-rss-square": "0xf143",
556 | "fa-rub": "0xf158",
557 | "fa-ruble": "0xf158",
558 | "fa-rupee": "0xf156",
559 | "fa-s15": "0xf2cd",
560 | "fa-safari": "0xf267",
561 | "fa-save": "0xf0c7",
562 | "fa-scissors": "0xf0c4",
563 | "fa-scribd": "0xf28a",
564 | "fa-search": "0xf002",
565 | "fa-search-minus": "0xf010",
566 | "fa-search-plus": "0xf00e",
567 | "fa-sellsy": "0xf213",
568 | "fa-send": "0xf1d8",
569 | "fa-send-o": "0xf1d9",
570 | "fa-server": "0xf233",
571 | "fa-share": "0xf064",
572 | "fa-share-alt": "0xf1e0",
573 | "fa-share-alt-square": "0xf1e1",
574 | "fa-share-square": "0xf14d",
575 | "fa-share-square-o": "0xf045",
576 | "fa-shekel": "0xf20b",
577 | "fa-sheqel": "0xf20b",
578 | "fa-shield": "0xf132",
579 | "fa-ship": "0xf21a",
580 | "fa-shirtsinbulk": "0xf214",
581 | "fa-shopping-bag": "0xf290",
582 | "fa-shopping-basket": "0xf291",
583 | "fa-shopping-cart": "0xf07a",
584 | "fa-shower": "0xf2cc",
585 | "fa-sign-in": "0xf090",
586 | "fa-sign-language": "0xf2a7",
587 | "fa-sign-out": "0xf08b",
588 | "fa-signal": "0xf012",
589 | "fa-signing": "0xf2a7",
590 | "fa-simplybuilt": "0xf215",
591 | "fa-sitemap": "0xf0e8",
592 | "fa-skyatlas": "0xf216",
593 | "fa-skype": "0xf17e",
594 | "fa-slack": "0xf198",
595 | "fa-sliders": "0xf1de",
596 | "fa-slideshare": "0xf1e7",
597 | "fa-smile-o": "0xf118",
598 | "fa-snapchat": "0xf2ab",
599 | "fa-snapchat-ghost": "0xf2ac",
600 | "fa-snapchat-square": "0xf2ad",
601 | "fa-snowflake-o": "0xf2dc",
602 | "fa-soccer-ball-o": "0xf1e3",
603 | "fa-sort": "0xf0dc",
604 | "fa-sort-alpha-asc": "0xf15d",
605 | "fa-sort-alpha-desc": "0xf15e",
606 | "fa-sort-amount-asc": "0xf160",
607 | "fa-sort-amount-desc": "0xf161",
608 | "fa-sort-asc": "0xf0de",
609 | "fa-sort-desc": "0xf0dd",
610 | "fa-sort-down": "0xf0dd",
611 | "fa-sort-numeric-asc": "0xf162",
612 | "fa-sort-numeric-desc": "0xf163",
613 | "fa-sort-up": "0xf0de",
614 | "fa-soundcloud": "0xf1be",
615 | "fa-space-shuttle": "0xf197",
616 | "fa-spinner": "0xf110",
617 | "fa-spoon": "0xf1b1",
618 | "fa-spotify": "0xf1bc",
619 | "fa-square": "0xf0c8",
620 | "fa-square-o": "0xf096",
621 | "fa-stack-exchange": "0xf18d",
622 | "fa-stack-overflow": "0xf16c",
623 | "fa-star": "0xf005",
624 | "fa-star-half": "0xf089",
625 | "fa-star-half-empty": "0xf123",
626 | "fa-star-half-full": "0xf123",
627 | "fa-star-half-o": "0xf123",
628 | "fa-star-o": "0xf006",
629 | "fa-steam": "0xf1b6",
630 | "fa-steam-square": "0xf1b7",
631 | "fa-step-backward": "0xf048",
632 | "fa-step-forward": "0xf051",
633 | "fa-stethoscope": "0xf0f1",
634 | "fa-sticky-note": "0xf249",
635 | "fa-sticky-note-o": "0xf24a",
636 | "fa-stop": "0xf04d",
637 | "fa-stop-circle": "0xf28d",
638 | "fa-stop-circle-o": "0xf28e",
639 | "fa-street-view": "0xf21d",
640 | "fa-strikethrough": "0xf0cc",
641 | "fa-stumbleupon": "0xf1a4",
642 | "fa-stumbleupon-circle": "0xf1a3",
643 | "fa-subscript": "0xf12c",
644 | "fa-subway": "0xf239",
645 | "fa-suitcase": "0xf0f2",
646 | "fa-sun-o": "0xf185",
647 | "fa-superpowers": "0xf2dd",
648 | "fa-superscript": "0xf12b",
649 | "fa-support": "0xf1cd",
650 | "fa-table": "0xf0ce",
651 | "fa-tablet": "0xf10a",
652 | "fa-tachometer": "0xf0e4",
653 | "fa-tag": "0xf02b",
654 | "fa-tags": "0xf02c",
655 | "fa-tasks": "0xf0ae",
656 | "fa-taxi": "0xf1ba",
657 | "fa-telegram": "0xf2c6",
658 | "fa-television": "0xf26c",
659 | "fa-tencent-weibo": "0xf1d5",
660 | "fa-terminal": "0xf120",
661 | "fa-text-height": "0xf034",
662 | "fa-text-width": "0xf035",
663 | "fa-th": "0xf00a",
664 | "fa-th-large": "0xf009",
665 | "fa-th-list": "0xf00b",
666 | "fa-themeisle": "0xf2b2",
667 | "fa-thermometer": "0xf2c7",
668 | "fa-thermometer-0": "0xf2cb",
669 | "fa-thermometer-1": "0xf2ca",
670 | "fa-thermometer-2": "0xf2c9",
671 | "fa-thermometer-3": "0xf2c8",
672 | "fa-thermometer-4": "0xf2c7",
673 | "fa-thermometer-empty": "0xf2cb",
674 | "fa-thermometer-full": "0xf2c7",
675 | "fa-thermometer-half": "0xf2c9",
676 | "fa-thermometer-quarter": "0xf2ca",
677 | "fa-thermometer-three-quarters": "0xf2c8",
678 | "fa-thumb-tack": "0xf08d",
679 | "fa-thumbs-down": "0xf165",
680 | "fa-thumbs-o-down": "0xf088",
681 | "fa-thumbs-o-up": "0xf087",
682 | "fa-thumbs-up": "0xf164",
683 | "fa-ticket": "0xf145",
684 | "fa-times": "0xf00d",
685 | "fa-times-circle": "0xf057",
686 | "fa-times-circle-o": "0xf05c",
687 | "fa-times-rectangle": "0xf2d3",
688 | "fa-times-rectangle-o": "0xf2d4",
689 | "fa-tint": "0xf043",
690 | "fa-toggle-down": "0xf150",
691 | "fa-toggle-left": "0xf191",
692 | "fa-toggle-off": "0xf204",
693 | "fa-toggle-on": "0xf205",
694 | "fa-toggle-right": "0xf152",
695 | "fa-toggle-up": "0xf151",
696 | "fa-trademark": "0xf25c",
697 | "fa-train": "0xf238",
698 | "fa-transgender": "0xf224",
699 | "fa-transgender-alt": "0xf225",
700 | "fa-trash": "0xf1f8",
701 | "fa-trash-o": "0xf014",
702 | "fa-tree": "0xf1bb",
703 | "fa-trello": "0xf181",
704 | "fa-tripadvisor": "0xf262",
705 | "fa-trophy": "0xf091",
706 | "fa-truck": "0xf0d1",
707 | "fa-try": "0xf195",
708 | "fa-tty": "0xf1e4",
709 | "fa-tumblr": "0xf173",
710 | "fa-tumblr-square": "0xf174",
711 | "fa-turkish-lira": "0xf195",
712 | "fa-tv": "0xf26c",
713 | "fa-twitch": "0xf1e8",
714 | "fa-twitter": "0xf099",
715 | "fa-twitter-square": "0xf081",
716 | "fa-umbrella": "0xf0e9",
717 | "fa-underline": "0xf0cd",
718 | "fa-undo": "0xf0e2",
719 | "fa-universal-access": "0xf29a",
720 | "fa-university": "0xf19c",
721 | "fa-unlink": "0xf127",
722 | "fa-unlock": "0xf09c",
723 | "fa-unlock-alt": "0xf13e",
724 | "fa-unsorted": "0xf0dc",
725 | "fa-upload": "0xf093",
726 | "fa-usb": "0xf287",
727 | "fa-usd": "0xf155",
728 | "fa-user": "0xf007",
729 | "fa-user-circle": "0xf2bd",
730 | "fa-user-circle-o": "0xf2be",
731 | "fa-user-md": "0xf0f0",
732 | "fa-user-o": "0xf2c0",
733 | "fa-user-plus": "0xf234",
734 | "fa-user-secret": "0xf21b",
735 | "fa-user-times": "0xf235",
736 | "fa-users": "0xf0c0",
737 | "fa-vcard": "0xf2bb",
738 | "fa-vcard-o": "0xf2bc",
739 | "fa-venus": "0xf221",
740 | "fa-venus-double": "0xf226",
741 | "fa-venus-mars": "0xf228",
742 | "fa-viacoin": "0xf237",
743 | "fa-viadeo": "0xf2a9",
744 | "fa-viadeo-square": "0xf2aa",
745 | "fa-video-camera": "0xf03d",
746 | "fa-vimeo": "0xf27d",
747 | "fa-vimeo-square": "0xf194",
748 | "fa-vine": "0xf1ca",
749 | "fa-vk": "0xf189",
750 | "fa-volume-control-phone": "0xf2a0",
751 | "fa-volume-down": "0xf027",
752 | "fa-volume-off": "0xf026",
753 | "fa-volume-up": "0xf028",
754 | "fa-warning": "0xf071",
755 | "fa-wechat": "0xf1d7",
756 | "fa-weibo": "0xf18a",
757 | "fa-weixin": "0xf1d7",
758 | "fa-whatsapp": "0xf232",
759 | "fa-wheelchair": "0xf193",
760 | "fa-wheelchair-alt": "0xf29b",
761 | "fa-wifi": "0xf1eb",
762 | "fa-wikipedia-w": "0xf266",
763 | "fa-window-close": "0xf2d3",
764 | "fa-window-close-o": "0xf2d4",
765 | "fa-window-maximize": "0xf2d0",
766 | "fa-window-minimize": "0xf2d1",
767 | "fa-window-restore": "0xf2d2",
768 | "fa-windows": "0xf17a",
769 | "fa-won": "0xf159",
770 | "fa-wordpress": "0xf19a",
771 | "fa-wpbeginner": "0xf297",
772 | "fa-wpexplorer": "0xf2de",
773 | "fa-wpforms": "0xf298",
774 | "fa-wrench": "0xf0ad",
775 | "fa-xing": "0xf168",
776 | "fa-xing-square": "0xf169",
777 | "fa-y-combinator": "0xf23b",
778 | "fa-y-combinator-square": "0xf1d4",
779 | "fa-yahoo": "0xf19e",
780 | "fa-yc": "0xf23b",
781 | "fa-yc-square": "0xf1d4",
782 | "fa-yelp": "0xf1e9",
783 | "fa-yen": "0xf157",
784 | "fa-yoast": "0xf2b1",
785 | "fa-youtube": "0xf167",
786 | "fa-youtube-play": "0xf16a",
787 | "fa-youtube-square": "0xf166"
788 | }
789 |
--------------------------------------------------------------------------------
/CustomWidgets/CFontIcon/Fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/CustomWidgets/CFontIcon/Fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/CustomWidgets/CFontIcon/Fonts/materialdesignicons-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/CustomWidgets/CFontIcon/Fonts/materialdesignicons-webfont.ttf
--------------------------------------------------------------------------------
/CustomWidgets/CFontIcon/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/CustomWidgets/CFontIcon/__init__.py
--------------------------------------------------------------------------------
/CustomWidgets/CFramelessWidget.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月16日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CFramelessBase
10 | @description: 无边框窗口
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtGui import QPainter, QPen, QColor, QEnterEvent
14 | from PyQt5.QtWidgets import QWidget, QDialog
15 |
16 |
17 | __Author__ = 'Irony'
18 | __Copyright__ = 'Copyright (c) 2019'
19 |
20 | LEFT = 1
21 | TOP = 2
22 | RIGHT = 4
23 | BOTTOM = 8
24 | LEFTTOP = LEFT | TOP
25 | RIGHTTOP = RIGHT | TOP
26 | LEFTBOTTOM = LEFT | BOTTOM
27 | RIGHTBOTTOM = RIGHT | BOTTOM
28 |
29 |
30 | class CFramelessBase:
31 |
32 | Margins = 4
33 | BaseClass = QWidget
34 |
35 | def __init__(self, *args, **kwargs):
36 | super(CFramelessBase, self).__init__(*args, **kwargs)
37 | self.dragParams = {'type': 0, 'x': 0,
38 | 'y': 0, 'margin': 0, 'draging': False}
39 | self.originalCusor = None
40 | self.setMouseTracking(True)
41 | # 设置背景透明
42 | self.setAttribute(Qt.WA_TranslucentBackground, True)
43 | # 设置无边框
44 | self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
45 |
46 | def isResizable(self):
47 | """是否可调整
48 | """
49 | return self.minimumSize() != self.maximumSize()
50 |
51 | def getEdge(self, pos):
52 | """返回点与边距接触的边的方向
53 | :param pos:
54 | """
55 | rect = self.rect()
56 | edge = 0
57 | if not self.isResizable():
58 | return edge
59 | if pos.x() <= rect.left() + self.Margins:
60 | edge |= LEFT
61 | elif pos.x() >= rect.right() - self.Margins:
62 | edge |= RIGHT
63 | if pos.y() <= rect.top() + self.Margins:
64 | edge |= TOP
65 | elif pos.y() >= rect.bottom() - self.Margins:
66 | edge |= BOTTOM
67 | return edge
68 |
69 | def adjustCursor(self, edge):
70 | """根据边方向调整光标样式
71 | :param edge:
72 | """
73 | cursor = None
74 | if edge in (TOP, BOTTOM):
75 | cursor = Qt.SizeVerCursor
76 | elif edge in (LEFT, RIGHT):
77 | cursor = Qt.SizeHorCursor
78 | elif edge in (LEFT | TOP, RIGHT | BOTTOM):
79 | cursor = Qt.SizeFDiagCursor
80 | elif edge in (TOP | RIGHT, BOTTOM | LEFT):
81 | cursor = Qt.SizeBDiagCursor
82 | if cursor and cursor != self.cursor():
83 | self.setCursor(cursor)
84 |
85 | def eventFilter(self, obj, event):
86 | """事件过滤器,用于解决鼠标进入其它控件后还原为标准鼠标样式
87 | """
88 | if isinstance(event, QEnterEvent):
89 | self.setCursor(self.originalCusor or Qt.ArrowCursor)
90 | return self.BaseClass.eventFilter(self, obj, event)
91 |
92 | def paintEvent(self, event):
93 | """由于是全透明背景窗口,重绘事件中绘制透明度为1的难以发现的边框,用于调整窗口大小
94 | """
95 | self.BaseClass.paintEvent(self, event)
96 | painter = QPainter(self)
97 | painter.setPen(QPen(QColor(255, 255, 255, 1), 2 * self.Margins))
98 | painter.drawRect(self.rect())
99 |
100 | def showEvent(self, event):
101 | """第一次显示时设置控件的layout的边距
102 | :param event:
103 | """
104 | layout = self.layout()
105 | if self.originalCusor == None and layout:
106 | self.originalCusor = self.cursor()
107 | layout.setContentsMargins(
108 | self.Margins, self.Margins, self.Margins, self.Margins)
109 | # 对所有子控件增加事件过滤器
110 | for w in self.children():
111 | if isinstance(w, QWidget):
112 | w.installEventFilter(self)
113 | self.BaseClass.showEvent(self, event)
114 |
115 | def mousePressEvent(self, event):
116 | """鼠标按下设置标志
117 | :param event:
118 | """
119 | if not self.isResizable() or self.childAt(event.pos()):
120 | return
121 | self.dragParams['x'] = event.x()
122 | self.dragParams['y'] = event.y()
123 | self.dragParams['globalX'] = event.globalX()
124 | self.dragParams['globalY'] = event.globalY()
125 | self.dragParams['width'] = self.width()
126 | self.dragParams['height'] = self.height()
127 | if event.button() == Qt.LeftButton and self.dragParams['type'] != 0 \
128 | and not self.isMaximized() and not self.isFullScreen():
129 | self.dragParams['draging'] = True
130 |
131 | def mouseReleaseEvent(self, event):
132 | """释放鼠标还原光标样式
133 | :param event:
134 | """
135 | self.dragParams['draging'] = False
136 | self.dragParams['type'] = 0
137 |
138 | def mouseMoveEvent(self, event):
139 | """鼠标移动用于设置鼠标样式或者调整窗口大小
140 | :param event:
141 | """
142 | if self.isMaximized() or self.isFullScreen() or not self.isResizable():
143 | return
144 |
145 | # 判断鼠标类型
146 | cursorType = self.dragParams['type']
147 | if not self.dragParams['draging']:
148 | cursorType = self.dragParams['type'] = self.getEdge(event.pos())
149 | self.adjustCursor(cursorType)
150 |
151 | # 判断窗口拖动
152 | if self.dragParams['draging']:
153 | x = self.x()
154 | y = self.y()
155 | width = self.width()
156 | height = self.height()
157 |
158 | if cursorType & TOP == TOP:
159 | y = event.globalY() - self.dragParams['margin']
160 | height = self.dragParams['height'] + \
161 | self.dragParams['globalY'] - event.globalY()
162 | if cursorType & BOTTOM == BOTTOM:
163 | height = self.dragParams['height'] - \
164 | self.dragParams['globalY'] + event.globalY()
165 | if cursorType & LEFT == LEFT:
166 | x = event.globalX() - self.dragParams['margin']
167 | width = self.dragParams['width'] + \
168 | self.dragParams['globalX'] - event.globalX()
169 | if cursorType & RIGHT == RIGHT:
170 | width = self.dragParams['width'] - \
171 | self.dragParams['globalX'] + event.globalX()
172 |
173 | minw = self.minimumWidth()
174 | maxw = self.maximumWidth()
175 | minh = self.minimumHeight()
176 | maxh = self.maximumHeight()
177 | if width < minw or width > maxw or height < minh or height > maxh:
178 | return
179 |
180 | self.setGeometry(x, y, width, height)
181 |
182 |
183 | class CFramelessWidget(QWidget, CFramelessBase):
184 |
185 | BaseClass = QWidget
186 |
187 |
188 | class CFramelessDialog(QDialog, CFramelessBase):
189 |
190 | BaseClass = QDialog
191 |
--------------------------------------------------------------------------------
/CustomWidgets/CLoadingBar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月30日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CLoadingBar
10 | @description: 加载条
11 | """
12 | from PyQt5.QtCore import Qt, QRectF, pyqtProperty, QPropertyAnimation,\
13 | QEasingCurve
14 | from PyQt5.QtGui import QColor, QPainter
15 | from PyQt5.QtWidgets import QProgressBar
16 |
17 |
18 | __Author__ = 'Irony'
19 | __Copyright__ = 'Copyright (c) 2019'
20 |
21 |
22 | class CLoadingBar(QProgressBar):
23 |
24 | Instances = {}
25 | Height = 2
26 | Color = QColor('#2d8cf0')
27 | FailedColor = QColor('#ed4014')
28 | # 位置
29 | TOP = 0
30 | BOTTOM = 1
31 | Direction = 0
32 |
33 | def __init__(self, *args, **kwargs):
34 | super(CLoadingBar, self).__init__(*args, **kwargs)
35 | self._height = None # 进度条高度
36 | self._color = None # 正常颜色
37 | self._failedColor = None # 失败颜色
38 | self._direction = None # 进度条位置(上下)
39 | self._alpha = 255 # 透明度
40 | self.isError = False # 是否错误
41 | self.setOrientation(Qt.Horizontal)
42 | self.setTextVisible(False)
43 | self.animation = QPropertyAnimation(
44 | self, b'alpha', self, loopCount=1, duration=1000)
45 | self.animation.setEasingCurve(QEasingCurve.SineCurve)
46 | self.animation.setStartValue(0)
47 | self.animation.setEndValue(255)
48 |
49 | @pyqtProperty(int)
50 | def alpha(self):
51 | return self._alpha
52 |
53 | @alpha.setter
54 | def alpha(self, alpha):
55 | self._alpha = alpha
56 | QProgressBar.update(self)
57 |
58 | def paintEvent(self, _):
59 | painter = QPainter(self)
60 | painter.setRenderHint(QPainter.Antialiasing, True)
61 | painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
62 | painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
63 | # 背景
64 | painter.fillRect(self.rect(), Qt.transparent)
65 | # 进度块
66 | ratio = (self.value() - self.minimum()) / \
67 | (self.maximum() - self.minimum())
68 | width = self.rect().width() * ratio
69 | if self.isError:
70 | color = QColor(self._failedColor or CLoadingBar.FailedColor)
71 | else:
72 | color = QColor(self._color or CLoadingBar.Color)
73 | color.setAlpha(self._alpha)
74 | painter.setBrush(color)
75 | painter.setPen(Qt.NoPen)
76 | painter.drawRoundedRect(QRectF(0, 0, width, self.height()), 2, 2)
77 |
78 | def eventFilter(self, obj, event):
79 | if event.type() == event.Resize:
80 | # 重新调整大小
81 | widget = CLoadingBar.Instances.get(obj, None)
82 | if widget:
83 | direction = widget._direction or CLoadingBar.Direction
84 | height = widget._height or CLoadingBar.Height
85 | widget.setGeometry(
86 | 0, 0 if direction == CLoadingBar.TOP else obj.height() - height,
87 | obj.width(), height
88 | )
89 | return super(CLoadingBar, self).eventFilter(obj, event)
90 |
91 | @classmethod
92 | def config(cls, height=2, direction=0, color='#2d8cf0', failedColor='#ed4014'):
93 | """全局配置
94 | :param cls:
95 | :param height: 进度条高度
96 | :param direction: 进度条位置
97 | :param color: 进度条加载颜色
98 | :param failedColor: 进度条失败颜色
99 | """
100 | CLoadingBar.Height = height
101 | CLoadingBar.Color = QColor(color)
102 | CLoadingBar.FailedColor = QColor(failedColor)
103 | CLoadingBar.Direction = direction
104 | return cls
105 |
106 | @classmethod
107 | def start(cls, parent, minimum=0, maximum=100, height=None, direction=None, color=None, failedColor=None):
108 | """创建加载条
109 | :param cls:
110 | :param widget: 目标对象
111 | :param minimum: 进度条最小值
112 | :param maximum: 进度条最大值
113 | :param height: 进度条高度
114 | :param direction: 进度条位置
115 | :param color: 进度条加载颜色
116 | :param failedColor: 进度条失败颜色
117 | """
118 | if parent not in CLoadingBar.Instances:
119 | widget = CLoadingBar(parent)
120 | CLoadingBar.Instances[parent] = widget
121 | # 对父控件安装事件过滤器
122 | parent.installEventFilter(widget)
123 | else:
124 | widget = CLoadingBar.Instances[parent]
125 | widget._height = height
126 | widget._color = color
127 | widget._failedColor = failedColor
128 | widget._direction = direction
129 | widget.setRange(minimum, maximum)
130 | widget.setValue(minimum)
131 | direction = widget._direction or CLoadingBar.Direction
132 | height = widget._height or CLoadingBar.Height
133 | widget.setGeometry(
134 | 0, 0 if direction == CLoadingBar.TOP else parent.height() - height,
135 | parent.width(), height
136 | )
137 |
138 | @classmethod
139 | def finish(cls, parent):
140 | if parent not in CLoadingBar.Instances:
141 | return
142 | widget = CLoadingBar.Instances[parent]
143 | widget._alpha = 255
144 | widget.isError = False
145 | widget.setValue(widget.maximum())
146 | widget.animation.start()
147 |
148 | @classmethod
149 | def error(cls, parent):
150 | if parent not in CLoadingBar.Instances:
151 | return
152 | widget = CLoadingBar.Instances[parent]
153 | widget._alpha = 255
154 | widget.isError = True
155 | widget.setValue(widget.maximum())
156 | widget.animation.start()
157 |
158 | @classmethod
159 | def update(cls, parent, value):
160 | if parent in CLoadingBar.Instances:
161 | widget = CLoadingBar.Instances[parent]
162 | widget._alpha = 255
163 | widget.isError = False
164 | widget.show()
165 | widget.setValue(value)
166 |
--------------------------------------------------------------------------------
/CustomWidgets/CPaginationBar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月23日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CPaginationBar
10 | @description: 分页条
11 | """
12 | from PyQt5.QtCore import pyqtSignal, Qt
13 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QSpacerItem, QSizePolicy,\
14 | QPushButton, QLabel, QSpinBox
15 |
16 |
17 | __Author__ = 'Irony'
18 | __Copyright__ = 'Copyright (c) 2019 Irony'
19 | __Version__ = 1.0
20 |
21 | # 扁平样式
22 | FlatStyle = """
23 | CPaginationBar > QPushButton {
24 | border: none;
25 | qproperty-minimumSize: 36px 28px;
26 | qproperty-maximumSize: 36px 28px;
27 | font-weight: 500;
28 | font-size: 16px;
29 | }
30 | CPaginationBar > QPushButton:hover {
31 | color: #409eff;
32 | }
33 | CPaginationBar > QPushButton:disabled {
34 | color: #409eff;
35 | }
36 | #CPaginationBar_buttonPrevious:disabled, #CPaginationBar_buttonNext:disabled {
37 | color: #c0c4cc;
38 | }
39 | #CPaginationBar_labelInfos {
40 | }
41 | #CPaginationBar_editJump {
42 | border: 1px solid #dcdee2;
43 | border-radius: 4px;
44 | qproperty-minimumSize: 46px 28px;
45 | qproperty-maximumSize: 46px 28px;
46 | }
47 | #CPaginationBar_editJump:focus {
48 | border-color: #409eff;
49 | }
50 | """
51 |
52 | # 普通样式
53 | Style = """
54 | CPaginationBar > QPushButton {
55 | border: 1px solid #dcdee2;
56 | border-radius: 4px;
57 | qproperty-minimumSize: 36px 28px;
58 | qproperty-maximumSize: 36px 28px;
59 | font-weight: 500;
60 | font-size: 16px;
61 | }
62 | CPaginationBar > QPushButton:hover {
63 | color: #409eff;
64 | border-color: #409eff;
65 | }
66 | CPaginationBar > QPushButton:disabled {
67 | color: #409eff;
68 | border-color: #409eff;
69 | }
70 | #CPaginationBar_buttonPrevious:disabled, #CPaginationBar_buttonNext:disabled {
71 | color: #c0c4cc;
72 | border-color: #c0c4cc;
73 | }
74 | #CPaginationBar_labelInfos {
75 | }
76 | #CPaginationBar_editJump {
77 | border: 1px solid #dcdee2;
78 | border-radius: 4px;
79 | qproperty-minimumSize: 46px 28px;
80 | qproperty-maximumSize: 46px 28px;
81 | }
82 | #CPaginationBar_editJump:focus {
83 | border-color: #409eff;
84 | }
85 | """
86 |
87 |
88 | class _CPaginationJumpBar(QWidget):
89 | """跳转控件
90 | """
91 |
92 | valueChanged = pyqtSignal(int)
93 |
94 | def __init__(self, totalPages, *args, **kwargs):
95 | super(_CPaginationJumpBar, self).__init__(*args, **kwargs)
96 | self.setAttribute(Qt.WA_StyledBackground, True)
97 | layout = QHBoxLayout(self)
98 | layout.setSpacing(4)
99 | layout.setContentsMargins(0, 0, 0, 0)
100 | layout.addWidget(QLabel('前往', self, alignment=Qt.AlignCenter))
101 | self.editPage = QSpinBox(self)
102 | layout.addWidget(self.editPage)
103 | layout.addWidget(QLabel('页', self, alignment=Qt.AlignCenter))
104 |
105 | self.setTotalPages(totalPages)
106 | self.editPage.setObjectName('CPaginationBar_editJump')
107 | self.editPage.setFrame(False)
108 | self.editPage.setAlignment(Qt.AlignCenter)
109 | self.editPage.setButtonSymbols(QSpinBox.NoButtons)
110 | self.editPage.editingFinished.connect(self.onValueChanged)
111 | # 禁止鼠标滑轮
112 | self.editPage.wheelEvent = self._wheelEvent
113 |
114 | def _wheelEvent(self, event):
115 | event.ignore()
116 |
117 | def onValueChanged(self):
118 | self.valueChanged.emit(self.editPage.value())
119 |
120 | def setCurrentPage(self, currentPage):
121 | """设置当前页
122 | :param currentPage:
123 | """
124 | self.editPage.blockSignals(True)
125 | self.editPage.setValue(currentPage)
126 | self.editPage.blockSignals(False)
127 |
128 | def setTotalPages(self, totalPages):
129 | """设置最大值
130 | :param totalPages:
131 | """
132 | self.totalPages = max(1, totalPages)
133 | self.editPage.setRange(1, self.totalPages)
134 |
135 |
136 | class CPaginationBar(QWidget):
137 |
138 | pageChanged = pyqtSignal(int)
139 |
140 | def __init__(self, *args, **kwargs):
141 | totalPages = kwargs.pop('totalPages', 0)
142 | super(CPaginationBar, self).__init__(*args, **kwargs)
143 | self.setAttribute(Qt.WA_StyledBackground, True)
144 | self.currentPage = 1
145 | self.totalPages = 0
146 | self._buttons = []
147 | self.setupUi()
148 | if totalPages > 0 and isinstance(totalPages, int):
149 | self.setTotalPages(totalPages)
150 |
151 | def getCurrentPage(self):
152 | """得到当前页
153 | """
154 | return self.currentPage
155 |
156 | def setInfos(self, text):
157 | """设置统计信息文字
158 | :param text: 如果没有文字内容则隐藏
159 | """
160 | self.labelInfos.setText(text)
161 | self.labelInfos.setVisible(len(text))
162 |
163 | def setJumpWidget(self, visible):
164 | """开启跳转控件
165 | :param visible:
166 | """
167 | self.paginationJumpBar.setVisible(visible)
168 |
169 | def setCurrentPage(self, currentPage):
170 | """设置当前页
171 | :param currentPage:
172 | """
173 | if self.currentPage > self.totalPages:
174 | currentPage = 1
175 | self.currentPage = currentPage
176 | self.paginationJumpBar.setCurrentPage(self.currentPage)
177 | self._calculate()
178 |
179 | def setTotalPages(self, totalPages):
180 | """设置总页数,后需要重新安排按钮
181 | :param totalPages:
182 | """
183 | if totalPages < 1:
184 | totalPages = 1
185 | if self.totalPages == totalPages:
186 | return
187 | self.totalPages = totalPages
188 | if self.currentPage > totalPages:
189 | self.currentPage = 1
190 | self.previousPage = -1
191 | self.totalButtons = 7 # 总的按钮个数
192 | # 更新跳转控件的值
193 | self.paginationJumpBar.setTotalPages(self.totalPages)
194 | self.paginationJumpBar.setCurrentPage(self.currentPage)
195 | # 清空布局中的内容
196 | self._clearLayouts()
197 |
198 | layout = self.layout()
199 | # 左侧拉伸占位
200 | layout.addItem(QSpacerItem(
201 | 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
202 | # 添加信息控件
203 | layout.addWidget(self.labelInfos)
204 | layout.addItem(QSpacerItem(
205 | 16, 20, QSizePolicy.Fixed, QSizePolicy.Minimum))
206 | # 添加上一页按钮
207 | self.buttonPrevious.setVisible(totalPages > 1)
208 | layout.addWidget(self.buttonPrevious)
209 |
210 | if self.totalButtons > totalPages:
211 | self.totalButtons = totalPages
212 |
213 | self._initButtons()
214 |
215 | # 添加下一页按钮
216 | self.buttonNext.setVisible(totalPages > 1)
217 | self.buttonNext.setEnabled(totalPages > 1)
218 | layout.addWidget(self.buttonNext)
219 | layout.addItem(QSpacerItem(
220 | 16, 20, QSizePolicy.Fixed, QSizePolicy.Minimum))
221 | # 添加跳转控件
222 | layout.addWidget(self.paginationJumpBar)
223 | # 右侧拉伸占位
224 | layout.addItem(QSpacerItem(
225 | 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
226 |
227 | self._calculate()
228 |
229 | def _initButtons(self):
230 | # 初始化按钮
231 | for i in range(self.totalButtons):
232 | button = QPushButton(self)
233 | pageNumber = -1
234 | if self.totalPages >= self.totalButtons + 1:
235 | if i == 0:
236 | button.setProperty('page', 1)
237 | pageNumber = 1
238 | elif i == self.totalButtons - 1:
239 | button.setProperty('page', self.totalPages)
240 | pageNumber = self.totalPages
241 | else:
242 | if i <= 4:
243 | pageNumber = i + 1
244 | else:
245 | pageNumber = i + 1
246 |
247 | button.setProperty('page', pageNumber)
248 | button.setText(str(pageNumber) if pageNumber > 0 else '...')
249 | self.layout().addWidget(button)
250 | self._buttons.append(button)
251 | button.clicked.connect(self._doButtonTurning)
252 |
253 | def _clearLayouts(self):
254 | """清空控件中的内容
255 | """
256 | layout = self.layout()
257 | self._buttons.clear()
258 | for _ in range(layout.count()):
259 | item = layout.takeAt(0)
260 | widget = item.widget()
261 | if widget:
262 | # 一定要保留这些控件的对象引用
263 | if widget == self.buttonPrevious:
264 | self.buttonPrevious = widget
265 | elif widget == self.buttonNext:
266 | self.buttonNext = widget
267 | elif widget == self.labelInfos:
268 | self.labelInfos = widget
269 | elif widget == self.paginationJumpBar:
270 | self.paginationJumpBar = widget
271 | else:
272 | widget.deleteLater()
273 | del widget
274 | del item
275 |
276 | def _updateButton(self, index, pageNumber):
277 | """更新按钮的文本和属性
278 | :param index:
279 | :param pageNumber:
280 | """
281 | button = self._buttons[index]
282 | button.setText(str(pageNumber) if pageNumber > 0 else '...')
283 | button.setProperty('page', pageNumber)
284 |
285 | def _calculate(self):
286 | """重新计算并更新按钮
287 | """
288 | if self.totalPages > self.totalButtons:
289 | button1 = False
290 | button5 = False
291 | if self.currentPage - 1 > 3:
292 | button1 = True
293 | self._updateButton(1, -2)
294 | else:
295 | self._updateButton(1, 2)
296 | self._updateButton(2, 3)
297 | self._updateButton(3, 4)
298 | self._updateButton(4, 5)
299 |
300 | if self.totalPages - self.currentPage > 3:
301 | button5 = True
302 | self._updateButton(5, -1)
303 | else:
304 | self._updateButton(2, self.totalPages - 4)
305 | self._updateButton(3, self.totalPages - 3)
306 | self._updateButton(4, self.totalPages - 2)
307 | self._updateButton(5, self.totalPages - 1)
308 |
309 | if button1 and button5:
310 | self._updateButton(2, self.currentPage - 1)
311 | self._updateButton(3, self.currentPage)
312 | self._updateButton(4, self.currentPage + 1)
313 |
314 | for button in self._buttons:
315 | page = button.property('page')
316 | button.setEnabled(self.currentPage != page)
317 | button.setCursor(
318 | Qt.PointingHandCursor if button.isEnabled() else Qt.ArrowCursor)
319 |
320 | self.buttonPrevious.setEnabled(self.currentPage > 1)
321 | self.buttonNext.setEnabled(self.currentPage < self.totalPages)
322 |
323 | # 这里ForbiddenCursor不会生效,这可能是一个Bug,当按钮不可用时忽略了鼠标样式
324 | self.buttonPrevious.setCursor(
325 | Qt.PointingHandCursor if self.buttonPrevious.isEnabled() else Qt.ForbiddenCursor)
326 | self.buttonNext.setCursor(
327 | Qt.PointingHandCursor if self.buttonNext.isEnabled() else Qt.ForbiddenCursor)
328 |
329 | def _doButtonTurning(self):
330 | """按钮点击切换
331 | """
332 | pageNumber = self.sender().property('page')
333 | newCurrentPage = self.currentPage
334 |
335 | if pageNumber > 0:
336 | newCurrentPage = pageNumber
337 | elif pageNumber == -1:
338 | newCurrentPage = self.currentPage + 3
339 | elif pageNumber == -2:
340 | newCurrentPage = self.currentPage - 3
341 |
342 | if newCurrentPage < 1:
343 | newCurrentPage = 1
344 | elif newCurrentPage > self.totalPages:
345 | newCurrentPage = self.totalPages
346 |
347 | self.previousPage = self.currentPage
348 | self.currentPage = newCurrentPage
349 | self._calculate()
350 | self.pageChanged.emit(self.currentPage)
351 |
352 | def _doPageTurning(self):
353 | """上、下一页按钮
354 | """
355 | self.previousPage = self.currentPage
356 | if self.sender() == self.buttonPrevious:
357 | self.currentPage -= 1
358 | elif self.sender() == self.buttonNext:
359 | self.currentPage += 1
360 | self._calculate()
361 | self.pageChanged.emit(self.currentPage)
362 |
363 | def setupUi(self):
364 | # 横向布局
365 | layout = QHBoxLayout(self)
366 | layout.setSpacing(6)
367 | layout.setContentsMargins(0, 0, 0, 0)
368 | # 总数据量统计
369 | self.labelInfos = QLabel(
370 | self, visible=False, alignment=Qt.AlignCenter, objectName='CPaginationBar_labelInfos')
371 | # 上一页按钮
372 | self.buttonPrevious = QPushButton(
373 | '<', self, enabled=False, visible=False, clicked=self._doPageTurning,
374 | objectName='CPaginationBar_buttonPrevious')
375 | # 下一页按钮
376 | self.buttonNext = QPushButton(
377 | '>', self, enabled=False, visible=False, clicked=self._doPageTurning,
378 | objectName='CPaginationBar_buttonNext')
379 | # 跳转控件
380 | self.paginationJumpBar = _CPaginationJumpBar(
381 | 1, self, visible=False, objectName='CPaginationBar_paginationJumpBar')
382 | self.paginationJumpBar.valueChanged.connect(self.setCurrentPage)
383 |
--------------------------------------------------------------------------------
/CustomWidgets/CSlider.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月24日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CSlider
10 | @description: 滑块
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle
14 |
15 |
16 | __Author__ = 'Irony'
17 | __Copyright__ = 'Copyright (c) 2019 Irony'
18 | __Version__ = 1.0
19 |
20 | # 普通样式
21 | Style = """
22 | /*横向*/
23 | QSlider:horizontal {
24 | min-height: 60px;
25 | }
26 | QSlider::groove:horizontal {
27 | height: 2px;
28 | background: white;
29 | }
30 | QSlider::handle:horizontal {
31 | width: 30px;
32 | margin: 0 -6px;
33 | margin-top: -15px;
34 | margin-bottom: -15px;
35 | border-radius: 15px;
36 | background: qradialgradient(spread:reflect, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.5 rgba(255, 255, 255, 255), stop:0.6 rgba(255, 255, 255, 100), stop:0.61 rgba(255, 255, 255, 0));
37 | }
38 | QSlider::handle:horizontal:hover {
39 | background: qradialgradient(spread:reflect, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.40113 rgba(255, 255, 255, 255), stop:0.502825 rgba(44, 167, 248, 255), stop:0.59887 rgba(44, 167, 248, 100), stop:0.61 rgba(44, 167, 248, 0));
40 | }
41 | QSlider::add-page:horizontal {
42 | background: white;
43 | }
44 |
45 | QSlider::sub-page:horizontal {
46 | background: rgb(44, 167, 248);
47 | }
48 | /*竖向*/
49 | QSlider:vertical {
50 | min-width: 60px;
51 | }
52 | QSlider::groove:vertical {
53 | width: 2px;
54 | background: white;
55 | }
56 | QSlider::handle:vertical {
57 | height: 30px;
58 | margin: -6px 0;
59 | margin-left: -15px;
60 | margin-right: -15px;
61 | border-radius: 15px;
62 | background: qradialgradient(spread:reflect, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.5 rgba(255, 255, 255, 255), stop:0.6 rgba(255, 255, 255, 100), stop:0.61 rgba(255, 255, 255, 0));
63 | }
64 | QSlider::handle:vertical:hover {
65 | background: qradialgradient(spread:reflect, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.40113 rgba(255, 255, 255, 255), stop:0.502825 rgba(44, 167, 248, 255), stop:0.59887 rgba(44, 167, 248, 100), stop:0.61 rgba(44, 167, 248, 0));
66 | }
67 | QSlider::add-page:vertical {
68 | background: white;
69 | }
70 |
71 | QSlider::sub-page:vertical {
72 | background: rgb(44, 167, 248);
73 | }
74 | """
75 |
76 |
77 | class CSlider(QSlider):
78 |
79 | def __init__(self, *args, **kwargs):
80 | super(CSlider, self).__init__(*args, **kwargs)
81 | self.setCursor(Qt.PointingHandCursor)
82 | self.setStyleSheet(Style)
83 |
84 | def mousePressEvent(self, event):
85 | # 获取上面的拉动块位置
86 | option = QStyleOptionSlider()
87 | self.initStyleOption(option)
88 | rect = self.style().subControlRect(
89 | QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)
90 | if rect.contains(event.pos()):
91 | # 如果鼠标点击的位置在滑块上则交给Qt自行处理
92 | super(CSlider, self).mousePressEvent(event)
93 | return
94 | if self.orientation() == Qt.Horizontal:
95 | # 横向,要考虑invertedAppearance是否反向显示的问题
96 | self.setValue(self.style().sliderValueFromPosition(
97 | self.minimum(), self.maximum(),
98 | event.x() if not self.invertedAppearance() else (self.width(
99 | ) - event.x()), self.width()))
100 | else:
101 | # 纵向
102 | self.setValue(self.style().sliderValueFromPosition(
103 | self.minimum(), self.maximum(),
104 | (self.height() - event.y()) if not self.invertedAppearance(
105 | ) else event.y(), self.height()))
106 |
--------------------------------------------------------------------------------
/CustomWidgets/CTitleBar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月15日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: CustomWidgets.CTitleBar
10 | @description: 自定义标题栏
11 | """
12 |
13 | from PyQt5.QtCore import Qt, QPointF
14 | from PyQt5.QtGui import QWindowStateChangeEvent, QFont, QMouseEvent
15 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QSpacerItem, QSizePolicy, \
16 | QLabel, QPushButton, QApplication
17 |
18 |
19 | __Author__ = 'Irony'
20 | __Copyright__ = 'Copyright (c) 2019'
21 |
22 |
23 | class CTitleBar(QWidget):
24 |
25 | Radius = 38
26 |
27 | def __init__(self, *args, title='', **kwargs):
28 | super(CTitleBar, self).__init__(*args, **kwargs)
29 | self.setupUi()
30 | # 支持设置背景
31 | self.setAttribute(Qt.WA_StyledBackground, True)
32 | self.mPos = None
33 | # 找到父控件(或者自身)
34 | self._root = self.window() # self.parent() or self
35 |
36 | self.labelTitle.setText(title)
37 | # 是否需要隐藏最小化或者最大化按钮
38 | self.showMinimizeButton(self.isMinimizeable())
39 | self.showNormalButton(False)
40 | self.showMaximizeButton(self.isMaximizeable())
41 |
42 | # 绑定信号
43 | self._root.windowTitleChanged.connect(self.setWindowTitle)
44 | self.buttonMinimum.clicked.connect(self.showMinimized)
45 | self.buttonMaximum.clicked.connect(self.showMaximized)
46 | self.buttonNormal.clicked.connect(self.showNormal)
47 | self.buttonClose.clicked.connect(self._root.close)
48 | # 对父控件(或者自身)安装事件过滤器
49 | self._root.installEventFilter(self)
50 |
51 | def showMinimized(self):
52 | self._root.showMinimized()
53 | # 强制取消hover状态
54 | QApplication.sendEvent(self.buttonMinimum, QMouseEvent(
55 | QMouseEvent.Leave, QPointF(), Qt.LeftButton, Qt.NoButton, Qt.NoModifier))
56 |
57 | def showNormal(self):
58 | self._root.showNormal()
59 | # 强制取消hover状态
60 | QApplication.sendEvent(self.buttonMaximum, QMouseEvent(
61 | QMouseEvent.Leave, QPointF(), Qt.LeftButton, Qt.NoButton, Qt.NoModifier))
62 |
63 | def showMaximized(self):
64 | self._root.showMaximized()
65 | # 强制取消hover状态
66 | QApplication.sendEvent(self.buttonNormal, QMouseEvent(
67 | QMouseEvent.Leave, QPointF(), Qt.LeftButton, Qt.NoButton, Qt.NoModifier))
68 |
69 | def isMinimizeable(self):
70 | """是否可以最小化
71 | """
72 | return self.testWindowFlags(Qt.WindowMinimizeButtonHint)
73 |
74 | def isMaximizeable(self):
75 | """是否可以最大化
76 | """
77 | return self.testWindowFlags(Qt.WindowMaximizeButtonHint)
78 |
79 | def isResizable(self):
80 | """是否可调整
81 | """
82 | return self._root.minimumSize() != self._root.maximumSize()
83 |
84 | def showMinimizeButton(self, show=True):
85 | """显示隐藏最小化按钮
86 | """
87 | self.buttonMinimum.setVisible(show)
88 | self.widgetMinimum.setVisible(show)
89 |
90 | def showMaximizeButton(self, show=True):
91 | """显示隐藏最大化按钮
92 | """
93 | self.buttonMaximum.setVisible(show)
94 | self.widgetMaximum.setVisible(show)
95 |
96 | def showNormalButton(self, show=True):
97 | """显示隐藏还原按钮
98 | """
99 | self.buttonNormal.setVisible(show)
100 | self.widgetNormal.setVisible(show)
101 |
102 | def showEvent(self, event):
103 | super(CTitleBar, self).showEvent(event)
104 | if not self.isResizable():
105 | self.showMaximizeButton(False)
106 | self.showNormalButton(False)
107 | else:
108 | self.showMaximizeButton(
109 | self.isMaximizeable() and not self._root.isMaximized())
110 | self.showNormalButton(self.isMaximizeable()
111 | and self._root.isMaximized())
112 |
113 | def eventFilter(self, target, event):
114 | if isinstance(event, QWindowStateChangeEvent):
115 | if self._root.isVisible() and not self._root.isMinimized() and \
116 | self.testWindowFlags(Qt.WindowMinMaxButtonsHint):
117 | # 如果当前是最大化则隐藏最大化按钮
118 | maximized = self._root.isMaximized()
119 | self.showMaximizeButton(not maximized)
120 | self.showNormalButton(maximized)
121 | # 修复最大化边距空白问题
122 | if maximized:
123 | self._oldMargins = self._root.layout().getContentsMargins()
124 | self._root.layout().setContentsMargins(0, 0, 0, 0)
125 | else:
126 | if hasattr(self, '_oldMargins'):
127 | self._root.layout().setContentsMargins(*self._oldMargins)
128 | return super(CTitleBar, self).eventFilter(target, event)
129 |
130 | def mouseDoubleClickEvent(self, event):
131 | """双击标题栏最大化
132 | :param event:
133 | """
134 | if not self.isMaximizeable() or not self.isResizable():
135 | # 不能最大化或者不能调整大小
136 | return
137 | if self._root.isMaximized():
138 | self._root.showNormal()
139 | else:
140 | self._root.showMaximized()
141 |
142 | def mousePressEvent(self, event):
143 | """鼠标按下记录坐标
144 | :param event:
145 | """
146 | if event.button() == Qt.LeftButton:
147 | self.mPos = event.pos()
148 |
149 | def mouseReleaseEvent(self, event):
150 | """鼠标释放删除坐标
151 | :param event:
152 | """
153 | self.mPos = None
154 |
155 | def mouseMoveEvent(self, event):
156 | """鼠标移动移动窗口
157 | :param event:
158 | """
159 | if self._root.isMaximized():
160 | # 最大化时不可移动
161 | return
162 | if event.buttons() == Qt.LeftButton and self.mPos:
163 | pos = event.pos() - self.mPos
164 | self._root.move(self._root.pos() + pos)
165 |
166 | def testWindowFlags(self, windowFlags):
167 | """判断当前窗口是否有该flags
168 | :param windowFlags:
169 | """
170 | return bool(self._root.windowFlags() & windowFlags)
171 |
172 | def setWindowTitle(self, title):
173 | """设置标题
174 | :param title:
175 | """
176 | self.labelTitle.setText(title)
177 |
178 | def setupUi(self):
179 | """创建UI
180 | """
181 | self.setMinimumSize(0, self.Radius)
182 | self.setMaximumSize(0xFFFFFF, self.Radius)
183 | layout = QHBoxLayout(self)
184 | layout.setContentsMargins(0, 0, 0, 0)
185 | layout.setSpacing(0)
186 | # 左侧 添加4个对应的空白占位
187 | for name in ('widgetMinimum', 'widgetMaximum', 'widgetNormal', 'widgetClose'):
188 | widget = QWidget(self)
189 | widget.setMinimumSize(self.Radius, self.Radius)
190 | widget.setMaximumSize(self.Radius, self.Radius)
191 | widget.setObjectName('CTitleBar_%s' % name)
192 | setattr(self, name, widget)
193 | layout.addWidget(widget)
194 | layout.addItem(QSpacerItem(
195 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
196 | # 标题
197 | self.labelTitle = QLabel(self, alignment=Qt.AlignCenter)
198 | self.labelTitle.setObjectName('CTitleBar_labelTitle')
199 | layout.addWidget(self.labelTitle)
200 | layout.addItem(QSpacerItem(
201 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
202 | # 最小化,最大化,还原,关闭按钮
203 | for name, text in (('buttonMinimum', '0'), ('buttonMaximum', '1'),
204 | ('buttonNormal', '2'), ('buttonClose', 'r')):
205 | button = QPushButton(text, self, font=QFont('Webdings'))
206 | button.setMinimumSize(self.Radius, self.Radius)
207 | button.setMaximumSize(self.Radius, self.Radius)
208 | button.setObjectName('CTitleBar_%s' % name)
209 | setattr(self, name, button)
210 | layout.addWidget(button)
211 |
--------------------------------------------------------------------------------
/CustomWidgets/README.md:
--------------------------------------------------------------------------------
1 | # Components - 组件
2 |
3 | ## CTitleBar
4 | - [使用方法](../TestCTitleBar.py)
5 | - 
6 |
7 | ## CFramelessWidget
8 | - [使用方法](../TestCFramelessWidget.py)
9 | - 
10 |
11 | ## CColorPicker
12 | - [使用方法](../TestCColorPicker.py)
13 | - 
14 |
15 | ## CPaginationBar
16 | - [使用方法](../TestCPaginationBar.py)
17 | - 
18 |
19 | ## CDrawer
20 | - [使用方法](../TestCDrawer.py)
21 | - 
22 |
23 | ## CAvatar
24 | - [使用方法](../TestCAvatar.py)
25 | - 
26 |
27 | ## CLoadingBar
28 | - [使用方法](../TestCLoadingBar.py)
29 | - 
30 |
31 | ## CCountUp
32 | - [使用方法](../TestCCountUp.py)
33 | - 
--------------------------------------------------------------------------------
/CustomWidgets/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/CustomWidgets/__init__.py
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Components - 组件
2 |
3 | [](https://codebeat.co/projects/github-com-pyqt5-customwidgets-master)
4 |
5 | PyQt Custom Widgets - PyQt 自定义控件
6 |
7 | # 目录
8 | - [CTitleBar 自定义标题栏](CustomWidgets/#CTitleBar)
9 | - [CTitleBar 无边框窗口](CustomWidgets/#CFramelessWidget)
10 | - [CColorPicker 颜色选择对话框](CustomWidgets/#CColorPicker)
11 | - [CPaginationBar 分页控件](CustomWidgets/#CPaginationBar)
12 | - [CDrawer 侧边栏抽屉控件](CustomWidgets/#CDrawer)
13 | - [CAvatar 头像控件](CustomWidgets/#CAvatar)
14 | - [CLoadingBar 加载条](CustomWidgets/#CLoadingBar)
15 | - [CCountUp 数字动画](CustomWidgets/#CCountUp)
16 |
17 | ## QQ群
18 |
19 | [PyQt 学习](https://jq.qq.com/?_wv=1027&k=5QVVEdF)
20 |
21 | ## 博客
22 |
23 | [](https://pyqt5.com)
24 |
25 | https://pyqt5.com
26 |
--------------------------------------------------------------------------------
/ScreenShot/CAvatar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CAvatar.gif
--------------------------------------------------------------------------------
/ScreenShot/CColorPicker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CColorPicker.gif
--------------------------------------------------------------------------------
/ScreenShot/CCountUp.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CCountUp.gif
--------------------------------------------------------------------------------
/ScreenShot/CDrawer.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CDrawer.gif
--------------------------------------------------------------------------------
/ScreenShot/CFramelessWidget.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CFramelessWidget.gif
--------------------------------------------------------------------------------
/ScreenShot/CLoadingBar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CLoadingBar.gif
--------------------------------------------------------------------------------
/ScreenShot/CPaginationBar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CPaginationBar.gif
--------------------------------------------------------------------------------
/ScreenShot/CTitleBar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/ScreenShot/CTitleBar.gif
--------------------------------------------------------------------------------
/TestCAvatar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月26日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCAvatar
10 | @description:
11 | """
12 | from PyQt5.QtCore import QSize
13 | from PyQt5.QtWidgets import QWidget, QHBoxLayout
14 |
15 | from CustomWidgets.CAvatar import CAvatar
16 |
17 |
18 | __Author__ = 'Irony'
19 | __Copyright__ = 'Copyright (c) 2019 Irony'
20 | __Version__ = 1.0
21 |
22 |
23 | class Window(QWidget):
24 |
25 | def __init__(self, *args, **kwargs):
26 | super(Window, self).__init__(*args, **kwargs)
27 | layout = QHBoxLayout(self)
28 | # 没有头像
29 | layout.addWidget(CAvatar(self))
30 | # 路径错误
31 | layout.addWidget(CAvatar(self, url='test.jpg'))
32 | # 本地三种尺寸头像
33 | layout.addWidget(CAvatar(
34 | self, shape=CAvatar.Circle, url='TestData/example-1.jpg', size=CAvatar.SizeSmall))
35 | layout.addWidget(CAvatar(
36 | self, shape=CAvatar.Circle, url='TestData/example-2.jpg', animation=True))
37 | layout.addWidget(CAvatar(
38 | self, shape=CAvatar.Rectangle, url='TestData/example-3.jpg', size=QSize(48, 48)))
39 |
40 | # 本地gif, 不支持animation
41 | layout.addWidget(CAvatar(
42 | self, shape=CAvatar.Circle, url='TestData/example-1.gif'))
43 |
44 | # 网络头像
45 | layout.addWidget(CAvatar(
46 | self, shape=CAvatar.Rectangle,
47 | url='https://www.thiswaifudoesnotexist.net/example-1000.jpg', size=CAvatar.SizeSmall))
48 | layout.addWidget(CAvatar(
49 | self, shape=CAvatar.Circle, animation=True,
50 | url='https://www.thiswaifudoesnotexist.net/example-1001.jpg'))
51 | # 假装路径错误
52 | layout.addWidget(CAvatar(
53 | self, shape=CAvatar.Rectangle,
54 | url='https://www.thiswaifudoesnotexist.net/example.jpg'))
55 | # 加载网络gif
56 | layout.addWidget(CAvatar(
57 | self, shape=CAvatar.Rectangle, size=CAvatar.SizeLarge,
58 | url='https://n.sinaimg.cn/tech/transform/761/w404h357/20190923/d71b-iewtena6804980.gif'))
59 |
60 |
61 | if __name__ == '__main__':
62 | import sys
63 | import cgitb
64 | sys.excepthook = cgitb.enable(1, None, 5, '')
65 | from PyQt5.QtWidgets import QApplication
66 | app = QApplication(sys.argv)
67 | w = Window()
68 | w.show()
69 | sys.exit(app.exec_())
70 |
--------------------------------------------------------------------------------
/TestCColorPicker.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年4月25日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCColorPicker
10 | @description:
11 | """
12 | __Author__ = 'Irony'
13 | __Copyright__ = 'Copyright (c) 2019'
14 |
15 | import cgitb
16 | import sys
17 |
18 | from PyQt5.QtCore import Qt
19 | from PyQt5.QtWidgets import QApplication, QLabel, QDialog, QWidget, QVBoxLayout,\
20 | QPushButton
21 |
22 | from CustomWidgets.CColorPicker.CColorPicker import CColorPicker
23 |
24 |
25 | sys.excepthook = cgitb.enable(1, None, 5, '')
26 | app = QApplication(sys.argv)
27 |
28 |
29 | def getColor():
30 | ret, color = CColorPicker.getColor()
31 | if ret == QDialog.Accepted:
32 | r, g, b, a = color.red(), color.green(), color.blue(), color.alpha()
33 | label.setText('color: rgba(%d, %d, %d, %d)' % (r, g, b, a))
34 | label.setStyleSheet(
35 | 'background: rgba(%d, %d, %d, %d);' % (r, g, b, a))
36 |
37 |
38 | window = QWidget()
39 | window.resize(200, 200)
40 | layout = QVBoxLayout(window)
41 | label = QLabel('', window, alignment=Qt.AlignCenter)
42 | button = QPushButton('点击选择颜色', window, clicked=getColor)
43 | layout.addWidget(label)
44 | layout.addWidget(button)
45 | window.show()
46 |
47 | sys.exit(app.exec_())
48 |
--------------------------------------------------------------------------------
/TestCCountUp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月31日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCCountUp
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout,\
14 | QPushButton
15 |
16 | from CustomWidgets.CCountUp import CCountUp
17 |
18 |
19 | __Author__ = 'Irony'
20 | __Copyright__ = 'Copyright (c) 2019 Irony'
21 | __Version__ = 1.0
22 |
23 | Style = """
24 | QLabel, QPushButton {
25 | font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,"\5FAE\8F6F\96C5\9ED1",Arial,sans-serif;
26 | font-size: 14px;
27 | }
28 | QPushButton {
29 | font-size: 12px;
30 | }
31 | CCountUp {
32 | font-size: 24px;
33 | }
34 | """
35 |
36 |
37 | class Window(QWidget):
38 |
39 | def __init__(self, *args, **kwargs):
40 | super(Window, self).__init__(*args, **kwargs)
41 | self.setStyleSheet(Style)
42 | layout = QVBoxLayout(self)
43 | layout.addWidget(QLabel('目标值:1234,持续时间:6秒', self))
44 | self.countLabel = CCountUp(self)
45 | self.countLabel.setAlignment(Qt.AlignCenter)
46 | self.countLabel.setMinimumSize(100, 100)
47 | self.countLabel.setDuration(6000) # 动画时间 6 秒
48 | layout.addWidget(self.countLabel)
49 |
50 | cw = QWidget(self)
51 | layout.addWidget(cw)
52 | layoutc = QHBoxLayout(cw)
53 | layoutc.addWidget(QPushButton(
54 | '开始', cw, clicked=lambda: self.countLabel.setNum(1234)))
55 | layoutc.addWidget(QPushButton('重置', cw, clicked=self.countLabel.reset))
56 | layoutc.addWidget(QPushButton('暂停/继续', cw, clicked=self.doContinue))
57 |
58 | layoutc.addWidget(QPushButton(
59 | '开始负小数-1234.00', cw, clicked=lambda: self.countLabel.setNum(-1234.00)))
60 |
61 | def doContinue(self):
62 | if self.countLabel.isPaused():
63 | self.countLabel.resume()
64 | else:
65 | self.countLabel.pause()
66 |
67 |
68 | if __name__ == '__main__':
69 | import sys
70 | from PyQt5.QtWidgets import QApplication
71 | app = QApplication(sys.argv)
72 | w = Window()
73 | w.show()
74 | sys.exit(app.exec_())
75 |
--------------------------------------------------------------------------------
/TestCDrawer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月24日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCDrawer
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtWidgets import QWidget, QGridLayout, QPushButton, QVBoxLayout,\
14 | QLineEdit
15 |
16 | from CustomWidgets.CDrawer import CDrawer
17 |
18 |
19 | __Author__ = 'Irony'
20 | __Copyright__ = 'Copyright (c) 2019'
21 |
22 |
23 | class DrawerWidget(QWidget):
24 |
25 | def __init__(self, *args, **kwargs):
26 | super(DrawerWidget, self).__init__(*args, **kwargs)
27 | self.setAttribute(Qt.WA_StyledBackground, True)
28 | self.setStyleSheet('DrawerWidget{background:white;}')
29 | layout = QVBoxLayout(self)
30 | layout.addWidget(QLineEdit(self))
31 | layout.addWidget(QPushButton('button', self))
32 |
33 |
34 | class Window(QWidget):
35 |
36 | def __init__(self, *args, **kwargs):
37 | super(Window, self).__init__(*args, **kwargs)
38 | self.resize(400, 300)
39 | layout = QGridLayout(self)
40 | layout.addWidget(QPushButton('上', self, clicked=self.doOpenTop), 0, 1)
41 | layout.addWidget(QPushButton('左', self, clicked=self.doOpenLeft), 1, 0)
42 | layout.addWidget(QPushButton(
43 | '右', self, clicked=self.doOpenRight), 1, 2)
44 | layout.addWidget(QPushButton(
45 | '下', self, clicked=self.doOpenBottom), 2, 1)
46 |
47 | def doOpenTop(self):
48 | if not hasattr(self, 'topDrawer'):
49 | self.topDrawer = CDrawer(self, stretch=0.5, direction=CDrawer.TOP)
50 | self.topDrawer.setWidget(DrawerWidget(self.topDrawer))
51 | self.topDrawer.show()
52 |
53 | def doOpenLeft(self):
54 | if not hasattr(self, 'leftDrawer'):
55 | self.leftDrawer = CDrawer(self, direction=CDrawer.LEFT)
56 | self.leftDrawer.setWidget(DrawerWidget(self.leftDrawer))
57 | self.leftDrawer.show()
58 |
59 | def doOpenRight(self):
60 | if not hasattr(self, 'rightDrawer'):
61 | self.rightDrawer = CDrawer(self, widget=DrawerWidget())
62 | self.rightDrawer.setDirection(CDrawer.RIGHT)
63 | self.rightDrawer.show()
64 |
65 | def doOpenBottom(self):
66 | if not hasattr(self, 'bottomDrawer'):
67 | self.bottomDrawer = CDrawer(
68 | self, direction=CDrawer.BOTTOM, widget=DrawerWidget())
69 | self.bottomDrawer.setStretch(0.5)
70 | self.bottomDrawer.show()
71 |
72 |
73 | if __name__ == '__main__':
74 | import sys
75 | import cgitb
76 | sys.excepthook = cgitb.enable(1, None, 5, '')
77 | from PyQt5.QtWidgets import QApplication
78 | app = QApplication(sys.argv)
79 | w = Window()
80 | w.show()
81 | sys.exit(app.exec_())
82 |
--------------------------------------------------------------------------------
/TestCFontIcon.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月28日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/PyQt5
8 | @email: 892768447@qq.com
9 | @file: TestCFontIcon
10 | @description:
11 | """
12 | import json
13 |
14 | from PyQt5.QtCore import Qt, QSortFilterProxyModel, QSize
15 | from PyQt5.QtGui import QStandardItemModel, QStandardItem, QIcon
16 | from PyQt5.QtWidgets import QTabWidget, QWidget, QVBoxLayout, QLineEdit, QListView,\
17 | QMainWindow, QStatusBar, QToolButton, QGridLayout, QLabel, QPushButton
18 |
19 | from CustomWidgets.CFontIcon.CFontIcon import CIconLoader, CIconAnimationSpin
20 |
21 |
22 | __Author__ = 'Irony'
23 | __Copyright__ = 'Copyright (c) 2019'
24 | __Version__ = 'Version 1.0'
25 |
26 |
27 | class FontViewWidget(QWidget):
28 |
29 | def __init__(self, statusBar, config, *args, **kwargs):
30 | super(FontViewWidget, self).__init__(*args, **kwargs)
31 | self.statusBar = statusBar
32 | layout = QVBoxLayout(self)
33 | self.retButton = QToolButton(self)
34 | self.retButton.setIconSize(QSize(60, 60))
35 | self.retButton.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
36 | self.retButton.setMinimumSize(200, 100)
37 | self.retButton.setMaximumSize(200, 100)
38 | layout.addWidget(self.retButton)
39 | # 过滤输入框
40 | layout.addWidget(
41 | QLineEdit(self, textChanged=self.doFilter, placeholderText='过滤...'))
42 | # Material Font
43 | self.listView = QListView(self)
44 | self.listView.setMouseTracking(True)
45 | self.listView.setViewMode(QListView.IconMode)
46 | self.listView.setMovement(QListView.Static)
47 | self.listView.setFlow(QListView.LeftToRight)
48 | self.listView.setWrapping(True)
49 | self.listView.setEditTriggers(QListView.NoEditTriggers)
50 | self.listView.setResizeMode(QListView.Adjust)
51 | self.listView.doubleClicked.connect(self.onDoubleClicked)
52 | self.listView.entered.connect(self.onEntered)
53 | self.dmodel = QStandardItemModel(self.listView)
54 | self.fmodel = QSortFilterProxyModel(self.listView)
55 | self.fmodel.setSourceModel(self.dmodel)
56 | self.fmodel.setFilterRole(Qt.ToolTipRole)
57 | self.listView.setModel(self.fmodel)
58 | layout.addWidget(self.listView)
59 |
60 | # 字体加载器
61 | loader = config[0]
62 |
63 | # 添加Item
64 | fontMap = json.loads(open(config[1], 'rb').read().decode(
65 | encoding='utf_8', errors='ignore'), encoding='utf_8')
66 | for name, _ in fontMap.items():
67 | item = QStandardItem(loader.icon(name), '')
68 | item.setData(name, Qt.ToolTipRole)
69 | item.setData(name, Qt.StatusTipRole)
70 | item.setTextAlignment(Qt.AlignCenter)
71 | item.setFlags(item.flags())
72 | self.dmodel.appendRow(item)
73 |
74 | def doFilter(self, _):
75 | self.fmodel.setFilterRegExp(self.sender().text())
76 |
77 | def onEntered(self, index):
78 | index = self.fmodel.mapToSource(index)
79 | text = index.data(Qt.ToolTipRole)
80 | if text:
81 | self.retButton.setText(text)
82 | self.retButton.setIcon(self.dmodel.itemFromIndex(index).icon())
83 |
84 | def onDoubleClicked(self, index):
85 | index = self.fmodel.mapToSource(index)
86 | text = index.data(Qt.ToolTipRole)
87 | if text:
88 | QApplication.clipboard().setText(text)
89 | self.statusBar.showMessage('已复制: %s' % text)
90 |
91 |
92 | class ButtonsWidget(QWidget):
93 |
94 | def __init__(self, *args, **kwargs):
95 | super(ButtonsWidget, self).__init__(*args, **kwargs)
96 | layout = QGridLayout(self)
97 | loader = CIconLoader.fontMaterial()
98 |
99 | # 创建一个多态的icon
100 | icon = loader.icon('mdi-qqchat')
101 | icon.add('mdi-access-point', Qt.red, QIcon.Normal, QIcon.On)
102 |
103 | icon.add('mdi-camera-metering-matrix',
104 | Qt.green, QIcon.Disabled, QIcon.Off)
105 | icon.add('mdi-file-document-box-check',
106 | Qt.blue, QIcon.Disabled, QIcon.On)
107 |
108 | icon.add('mdi-magnify-minus', Qt.cyan, QIcon.Active, QIcon.Off)
109 | icon.add('mdi-account', Qt.magenta, QIcon.Active, QIcon.On)
110 |
111 | icon.add('mdi-camera-off', Qt.yellow, QIcon.Selected, QIcon.Off)
112 | icon.add('mdi-set-center', Qt.white, QIcon.Selected, QIcon.On)
113 |
114 | layout.addWidget(QLabel('Normal', self), 0, 0)
115 | layout.addWidget(QPushButton(self, icon=icon, text=loader.value(
116 | 'mdi-qqchat'), font=loader.font, iconSize=QSize(36, 36)), 0, 1)
117 |
118 | layout.addWidget(QLabel('Disabled', self), 1, 0)
119 | layout.addWidget(QPushButton(self, icon=icon, text=loader.value(
120 | 'mdi-qqchat'), enabled=False, font=loader.font, iconSize=QSize(48, 48)), 1, 1)
121 |
122 | layout.addWidget(QLabel('Active', self), 2, 0)
123 | layout.addWidget(QPushButton(self, icon=icon, text=loader.value(
124 | 'mdi-qqchat'), font=loader.font, iconSize=QSize(64, 64)), 2, 1)
125 |
126 | layout.addWidget(QLabel('Selected', self), 3, 0)
127 | layout.addWidget(QPushButton(self, icon=icon, text=loader.value(
128 | 'mdi-qqchat'), font=loader.font, checkable=True, checked=True), 3, 1)
129 |
130 | # 旋转动画
131 | aniButton = QPushButton(self, iconSize=QSize(48, 48))
132 | loader = CIconLoader.fontAwesome()
133 | icon = loader.icon(
134 | 'fa-spinner', animation=CIconAnimationSpin(aniButton, 10, 4))
135 | aniButton.setIcon(icon)
136 | layout.addWidget(QLabel('动画', self), 4, 0)
137 | layout.addWidget(aniButton, 4, 1)
138 |
139 |
140 | class Window(QMainWindow):
141 |
142 | def __init__(self, *args, **kwargs):
143 | super(Window, self).__init__(*args, **kwargs)
144 | self.resize(800, 600)
145 | self.tabWidget = QTabWidget(self)
146 | self.setCentralWidget(self.tabWidget)
147 | self.setStatusBar(QStatusBar(self))
148 | self.statusBar().showMessage('双击复制')
149 | self.tabWidget.addTab(FontViewWidget(
150 | self.statusBar(),
151 | [
152 | CIconLoader.fontMaterial(),
153 | 'CustomWidgets/CFontIcon/Fonts/materialdesignicons-webfont.json'
154 | ],
155 | self.tabWidget), 'Material Font')
156 | self.tabWidget.addTab(FontViewWidget(
157 | self.statusBar(),
158 | [
159 | CIconLoader.fontAwesome(),
160 | 'CustomWidgets/CFontIcon/Fonts/fontawesome-webfont.json'
161 | ],
162 | self.tabWidget), 'Awesome Font')
163 | self.tabWidget.addTab(ButtonsWidget(self.tabWidget), '按钮状态')
164 |
165 |
166 | if __name__ == '__main__':
167 | import sys
168 | import cgitb
169 | sys.excepthook = cgitb.enable(1, None, 5, '')
170 | from PyQt5.QtWidgets import QApplication
171 | app = QApplication(sys.argv)
172 | w = Window()
173 | w.show()
174 | sys.exit(app.exec_())
175 |
--------------------------------------------------------------------------------
/TestCFramelessWidget.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月16日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCFramelessWidget
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLineEdit
14 |
15 | from CustomWidgets.CFramelessWidget import CFramelessWidget, CFramelessDialog
16 | from CustomWidgets.CTitleBar import CTitleBar
17 |
18 |
19 | __Author__ = 'Irony'
20 | __Copyright__ = 'Copyright (c) 2019'
21 |
22 |
23 | class TestCFramelessBase:
24 |
25 | def __init__(self, *args, **kwargs):
26 | super(TestCFramelessBase, self).__init__(*args, **kwargs)
27 | self.resize(500, 400)
28 | layout = QVBoxLayout(self)
29 | layout.setSpacing(0)
30 | # 添加自定义标题栏
31 | layout.addWidget(CTitleBar(self, title='CTitleBar'))
32 | layout.addWidget(QLineEdit('输入框', self))
33 | # 底部空白占位
34 | layout.addWidget(
35 | QWidget(self, objectName='bottomWidget', cursor=Qt.PointingHandCursor))
36 |
37 |
38 | class TestCFramelessWidget(CFramelessWidget, TestCFramelessBase):
39 | pass
40 |
41 |
42 | class TestCFramelessDialog(CFramelessDialog, TestCFramelessBase):
43 | pass
44 |
45 |
46 | # 标题栏样式
47 | Style = """
48 | /*标题栏颜色*/
49 | CTitleBar {
50 | background: rgb(65, 148, 216);
51 | }
52 |
53 | /*标题栏圆角*/
54 | CTitleBar {
55 | border-top-right-radius: 5px;
56 | border-top-left-radius: 5px;
57 | }
58 |
59 | #CTitleBar_buttonClose {
60 | /*需要把右侧的关闭按钮考虑进去*/
61 | border-top-right-radius: 5px;
62 | }
63 |
64 | /*底部圆角和背景*/
65 | #bottomWidget {
66 | background: white;
67 | border-bottom-right-radius: 5px;
68 | border-bottom-left-radius: 5px;
69 | }
70 |
71 | /*最小化、最大化、还原按钮*/
72 | CTitleBar > QPushButton {
73 | background: transparent;
74 | }
75 | CTitleBar > QPushButton:hover {
76 | background: rgba(0, 0, 0, 30);
77 | }
78 | CTitleBar > QPushButton:pressed {
79 | background: rgba(0, 0, 0, 60);
80 | }
81 |
82 | /*关闭按钮*/
83 | #CTitleBar_buttonClose:hover {
84 | color: white;
85 | background: rgb(232, 17, 35);
86 | }
87 | #CTitleBar_buttonClose:pressed {
88 | color: white;
89 | background: rgb(165, 69, 106);
90 | }
91 | """
92 |
93 | if __name__ == '__main__':
94 | import sys
95 | import cgitb
96 | sys.excepthook = cgitb.enable(1, None, 5, '')
97 | from PyQt5.QtWidgets import QApplication
98 | app = QApplication(sys.argv)
99 | app.setStyleSheet(Style)
100 | w = TestCFramelessWidget()
101 | w.show()
102 | # 限制大小
103 | w1 = TestCFramelessWidget()
104 | w1.setMinimumSize(400, 400)
105 | w1.setMaximumSize(400, 400)
106 | w1.show()
107 | w2 = TestCFramelessDialog()
108 | w2.setWindowTitle('模态')
109 | w2.exec_()
110 | sys.exit(app.exec_())
111 |
--------------------------------------------------------------------------------
/TestCLoadingBar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月30日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCLoadingBar
10 | @description:
11 | """
12 |
13 | from PyQt5.QtCore import QTimeLine
14 | from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton
15 |
16 | from CustomWidgets.CLoadingBar import CLoadingBar
17 |
18 |
19 | __Author__ = 'Irony'
20 | __Copyright__ = 'Copyright (c) 2019'
21 |
22 |
23 | class Window(QWidget):
24 |
25 | def __init__(self, *args, **kwargs):
26 | super(Window, self).__init__(*args, **kwargs)
27 | self.resize(600, 200)
28 | layout = QVBoxLayout(self)
29 |
30 | # 配置全局属性(也可以通过start方法里的参数配置单独的属性)
31 | CLoadingBar.config(
32 | height=2, direction=CLoadingBar.TOP,
33 | color='#2d8cf0', failedColor='#ed4014')
34 |
35 | # 子控件顶部进度
36 | self.widget1 = QWidget(self)
37 | layout.addWidget(self.widget1)
38 | CLoadingBar.start(self.widget1, color='#19be6b', failedColor='#ff9900')
39 |
40 | widget = QWidget(self)
41 | layoutc = QHBoxLayout(widget)
42 | layoutc.addWidget(QPushButton('开始', self, clicked=self.doStart))
43 | layoutc.addWidget(QPushButton('结束', self, clicked=self.doFinish))
44 | layoutc.addWidget(QPushButton('错误', self, clicked=self.doError))
45 | layout.addWidget(widget)
46 |
47 | # 子控件底部进度
48 | self.widget2 = QWidget(self)
49 | layout.addWidget(self.widget2)
50 | CLoadingBar.start(self.widget2, direction=CLoadingBar.BOTTOM, height=6)
51 |
52 | # 模拟进度
53 | self.updateTimer = QTimeLine(
54 | 10000, self, frameChanged=self.doUpdateProgress)
55 | self.updateTimer.setFrameRange(0, 100)
56 | # 设置数字变化曲线模拟进度的不规则变化
57 | self.updateTimer.setCurveShape(QTimeLine.EaseInOutCurve)
58 |
59 | def doStart(self):
60 | """模拟开始
61 | """
62 | self.updateTimer.stop()
63 | self.updateTimer.start()
64 |
65 | def doFinish(self):
66 | """模拟结束
67 | """
68 | self.updateTimer.stop()
69 | CLoadingBar.finish(self.widget1)
70 | CLoadingBar.finish(self.widget2)
71 |
72 | def doError(self):
73 | """模拟出错
74 | """
75 | self.updateTimer.stop()
76 | CLoadingBar.error(self.widget1)
77 | CLoadingBar.error(self.widget2)
78 |
79 | def doUpdateProgress(self, value):
80 | """模拟进度值变化
81 | :param value:
82 | """
83 | CLoadingBar.update(self.widget1, value)
84 | CLoadingBar.update(self.widget2, value)
85 | if value == 100:
86 | self.updateTimer.stop()
87 | self.doFinish()
88 |
89 |
90 | if __name__ == '__main__':
91 | import sys
92 | import cgitb
93 | sys.excepthook = cgitb.enable(1, None, 5, '')
94 | from PyQt5.QtWidgets import QApplication
95 | app = QApplication(sys.argv)
96 | w = Window()
97 | w.show()
98 | sys.exit(app.exec_())
99 |
--------------------------------------------------------------------------------
/TestCPaginationBar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月23日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCpaginationBar1
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtGui import QIntValidator
14 | from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSpacerItem,\
15 | QSizePolicy, QLineEdit, QPushButton
16 |
17 | from CustomWidgets.CPaginationBar import CPaginationBar, FlatStyle, Style
18 |
19 |
20 | __Author__ = 'Irony'
21 | __Copyright__ = 'Copyright (c) 2019 Irony'
22 | __Version__ = 1.0
23 |
24 |
25 | class Window(QWidget):
26 |
27 | def __init__(self, *args, **kwargs):
28 | super(Window, self).__init__(*args, **kwargs)
29 | layout = QVBoxLayout(self)
30 |
31 | # 测试1
32 | self.pageLabel1 = QLabel('当前页: 1', self, alignment=Qt.AlignCenter)
33 | layout.addWidget(self.pageLabel1)
34 | # 分页控件
35 | self.paginationBar1 = CPaginationBar(self, totalPages=20)
36 | # 设置扁平样式
37 | self.paginationBar1.setStyleSheet(FlatStyle)
38 | self.paginationBar1.pageChanged.connect(
39 | lambda page: self.pageLabel1.setText('当前页: %d' % page))
40 | layout.addWidget(self.paginationBar1)
41 | layout.addItem(QSpacerItem(
42 | 20, 40, QSizePolicy.Minimum, QSizePolicy.Minimum))
43 |
44 | # 测试2
45 | self.pageLabel2 = QLabel('当前页: 1', self, alignment=Qt.AlignCenter)
46 | layout.addWidget(self.pageLabel2)
47 | # 分页控件
48 | self.paginationBar2 = CPaginationBar(self, totalPages=20)
49 | # 设置普通样式
50 | self.paginationBar2.setStyleSheet(Style)
51 | self.paginationBar2.pageChanged.connect(
52 | lambda page: self.pageLabel2.setText('当前页: %d' % page))
53 | layout.addWidget(self.paginationBar2)
54 | layout.addItem(QSpacerItem(
55 | 20, 40, QSizePolicy.Minimum, QSizePolicy.Minimum))
56 |
57 | # 测试3
58 | self.pageLabel3 = QLabel('当前页: 1', self, alignment=Qt.AlignCenter)
59 | layout.addWidget(self.pageLabel3)
60 | # 分页控件
61 | self.paginationBar3 = CPaginationBar(self, totalPages=20)
62 | # 设置信息
63 | self.paginationBar3.setInfos('共 400 条')
64 | # 设置扁平样式
65 | self.paginationBar3.setStyleSheet(FlatStyle)
66 | self.paginationBar3.pageChanged.connect(
67 | lambda page: self.pageLabel3.setText('当前页: %d' % page))
68 | layout.addWidget(self.paginationBar3)
69 | layout.addItem(QSpacerItem(
70 | 20, 40, QSizePolicy.Minimum, QSizePolicy.Minimum))
71 |
72 | # 测试4
73 | self.pageLabel4 = QLabel('当前页: 1', self, alignment=Qt.AlignCenter)
74 | layout.addWidget(self.pageLabel4)
75 | # 分页控件
76 | self.paginationBar4 = CPaginationBar(self, totalPages=20)
77 | # 设置信息
78 | self.paginationBar4.setInfos('共 400 条')
79 | # 开启跳转功能
80 | self.paginationBar4.setJumpWidget(True)
81 | # 设置普通样式
82 | self.paginationBar4.setStyleSheet(Style)
83 | self.paginationBar4.pageChanged.connect(
84 | lambda page: self.pageLabel4.setText('当前页: %d' % page))
85 | layout.addWidget(self.paginationBar4)
86 | layout.addItem(QSpacerItem(
87 | 40, 40, QSizePolicy.Minimum, QSizePolicy.Minimum))
88 |
89 | self.pageEdit = QLineEdit(self)
90 | self.pageEdit.setValidator(QIntValidator(self.pageEdit))
91 | self.pageEdit.setPlaceholderText('输入总页数')
92 | layout.addWidget(self.pageEdit)
93 | self.setButton = QPushButton(
94 | '设置总页数', self, clicked=self.doSetPageNumber)
95 | layout.addWidget(self.setButton)
96 |
97 | def doSetPageNumber(self):
98 | page = self.pageEdit.text().strip()
99 | if not page:
100 | return
101 | page = int(page)
102 | self.paginationBar1.setTotalPages(page)
103 | self.paginationBar2.setTotalPages(page)
104 | self.paginationBar3.setTotalPages(page)
105 | self.paginationBar4.setTotalPages(page)
106 |
107 |
108 | if __name__ == '__main__':
109 | import sys
110 | import cgitb
111 | sys.excepthook = cgitb.enable(1, None, 5, '')
112 | from PyQt5.QtWidgets import QApplication
113 | app = QApplication(sys.argv)
114 | w = Window()
115 | w.show()
116 | sys.exit(app.exec_())
117 |
--------------------------------------------------------------------------------
/TestCSlider.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月24日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCSlider
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
14 |
15 | from CustomWidgets.CSlider import CSlider
16 |
17 |
18 | __Author__ = 'Irony'
19 | __Copyright__ = 'Copyright (c) 2019 Irony'
20 | __Version__ = 1.0
21 |
22 |
23 | class Window(QWidget):
24 |
25 | def __init__(self, *args, **kwargs):
26 | super(Window, self).__init__(*args, **kwargs)
27 | self.setAttribute(Qt.WA_StyledBackground, True)
28 | self.setStyleSheet('Window{background:gray;}')
29 | layout = QVBoxLayout(self)
30 | self.labelValue = QLabel(self)
31 | layout.addWidget(CSlider(
32 | Qt.Vertical, self, valueChanged=lambda v: self.labelValue.setText(str(v))))
33 | layout.addWidget(CSlider(
34 | Qt.Horizontal, self, valueChanged=lambda v: self.labelValue.setText(str(v))))
35 | layout.addWidget(self.labelValue)
36 |
37 |
38 | if __name__ == '__main__':
39 | import sys
40 | from PyQt5.QtWidgets import QApplication
41 | app = QApplication(sys.argv)
42 | w = Window()
43 | w.show()
44 | sys.exit(app.exec_())
45 |
--------------------------------------------------------------------------------
/TestCTitleBar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Created on 2019年7月15日
6 | @author: Irony
7 | @site: https://pyqt5.com https://github.com/892768447
8 | @email: 892768447@qq.com
9 | @file: TestCTitleBar
10 | @description:
11 | """
12 | from PyQt5.QtCore import Qt
13 | from PyQt5.QtWidgets import QWidget, QVBoxLayout, QDialog
14 |
15 | from CustomWidgets.CTitleBar import CTitleBar
16 |
17 |
18 | __Author__ = 'Irony'
19 | __Copyright__ = 'Copyright (c) 2019'
20 |
21 |
22 | class TestCTitleBarBase:
23 |
24 | def __init__(self, *args, **kwargs):
25 | super(TestCTitleBarBase, self).__init__(*args, **kwargs)
26 | self.resize(500, 400)
27 | # 设置背景透明
28 | self.setAttribute(Qt.WA_TranslucentBackground, True)
29 | # 设置无边框
30 | self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
31 | layout = QVBoxLayout(self)
32 | layout.setSpacing(0)
33 | # 添加自定义标题栏
34 | layout.addWidget(CTitleBar(self, title='CTitleBar'))
35 | # 底部空白占位
36 | layout.addWidget(QWidget(self, objectName='bottomWidget'))
37 |
38 |
39 | class TestCTitleBarWidget(QWidget, TestCTitleBarBase):
40 | pass
41 |
42 |
43 | class TestCTitleBarDialog(QDialog, TestCTitleBarBase):
44 | pass
45 |
46 |
47 | # 标题栏样式
48 | Style = """
49 | /*标题栏颜色*/
50 | CTitleBar {
51 | background: rgb(65, 148, 216);
52 | }
53 |
54 | /*标题栏圆角*/
55 | CTitleBar {
56 | border-top-right-radius: 10px;
57 | border-top-left-radius: 10px;
58 | }
59 |
60 | #CTitleBar_buttonClose {
61 | /*需要把右侧的关闭按钮考虑进去*/
62 | border-top-right-radius: 10px;
63 | }
64 |
65 | /*底部圆角和背景*/
66 | #bottomWidget {
67 | background: white;
68 | border-bottom-right-radius: 10px;
69 | border-bottom-left-radius: 10px;
70 | }
71 |
72 | /*最小化、最大化、还原按钮*/
73 | CTitleBar > QPushButton {
74 | background: transparent;
75 | }
76 | CTitleBar > QPushButton:hover {
77 | background: rgba(0, 0, 0, 30);
78 | }
79 | CTitleBar > QPushButton:pressed {
80 | background: rgba(0, 0, 0, 60);
81 | }
82 |
83 | /*关闭按钮*/
84 | #CTitleBar_buttonClose:hover {
85 | color: white;
86 | background: rgb(232, 17, 35);
87 | }
88 | #CTitleBar_buttonClose:pressed {
89 | color: white;
90 | background: rgb(165, 69, 106);
91 | }
92 | """
93 |
94 | if __name__ == '__main__':
95 | import sys
96 | import cgitb
97 | sys.excepthook = cgitb.enable(1, None, 5, '')
98 | from PyQt5.QtWidgets import QApplication
99 | app = QApplication(sys.argv)
100 | app.setStyleSheet(Style)
101 | w = TestCTitleBarWidget()
102 | w.show()
103 |
104 | # 模态属性
105 | w1 = TestCTitleBarDialog()
106 | w1.setWindowTitle('对话框')
107 | w1.show()
108 |
109 | # 不可调整大小
110 | w2 = TestCTitleBarWidget()
111 | w2.setWindowTitle('不可调整大小')
112 | w2.setMinimumSize(400, 400)
113 | w2.setMaximumSize(400, 400)
114 | w2.show()
115 | sys.exit(app.exec_())
116 |
--------------------------------------------------------------------------------
/TestData/example-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/TestData/example-1.gif
--------------------------------------------------------------------------------
/TestData/example-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/TestData/example-1.jpg
--------------------------------------------------------------------------------
/TestData/example-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/TestData/example-2.jpg
--------------------------------------------------------------------------------
/TestData/example-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PyQt5/CustomWidgets/9018d7e96602b8ada4a6d88030c33c47f8e29680/TestData/example-3.jpg
--------------------------------------------------------------------------------