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 45import math 46 47from PyQt5.QtCore import (qAbs, QLineF, QPointF, QRectF, qrand, qsrand, Qt, 48 QTime, QTimer) 49from PyQt5.QtGui import (QBrush, QColor, QPainter, QPainterPath, QPixmap, 50 QPolygonF) 51from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene, 52 QGraphicsView, QGraphicsWidget) 53 54import mice_rc 55 56 57class Mouse(QGraphicsItem): 58 Pi = math.pi 59 TwoPi = 2.0 * Pi 60 61 # Create the bounding rectangle once. 62 adjust = 0.5 63 BoundingRect = QRectF(-20 - adjust, -22 - adjust, 40 + adjust, 83 + adjust) 64 65 def __init__(self): 66 super(Mouse, self).__init__() 67 68 self.angle = 0.0 69 self.speed = 0.0 70 self.mouseEyeDirection = 0.0 71 self.color = QColor(qrand() % 256, qrand() % 256, qrand() % 256) 72 73 self.setRotation(qrand() % (360 * 16)) 74 75 # In the C++ version of this example, this class is also derived from 76 # QObject in order to receive timer events. PyQt does not support 77 # deriving from more than one wrapped class so we just create an 78 # explicit timer instead. 79 self.timer = QTimer() 80 self.timer.timeout.connect(self.timerEvent) 81 self.timer.start(1000 / 33) 82 83 @staticmethod 84 def normalizeAngle(angle): 85 while angle < 0: 86 angle += Mouse.TwoPi 87 while angle > Mouse.TwoPi: 88 angle -= Mouse.TwoPi 89 return angle 90 91 def boundingRect(self): 92 return Mouse.BoundingRect 93 94 def shape(self): 95 path = QPainterPath() 96 path.addRect(-10, -20, 20, 40) 97 return path; 98 99 def paint(self, painter, option, widget): 100 # Body. 101 painter.setBrush(self.color) 102 painter.drawEllipse(-10, -20, 20, 40) 103 104 # Eyes. 105 painter.setBrush(Qt.white) 106 painter.drawEllipse(-10, -17, 8, 8) 107 painter.drawEllipse(2, -17, 8, 8) 108 109 # Nose. 110 painter.setBrush(Qt.black) 111 painter.drawEllipse(QRectF(-2, -22, 4, 4)) 112 113 # Pupils. 114 painter.drawEllipse(QRectF(-8.0 + self.mouseEyeDirection, -17, 4, 4)) 115 painter.drawEllipse(QRectF(4.0 + self.mouseEyeDirection, -17, 4, 4)) 116 117 # Ears. 118 if self.scene().collidingItems(self): 119 painter.setBrush(Qt.red) 120 else: 121 painter.setBrush(Qt.darkYellow) 122 123 painter.drawEllipse(-17, -12, 16, 16) 124 painter.drawEllipse(1, -12, 16, 16) 125 126 # Tail. 127 path = QPainterPath(QPointF(0, 20)) 128 path.cubicTo(-5, 22, -5, 22, 0, 25) 129 path.cubicTo(5, 27, 5, 32, 0, 30) 130 path.cubicTo(-5, 32, -5, 42, 0, 35) 131 painter.setBrush(Qt.NoBrush) 132 painter.drawPath(path) 133 134 def timerEvent(self): 135 # Don't move too far away. 136 lineToCenter = QLineF(QPointF(0, 0), self.mapFromScene(0, 0)) 137 if lineToCenter.length() > 150: 138 angleToCenter = math.acos(lineToCenter.dx() / lineToCenter.length()) 139 if lineToCenter.dy() < 0: 140 angleToCenter = Mouse.TwoPi - angleToCenter; 141 angleToCenter = Mouse.normalizeAngle((Mouse.Pi - angleToCenter) + Mouse.Pi / 2) 142 143 if angleToCenter < Mouse.Pi and angleToCenter > Mouse.Pi / 4: 144 # Rotate left. 145 self.angle += [-0.25, 0.25][self.angle < -Mouse.Pi / 2] 146 elif angleToCenter >= Mouse.Pi and angleToCenter < (Mouse.Pi + Mouse.Pi / 2 + Mouse.Pi / 4): 147 # Rotate right. 148 self.angle += [-0.25, 0.25][self.angle < Mouse.Pi / 2] 149 elif math.sin(self.angle) < 0: 150 self.angle += 0.25 151 elif math.sin(self.angle) > 0: 152 self.angle -= 0.25 153 154 # Try not to crash with any other mice. 155 dangerMice = self.scene().items(QPolygonF([self.mapToScene(0, 0), 156 self.mapToScene(-30, -50), 157 self.mapToScene(30, -50)])) 158 159 for item in dangerMice: 160 if item is self: 161 continue 162 163 lineToMouse = QLineF(QPointF(0, 0), self.mapFromItem(item, 0, 0)) 164 angleToMouse = math.acos(lineToMouse.dx() / lineToMouse.length()) 165 if lineToMouse.dy() < 0: 166 angleToMouse = Mouse.TwoPi - angleToMouse 167 angleToMouse = Mouse.normalizeAngle((Mouse.Pi - angleToMouse) + Mouse.Pi / 2) 168 169 if angleToMouse >= 0 and angleToMouse < Mouse.Pi / 2: 170 # Rotate right. 171 self.angle += 0.5 172 elif angleToMouse <= Mouse.TwoPi and angleToMouse > (Mouse.TwoPi - Mouse.Pi / 2): 173 # Rotate left. 174 self.angle -= 0.5 175 176 # Add some random movement. 177 if len(dangerMice) > 1 and (qrand() % 10) == 0: 178 if qrand() % 1: 179 self.angle += (qrand() % 100) / 500.0 180 else: 181 self.angle -= (qrand() % 100) / 500.0 182 183 self.speed += (-50 + qrand() % 100) / 100.0 184 185 dx = math.sin(self.angle) * 10 186 self.mouseEyeDirection = 0.0 if qAbs(dx / 5) < 1 else dx / 5 187 188 self.setRotation(self.rotation() + dx) 189 self.setPos(self.mapToParent(0, -(3 + math.sin(self.speed) * 3))) 190 191 192if __name__ == '__main__': 193 194 import sys 195 196 MouseCount = 7 197 198 app = QApplication(sys.argv) 199 qsrand(QTime(0,0,0).secsTo(QTime.currentTime())) 200 201 scene = QGraphicsScene() 202 scene.setSceneRect(-300, -300, 600, 600) 203 scene.setItemIndexMethod(QGraphicsScene.NoIndex) 204 205 for i in range(MouseCount): 206 mouse = Mouse() 207 mouse.setPos(math.sin((i * 6.28) / MouseCount) * 200, 208 math.cos((i * 6.28) / MouseCount) * 200) 209 scene.addItem(mouse) 210 211 view = QGraphicsView(scene) 212 view.setRenderHint(QPainter.Antialiasing) 213 view.setBackgroundBrush(QBrush(QPixmap(':/images/cheese.jpg'))) 214 view.setCacheMode(QGraphicsView.CacheBackground) 215 view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) 216 view.setDragMode(QGraphicsView.ScrollHandDrag) 217 view.setWindowTitle("Colliding Mice") 218 view.resize(400, 300) 219 view.show() 220 221 sys.exit(app.exec_()) 222