1 | /*************************************************************************** |
2 | * Copyright 2010 Stefan Majewsky <majewsky@gmx.net> * |
3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * |
5 | * it under the terms of the GNU Library General Public License * |
6 | * version 2 as published by the Free Software Foundation * |
7 | * * |
8 | * This program is distributed in the hope that it will be useful, * |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
11 | * GNU Library General Public License for more details. * |
12 | * * |
13 | * You should have received a copy of the GNU Library General Public * |
14 | * License along with this program; if not, write to the * |
15 | * Free Software Foundation, Inc., * |
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
17 | ***************************************************************************/ |
18 | |
19 | #include "kgamerenderedobjectitem.h" |
20 | #include "kgamerenderer.h" |
21 | |
22 | #include <QtCore/qmath.h> |
23 | #include <QtGui/QGraphicsView> |
24 | |
25 | class KGameRenderedObjectItemPrivate : public QGraphicsPixmapItem |
26 | { |
27 | public: |
28 | KGameRenderedObjectItemPrivate(KGameRenderedObjectItem* parent); |
29 | bool adjustRenderSize(); //returns whether an adjustment was made; WARNING: only call when m_primaryView != 0 |
30 | void adjustTransform(); |
31 | |
32 | //QGraphicsItem reimplementations (see comment below for why we need all of this) |
33 | virtual bool contains(const QPointF& point) const; |
34 | virtual bool isObscuredBy(const QGraphicsItem* item) const; |
35 | virtual QPainterPath opaqueArea() const; |
36 | virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); |
37 | virtual QPainterPath shape() const; |
38 | public: |
39 | KGameRenderedObjectItem* m_parent; |
40 | QGraphicsView* m_primaryView; |
41 | QSize m_correctRenderSize; |
42 | QSizeF m_fixedSize; |
43 | }; |
44 | |
45 | KGameRenderedObjectItemPrivate::KGameRenderedObjectItemPrivate(KGameRenderedObjectItem* parent) |
46 | : QGraphicsPixmapItem(parent) |
47 | , m_parent(parent) |
48 | , m_primaryView(0) |
49 | , m_correctRenderSize(0, 0) |
50 | , m_fixedSize(-1, -1) |
51 | { |
52 | } |
53 | |
54 | static inline int vectorLength(const QPointF& point) |
55 | { |
56 | return qSqrt(point.x() * point.x() + point.y() * point.y()); |
57 | } |
58 | |
59 | bool KGameRenderedObjectItemPrivate::adjustRenderSize() |
60 | { |
61 | Q_ASSERT(m_primaryView); |
62 | //create a polygon from the item's boundingRect |
63 | const QRectF itemRect = m_parent->boundingRect(); |
64 | QPolygonF itemPolygon(3); |
65 | itemPolygon[0] = itemRect.topLeft(); |
66 | itemPolygon[1] = itemRect.topRight(); |
67 | itemPolygon[2] = itemRect.bottomLeft(); |
68 | //determine correct render size |
69 | const QPolygonF scenePolygon = m_parent->sceneTransform().map(itemPolygon); |
70 | const QPolygon viewPolygon = m_primaryView->mapFromScene(scenePolygon); |
71 | m_correctRenderSize.setWidth(qMax(vectorLength(viewPolygon[1] - viewPolygon[0]), 1)); |
72 | m_correctRenderSize.setHeight(qMax(vectorLength(viewPolygon[2] - viewPolygon[0]), 1)); |
73 | //ignore fluctuations in the render size which result from rounding errors |
74 | const QSize diff = m_parent->renderSize() - m_correctRenderSize; |
75 | if (qAbs(diff.width()) <= 1 && qAbs(diff.height()) <= 1) |
76 | { |
77 | return false; |
78 | } |
79 | m_parent->setRenderSize(m_correctRenderSize); |
80 | adjustTransform(); |
81 | return true; |
82 | } |
83 | |
84 | void KGameRenderedObjectItemPrivate::adjustTransform() |
85 | { |
86 | //calculate new transform for this item |
87 | QTransform t; |
88 | t.scale(m_fixedSize.width() / m_correctRenderSize.width(), m_fixedSize.height() / m_correctRenderSize.height()); |
89 | //render item |
90 | m_parent->prepareGeometryChange(); |
91 | setTransform(t); |
92 | m_parent->update(); |
93 | } |
94 | |
95 | KGameRenderedObjectItem::KGameRenderedObjectItem(KGameRenderer* renderer, const QString& spriteKey, QGraphicsItem* parent) |
96 | : QGraphicsObject(parent) |
97 | , KGameRendererClient(renderer, spriteKey) |
98 | , d(new KGameRenderedObjectItemPrivate(this)) |
99 | { |
100 | setPrimaryView(renderer->defaultPrimaryView()); |
101 | } |
102 | |
103 | KGameRenderedObjectItem::~KGameRenderedObjectItem() |
104 | { |
105 | delete d; |
106 | } |
107 | |
108 | QPointF KGameRenderedObjectItem::offset() const |
109 | { |
110 | return d->pos(); |
111 | } |
112 | |
113 | void KGameRenderedObjectItem::setOffset(const QPointF& offset) |
114 | { |
115 | if (d->pos() != offset) |
116 | { |
117 | prepareGeometryChange(); |
118 | d->setPos(offset); |
119 | update(); |
120 | } |
121 | } |
122 | |
123 | void KGameRenderedObjectItem::setOffset(qreal x, qreal y) |
124 | { |
125 | setOffset(QPointF(x, y)); |
126 | } |
127 | |
128 | QSizeF KGameRenderedObjectItem::fixedSize() const |
129 | { |
130 | return d->m_fixedSize; |
131 | } |
132 | |
133 | void KGameRenderedObjectItem::setFixedSize(const QSizeF& fixedSize) |
134 | { |
135 | if (d->m_primaryView) |
136 | { |
137 | d->m_fixedSize = fixedSize.expandedTo(QSize(1, 1)); |
138 | d->adjustTransform(); |
139 | } |
140 | } |
141 | |
142 | QGraphicsView* KGameRenderedObjectItem::primaryView() const |
143 | { |
144 | return d->m_primaryView; |
145 | } |
146 | |
147 | void KGameRenderedObjectItem::setPrimaryView(QGraphicsView* view) |
148 | { |
149 | if (d->m_primaryView != view) |
150 | { |
151 | d->m_primaryView = view; |
152 | if (view) |
153 | { |
154 | if (!d->m_fixedSize.isValid()) |
155 | { |
156 | d->m_fixedSize = QSize(1, 1); |
157 | } |
158 | //determine render size and adjust coordinate system |
159 | d->m_correctRenderSize = QSize(-10, -10); //force adjustment to be made |
160 | d->adjustRenderSize(); |
161 | } |
162 | else |
163 | { |
164 | d->m_fixedSize = QSize(-1, -1); |
165 | //reset transform to make coordinate systems of this item and the private item equal |
166 | prepareGeometryChange(); |
167 | d->setTransform(QTransform()); |
168 | update(); |
169 | } |
170 | } |
171 | } |
172 | |
173 | void KGameRenderedObjectItem::receivePixmap(const QPixmap& pixmap) |
174 | { |
175 | prepareGeometryChange(); |
176 | d->setPixmap(pixmap); |
177 | update(); |
178 | } |
179 | |
180 | //We want to make sure that all interactional events are sent ot this item, and |
181 | //not to the contained QGraphicsPixmapItem which provides the visual |
182 | //representation (and the metrics calculations). |
183 | //At the same time, we do not want the contained QGraphicsPixmapItem to slow |
184 | //down operations like QGraphicsScene::collidingItems(). |
185 | //So the strategy is to use the QGraphicsPixmapItem implementation from |
186 | //KGameRenderedObjectItemPrivate for KGameRenderedObjectItem. |
187 | //Then the relevant methods in KGameRenderedObjectItemPrivate are reimplemented empty |
188 | //to effectively clear the item and hide it from any collision detection. This |
189 | //strategy allows us to use the nifty QGraphicsPixmapItem logic without exposing |
190 | //a QGraphicsPixmapItem subclass (which would conflict with QGraphicsObject). |
191 | |
192 | //BEGIN QGraphicsItem reimplementation of KGameRenderedObjectItem |
193 | |
194 | QRectF KGameRenderedObjectItem::boundingRect() const |
195 | { |
196 | return d->mapRectToParent(d->QGraphicsPixmapItem::boundingRect()); |
197 | } |
198 | |
199 | bool KGameRenderedObjectItem::contains(const QPointF& point) const |
200 | { |
201 | return d->QGraphicsPixmapItem::contains(d->mapFromParent(point)); |
202 | } |
203 | |
204 | bool KGameRenderedObjectItem::isObscuredBy(const QGraphicsItem* item) const |
205 | { |
206 | return d->QGraphicsPixmapItem::isObscuredBy(item); |
207 | } |
208 | |
209 | QPainterPath KGameRenderedObjectItem::opaqueArea() const |
210 | { |
211 | return d->mapToParent(d->QGraphicsPixmapItem::opaqueArea()); |
212 | } |
213 | |
214 | void KGameRenderedObjectItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) |
215 | { |
216 | Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget) |
217 | } |
218 | |
219 | QPainterPath KGameRenderedObjectItem::shape() const |
220 | { |
221 | return d->mapToParent(d->QGraphicsPixmapItem::shape()); |
222 | } |
223 | |
224 | //END QGraphicsItem reimplementation of KGameRenderedObjectItem |
225 | //BEGIN QGraphicsItem reimplementation of KGameRenderedObjectItemPrivate |
226 | |
227 | bool KGameRenderedObjectItemPrivate::contains(const QPointF& point) const |
228 | { |
229 | Q_UNUSED(point) |
230 | return false; |
231 | } |
232 | |
233 | bool KGameRenderedObjectItemPrivate::isObscuredBy(const QGraphicsItem* item) const |
234 | { |
235 | Q_UNUSED(item) |
236 | return false; |
237 | } |
238 | |
239 | QPainterPath KGameRenderedObjectItemPrivate::opaqueArea() const |
240 | { |
241 | return QPainterPath(); |
242 | } |
243 | |
244 | void KGameRenderedObjectItemPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) |
245 | { |
246 | //Trivial stuff up to now. The fun stuff starts here. ;-) |
247 | //There is no way to get informed when the viewport's coordinate system |
248 | //(relative to this item's coordinate system) has changed, so we're checking |
249 | //the renderSize in each paintEvent coming from the primary view. |
250 | if (m_primaryView) |
251 | { |
252 | if (m_primaryView == widget || m_primaryView->isAncestorOf(widget)) |
253 | { |
254 | const bool isSimpleTransformation = !painter->transform().isRotating(); |
255 | //If an adjustment was made, do not paint now, but wait for the next |
256 | //painting. However, paint directly if the transformation is |
257 | //complex, in order to avoid flicker. |
258 | if (adjustRenderSize()) |
259 | { |
260 | if (isSimpleTransformation) |
261 | { |
262 | return; |
263 | } |
264 | } |
265 | if (isSimpleTransformation) |
266 | { |
267 | //draw pixmap directly in physical coordinates |
268 | const QPoint basePos = painter->transform().map(QPointF()).toPoint(); |
269 | painter->save(); |
270 | painter->setTransform(QTransform()); |
271 | painter->drawPixmap(basePos, pixmap()); |
272 | painter->restore(); |
273 | return; |
274 | } |
275 | } |
276 | } |
277 | QGraphicsPixmapItem::paint(painter, option, widget); |
278 | } |
279 | |
280 | QPainterPath KGameRenderedObjectItemPrivate::shape() const |
281 | { |
282 | return QPainterPath(); |
283 | } |
284 | |
285 | //END QGraphicsItem reimplementation of KGameRenderedObjectItemPrivate |
286 | |
287 | #include "kgamerenderedobjectitem.moc" |
288 | |