File indexing completed on 2024-05-12 04:06:23

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 "constraintvisualizer.h"
0008 #include "scene.h"
0009 
0010 #include <QPropertyAnimation>
0011 #include <QCursor>
0012 #include "palapeli_debug.h" // IDW test.
0013 
0014 Palapeli::ConstraintVisualizer::ConstraintVisualizer(Palapeli::Scene* scene)
0015     : m_scene(scene)
0016     , m_active(false)
0017     , m_shadowItems(SideCount)
0018     , m_handleItems(HandleCount)
0019     , m_sceneRect(QRectF())
0020     , m_animator(new QPropertyAnimation(this, "opacity", this))
0021     , m_isStopped(true)
0022     , m_thickness(5.0)
0023 {
0024     // All QGraphicsRectItems have null size until the first update().
0025     setOpacity(0.3);
0026     // Create shadow items. These are outside the puzzle table.
0027     QColor rectColor(Qt::black);
0028     // IDW test. rectColor.setAlpha(80);
0029     rectColor.setAlpha(40);             // Outer area is paler.
0030     for (int i = 0; i < SideCount; ++i)
0031     {
0032         m_shadowItems[i] = new QGraphicsRectItem(this);
0033         m_shadowItems[i]->setPen(Qt::NoPen);
0034         m_shadowItems[i]->setBrush(rectColor);
0035     }
0036     // Create handle items. These are the edges and corners of the table.
0037     // IDW test. rectColor.setAlpha(rectColor.alpha() / 2);
0038     rectColor.setAlpha(rectColor.alpha() * 2);  // Table edge is darker.
0039     Qt::CursorShape shapes[] = { Qt::SizeHorCursor, Qt::SizeFDiagCursor,
0040                      Qt::SizeVerCursor, Qt::SizeBDiagCursor };
0041     for (int i = 0; i < HandleCount; ++i)
0042     {
0043         m_handleItems[i] = new QGraphicsRectItem(this);
0044         m_handleItems[i]->setPen(Qt::NoPen);
0045         m_handleItems[i]->setBrush(rectColor);
0046         m_handleItems[i]->setCursor(shapes[i % 4]);
0047     }
0048     //more initialization
0049     QObject::setParent(scene); //delete myself automatically when the scene is destroyed
0050 }
0051 
0052 void Palapeli::ConstraintVisualizer::start (const QRectF& sceneRect,
0053                         const int thickness)
0054 {
0055     // Puzzle loading nearly finished: add resize handles and shadow areas.
0056     if (!m_isStopped) {
0057         return;     // Duplicate call.
0058     }
0059     m_thickness = thickness;
0060     this->update(sceneRect);
0061     m_scene->addItem(this);
0062     setZValue(-1);
0063 
0064     // NOTE: The QueuedConnection is necessary because setSceneRect() sends
0065     // out the sceneRectChanged() signal before it disables automatic
0066     // growing of the scene rect. If the connection was direct, we could
0067     // thus enter an infinite loop when the constraint visualizer enlarges
0068     // itself in reaction to the changed sceneRect, thereby changing the
0069     // autogrowing sceneRect again.
0070     connect(m_scene, &Palapeli::Scene::sceneRectChanged, this, &ConstraintVisualizer::update, Qt::QueuedConnection);
0071     m_isStopped = false;
0072 }
0073 
0074 void Palapeli::ConstraintVisualizer::stop()
0075 {
0076     if (m_isStopped) {
0077         return;     // Starting first loadPuzzle(): nothing to do.
0078     }
0079     m_scene->removeItem(this);
0080     disconnect(m_scene, &QGraphicsScene::sceneRectChanged, this, &ConstraintVisualizer::update);
0081     m_sceneRect = QRectF();
0082     m_isStopped = true;
0083 }
0084 
0085 bool Palapeli::ConstraintVisualizer::isActive() const
0086 {
0087     return m_active;
0088 }
0089 
0090 void Palapeli::ConstraintVisualizer::setActive(bool active)
0091 {
0092     if (m_active == active)
0093         return;
0094     m_active = active;
0095     const qreal targetOpacity = active ? 1.0 : 0.3;
0096     m_animator->setDuration(150 * qAbs(targetOpacity - opacity()));
0097     m_animator->setStartValue(opacity());
0098     m_animator->setEndValue(targetOpacity);
0099     m_animator->start();
0100 }
0101 
0102 void Palapeli::ConstraintVisualizer::update(const QRectF& sceneRect)
0103 {
0104     if (m_sceneRect == sceneRect)
0105         return;
0106     // Make sure the ConstraintVisualizer stays outside the pieces' area.
0107     QRectF minimumRect = m_scene->extPiecesBoundingRect();
0108     m_sceneRect = sceneRect;
0109     if(!sceneRect.contains(minimumRect)) {
0110         // IDW TODO - Works and seems safe,
0111         //            but it may be better for interactor to check.
0112         m_sceneRect = minimumRect;
0113         m_scene->setSceneRect(minimumRect);
0114     }
0115     // Find a fictional viewport we want to cover (except for scene rect).
0116     const qreal viewportRectSizeFactor = 10;
0117     QRectF viewportRect = m_sceneRect;
0118     viewportRect.setSize(viewportRectSizeFactor * m_sceneRect.size());
0119     viewportRect.moveCenter(m_sceneRect.center());
0120     // The shadow areas are the areas outside the puzzle table.
0121     //adjust left shadow area
0122     QRectF itemRect = viewportRect;
0123     itemRect.setRight(m_sceneRect.left());
0124     m_shadowItems[LeftSide]->setRect(itemRect);
0125     //adjust right shadow area
0126     itemRect = viewportRect;
0127     itemRect.setLeft(m_sceneRect.right());
0128     m_shadowItems[RightSide]->setRect(itemRect);
0129     //adjust top shadow area
0130     itemRect = viewportRect;
0131     itemRect.setBottom(m_sceneRect.top());
0132     itemRect.setLeft(m_sceneRect.left()); //do not overlap left area...
0133     itemRect.setRight(m_sceneRect.right()); //..and right area
0134     m_shadowItems[TopSide]->setRect(itemRect);
0135     //adjust bottom shadow area
0136     itemRect = viewportRect;
0137     itemRect.setTop(m_sceneRect.bottom());
0138     itemRect.setLeft(m_sceneRect.left()); //same as above
0139     itemRect.setRight(m_sceneRect.right());
0140     m_shadowItems[BottomSide]->setRect(itemRect);
0141     //
0142     // The handles are the draggable borders of the puzzle table.
0143     //adjust edge handles
0144     // IDW test.QRectF handleRect(QPointF(), handleSize);
0145     QRectF handleRect(QPointF(), QSizeF(m_thickness, m_thickness));
0146     handleRect.moveTopLeft(m_sceneRect.topLeft());
0147     m_handleItems[TopLeftHandle]->setRect(handleRect);
0148     handleRect.moveTopRight(m_sceneRect.topRight());
0149     m_handleItems[TopRightHandle]->setRect(handleRect);
0150     handleRect.moveBottomLeft(m_sceneRect.bottomLeft());
0151     m_handleItems[BottomLeftHandle]->setRect(handleRect);
0152     handleRect.moveBottomRight(m_sceneRect.bottomRight());
0153     m_handleItems[BottomRightHandle]->setRect(handleRect);
0154     //adjust top/bottom handles
0155     // IDW test. handleRect.setSize(QSizeF(m_sceneRect.width() - 2 * handleSize.width(), handleSize.height()));
0156     handleRect.setSize(QSizeF(m_sceneRect.width() - 2 * m_thickness,
0157                 m_thickness));
0158     handleRect.moveCenter(m_sceneRect.center());
0159     handleRect.moveTop(m_sceneRect.top());
0160     m_handleItems[TopHandle]->setRect(handleRect);
0161     handleRect.moveBottom(m_sceneRect.bottom());
0162     m_handleItems[BottomHandle]->setRect(handleRect);
0163     //adjust left/right handles
0164     // IDW test. handleRect.setSize(QSizeF(handleSize.width(), m_sceneRect.height() - 2 * handleSize.height()));
0165     handleRect.setSize(QSizeF(m_thickness,
0166                 m_sceneRect.height() - 2 * m_thickness));
0167     handleRect.moveCenter(m_sceneRect.center());
0168     handleRect.moveLeft(m_sceneRect.left());
0169     m_handleItems[LeftHandle]->setRect(handleRect);
0170     handleRect.moveRight(m_sceneRect.right());
0171     m_handleItems[RightHandle]->setRect(handleRect);
0172 }
0173 
0174 #include "moc_constraintvisualizer.cpp"