├── .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 | ![CColorPicker](/ScreenShot/CColorPicker.gif) -------------------------------------------------------------------------------- /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 | - ![CTitleBar](../ScreenShot/CTitleBar.gif) 6 | 7 | ## CFramelessWidget 8 | - [使用方法](../TestCFramelessWidget.py) 9 | - ![CFramelessWidget](../ScreenShot/CFramelessWidget.gif) 10 | 11 | ## CColorPicker 12 | - [使用方法](../TestCColorPicker.py) 13 | - ![CColorPicker](../ScreenShot/CColorPicker.gif) 14 | 15 | ## CPaginationBar 16 | - [使用方法](../TestCPaginationBar.py) 17 | - ![CPaginationBar](../ScreenShot/CPaginationBar.gif) 18 | 19 | ## CDrawer 20 | - [使用方法](../TestCDrawer.py) 21 | - ![CDrawer](../ScreenShot/CDrawer.gif) 22 | 23 | ## CAvatar 24 | - [使用方法](../TestCAvatar.py) 25 | - ![CAvatar](../ScreenShot/CAvatar.gif) 26 | 27 | ## CLoadingBar 28 | - [使用方法](../TestCLoadingBar.py) 29 | - ![CLoadingBar](../ScreenShot/CLoadingBar.gif) 30 | 31 | ## CCountUp 32 | - [使用方法](../TestCCountUp.py) 33 | - ![CCountUp](../ScreenShot/CCountUp.gif) -------------------------------------------------------------------------------- /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 | [![codebeat badge](https://codebeat.co/badges/204bac2f-e1d0-42f4-824e-a28955cf34a5)](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 | [![Blog](https://img.shields.io/badge/blog-pyqt5-green.svg)](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 --------------------------------------------------------------------------------