File indexing completed on 2024-05-19 04:07:54

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jakob Gruber <jakob.gruber@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "cellitem.h"
0008 
0009 #include <QGraphicsSceneMouseEvent>
0010 #include <QKeyEvent>
0011 #include <QParallelAnimationGroup>
0012 #include <QPropertyAnimation>
0013 #include <assert.h>
0014 
0015 #include "src/constants.h"
0016 #include "src/gui/renderer.h"
0017 #include "src/gui/scene.h"
0018 
0019 class DragManager
0020 {
0021 public:
0022     DragManager(QSharedPointer<Picmi> game, Scene *scene, QPoint start);
0023 
0024     void init(Board::State state);
0025     void move(int x, int y);
0026 
0027 private:
0028     QPoint normCoordinates(int x, int y);
0029 
0030 private:
0031 
0032     enum DragDirection {
0033         Horizontal,
0034         Vertical,
0035         Undefined
0036     };
0037 
0038     const QSharedPointer<Picmi> m_game;
0039     const QPoint m_start;
0040     QPoint m_prev_pos;
0041     Scene *m_scene;
0042     Board::State m_before, m_after, m_request;
0043     DragManager::DragDirection m_direction;
0044     bool m_initialized;
0045 };
0046 
0047 DragManager::DragManager(QSharedPointer<Picmi> game, Scene *scene, QPoint start) :
0048     m_game(game), m_start(start), m_prev_pos(start), m_scene(scene), m_initialized(false)
0049 {
0050     m_direction = Undefined;
0051 }
0052 
0053 void DragManager::init(Board::State state) {
0054     m_before = m_game->stateAt(m_start.x(), m_start.y());
0055     m_request = state;
0056     m_scene->press(m_start.x(), m_start.y(), state);
0057     m_after = m_game->stateAt(m_start.x(), m_start.y());
0058     m_initialized = true;
0059 }
0060 
0061 void DragManager::move(int x, int y) {
0062     const QPoint curr_pos = normCoordinates(x, y);
0063     if (curr_pos == m_prev_pos) {
0064         return;
0065     }
0066 
0067     /* When moving the mouse quickly, it can happen that subsequent calls to move()
0068      * skip a tile. Ensure that all tiles between m_prev_pos and curr_pos are processed. */
0069 
0070     QPoint step = curr_pos - m_prev_pos;
0071     assert(step.x() == 0 || step.y() == 0);
0072     step /= qMax(qAbs(step.x()), qAbs(step.y()));
0073     assert(qAbs(step.x()) == 1 || qAbs(step.y()) == 1);
0074 
0075     for (QPoint i = m_prev_pos + step; ; i += step) {
0076         const Board::State current = m_game->stateAt(i.x(), i.y());
0077         if (current == m_before && current != m_after && m_initialized) {
0078             m_scene->press(i.x(), i.y(), m_request);
0079         } else {
0080             m_scene->hover(i.x(), i.y());
0081         }
0082 
0083         if (i == curr_pos) {
0084             break;
0085         }
0086     }
0087 
0088     m_prev_pos = curr_pos;
0089 }
0090 
0091 QPoint DragManager::normCoordinates(int x, int y) {
0092     if (m_direction == Undefined) {
0093         int abs_dx = abs(m_start.x() - x);
0094         int abs_dy = abs(m_start.y() - y);
0095 
0096         if (abs_dx == 0 && abs_dy == 0) {
0097             return m_start;
0098         }
0099 
0100         if (abs_dx > abs_dy) {
0101             m_direction = Horizontal;
0102         } else {
0103             m_direction = Vertical;
0104         }
0105     }
0106 
0107     switch (m_direction) {
0108     case Horizontal: return QPoint(x, m_start.y());
0109     case Vertical: return QPoint(m_start.x(), y);
0110     default: assert(0);
0111     }
0112 
0113     return QPoint();
0114 }
0115 
0116 CellItem::CellItem(int x, int y, QSharedPointer<Picmi> game, QGraphicsItem *parent) :
0117     QGraphicsPixmapItem(parent), ReloadableItem(x, y), m_game(game)
0118 {
0119     setZValue(ZVALUE_CELLITEM);
0120     setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
0121 }
0122 
0123 void CellItem::refresh() {
0124     setPixmap(getPixmap());
0125 }
0126 
0127 void CellItem::reload(const QSize &size) {
0128     Q_UNUSED(size);
0129     const int tilesize = getTilesize();
0130     setPos(m_x * tilesize, m_y * tilesize);
0131 
0132     refresh();
0133 }
0134 
0135 OverviewCellItem::OverviewCellItem(int x, int y, QSharedPointer<Picmi> game, QGraphicsItem *parent) :
0136     CellItem(x, y, game, parent)
0137 {
0138     setEnabled(false);
0139     reload(QSize());
0140 }
0141 
0142 QPixmap OverviewCellItem::getPixmap() const {
0143     switch(m_game->stateAt(m_x, m_y)) {
0144     case Board::Nothing: return Renderer::instance()->getPixmap(Renderer::Transparent);
0145     case Board::Box: return Renderer::instance()->getPixmap(Renderer::OverviewBox);
0146     case Board::Cross: return Renderer::instance()->getPixmap(Renderer::OverviewCross);
0147     default: assert(0);
0148     }
0149 
0150     throw OutOfBoundsException();
0151 }
0152 
0153 int OverviewCellItem::getTilesize() const {
0154     return Renderer::instance()->getOverviewTilesize();
0155 }
0156 
0157 GameCellItem::GameCellItem(int x, int y, QSharedPointer<Picmi> game, Scene *scene, QGraphicsItem *parent) :
0158     CellItem(x, y, game, parent), m_scene(scene), m_state(Board::Nothing)
0159 {
0160     setFlag(QGraphicsItem::ItemIsFocusable);
0161     setAcceptHoverEvents(true);
0162 
0163     m_anim = createAnimation();
0164 
0165     reload(QSize());
0166 }
0167 
0168 QAbstractAnimation *GameCellItem::createAnimation() {
0169     QParallelAnimationGroup *anim_group = new QParallelAnimationGroup(this);
0170 
0171     QPropertyAnimation *anim = new QPropertyAnimation(this, "opacity");
0172     anim->setDuration(150);
0173     anim->setStartValue(0.1);
0174     anim->setEndValue(1.0);
0175     anim_group->addAnimation(anim);
0176 
0177     anim = new QPropertyAnimation(this, "scale");
0178     anim->setDuration(150);
0179     anim->setStartValue(0.3);
0180     anim->setEndValue(1.0);
0181     anim_group->addAnimation(anim);
0182 
0183     return anim_group;
0184 }
0185 
0186 void GameCellItem::refresh() {
0187     CellItem::refresh();
0188 
0189     /* Only start animation when the cell state has changed. */
0190 
0191     const Board::State curr_state = m_game->stateAt(m_x, m_y);
0192     if (curr_state == m_state) {
0193         return;
0194     }
0195 
0196     m_state = curr_state;
0197     m_anim->start();
0198 }
0199 
0200 void GameCellItem::reload(const QSize &size) {
0201     /* Reset the transformation origin point for our scaling animation. */
0202     const int tilesize = getTilesize();
0203     setTransformOriginPoint(tilesize / 2, tilesize / 2);
0204 
0205     CellItem::reload(size);
0206 
0207     /* Save the original scene position - setTransformOriginPoint() together
0208      * with scaling modify it. */
0209     m_sceneorigin = scenePos();
0210 }
0211 
0212 QPixmap GameCellItem::getPixmap() const {
0213     switch(m_game->stateAt(m_x, m_y)) {
0214     case Board::Nothing: return Renderer::instance()->getPixmap(Renderer::Transparent);
0215     case Board::Box: return Renderer::instance()->getPixmap(Renderer::Box);
0216     case Board::Cross: return Renderer::instance()->getPixmap(Renderer::Cross);
0217     default: assert(0);
0218     }
0219 
0220     throw OutOfBoundsException();
0221 }
0222 
0223 int GameCellItem::getTilesize() const {
0224     return Renderer::instance()->getTilesize();
0225 }
0226 
0227 void GameCellItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
0228     if (m_dragmanager) {
0229         /* a second button was clicked during a drag; ignored */
0230         return;
0231     }
0232 
0233     m_dragmanager = QSharedPointer<DragManager>(new DragManager(m_game, m_scene, QPoint(m_x, m_y)));
0234     m_dragbutton = event->button();
0235     switch (m_dragbutton) {
0236     case Qt::LeftButton: m_dragmanager->init(Board::Box); break;
0237     case Qt::RightButton: m_dragmanager->init(Board::Cross); break;
0238     default: break;    /* for example, middle mouse button */
0239     }
0240 }
0241 
0242 void GameCellItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
0243     Q_UNUSED(event);
0244     m_scene->hover(m_x, m_y);
0245 }
0246 
0247 QPoint GameCellItem::sceneToGame(const QPointF &p) const {
0248     const QPointF pf = (p - m_sceneorigin) / Renderer::instance()->getTilesize();
0249     return QPoint(m_x + pf.x(), m_y + pf.y());
0250 }
0251 
0252 void GameCellItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
0253     if (!m_dragmanager) {
0254         return;
0255     }
0256 
0257     const QPoint p = sceneToGame(event->scenePos());
0258     if (m_game->outOfBounds(p.x(), p.y())) {
0259         return;
0260     }
0261 
0262     m_dragmanager->move(p.x(), p.y());
0263 }
0264 
0265 void GameCellItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
0266     if (event->button() == m_dragbutton) {
0267         m_dragmanager.clear();
0268     }
0269 }
0270 
0271 void GameCellItem::keyPressEvent(QKeyEvent *event) {
0272     switch (event->key()) {
0273     case Qt::Key_H:
0274     case Qt::Key_Left: m_scene->move(-1, 0); break;
0275     case Qt::Key_L:
0276     case Qt::Key_Right: m_scene->move(1, 0); break;
0277     case Qt::Key_K:
0278     case Qt::Key_Up: m_scene->move(0, -1); break;
0279     case Qt::Key_J:
0280     case Qt::Key_Down: m_scene->move(0, 1); break;
0281     case Qt::Key_Y: m_scene->move(-1, -1); break;
0282     case Qt::Key_U: m_scene->move(1, -1); break;
0283     case Qt::Key_B: m_scene->move(-1, 1); break;
0284     case Qt::Key_N: m_scene->move(1, 1); break;
0285     case Qt::Key_Space: m_scene->press(m_x, m_y, Board::Box); break;
0286     case Qt::Key_X: m_scene->press(m_x, m_y, Board::Cross); break;
0287     default: break;
0288     }
0289 }
0290 
0291 #include "moc_cellitem.cpp"