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