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"