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 }