File indexing completed on 2024-04-28 04:05:03
0001 /* 0002 SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "kgamerenderedgraphicsobject.h" 0008 0009 // own 0010 #include "kgamegraphicsviewrenderer.h" 0011 // Qt 0012 #include <QGraphicsView> 0013 #include <QtMath> 0014 0015 class KGameRenderedGraphicsObjectPrivate : public QGraphicsPixmapItem 0016 { 0017 public: 0018 explicit KGameRenderedGraphicsObjectPrivate(KGameRenderedGraphicsObject *parent); 0019 0020 bool adjustRenderSize(); // returns whether an adjustment was made; WARNING: only call when m_primaryView != 0 0021 void adjustTransform(); 0022 0023 // QGraphicsItem reimplementations (see comment below for why we need all of this) 0024 bool contains(const QPointF &point) const override; 0025 bool isObscuredBy(const QGraphicsItem *item) const override; 0026 QPainterPath opaqueArea() const override; 0027 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; 0028 QPainterPath shape() const override; 0029 0030 public: 0031 KGameRenderedGraphicsObject *const m_parent; 0032 0033 QGraphicsView *m_primaryView = nullptr; 0034 QSize m_correctRenderSize = {0, 0}; 0035 QSizeF m_fixedSize = {-1, -1}; 0036 }; 0037 0038 KGameRenderedGraphicsObjectPrivate::KGameRenderedGraphicsObjectPrivate(KGameRenderedGraphicsObject *parent) 0039 : QGraphicsPixmapItem(parent) 0040 , m_parent(parent) 0041 { 0042 } 0043 0044 static inline int vectorLength(QPointF point) 0045 { 0046 return qSqrt(point.x() * point.x() + point.y() * point.y()); 0047 } 0048 0049 bool KGameRenderedGraphicsObjectPrivate::adjustRenderSize() 0050 { 0051 Q_ASSERT(m_primaryView); 0052 // create a polygon from the item's boundingRect 0053 const QRectF itemRect = m_parent->boundingRect(); 0054 QPolygonF itemPolygon(3); 0055 itemPolygon[0] = itemRect.topLeft(); 0056 itemPolygon[1] = itemRect.topRight(); 0057 itemPolygon[2] = itemRect.bottomLeft(); 0058 // determine correct render size 0059 const QPolygonF scenePolygon = m_parent->sceneTransform().map(itemPolygon); 0060 const QPolygon viewPolygon = m_primaryView->mapFromScene(scenePolygon); 0061 m_correctRenderSize.setWidth(qMax(vectorLength(viewPolygon[1] - viewPolygon[0]), 1)); 0062 m_correctRenderSize.setHeight(qMax(vectorLength(viewPolygon[2] - viewPolygon[0]), 1)); 0063 // ignore fluctuations in the render size which result from rounding errors 0064 const QSize diff = m_parent->renderSize() - m_correctRenderSize; 0065 if (qAbs(diff.width()) <= 1 && qAbs(diff.height()) <= 1) { 0066 return false; 0067 } 0068 m_parent->setRenderSize(m_correctRenderSize); 0069 adjustTransform(); 0070 return true; 0071 } 0072 0073 void KGameRenderedGraphicsObjectPrivate::adjustTransform() 0074 { 0075 // calculate new transform for this item 0076 QTransform t; 0077 t.scale(m_fixedSize.width() / m_correctRenderSize.width(), m_fixedSize.height() / m_correctRenderSize.height()); 0078 // render item 0079 m_parent->prepareGeometryChange(); 0080 setTransform(t); 0081 m_parent->update(); 0082 } 0083 0084 KGameRenderedGraphicsObject::KGameRenderedGraphicsObject(KGameGraphicsViewRenderer *renderer, const QString &spriteKey, QGraphicsItem *parent) 0085 : QGraphicsObject(parent) 0086 , KGameRendererClient(renderer, spriteKey) 0087 , d_ptr(new KGameRenderedGraphicsObjectPrivate(this)) 0088 { 0089 setPrimaryView(renderer->defaultPrimaryView()); 0090 } 0091 0092 KGameRenderedGraphicsObject::~KGameRenderedGraphicsObject() = default; 0093 0094 QPointF KGameRenderedGraphicsObject::offset() const 0095 { 0096 Q_D(const KGameRenderedGraphicsObject); 0097 0098 return d->pos(); 0099 } 0100 0101 void KGameRenderedGraphicsObject::setOffset(QPointF offset) 0102 { 0103 Q_D(KGameRenderedGraphicsObject); 0104 0105 if (d->pos() != offset) { 0106 prepareGeometryChange(); 0107 d->setPos(offset); 0108 update(); 0109 } 0110 } 0111 0112 void KGameRenderedGraphicsObject::setOffset(qreal x, qreal y) 0113 { 0114 setOffset(QPointF(x, y)); 0115 } 0116 0117 QSizeF KGameRenderedGraphicsObject::fixedSize() const 0118 { 0119 Q_D(const KGameRenderedGraphicsObject); 0120 0121 return d->m_fixedSize; 0122 } 0123 0124 void KGameRenderedGraphicsObject::setFixedSize(QSizeF fixedSize) 0125 { 0126 Q_D(KGameRenderedGraphicsObject); 0127 0128 if (d->m_primaryView) { 0129 d->m_fixedSize = fixedSize.expandedTo(QSize(1, 1)); 0130 d->adjustTransform(); 0131 } 0132 } 0133 0134 QGraphicsView *KGameRenderedGraphicsObject::primaryView() const 0135 { 0136 Q_D(const KGameRenderedGraphicsObject); 0137 0138 return d->m_primaryView; 0139 } 0140 0141 void KGameRenderedGraphicsObject::setPrimaryView(QGraphicsView *view) 0142 { 0143 Q_D(KGameRenderedGraphicsObject); 0144 0145 if (d->m_primaryView != view) { 0146 d->m_primaryView = view; 0147 if (view) { 0148 if (!d->m_fixedSize.isValid()) { 0149 d->m_fixedSize = QSize(1, 1); 0150 } 0151 // determine render size and adjust coordinate system 0152 d->m_correctRenderSize = QSize(-10, -10); // force adjustment to be made 0153 d->adjustRenderSize(); 0154 } else { 0155 d->m_fixedSize = QSize(-1, -1); 0156 // reset transform to make coordinate systems of this item and the private item equal 0157 prepareGeometryChange(); 0158 d->setTransform(QTransform()); 0159 update(); 0160 } 0161 } 0162 } 0163 0164 void KGameRenderedGraphicsObject::receivePixmap(const QPixmap &pixmap) 0165 { 0166 Q_D(KGameRenderedGraphicsObject); 0167 0168 prepareGeometryChange(); 0169 d->setPixmap(pixmap); 0170 update(); 0171 } 0172 0173 // We want to make sure that all interactional events are sent ot this item, and 0174 // not to the contained QGraphicsPixmapItem which provides the visual 0175 // representation (and the metrics calculations). 0176 // At the same time, we do not want the contained QGraphicsPixmapItem to slow 0177 // down operations like QGraphicsScene::collidingItems(). 0178 // So the strategy is to use the QGraphicsPixmapItem implementation from 0179 // KGameRenderedGraphicsObjectPrivate for KGameRenderedGraphicsObject. 0180 // Then the relevant methods in KGameRenderedGraphicsObjectPrivate are reimplemented empty 0181 // to effectively clear the item and hide it from any collision detection. This 0182 // strategy allows us to use the nifty QGraphicsPixmapItem logic without exposing 0183 // a QGraphicsPixmapItem subclass (which would conflict with QGraphicsObject). 0184 0185 // BEGIN QGraphicsItem reimplementation of KGameRenderedGraphicsObject 0186 0187 QRectF KGameRenderedGraphicsObject::boundingRect() const 0188 { 0189 Q_D(const KGameRenderedGraphicsObject); 0190 0191 return d->mapRectToParent(d->QGraphicsPixmapItem::boundingRect()); 0192 } 0193 0194 bool KGameRenderedGraphicsObject::contains(const QPointF &point) const 0195 { 0196 Q_D(const KGameRenderedGraphicsObject); 0197 0198 return d->QGraphicsPixmapItem::contains(d->mapFromParent(point)); 0199 } 0200 0201 bool KGameRenderedGraphicsObject::isObscuredBy(const QGraphicsItem *item) const 0202 { 0203 Q_D(const KGameRenderedGraphicsObject); 0204 0205 return d->QGraphicsPixmapItem::isObscuredBy(item); 0206 } 0207 0208 QPainterPath KGameRenderedGraphicsObject::opaqueArea() const 0209 { 0210 Q_D(const KGameRenderedGraphicsObject); 0211 0212 return d->mapToParent(d->QGraphicsPixmapItem::opaqueArea()); 0213 } 0214 0215 void KGameRenderedGraphicsObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 0216 { 0217 Q_UNUSED(painter) 0218 Q_UNUSED(option) 0219 Q_UNUSED(widget) 0220 } 0221 0222 QPainterPath KGameRenderedGraphicsObject::shape() const 0223 { 0224 Q_D(const KGameRenderedGraphicsObject); 0225 0226 return d->mapToParent(d->QGraphicsPixmapItem::shape()); 0227 } 0228 0229 // END QGraphicsItem reimplementation of KGameRenderedGraphicsObject 0230 // BEGIN QGraphicsItem reimplementation of KGameRenderedGraphicsObjectPrivate 0231 0232 bool KGameRenderedGraphicsObjectPrivate::contains(const QPointF &point) const 0233 { 0234 Q_UNUSED(point) 0235 return false; 0236 } 0237 0238 bool KGameRenderedGraphicsObjectPrivate::isObscuredBy(const QGraphicsItem *item) const 0239 { 0240 Q_UNUSED(item) 0241 return false; 0242 } 0243 0244 QPainterPath KGameRenderedGraphicsObjectPrivate::opaqueArea() const 0245 { 0246 return QPainterPath(); 0247 } 0248 0249 void KGameRenderedGraphicsObjectPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 0250 { 0251 // Trivial stuff up to now. The fun stuff starts here. ;-) 0252 // There is no way to get informed when the viewport's coordinate system 0253 //(relative to this item's coordinate system) has changed, so we're checking 0254 // the renderSize in each paintEvent coming from the primary view. 0255 if (m_primaryView) { 0256 if (m_primaryView == widget || m_primaryView->isAncestorOf(widget)) { 0257 const bool isSimpleTransformation = !painter->transform().isRotating(); 0258 // If an adjustment was made, do not paint now, but wait for the next 0259 // painting. However, paint directly if the transformation is 0260 // complex, in order to avoid flicker. 0261 if (adjustRenderSize()) { 0262 if (isSimpleTransformation) { 0263 return; 0264 } 0265 } 0266 if (isSimpleTransformation) { 0267 // draw pixmap directly in physical coordinates 0268 const QPoint basePos = painter->transform().map(QPointF()).toPoint(); 0269 painter->save(); 0270 painter->setTransform(QTransform()); 0271 painter->drawPixmap(basePos, pixmap()); 0272 painter->restore(); 0273 return; 0274 } 0275 } 0276 } 0277 QGraphicsPixmapItem::paint(painter, option, widget); 0278 } 0279 0280 QPainterPath KGameRenderedGraphicsObjectPrivate::shape() const 0281 { 0282 return QPainterPath(); 0283 } 0284 0285 // END QGraphicsItem reimplementation of KGameRenderedGraphicsObjectPrivate 0286 0287 #include "moc_kgamerenderedgraphicsobject.cpp"