File indexing completed on 2024-11-17 03:48:12
0001 /* 0002 SPDX-FileCopyrightText: 2022 Friedrich W. H. Kossebau <kossebau@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "GroundItem.h" 0008 0009 #include <KGameRenderer> 0010 0011 #include <QApplication> 0012 #include <QStyleOptionGraphicsItem> 0013 #include <QPainter> 0014 0015 #include <cmath> 0016 0017 // hardcoded for now, featch from theme 0018 static constexpr int SMALL_STONES = 4; 0019 static constexpr int LARGE_STONES = 6; 0020 0021 static constexpr int MIN_SQUARE_SIZE = 8; 0022 0023 GroundItem::GroundItem(const Map *map, KGameRenderer *renderer, QGraphicsItem *parent) 0024 : QGraphicsItem(parent) 0025 , m_renderer(renderer) 0026 , m_map(map) 0027 { 0028 setGraphicsItem(this); 0029 m_stoneIndex.setStoneCount(LARGE_STONES, SMALL_STONES); 0030 } 0031 0032 QPoint GroundItem::squareFromScene(QPointF scenePos) const 0033 { 0034 const QPointF cnntentPos = mapFromScene(scenePos) - m_contentOffset; 0035 0036 // using QPointF to also cover ]-1,0[ 0037 const QPointF squarePos = cnntentPos / m_squareSize; 0038 0039 if ((squarePos.x() < 0 ) || (squarePos.x() >= m_map->width()) || 0040 (squarePos.y() < 0 ) || (squarePos.y() >= m_map->height())) { 0041 return {-1, -1}; 0042 } 0043 0044 return {static_cast<int>(squarePos.x()), static_cast<int>(squarePos.y())}; 0045 } 0046 0047 QPointF GroundItem::squareToScene(QPoint square) const 0048 { 0049 return mapToScene(QPointF{static_cast<qreal>(m_squareSize * square.x() + m_contentOffset.x()), 0050 static_cast<qreal>(m_squareSize * square.y() + m_contentOffset.y())}); 0051 } 0052 0053 QRectF GroundItem::boundingRect() const 0054 { 0055 const int cols = m_map->width(); 0056 const int rows = m_map->height(); 0057 const int width = cols * m_squareSize; 0058 const int height = rows * m_squareSize; 0059 0060 return QRectF(m_contentOffset, QSizeF(width, height)); 0061 } 0062 0063 void GroundItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 0064 { 0065 Q_UNUSED(widget); 0066 0067 if (m_squareSize <= 0) 0068 return; 0069 0070 painter->save();; 0071 painter->translate(m_contentOffset); 0072 0073 const QRectF exposedContentRect = option->exposedRect.translated(-m_contentOffset.x(), -m_contentOffset.y()); 0074 0075 int minx = squareX(exposedContentRect.x()); 0076 int miny = squareY(exposedContentRect.y()); 0077 int maxx = squareX(exposedContentRect.x() + exposedContentRect.width() - 1); 0078 int maxy = squareY(exposedContentRect.y() + exposedContentRect.height() - 1); 0079 0080 if (minx < 0) 0081 minx = 0; 0082 if (miny < 0) 0083 miny = 0; 0084 if (maxx >= m_map->width()) 0085 maxx = m_map->width() - 1; 0086 if (maxy >= m_map->height()) 0087 maxy = m_map->height() - 1; 0088 0089 for (int y = miny; y <= maxy; y++) { 0090 for (int x = minx; x <= maxx; x++) { 0091 paintSquare(x, y, painter); 0092 } 0093 } 0094 painter->restore();; 0095 } 0096 0097 void GroundItem::setGeometry(const QRectF &geom) 0098 { 0099 prepareGeometryChange(); 0100 QGraphicsLayoutItem::setGeometry(geom); 0101 // use updated data 0102 const QRectF geometry = this->geometry(); 0103 setPos(geometry.topLeft()); 0104 0105 updateSquares(); 0106 } 0107 0108 void GroundItem::updateSquares() 0109 { 0110 // use updated data 0111 const QRectF geometry = this->geometry(); 0112 0113 const int cols = m_map->width(); 0114 const int rows = m_map->height(); 0115 0116 // FIXME: the line below should not be needed 0117 if (cols == 0 || rows == 0) 0118 return; 0119 0120 // for now render aligned to pixels 0121 int xsize = std::floor(geometry.width() / cols); 0122 int ysize = std::floor(geometry.height() / rows); 0123 0124 if (xsize < MIN_SQUARE_SIZE) 0125 xsize = MIN_SQUARE_SIZE; 0126 if (ysize < MIN_SQUARE_SIZE) 0127 ysize = MIN_SQUARE_SIZE; 0128 0129 m_squareSize = (xsize > ysize ? ysize : xsize); 0130 // ensure even number, for half squares to be aligned to pixels 0131 m_squareSize &= ~1; 0132 m_halfSquareSize = m_squareSize / 2; 0133 0134 const int groundX = (geometry.width() - cols * m_squareSize) / 2; 0135 const int groundY = (geometry.height()- rows * m_squareSize) / 2; 0136 0137 m_contentOffset.setX(groundX); 0138 m_contentOffset.setY(groundY); 0139 0140 // qDebug() << "GROUNDITEM::SETGEOMETRY" << geom << "squaresize" << m_squareSize; 0141 update(); 0142 } 0143 0144 QSizeF GroundItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const 0145 { 0146 Q_UNUSED(constraint); 0147 switch (which) { 0148 case Qt::MinimumSize: { 0149 const int cols = m_map->width(); 0150 const int rows = m_map->height(); 0151 const int width = MIN_SQUARE_SIZE * cols; 0152 const int height = MIN_SQUARE_SIZE * rows; 0153 0154 return QSizeF(width, height); 0155 } 0156 default: 0157 return QSizeF(); 0158 }; 0159 } 0160 0161 QPixmap GroundItem::stonePixmap(int stoneIndex) const 0162 { 0163 const QString spriteName = QStringLiteral("stone_%1").arg(stoneIndex); 0164 0165 return m_renderer->spritePixmap(spriteName, QSize(m_squareSize, m_halfSquareSize)); 0166 } 0167 0168 QPixmap GroundItem::halfStonePixmap(int stoneIndex) const 0169 { 0170 const QString spriteName = QStringLiteral("halfstone_%1").arg(stoneIndex); 0171 0172 return m_renderer->spritePixmap(spriteName, QSize(m_halfSquareSize, m_halfSquareSize)); 0173 } 0174 0175 void GroundItem::paintWall(int x, int y, QPainter *painter) 0176 { 0177 const int pixelX = squareToX(x); 0178 const int pixelY = squareToY(y); 0179 const int stoneIndex = x + y * (Map::MAX_X + 1); 0180 0181 const qreal dpr = qApp->devicePixelRatio(); 0182 const int deviceSize_ = m_squareSize * dpr; 0183 const int halfdeviceSize_ = deviceSize_ / 2; 0184 0185 if (m_map->wallLeft(x, y)) { 0186 painter->drawPixmap(pixelX, pixelY, stonePixmap(m_stoneIndex.upperLarge(stoneIndex - 1)), 0187 halfdeviceSize_, 0, -1, -1); 0188 } else { 0189 painter->drawPixmap(pixelX, pixelY, halfStonePixmap(m_stoneIndex.leftSmall(stoneIndex))); 0190 } 0191 0192 if (m_map->wallRight(x, y)) { 0193 painter->drawPixmap(pixelX + m_halfSquareSize, pixelY, stonePixmap(m_stoneIndex.upperLarge(stoneIndex)), 0194 0, 0, halfdeviceSize_, -1); 0195 } else { 0196 painter->drawPixmap(pixelX + m_halfSquareSize, pixelY, halfStonePixmap(m_stoneIndex.rightSmall(stoneIndex))); 0197 } 0198 0199 painter->drawPixmap(pixelX, pixelY + m_halfSquareSize, stonePixmap(m_stoneIndex.lowerLarge(stoneIndex))); 0200 } 0201 0202 void GroundItem::paintSquare(int x, int y, QPainter *painter) 0203 { 0204 if (m_map->wall(x, y)) { 0205 paintWall(x, y, painter); 0206 return; 0207 } 0208 0209 QString spriteName; 0210 if (m_map->xpos() == x && m_map->ypos() == y) { 0211 if (m_map->goal(x, y)) 0212 spriteName = QStringLiteral("saveman"); 0213 else { 0214 spriteName = QStringLiteral("man"); 0215 } 0216 } else if (m_map->empty(x, y)) { 0217 if (m_map->floor(x, y)) { 0218 if (m_map->goal(x, y)) 0219 spriteName = QStringLiteral("goal"); 0220 else { 0221 // shortcut for now, replace with theme pixmap (or color property) 0222 painter->fillRect(squareToX(x), squareToY(y), m_squareSize, m_squareSize, QColor(0x67, 0x67, 0x67, 255)); 0223 return; 0224 } 0225 } 0226 } else if (m_map->object(x, y)) { 0227 // TODO: add highlighting & other states to KGameRenderer 0228 #if 0 0229 if (highlightX_ == x && highlightY_ == y) { 0230 if (m_map->goal(x, y)) 0231 imageData_->brightTreasure(paint, squareToX(x), squareToY(y)); 0232 else 0233 imageData_->brightObject(paint, squareToX(x), squareToY(y)); 0234 return; 0235 } else 0236 #endif 0237 { 0238 if (m_map->goal(x, y)) 0239 spriteName = QStringLiteral("treasure"); 0240 else 0241 spriteName = QStringLiteral("object"); 0242 } 0243 } 0244 0245 if (spriteName.isEmpty()) { 0246 return; 0247 } 0248 0249 const QPixmap pixmap = m_renderer->spritePixmap(spriteName, QSize(m_squareSize, m_squareSize)); 0250 painter->drawPixmap(squareToX(x), squareToY(y), pixmap); 0251 }