1#!/usr/bin/env python 2 3 4############################################################################# 5## 6## Copyright (C) 2013 Riverbank Computing Limited. 7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 8## All rights reserved. 9## 10## This file is part of the examples of PyQt. 11## 12## $QT_BEGIN_LICENSE:BSD$ 13## You may use this file under the terms of the BSD license as follows: 14## 15## "Redistribution and use in source and binary forms, with or without 16## modification, are permitted provided that the following conditions are 17## met: 18## * Redistributions of source code must retain the above copyright 19## notice, this list of conditions and the following disclaimer. 20## * Redistributions in binary form must reproduce the above copyright 21## notice, this list of conditions and the following disclaimer in 22## the documentation and/or other materials provided with the 23## distribution. 24## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor 25## the names of its contributors may be used to endorse or promote 26## products derived from this software without specific prior written 27## permission. 28## 29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 40## $QT_END_LICENSE$ 41## 42############################################################################# 43 44 45from PyQt5.QtCore import (pyqtProperty, pyqtSignal, QEasingCurve, QObject, 46 QParallelAnimationGroup, QPointF, QPropertyAnimation, qrand, QRectF, 47 QState, QStateMachine, Qt, QTimer) 48from PyQt5.QtGui import (QBrush, QLinearGradient, QPainter, QPainterPath, 49 QPixmap) 50from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsPixmapItem, 51 QGraphicsRectItem, QGraphicsScene, QGraphicsView, QGraphicsWidget, 52 QStyle) 53 54import animatedtiles_rc 55 56 57# PyQt doesn't support deriving from more than one wrapped class so we use 58# composition and delegate the property. 59class Pixmap(QObject): 60 def __init__(self, pix): 61 super(Pixmap, self).__init__() 62 63 self.pixmap_item = QGraphicsPixmapItem(pix) 64 self.pixmap_item.setCacheMode(QGraphicsItem.DeviceCoordinateCache) 65 66 def _set_pos(self, pos): 67 self.pixmap_item.setPos(pos) 68 69 pos = pyqtProperty(QPointF, fset=_set_pos) 70 71 72class Button(QGraphicsWidget): 73 pressed = pyqtSignal() 74 75 def __init__(self, pixmap, parent=None): 76 super(Button, self).__init__(parent) 77 78 self._pix = pixmap 79 80 self.setAcceptHoverEvents(True) 81 self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) 82 83 def boundingRect(self): 84 return QRectF(-65, -65, 130, 130) 85 86 def shape(self): 87 path = QPainterPath() 88 path.addEllipse(self.boundingRect()) 89 90 return path 91 92 def paint(self, painter, option, widget): 93 down = option.state & QStyle.State_Sunken 94 r = self.boundingRect() 95 96 grad = QLinearGradient(r.topLeft(), r.bottomRight()) 97 if option.state & QStyle.State_MouseOver: 98 color_0 = Qt.white 99 else: 100 color_0 = Qt.lightGray 101 102 color_1 = Qt.darkGray 103 104 if down: 105 color_0, color_1 = color_1, color_0 106 107 grad.setColorAt(0, color_0) 108 grad.setColorAt(1, color_1) 109 110 painter.setPen(Qt.darkGray) 111 painter.setBrush(grad) 112 painter.drawEllipse(r) 113 114 color_0 = Qt.darkGray 115 color_1 = Qt.lightGray 116 117 if down: 118 color_0, color_1 = color_1, color_0 119 120 grad.setColorAt(0, color_0) 121 grad.setColorAt(1, color_1) 122 123 painter.setPen(Qt.NoPen) 124 painter.setBrush(grad) 125 126 if down: 127 painter.translate(2, 2) 128 129 painter.drawEllipse(r.adjusted(5, 5, -5, -5)) 130 painter.drawPixmap(-self._pix.width() / 2, -self._pix.height() / 2, 131 self._pix) 132 133 def mousePressEvent(self, ev): 134 self.pressed.emit() 135 self.update() 136 137 def mouseReleaseEvent(self, ev): 138 self.update() 139 140 141class View(QGraphicsView): 142 def resizeEvent(self, event): 143 super(View, self).resizeEvent(event) 144 self.fitInView(self.sceneRect(), Qt.KeepAspectRatio) 145 146 147if __name__ == '__main__': 148 149 import sys 150 import math 151 152 app = QApplication(sys.argv) 153 154 kineticPix = QPixmap(':/images/kinetic.png') 155 bgPix = QPixmap(':/images/Time-For-Lunch-2.jpg') 156 157 scene = QGraphicsScene(-350, -350, 700, 700) 158 159 items = [] 160 for i in range(64): 161 item = Pixmap(kineticPix) 162 item.pixmap_item.setOffset(-kineticPix.width() / 2, 163 -kineticPix.height() / 2) 164 item.pixmap_item.setZValue(i) 165 items.append(item) 166 scene.addItem(item.pixmap_item) 167 168 # Buttons. 169 buttonParent = QGraphicsRectItem() 170 ellipseButton = Button(QPixmap(':/images/ellipse.png'), buttonParent) 171 figure8Button = Button(QPixmap(':/images/figure8.png'), buttonParent) 172 randomButton = Button(QPixmap(':/images/random.png'), buttonParent) 173 tiledButton = Button(QPixmap(':/images/tile.png'), buttonParent) 174 centeredButton = Button(QPixmap(':/images/centered.png'), buttonParent) 175 176 ellipseButton.setPos(-100, -100) 177 figure8Button.setPos(100, -100) 178 randomButton.setPos(0, 0) 179 tiledButton.setPos(-100, 100) 180 centeredButton.setPos(100, 100) 181 182 scene.addItem(buttonParent) 183 buttonParent.setScale(0.75) 184 buttonParent.setPos(200, 200) 185 buttonParent.setZValue(65) 186 187 # States. 188 rootState = QState() 189 ellipseState = QState(rootState) 190 figure8State = QState(rootState) 191 randomState = QState(rootState) 192 tiledState = QState(rootState) 193 centeredState = QState(rootState) 194 195 # Values. 196 for i, item in enumerate(items): 197 # Ellipse. 198 ellipseState.assignProperty(item, 'pos', 199 QPointF(math.cos((i / 63.0) * 6.28) * 250, 200 math.sin((i / 63.0) * 6.28) * 250)) 201 202 # Figure 8. 203 figure8State.assignProperty(item, 'pos', 204 QPointF(math.sin((i / 63.0) * 6.28) * 250, 205 math.sin(((i * 2)/63.0) * 6.28) * 250)) 206 207 # Random. 208 randomState.assignProperty(item, 'pos', 209 QPointF(-250 + qrand() % 500, -250 + qrand() % 500)) 210 211 # Tiled. 212 tiledState.assignProperty(item, 'pos', 213 QPointF(((i % 8) - 4) * kineticPix.width() + kineticPix.width() / 2, 214 ((i // 8) - 4) * kineticPix.height() + kineticPix.height() / 2)) 215 216 # Centered. 217 centeredState.assignProperty(item, 'pos', QPointF()) 218 219 # Ui. 220 view = View(scene) 221 view.setWindowTitle("Animated Tiles") 222 view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) 223 view.setBackgroundBrush(QBrush(bgPix)) 224 view.setCacheMode(QGraphicsView.CacheBackground) 225 view.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) 226 view.show() 227 228 states = QStateMachine() 229 states.addState(rootState) 230 states.setInitialState(rootState) 231 rootState.setInitialState(centeredState) 232 233 group = QParallelAnimationGroup() 234 for i, item in enumerate(items): 235 anim = QPropertyAnimation(item, b'pos') 236 anim.setDuration(750 + i * 25) 237 anim.setEasingCurve(QEasingCurve.InOutBack) 238 group.addAnimation(anim) 239 240 trans = rootState.addTransition(ellipseButton.pressed, ellipseState) 241 trans.addAnimation(group) 242 243 trans = rootState.addTransition(figure8Button.pressed, figure8State) 244 trans.addAnimation(group) 245 246 trans = rootState.addTransition(randomButton.pressed, randomState) 247 trans.addAnimation(group) 248 249 trans = rootState.addTransition(tiledButton.pressed, tiledState) 250 trans.addAnimation(group) 251 252 trans = rootState.addTransition(centeredButton.pressed, centeredState) 253 trans.addAnimation(group) 254 255 timer = QTimer() 256 timer.start(125) 257 timer.setSingleShot(True) 258 trans = rootState.addTransition(timer.timeout, ellipseState) 259 trans.addAnimation(group) 260 261 states.start() 262 263 sys.exit(app.exec_()) 264