File indexing completed on 2024-05-12 04:06:25
0001 /* 0002 SPDX-FileCopyrightText: 2009, 2010 Stefan Majewsky <majewsky@gmx.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "scene.h" 0008 #include "constraintvisualizer.h" 0009 #include "mergegroup.h" 0010 #include "piece.h" 0011 #include "settings.h" 0012 0013 #include <QGraphicsView> 0014 #include "palapeli_debug.h" 0015 0016 Palapeli::Scene::Scene(QObject* parent) 0017 : QGraphicsScene(parent) 0018 , m_constrained(false) 0019 , m_constraintVisualizer(new Palapeli::ConstraintVisualizer(this)) 0020 , m_puzzle(nullptr) 0021 , m_pieceAreaSize(QSizeF(32.0, 32.0)) // Allow 1024 pixels initially. 0022 , m_margin(10.0) 0023 , m_handleWidth(7.0) 0024 , m_minGrid(1) // Min. space for puzzle table. 0025 { 0026 initializeGrid(QPointF(0.0, 0.0)); 0027 } 0028 0029 void Palapeli::Scene::addPieceToList(Palapeli::Piece* piece) 0030 { 0031 m_pieces << piece; 0032 } 0033 0034 void Palapeli::Scene::addPieceItemsToScene() 0035 { 0036 for (Palapeli::Piece * piece : std::as_const(m_pieces)) { 0037 addItem(piece); 0038 connect(piece, &Piece::moved, this, &Scene::pieceMoved); 0039 } 0040 } 0041 0042 void Palapeli::Scene::dispatchPieces(const QList<Palapeli::Piece*> &pieces) 0043 { 0044 for (Palapeli::Piece * piece : pieces) { 0045 piece->setSelected(false); 0046 removeItem(piece); 0047 m_pieces.removeAll(piece); 0048 disconnect(piece, &Piece::moved, this, &Scene::pieceMoved); 0049 } 0050 } 0051 0052 void Palapeli::Scene::clearPieces() 0053 { 0054 qCDebug(PALAPELI_LOG) << "Palapeli::Scene Delete" << m_pieces.count() << "pieces in m_pieces list."; 0055 qDeleteAll(m_pieces); 0056 qCDebug(PALAPELI_LOG) << "Palapeli::Scene Clear m_pieces list."; 0057 m_pieces.clear(); 0058 qCDebug(PALAPELI_LOG) << "Palapeli::Scene Stop m_constraintVisualizer."; 0059 m_constraintVisualizer->stop(); 0060 } 0061 0062 void Palapeli::Scene::addMargin(const qreal handleWidth, const qreal spacer) { 0063 m_handleWidth = handleWidth; 0064 m_margin = handleWidth + spacer; 0065 QRectF r = piecesBoundingRect(); 0066 r.adjust(-m_margin, -m_margin, m_margin, m_margin); 0067 setSceneRect(r); 0068 m_constraintVisualizer->stop(); 0069 m_constraintVisualizer->start(r, handleWidth); 0070 views()[0]->fitInView(r, Qt::KeepAspectRatio); 0071 qCDebug(PALAPELI_LOG) << "SCENE RECT" << r << "VIEW SIZE" << views()[0]->size(); 0072 } 0073 0074 QRectF Palapeli::Scene::extPiecesBoundingRect() const 0075 { 0076 // Bounding rectangle of pieces plus constraint visualizer (margin). 0077 QRectF result = piecesBoundingRect(); 0078 result.adjust(-m_margin, -m_margin, m_margin, m_margin); 0079 return result; 0080 } 0081 0082 void Palapeli::Scene::setMinGrid(const int minGrid) 0083 { 0084 m_minGrid = minGrid; 0085 } 0086 0087 QRectF Palapeli::Scene::piecesBoundingRect() const 0088 { 0089 // If no pieces, space is >= m_minGrid*m_minGrid pieces (e.g. for a new 0090 // PieceHolder). Default is >= 1 piece for the puzzle table. 0091 QRectF result; 0092 for (Palapeli::Piece* piece : m_pieces) 0093 result |= piece->sceneBareBoundingRect(); 0094 QSizeF minSize = m_minGrid * m_gridSpacing; 0095 QRectF minRect(QPointF(0.0, 0.0), minSize); 0096 if (!m_pieces.isEmpty()) { 0097 // Center the minRect over the piece(s). 0098 minRect.moveTopLeft(result.center() - minRect.center()); 0099 } 0100 return (result | minRect); 0101 } 0102 0103 bool Palapeli::Scene::isConstrained() const 0104 { 0105 return m_constrained; 0106 } 0107 0108 void Palapeli::Scene::setConstrained(bool constrained) 0109 { 0110 if (m_constrained == constrained) 0111 return; 0112 m_constrained = constrained; 0113 m_constraintVisualizer->setActive(constrained); 0114 Q_EMIT constrainedChanged(constrained); 0115 } 0116 0117 void Palapeli::Scene::validatePiecePosition(Palapeli::Piece* piece) 0118 { 0119 // Get current scene rectangle. 0120 const QRectF sr = sceneRect(); 0121 // Get bounding rectangle of all pieces and add margin. 0122 QRectF br = piece->sceneBareBoundingRect(); //br = bounding rect 0123 br.adjust(-m_margin, -m_margin, m_margin, m_margin); 0124 if (sr.contains(br)) 0125 return; 0126 0127 // Check for constraint (ie. pieces must not "push" puzzle table edges). 0128 if (m_constrained) { 0129 // Constraint active -> make sure piece stays inside scene rect. 0130 QPointF pos = piece->pos(); 0131 if (br.left() < sr.left()) 0132 pos.rx() += sr.left() - br.left(); 0133 if (br.right() > sr.right()) 0134 pos.rx() += sr.right() - br.right(); 0135 if (br.top() < sr.top()) 0136 pos.ry() += sr.top() - br.top(); 0137 if (br.bottom() > sr.bottom()) 0138 pos.ry() += sr.bottom() - br.bottom(); 0139 piece->setPos(pos); 0140 } 0141 else { 0142 // Constraint not active -> enlarge scene rect as necessary. 0143 setSceneRect(sr | br); 0144 } 0145 } 0146 0147 void Palapeli::Scene::mergeLoadedPieces() 0148 { 0149 // After loading, merge previously assembled pieces, with no animation. 0150 // We need to check all the loaded atomic pieces in each scene. 0151 searchConnections(m_pieces, false); 0152 } 0153 0154 void Palapeli::Scene::searchConnections(const QList<Palapeli::Piece*>& pieces, 0155 const bool animatedMerging) 0156 { 0157 // Look for pieces that can be joined after moving or loading. 0158 // If any are found, merge them, with or without animation. 0159 QList<Palapeli::Piece*> uncheckedPieces(pieces); 0160 while (!uncheckedPieces.isEmpty()) 0161 { 0162 Palapeli::Piece* piece = uncheckedPieces.takeFirst(); 0163 const QList<Palapeli::Piece*> pieceGroup = 0164 Palapeli::MergeGroup::tryGrowMergeGroup(piece); 0165 for (Palapeli::Piece* checkedPiece : pieceGroup) 0166 uncheckedPieces.removeAll(checkedPiece); 0167 if (pieceGroup.size() > 1) 0168 { 0169 Palapeli::MergeGroup* mergeGroup = 0170 new Palapeli::MergeGroup(pieceGroup, this, 0171 m_pieceAreaSize, animatedMerging); 0172 connect(mergeGroup, &Palapeli::MergeGroup::pieceInstanceTransaction, this, &Scene::pieceInstanceTransaction); 0173 mergeGroup->start(); 0174 } 0175 } 0176 } 0177 0178 void Palapeli::Scene::pieceInstanceTransaction(const QList<Palapeli::Piece*>& deletedPieces, const QList<Palapeli::Piece*>& createdPieces) 0179 { 0180 // qCDebug(PALAPELI_LOG) << "Scene::pieceInstanceTransaction(delete" << deletedPieces.count() << "add" << createdPieces.count(); 0181 const int oldPieceCount = m_pieces.count(); 0182 dispatchPieces(deletedPieces); 0183 for (Palapeli::Piece* newPiece : createdPieces) 0184 { 0185 addPieceToList (newPiece); 0186 connect(newPiece, &Piece::moved, 0187 this, &Scene::pieceMoved); 0188 } 0189 // qCDebug(PALAPELI_LOG) << "emit saveMove(" << oldPieceCount - m_pieces.count(); 0190 Q_EMIT saveMove(oldPieceCount - m_pieces.count()); 0191 } 0192 0193 void Palapeli::Scene::pieceMoved(bool finished) 0194 { 0195 if (!finished) { 0196 Q_EMIT saveMove(0); 0197 return; 0198 } 0199 // int before = m_pieces.count(); 0200 QList<Palapeli::Piece*> mergeCandidates; 0201 const auto selectedItems = this->selectedItems(); 0202 for (QGraphicsItem* item : selectedItems) 0203 { 0204 Palapeli::Piece* piece = Palapeli::Piece::fromSelectedItem(item); 0205 if (piece) 0206 mergeCandidates << piece; 0207 } 0208 searchConnections(mergeCandidates, true); // With animation. 0209 } 0210 0211 void Palapeli::Scene::initializeGrid(const QPointF& gridTopLeft) 0212 { 0213 m_gridTopLeft = gridTopLeft; 0214 m_gridSpacing = pieceAreaSize()*(1.0 + 0.05 * Settings::pieceSpacing()); 0215 m_gridRank = 1; 0216 m_gridX = 0; 0217 m_gridY = 0; 0218 // qCDebug(PALAPELI_LOG) << "GRID INITIALIZED" << m_gridTopLeft 0219 // << "spacing" << m_gridSpacing << "scene size" << sceneRect(); 0220 } 0221 0222 void Palapeli::Scene::addToGrid(Palapeli::Piece* piece) 0223 { 0224 // qCDebug(PALAPELI_LOG) << "ADD TO GRID AT" << m_gridTopLeft 0225 // << QPoint(m_gridX, m_gridY) 0226 // << "spacing" << m_gridSpacing << "random" << false; 0227 piece->setPlace(m_gridTopLeft, m_gridX, m_gridY, m_gridSpacing, false); 0228 // Calculate the next spot on the square grid. 0229 if (m_gridY == (m_gridRank - 1)) { 0230 m_gridX++; // Add to bottom row. 0231 if (m_gridX > (m_gridRank - 1)) { 0232 m_gridRank++; // Expand the square grid. 0233 m_gridX = m_gridRank - 1; 0234 m_gridY = 0; // Start right-hand column. 0235 } 0236 } 0237 else { 0238 m_gridY++; // Add to right-hand column. 0239 if (m_gridY == (m_gridRank - 1)) { 0240 m_gridX = 0; // Start bottom row. 0241 } 0242 } 0243 } 0244 0245 #include "moc_scene.cpp"