├── .gitignore ├── LICENSE.md ├── README.md ├── demo.py ├── setup.py └── waitingspinnerwidget.py /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | *.pro.user 16 | *.pro.user.* 17 | moc_*.cpp 18 | qrc_*.cpp 19 | Makefile 20 | *-build-* 21 | 22 | QtWaitingSpinner 23 | QtWaitingSpinner.exe 24 | build-QtWaitingSpinnerTest-Desktop-Debug* 25 | build-QtWaitingSpinnerTest-GCC-Debug* 26 | build-QtWaitingSpinnerTest-Desktop_Qt_5_3_GCC_64bit-Debug* 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2014 Alexander Turkin 4 | Copyright (c) 2014 William Hallatt 5 | Copyright (c) 2015 Jacob Dawid 6 | Copyright (c) 2016 Luca Weiss 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtWaitingSpinner 2 | 3 | QtWaitingSpinner is a highly configurable, custom Qt widget for showing "waiting" or "loading" spinner icons in Qt applications, e.g. the spinners below are all QtWaitingSpinner widgets differing only in their configuration: 4 | 5 | ![waiting spinner](https://github.com/z3ntu/QtWaitingSpinner/blob/gh-pages/waiting-spinners.gif) 6 | 7 | ### Configuration 8 | 9 | The following properties can all be controlled directly through their corresponding setters: 10 | 11 | * Color of the widget 12 | * "Roundness" of the lines 13 | * Speed (rotations per second) 14 | * Number of lines to be drawn 15 | * Line length 16 | * Line width 17 | * Radius of the spinner's "dead space" or inner circle 18 | * The percentage fade of the "trail" 19 | * The minimum opacity of the "trail" 20 | 21 | ### Usage 22 | 23 | Despite being highly configurable, QtWaitingSpinner is extremely easy to use and, to make things even easier, the "QtWaitingSpinnerTest" application can assist you in determining the exact shape, size and color you'd like your spinner to have. 24 | 25 | For example, the embedded spinner in the QtWaitingSpinnerTest screenshot below can be created as follows: 26 | 27 | ```python 28 | from waitingspinnerwidget import QtWaitingSpinner 29 | 30 | spinner = QtWaitingSpinner(self) 31 | 32 | spinner.setRoundness(70.0) 33 | spinner.setMinimumTrailOpacity(15.0) 34 | spinner.setTrailFadePercentage(70.0) 35 | spinner.setNumberOfLines(12) 36 | spinner.setLineLength(10) 37 | spinner.setLineWidth(5) 38 | spinner.setInnerRadius(10) 39 | spinner.setRevolutionsPerSecond(1) 40 | spinner.setColor(QColor(81, 4, 71)) 41 | 42 | spinner.start() 43 | ``` 44 | 45 | A demo is included with the `demo.py` file. 46 | 47 | ![demo dialog](https://github.com/z3ntu/QtWaitingSpinner/blob/gh-pages/test-dialog.png) 48 | 49 | As an alternative example, the code below will create a spinner that (1) blocks all user input to the main application for as long as the spinner is active, (2) automatically centers itself on its parent widget every time "start" is called and (3) makes use of the default shape, size and color settings. 50 | 51 | ```python 52 | spinner = QtWaitingSpinner(self, True, True, Qt.ApplicationModal) 53 | spinner.start() # starts spinning 54 | ``` 55 | 56 | Please use [use this link](https://github.com/z3ntu/QtWaitingSpinner/issues) for feedback, requests or issues. 57 | 58 | Enjoy! 59 | 60 | ### Thanks: 61 | to [@snowwlex](https://github.com/snowwlex) for the base of my port (and this README)! 62 | 63 | QtWaitingSpinner was inspired by the [spin.js](http://fgnass.github.io/spin.js/) project. 64 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2016 Luca Weiss 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | import sys 28 | 29 | from PyQt6.QtCore import * 30 | from PyQt6.QtWidgets import * 31 | 32 | from waitingspinnerwidget import QtWaitingSpinner 33 | 34 | 35 | class Demo(QWidget): 36 | sb_roundness = None 37 | sb_opacity = None 38 | sb_fadeperc = None 39 | sb_lines = None 40 | sb_line_length = None 41 | sb_line_width = None 42 | sb_inner_radius = None 43 | sb_rev_s = None 44 | 45 | btn_start = None 46 | btn_stop = None 47 | btn_pick_color = None 48 | 49 | spinner = None 50 | 51 | def __init__(self): 52 | super().__init__() 53 | self.init_ui() 54 | 55 | def init_ui(self): 56 | grid = QGridLayout() 57 | groupbox1 = QGroupBox() 58 | groupbox1_layout = QHBoxLayout() 59 | groupbox2 = QGroupBox() 60 | groupbox2_layout = QGridLayout() 61 | button_hbox = QHBoxLayout() 62 | self.setLayout(grid) 63 | self.setWindowTitle("QtWaitingSpinner Demo") 64 | self.setWindowFlags(Qt.WindowType.Dialog) 65 | 66 | # SPINNER 67 | self.spinner = QtWaitingSpinner(self) 68 | 69 | # Spinboxes 70 | self.sb_roundness = QDoubleSpinBox() 71 | self.sb_opacity = QDoubleSpinBox() 72 | self.sb_fadeperc = QDoubleSpinBox() 73 | self.sb_lines = QSpinBox() 74 | self.sb_line_length = QDoubleSpinBox() 75 | self.sb_line_width = QDoubleSpinBox() 76 | self.sb_inner_radius = QDoubleSpinBox() 77 | self.sb_rev_s = QDoubleSpinBox() 78 | 79 | # set spinbox default values 80 | self.sb_roundness.setValue(70) 81 | self.sb_roundness.setRange(0, 9999) 82 | self.sb_opacity.setValue(15) 83 | self.sb_opacity.setRange(0, 9999) 84 | self.sb_fadeperc.setValue(70) 85 | self.sb_fadeperc.setRange(0, 9999) 86 | self.sb_lines.setValue(12) 87 | self.sb_lines.setRange(1, 9999) 88 | self.sb_line_length.setValue(10) 89 | self.sb_line_length.setRange(0, 9999) 90 | self.sb_line_width.setValue(5) 91 | self.sb_line_width.setRange(0, 9999) 92 | self.sb_inner_radius.setValue(10) 93 | self.sb_inner_radius.setRange(0, 9999) 94 | self.sb_rev_s.setValue(1) 95 | self.sb_rev_s.setRange(0.1, 9999) 96 | 97 | # Buttons 98 | self.btn_start = QPushButton("Start") 99 | self.btn_stop = QPushButton("Stop") 100 | self.btn_pick_color = QPushButton("Pick Color") 101 | 102 | # Connects 103 | self.sb_roundness.valueChanged.connect(self.set_roundness) 104 | self.sb_opacity.valueChanged.connect(self.set_opacity) 105 | self.sb_fadeperc.valueChanged.connect(self.set_fadeperc) 106 | self.sb_lines.valueChanged.connect(self.set_lines) 107 | self.sb_line_length.valueChanged.connect(self.set_line_length) 108 | self.sb_line_width.valueChanged.connect(self.set_line_width) 109 | self.sb_inner_radius.valueChanged.connect(self.set_inner_radius) 110 | self.sb_rev_s.valueChanged.connect(self.set_rev_s) 111 | 112 | self.btn_start.clicked.connect(self.spinner_start) 113 | self.btn_stop.clicked.connect(self.spinner_stop) 114 | self.btn_pick_color.clicked.connect(self.show_color_picker) 115 | 116 | # Layout adds 117 | groupbox1_layout.addWidget(self.spinner) 118 | groupbox1.setLayout(groupbox1_layout) 119 | 120 | groupbox2_layout.addWidget(QLabel("Roundness:"), *(1, 1)) 121 | groupbox2_layout.addWidget(self.sb_roundness, *(1, 2)) 122 | groupbox2_layout.addWidget(QLabel("Opacity:"), *(2, 1)) 123 | groupbox2_layout.addWidget(self.sb_opacity, *(2, 2)) 124 | groupbox2_layout.addWidget(QLabel("Fade Perc:"), *(3, 1)) 125 | groupbox2_layout.addWidget(self.sb_fadeperc, *(3, 2)) 126 | groupbox2_layout.addWidget(QLabel("Lines:"), *(4, 1)) 127 | groupbox2_layout.addWidget(self.sb_lines, *(4, 2)) 128 | groupbox2_layout.addWidget(QLabel("Line Length:"), *(5, 1)) 129 | groupbox2_layout.addWidget(self.sb_line_length, *(5, 2)) 130 | groupbox2_layout.addWidget(QLabel("Line Width:"), *(6, 1)) 131 | groupbox2_layout.addWidget(self.sb_line_width, *(6, 2)) 132 | groupbox2_layout.addWidget(QLabel("Inner Radius:"), *(7, 1)) 133 | groupbox2_layout.addWidget(self.sb_inner_radius, *(7, 2)) 134 | groupbox2_layout.addWidget(QLabel("Rev/s:"), *(8, 1)) 135 | groupbox2_layout.addWidget(self.sb_rev_s, *(8, 2)) 136 | 137 | groupbox2.setLayout(groupbox2_layout) 138 | 139 | button_hbox.addWidget(self.btn_start) 140 | button_hbox.addWidget(self.btn_stop) 141 | button_hbox.addWidget(self.btn_pick_color) 142 | 143 | grid.addWidget(groupbox1, *(1, 1)) 144 | grid.addWidget(groupbox2, *(1, 2)) 145 | grid.layout().addLayout(button_hbox, *(2, 1)) 146 | 147 | self.spinner.start() 148 | self.show() 149 | 150 | def set_roundness(self): 151 | self.spinner.setRoundness(self.sb_roundness.value()) 152 | 153 | def set_opacity(self): 154 | self.spinner.setMinimumTrailOpacity(self.sb_opacity.value()) 155 | 156 | def set_fadeperc(self): 157 | self.spinner.setTrailFadePercentage(self.sb_fadeperc.value()) 158 | 159 | def set_lines(self): 160 | self.spinner.setNumberOfLines(self.sb_lines.value()) 161 | 162 | def set_line_length(self): 163 | self.spinner.setLineLength(self.sb_line_length.value()) 164 | 165 | def set_line_width(self): 166 | self.spinner.setLineWidth(self.sb_line_width.value()) 167 | 168 | def set_inner_radius(self): 169 | self.spinner.setInnerRadius(self.sb_inner_radius.value()) 170 | 171 | def set_rev_s(self): 172 | self.spinner.setRevolutionsPerSecond(self.sb_rev_s.value()) 173 | 174 | def spinner_start(self): 175 | self.spinner.start() 176 | 177 | def spinner_stop(self): 178 | self.spinner.stop() 179 | 180 | def show_color_picker(self): 181 | self.spinner.setColor(QColorDialog.getColor()) 182 | 183 | 184 | if __name__ == '__main__': 185 | app = QApplication(sys.argv) 186 | main = Demo() 187 | sys.exit(app.exec()) 188 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2016 Luca Weiss 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | from distutils.core import setup 28 | 29 | setup( 30 | name='QtWaitingSpinner', 31 | version='1.0', 32 | packages=[''], 33 | url='https://github.com/z3ntu/QtWaitingSpinner', 34 | license='MIT', 35 | author='Luca Weiss', 36 | author_email='luca@z3ntu.xyz', 37 | description='A waiting spinner for PyQt6', requires=['PyQt6'] 38 | ) 39 | -------------------------------------------------------------------------------- /waitingspinnerwidget.py: -------------------------------------------------------------------------------- 1 | """ 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2012-2014 Alexander Turkin 5 | Copyright (c) 2014 William Hallatt 6 | Copyright (c) 2015 Jacob Dawid 7 | Copyright (c) 2016 Luca Weiss 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | """ 27 | 28 | import math 29 | 30 | from PyQt6.QtCore import Qt, QTimer, QRect 31 | from PyQt6.QtGui import QColor, QPainter 32 | from PyQt6.QtWidgets import QWidget 33 | 34 | 35 | class QtWaitingSpinner(QWidget): 36 | def __init__(self, parent, centerOnParent=True, disableParentWhenSpinning=False, modality=Qt.WindowModality.NonModal): 37 | super().__init__(parent) 38 | 39 | self._centerOnParent = centerOnParent 40 | self._disableParentWhenSpinning = disableParentWhenSpinning 41 | 42 | # WAS IN initialize() 43 | self._color = QColor(Qt.GlobalColor.black) 44 | self._roundness = 100.0 45 | self._minimumTrailOpacity = 3.14159265358979323846 46 | self._trailFadePercentage = 80.0 47 | self._revolutionsPerSecond = 1.57079632679489661923 48 | self._numberOfLines = 20 49 | self._lineLength = 10 50 | self._lineWidth = 2 51 | self._innerRadius = 10 52 | self._currentCounter = 0 53 | self._isSpinning = False 54 | 55 | self._timer = QTimer(self) 56 | self._timer.timeout.connect(self.rotate) 57 | self.updateSize() 58 | self.updateTimer() 59 | self.hide() 60 | # END initialize() 61 | 62 | self.setWindowModality(modality) 63 | self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) 64 | 65 | def paintEvent(self, QPaintEvent): 66 | self.updatePosition() 67 | painter = QPainter(self) 68 | painter.fillRect(self.rect(), Qt.GlobalColor.transparent) 69 | painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) 70 | 71 | if self._currentCounter >= self._numberOfLines: 72 | self._currentCounter = 0 73 | 74 | painter.setPen(Qt.PenStyle.NoPen) 75 | for i in range(0, self._numberOfLines): 76 | painter.save() 77 | painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength) 78 | rotateAngle = float(360 * i) / float(self._numberOfLines) 79 | painter.rotate(rotateAngle) 80 | painter.translate(self._innerRadius, 0) 81 | distance = self.lineCountDistanceFromPrimary(i, self._currentCounter, self._numberOfLines) 82 | color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage, 83 | self._minimumTrailOpacity, self._color) 84 | painter.setBrush(color) 85 | rect = QRect(0, int(-self._lineWidth / 2), int(self._lineLength), int(self._lineWidth)) 86 | painter.drawRoundedRect(rect, self._roundness, self._roundness, Qt.SizeMode.RelativeSize) 87 | painter.restore() 88 | 89 | def start(self): 90 | self.updatePosition() 91 | self._isSpinning = True 92 | self.show() 93 | 94 | if self.parentWidget and self._disableParentWhenSpinning: 95 | self.parentWidget().setEnabled(False) 96 | 97 | if not self._timer.isActive(): 98 | self._timer.start() 99 | self._currentCounter = 0 100 | 101 | def stop(self): 102 | self._isSpinning = False 103 | self.hide() 104 | 105 | if self.parentWidget() and self._disableParentWhenSpinning: 106 | self.parentWidget().setEnabled(True) 107 | 108 | if self._timer.isActive(): 109 | self._timer.stop() 110 | self._currentCounter = 0 111 | 112 | def setNumberOfLines(self, lines): 113 | self._numberOfLines = lines 114 | self._currentCounter = 0 115 | self.updateTimer() 116 | 117 | def setLineLength(self, length): 118 | self._lineLength = length 119 | self.updateSize() 120 | 121 | def setLineWidth(self, width): 122 | self._lineWidth = width 123 | self.updateSize() 124 | 125 | def setInnerRadius(self, radius): 126 | self._innerRadius = radius 127 | self.updateSize() 128 | 129 | def color(self): 130 | return self._color 131 | 132 | def roundness(self): 133 | return self._roundness 134 | 135 | def minimumTrailOpacity(self): 136 | return self._minimumTrailOpacity 137 | 138 | def trailFadePercentage(self): 139 | return self._trailFadePercentage 140 | 141 | def revolutionsPersSecond(self): 142 | return self._revolutionsPerSecond 143 | 144 | def numberOfLines(self): 145 | return self._numberOfLines 146 | 147 | def lineLength(self): 148 | return self._lineLength 149 | 150 | def lineWidth(self): 151 | return self._lineWidth 152 | 153 | def innerRadius(self): 154 | return self._innerRadius 155 | 156 | def isSpinning(self): 157 | return self._isSpinning 158 | 159 | def setRoundness(self, roundness): 160 | self._roundness = max(0.0, min(100.0, roundness)) 161 | 162 | def setColor(self, color=Qt.GlobalColor.black): 163 | self._color = QColor(color) 164 | 165 | def setRevolutionsPerSecond(self, revolutionsPerSecond): 166 | self._revolutionsPerSecond = revolutionsPerSecond 167 | self.updateTimer() 168 | 169 | def setTrailFadePercentage(self, trail): 170 | self._trailFadePercentage = trail 171 | 172 | def setMinimumTrailOpacity(self, minimumTrailOpacity): 173 | self._minimumTrailOpacity = minimumTrailOpacity 174 | 175 | def rotate(self): 176 | self._currentCounter += 1 177 | if self._currentCounter >= self._numberOfLines: 178 | self._currentCounter = 0 179 | self.update() 180 | 181 | def updateSize(self): 182 | size = int((self._innerRadius + self._lineLength) * 2) 183 | self.setFixedSize(size, size) 184 | 185 | def updateTimer(self): 186 | self._timer.setInterval(int(1000 / (self._numberOfLines * self._revolutionsPerSecond))) 187 | 188 | def updatePosition(self): 189 | if self.parentWidget() and self._centerOnParent: 190 | self.move(int(self.parentWidget().width() / 2 - self.width() / 2), 191 | int(self.parentWidget().height() / 2 - self.height() / 2)) 192 | 193 | def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines): 194 | distance = primary - current 195 | if distance < 0: 196 | distance += totalNrOfLines 197 | return distance 198 | 199 | def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, colorinput): 200 | color = QColor(colorinput) 201 | if countDistance == 0: 202 | return color 203 | minAlphaF = minOpacity / 100.0 204 | distanceThreshold = int(math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0)) 205 | if countDistance > distanceThreshold: 206 | color.setAlphaF(minAlphaF) 207 | else: 208 | alphaDiff = color.alphaF() - minAlphaF 209 | gradient = alphaDiff / float(distanceThreshold + 1) 210 | resultAlpha = color.alphaF() - gradient * countDistance 211 | # If alpha is out of bounds, clip it. 212 | resultAlpha = min(1.0, max(0.0, resultAlpha)) 213 | color.setAlphaF(resultAlpha) 214 | return color 215 | --------------------------------------------------------------------------------