File indexing completed on 2024-05-19 04:07:50
0001 /* 0002 SPDX-FileCopyrightText: 2009, 2010 Stefan Majewsky <majewsky@gmx.net> 0003 SPDX-FileCopyrightText: 2010 Johannes Löhnert <loehnert.kde@gmx.de> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "piece.h" 0009 #include "piece_p.h" 0010 #include "scene.h" 0011 #include "settings.h" 0012 0013 #include <QApplication> 0014 #include <QGraphicsSceneMouseEvent> 0015 #include <QPalette> 0016 #include <QPropertyAnimation> 0017 #include <QPainter> // IDW test. 0018 #include <QRandomGenerator> 0019 0020 void Palapeli::Piece::commonInit(const Palapeli::PieceVisuals& pieceVisuals) 0021 { 0022 Palapeli::SelectionAwarePixmapItem* pieceItem = new Palapeli::SelectionAwarePixmapItem(pieceVisuals.pixmap(), this); 0023 connect(pieceItem, &Palapeli::SelectionAwarePixmapItem::selectedChanged, this, &Piece::pieceItemSelectedChanged); 0024 m_pieceItem = pieceItem; 0025 m_pieceItem->setOffset(pieceVisuals.offset()); 0026 //initialize behavior 0027 m_pieceItem->setAcceptedMouseButtons(Qt::LeftButton); 0028 m_pieceItem->setCursor(Qt::OpenHandCursor); 0029 m_pieceItem->setFlag(QGraphicsItem::ItemIsSelectable); 0030 // replacing m_pieceItems pixmap (in rerenderBevel()) causes weird pixel errors 0031 // when using fast transformation. SmoothTransformation looks better anyway... 0032 m_pieceItem->setTransformationMode(Qt::SmoothTransformation); 0033 m_offset = m_pieceItem->offset().toPoint(); 0034 } 0035 0036 Palapeli::Piece::Piece(const QImage& pieceImage, const QPoint& offset) 0037 : m_pieceItem(nullptr) 0038 , m_inactiveShadowItem(nullptr) 0039 , m_activeShadowItem(nullptr) 0040 , m_highlightItem(nullptr) 0041 , m_animator(nullptr) 0042 , m_offset(offset) 0043 { 0044 //create bevel map if wanted 0045 if (Settings::pieceBevelsEnabled()) 0046 { 0047 //NOTE: The bevel map calculation and application is divided because we 0048 //need different result visuals for different turning angles when piece 0049 //rotation gets implementation. 0050 const QSize size = pieceImage.size(); 0051 const int radius = 0.04 * (size.width() + size.height()); 0052 const Palapeli::BevelMap bevelMap = Palapeli::calculateBevelMap(pieceImage, radius); 0053 commonInit(Palapeli::PieceVisuals(Palapeli::applyBevelMap(pieceImage, bevelMap, /* angle= */ 0), offset)); 0054 } 0055 else 0056 commonInit(Palapeli::PieceVisuals(pieceImage, offset)); 0057 } 0058 0059 Palapeli::Piece::Piece(const Palapeli::PieceVisuals& pieceVisuals, const Palapeli::PieceVisuals& shadowVisuals, const Palapeli::PieceVisuals& highlightVisuals) 0060 : m_pieceItem(nullptr) 0061 , m_inactiveShadowItem(nullptr) 0062 , m_activeShadowItem(nullptr) 0063 , m_highlightItem(nullptr) 0064 , m_animator(nullptr) 0065 , m_offset(QPoint(0, 0)) // Gets set in Piece::commonInit(). 0066 { 0067 commonInit(pieceVisuals); 0068 if (!shadowVisuals.isNull()) 0069 createShadowItems(shadowVisuals); 0070 if (!highlightVisuals.isNull()) { 0071 m_highlightItem = new QGraphicsPixmapItem 0072 (highlightVisuals.pixmap(), this); 0073 m_highlightItem->setOffset(highlightVisuals.offset()); 0074 m_highlightItem->setZValue(-1); 0075 m_highlightItem->setVisible(isSelected()); 0076 } 0077 } 0078 0079 //BEGIN visuals 0080 0081 bool Palapeli::Piece::completeVisuals() 0082 { 0083 if (Settings::pieceShadowsEnabled() && !m_inactiveShadowItem) 0084 { 0085 createShadowItems(Palapeli::createShadow(pieceVisuals(), m_atomicSize)); 0086 return true; 0087 } 0088 return false; 0089 } 0090 0091 bool Palapeli::Piece::hasShadow() const 0092 { 0093 return (bool) m_inactiveShadowItem; 0094 } 0095 0096 bool Palapeli::Piece::hasHighlight() const 0097 { 0098 return (bool) m_highlightItem; 0099 } 0100 0101 void Palapeli::Piece::createShadowItems(const Palapeli::PieceVisuals& shadowVisuals) 0102 { 0103 /* IDW TODO - DELETE this. 0104 #ifdef Q_OS_MAC 0105 // On Apple OS X the QPalette::Highlight color is blue, but is 0106 // dimmed down, for highlighting black-on-white text presumably. 0107 const QColor activeShadowColor(Qt::cyan); 0108 // Note: Q_WS_MAC is deprecated and does not exist in Qt 5. 0109 #else 0110 const QColor activeShadowColor = 0111 QApplication::palette().color(QPalette::Highlight); 0112 #endif 0113 */ 0114 const QColor activeShadowColor = Settings::viewHighlightColor(); 0115 const Palapeli::PieceVisuals activeShadowVisuals = 0116 Palapeli::changeShadowColor(shadowVisuals, activeShadowColor); 0117 // Create inactive (unhighlighted) shadow item. 0118 m_inactiveShadowItem = new QGraphicsPixmapItem(shadowVisuals.pixmap(), this); 0119 m_inactiveShadowItem->setOffset(shadowVisuals.offset()); 0120 m_inactiveShadowItem->setZValue(-2); 0121 // Create active shadow item (highlighted) and animator for its opacity. 0122 m_activeShadowItem = new QGraphicsPixmapItem(activeShadowVisuals.pixmap(), this); 0123 m_activeShadowItem->setOffset(activeShadowVisuals.offset()); 0124 m_activeShadowItem->setZValue(-1); 0125 m_activeShadowItem->setOpacity(isSelected() ? 1.0 : 0.0); 0126 m_animator = new QPropertyAnimation(this, "activeShadowOpacity", this); 0127 } 0128 0129 void Palapeli::Piece::createHighlight(const QSizeF& pieceAreaSize) 0130 { 0131 QRectF rect = sceneBareBoundingRect(); 0132 // IDW TODO - Make the factor an adjustable setting (1.2-2.0). 0133 QSizeF area = 1.5 * pieceAreaSize; 0134 int w = area.width(); 0135 int h = area.height(); 0136 // IDW TODO - Paint pixmap just once (in Scene?) and shallow-copy it. 0137 QRadialGradient g(QPoint(w/2, h/2), qMin(w/2, h/2)); 0138 g.setColorAt(0, Settings::viewHighlightColor()); 0139 g.setColorAt(1,Qt::transparent); 0140 0141 QPixmap p(w, h); 0142 p.fill(Qt::transparent); 0143 QPainter pa; 0144 pa.begin(&p); 0145 pa.setPen(Qt::NoPen); 0146 pa.setBrush(QBrush(g)); 0147 pa.drawEllipse(0, 0, w, h); 0148 pa.end(); 0149 0150 m_highlightItem = new QGraphicsPixmapItem(p, this); 0151 m_highlightItem->setOffset(m_offset.x() - w/2 + rect.width()/2, 0152 m_offset.y() - h/2 + rect.height()/2); 0153 m_highlightItem->setZValue(-1); 0154 } 0155 0156 QRectF Palapeli::Piece::bareBoundingRect() const 0157 { 0158 return m_pieceItem->boundingRect(); 0159 } 0160 0161 QRectF Palapeli::Piece::sceneBareBoundingRect() const 0162 { 0163 return mapToScene(bareBoundingRect()).boundingRect(); 0164 } 0165 0166 Palapeli::PieceVisuals Palapeli::Piece::pieceVisuals() const 0167 { 0168 return Palapeli::PieceVisuals(m_pieceItem->pixmap(), m_pieceItem->offset().toPoint()); 0169 } 0170 0171 Palapeli::PieceVisuals Palapeli::Piece::shadowVisuals() const 0172 { 0173 if (!m_inactiveShadowItem) 0174 return Palapeli::PieceVisuals(); 0175 return Palapeli::PieceVisuals(m_inactiveShadowItem->pixmap(), m_inactiveShadowItem->offset().toPoint()); 0176 } 0177 0178 Palapeli::PieceVisuals Palapeli::Piece::highlightVisuals() const 0179 { 0180 if (!m_highlightItem) 0181 return Palapeli::PieceVisuals(); 0182 return Palapeli::PieceVisuals(m_highlightItem->pixmap(), m_highlightItem->offset().toPoint()); 0183 } 0184 0185 qreal Palapeli::Piece::activeShadowOpacity() const 0186 { 0187 return m_activeShadowItem ? m_activeShadowItem->opacity() : 0.0; 0188 } 0189 0190 void Palapeli::Piece::setActiveShadowOpacity(qreal opacity) 0191 { 0192 if (m_activeShadowItem) 0193 m_activeShadowItem->setOpacity(opacity); 0194 } 0195 0196 void Palapeli::Piece::pieceItemSelectedChanged(bool selected) 0197 { 0198 if (!m_activeShadowItem) { 0199 // No shadows: use a highlighter. 0200 if (!m_highlightItem) { 0201 createHighlight((qobject_cast<Palapeli::Scene*> 0202 (scene()))->pieceAreaSize()); 0203 } 0204 // IDW TODO - Use an animator to change the visibility? 0205 m_highlightItem->setVisible(selected); 0206 return; 0207 } 0208 //change visibility of active shadow 0209 // IDW TODO - On select, hide black shadow and brighten highlight. 0210 // m_inactiveShadowItem->setVisible(! selected); // IDW test. 0211 const qreal targetOpacity = selected ? 1.0 : 0.0; 0212 const qreal opacityDiff = qAbs(targetOpacity - activeShadowOpacity()); 0213 if (m_animator && opacityDiff != 0) 0214 { 0215 m_animator->setDuration(150 * opacityDiff); 0216 m_animator->setStartValue(activeShadowOpacity()); 0217 m_animator->setEndValue(targetOpacity); 0218 m_animator->start(); 0219 } 0220 } 0221 0222 //END visuals 0223 //BEGIN internal datastructures 0224 0225 void Palapeli::Piece::addRepresentedAtomicPieces(const QList<int>& representedAtomicPieces) 0226 { 0227 for (int id : representedAtomicPieces) 0228 if (!m_representedAtomicPieces.contains(id)) 0229 m_representedAtomicPieces << id; 0230 } 0231 0232 QList<int> Palapeli::Piece::representedAtomicPieces() const 0233 { 0234 return m_representedAtomicPieces; 0235 } 0236 0237 void Palapeli::Piece::addLogicalNeighbors(const QList<Palapeli::Piece*>& logicalNeighbors) 0238 { 0239 for (Palapeli::Piece* piece : logicalNeighbors) 0240 // IDW TODO - if (!m_logicalNeighbors.contains(piece) && piece) 0241 // If piece == 0, pieceID was not in m_loadedPieces. 0242 // This would be an integrity error in .puzzle file. 0243 if (!m_logicalNeighbors.contains(piece)) 0244 m_logicalNeighbors << piece; 0245 } 0246 0247 QList<Palapeli::Piece*> Palapeli::Piece::logicalNeighbors() const 0248 { 0249 return m_logicalNeighbors; 0250 } 0251 0252 void Palapeli::Piece::rewriteLogicalNeighbors(const QList<Palapeli::Piece*>& oldPieces, Palapeli::Piece* newPiece) 0253 { 0254 bool oldPiecesFound = false; 0255 for (Palapeli::Piece* oldPiece : oldPieces) 0256 { 0257 int index = m_logicalNeighbors.indexOf(oldPiece); 0258 if (index != -1) 0259 { 0260 oldPiecesFound = true; 0261 m_logicalNeighbors.removeAt(index); 0262 } 0263 } 0264 if (oldPiecesFound && newPiece) //newPiece == 0 allows to just drop the old pieces 0265 m_logicalNeighbors += newPiece; 0266 } 0267 0268 void Palapeli::Piece::announceReplaced(Palapeli::Piece* replacement) 0269 { 0270 Q_EMIT replacedBy(replacement); 0271 deleteLater(); 0272 } 0273 0274 void Palapeli::Piece::addAtomicSize(const QSize& size) 0275 { 0276 m_atomicSize = m_atomicSize.expandedTo(size); 0277 } 0278 0279 QSize Palapeli::Piece::atomicSize() const 0280 { 0281 return m_atomicSize; 0282 } 0283 0284 //END internal datastructures 0285 0286 void Palapeli::Piece::setPlace(const QPointF& topLeft, int x, int y, 0287 const QSizeF& area, bool random) 0288 { 0289 const QRectF b = sceneBareBoundingRect(); 0290 const QSizeF pieceSize = b.size(); 0291 QPointF areaOffset; 0292 // QPoint pieceOffset = m_pieceItem->offset().toPoint(); 0293 if (random) { 0294 int dx = area.width() - pieceSize.width(); 0295 int dy = area.height() - pieceSize.height(); 0296 auto *generator = QRandomGenerator::global(); 0297 areaOffset = QPointF( // Place the piece randomly in the cell. 0298 (dx > 0) ? (generator->bounded(dx)) : 0, // Avoid division by 0. 0299 (dy > 0) ? (generator->bounded(dy)) : 0); 0300 } 0301 else { 0302 areaOffset = QPointF( // Center the piece in the cell. 0303 (area.width() - pieceSize.width())/2.0, 0304 (area.height() - pieceSize.height())/2.0); 0305 } 0306 const QPointF gridPos(x * area.width(), y * area.height()); 0307 setPos(topLeft + gridPos + areaOffset - m_offset); // Move it. 0308 } 0309 0310 //BEGIN mouse interaction 0311 0312 bool Palapeli::Piece::isSelected() const 0313 { 0314 return m_pieceItem->isSelected(); 0315 } 0316 0317 void Palapeli::Piece::setSelected(bool selected) 0318 { 0319 m_pieceItem->setSelected(selected); 0320 } 0321 0322 void Palapeli::Piece::startClick() 0323 { 0324 m_pieceItem->setCursor(Qt::ClosedHandCursor); // Button pressed. 0325 } 0326 0327 void Palapeli::Piece::endClick() 0328 { 0329 m_pieceItem->setCursor(Qt::OpenHandCursor); // Button released. 0330 } 0331 0332 Palapeli::Piece* Palapeli::Piece::fromSelectedItem(QGraphicsItem* item) 0333 { 0334 //We expect: item == piece->m_pieceItem && item->parentItem() == piece 0335 return qgraphicsitem_cast<Palapeli::Piece*>(item->parentItem()); 0336 } 0337 0338 void Palapeli::Piece::beginMove() 0339 { 0340 static int zValue = 0; 0341 setZValue(++zValue); //move piece to top 0342 m_pieceItem->setCursor(Qt::ClosedHandCursor); 0343 } 0344 0345 void Palapeli::Piece::doMove() 0346 { 0347 Palapeli::Scene* scene = qobject_cast<Palapeli::Scene*>(this->scene()); 0348 if (scene) { 0349 scene->validatePiecePosition(this); 0350 Q_EMIT moved(false); // Still moving. 0351 } 0352 } 0353 0354 void Palapeli::Piece::endMove() 0355 { 0356 m_pieceItem->setCursor(Qt::OpenHandCursor); 0357 Q_EMIT moved(true); // Finishd moving. 0358 } 0359 0360 //END mouse interaction 0361 0362 0363 // 0364 0365 #include "moc_piece.cpp" 0366 #include "moc_piece_p.cpp"