valentina/src/app/puzzle/scene/vpgraphicspiececontrols.cpp
Roman Telezhynskyi 3fbe96c2a7 Piece rotation.
2021-08-09 15:09:10 +03:00

737 lines
24 KiB
C++

/************************************************************************
**
** @file vpgraphicspiececontrols.cpp
** @author Roman Telezhynskyi <dismine(at)gmail.com>
** @date 2 8, 2021
**
** @brief
** @copyright
** This source code is part of the Valentina project, a pattern making
** program, whose allow create and modeling patterns of clothing.
** Copyright (C) 2021 Valentina project
** <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
**
** Valentina is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** Valentina is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Valentina. If not, see <http://www.gnu.org/licenses/>.
**
*************************************************************************/
#include "vpgraphicspiececontrols.h"
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QIcon>
#include <QPainter>
#include <QtDebug>
#include "../vmisc/compatibility.h"
#include "../vwidgets/global.h"
#include "../layout/vplayout.h"
namespace
{
const qreal arcStartAngle = 105;
const qreal arcAngle = 25;
const qreal arcRadius = 15;
const qreal arrowTail = 2;
const qreal arrowSide = 2;
const qreal arrowLength = 4;
const qreal arrow1Angle = 13;
const qreal arrow2Angle = 38;
constexpr qreal penWidth = 2;
const qreal centerRadius1 = 5;
const qreal centerRadius2 = 10;
const QColor defaultColor = Qt::black;
const QColor hoverColor = Qt::green;
enum class HandleCorner : int
{
Invalid = 0,
TopLeft = 1,
TopRight = 2,
BottomRight = 3,
BottomLeft = 4
};
auto TransformationOrigin(VPLayout *layout, const QRectF &boundingRect) -> QPointF
{
SCASSERT(layout != nullptr)
VPSheet *sheet = layout->GetFocusedSheet();
if (sheet != nullptr)
{
VPTransformationOrigon origin = sheet->TransformationOrigin();
return origin.origin;
}
return boundingRect.center();
}
} // namespace
//---------------------------------------------------------------------------------------------------------------------
VPGraphicsTransformationOrigin::VPGraphicsTransformationOrigin(VPLayout *layout, QGraphicsItem *parent)
: QGraphicsObject(parent),
m_layout(layout),
m_color(defaultColor)
{
SCASSERT(m_layout != nullptr)
setCursor(Qt::OpenHandCursor);
setZValue(1);
setAcceptHoverEvents(true);
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::SetTransformationOrigin()
{
prepareGeometryChange();
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::on_HideHandles(bool hide)
{
m_originVisible = not hide;
prepareGeometryChange();
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::on_ShowOrigin(bool show)
{
setVisible(show);
}
//---------------------------------------------------------------------------------------------------------------------
QRectF VPGraphicsTransformationOrigin::boundingRect() const
{
constexpr qreal halfPenWidth = penWidth/2.;
return Center2().boundingRect().adjusted(-halfPenWidth, -halfPenWidth, halfPenWidth, halfPenWidth);
}
//---------------------------------------------------------------------------------------------------------------------
QPainterPath VPGraphicsTransformationOrigin::shape() const
{
return Center2();
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget);
Q_UNUSED(option);
const qreal scale = SceneScale(scene());
QPen pen(m_color, penWidth/scale, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter->setPen(pen);
if (m_originVisible)
{
painter->save();
painter->setBrush(QBrush(m_color));
painter->drawPath(Center1());
painter->restore();
painter->save();
painter->setBrush(QBrush());
painter->drawPath(Center2());
painter->restore();
}
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// change the cursor when clicking the left button
if((event->button() == Qt::LeftButton))
{
setCursor(Qt::ClosedHandCursor);
event->accept();
}
else
{
//perform the default behaviour
QGraphicsObject::mousePressEvent(event);
}
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
VPSheet *sheet = m_layout->GetFocusedSheet();
if (sheet != nullptr)
{
VPTransformationOrigon origin = sheet->TransformationOrigin();
origin.origin = event->scenePos();
origin.custom = true;
sheet->SetTransformationOrigin(origin);
}
prepareGeometryChange();
QGraphicsObject::mouseMoveEvent(event);
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
//perform the default behaviour
QGraphicsItem::mouseReleaseEvent(event);
// change the cursor when clicking left button
if (event->button() == Qt::LeftButton)
{
setCursor(Qt::OpenHandCursor);
}
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
m_color = hoverColor;
QGraphicsObject::hoverEnterEvent(event);
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsTransformationOrigin::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
m_color = defaultColor;
QGraphicsObject::hoverEnterEvent(event);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsTransformationOrigin::RotationCenter(QPainter *painter) const -> QPainterPath
{
QPainterPath path;
const qreal scale = SceneScale(scene());
qreal radius = centerRadius1/scale;
QPointF transformationOrigin = TransformationOrigin(m_layout, QRectF());
QRectF rect(transformationOrigin.x()-radius, transformationOrigin.y()-radius, radius*2., radius*2.);
QPainterPath center1;
center1.addEllipse(rect);
if (painter != nullptr)
{
painter->save();
painter->setBrush(QBrush(m_color));
painter->drawPath(Center1());
painter->restore();
}
path.addPath(center1);
radius = centerRadius2/scale;
rect = QRectF(transformationOrigin.x()-radius, transformationOrigin.y()-radius, radius*2., radius*2.);
QPainterPath center2;
center2.addEllipse(rect);
if (painter != nullptr)
{
painter->save();
painter->setBrush(QBrush());
painter->drawPath(Center2());
painter->restore();
}
path.addPath(center2);
return path;
}
//---------------------------------------------------------------------------------------------------------------------
QPainterPath VPGraphicsTransformationOrigin::Center1() const
{
const qreal scale = SceneScale(scene());
qreal radius = centerRadius1/scale;
QPointF transformationOrigin = TransformationOrigin(m_layout, QRectF());
QRectF rect(transformationOrigin.x()-radius, transformationOrigin.y()-radius, radius*2., radius*2.);
QPainterPath center1;
center1.addEllipse(rect);
return center1;
}
//---------------------------------------------------------------------------------------------------------------------
QPainterPath VPGraphicsTransformationOrigin::Center2() const
{
const qreal scale = SceneScale(scene());
qreal radius = centerRadius2/scale;
QPointF transformationOrigin = TransformationOrigin(m_layout, QRectF());
QRectF rect = QRectF(transformationOrigin.x()-radius, transformationOrigin.y()-radius, radius*2., radius*2.);
QPainterPath center2;
center2.addEllipse(rect);
return center2;
}
// VPGraphicsPieceControls
//---------------------------------------------------------------------------------------------------------------------
VPGraphicsPieceControls::VPGraphicsPieceControls(VPLayout *layout, QGraphicsItem *parent)
: QGraphicsObject(parent),
m_layout(layout)
{
SCASSERT(m_layout != nullptr)
QPixmap cursor_pixmap = QIcon("://puzzleicon/svg/cursor_rotate.svg").pixmap(QSize(32,32));
setCursor(QCursor(cursor_pixmap, 16, 16));
setZValue(1);
setAcceptHoverEvents(true);
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPieceControls::on_UpdateControls()
{
m_pieceRect = PiecesBoundingRect();
setVisible(not m_pieceRect.isNull());
if (not m_pieceRect.isNull())
{
VPSheet *sheet = m_layout->GetFocusedSheet();
if (sheet != nullptr)
{
VPTransformationOrigon origin = sheet->TransformationOrigin();
if (not origin.custom)
{
origin.origin = m_pieceRect.center();
sheet->SetTransformationOrigin(origin);
emit TransformationOriginChanged();
}
}
}
emit ShowOrigin(not m_pieceRect.isNull());
prepareGeometryChange();
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPieceControls::on_HideHandles(bool hide)
{
m_controlsVisible = not hide;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::boundingRect() const -> QRectF
{
constexpr qreal halfPenWidth = penWidth/2.;
return Handles().boundingRect().adjusted(-halfPenWidth, -halfPenWidth, halfPenWidth, halfPenWidth);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::shape() const -> QPainterPath
{
return Handles();
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPieceControls::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget);
Q_UNUSED(option);
const qreal scale = SceneScale(scene());
QPen pen(defaultColor, penWidth/scale, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter->setPen(pen);
if (m_controlsVisible)
{
TopLeftControl(painter);
TopRightControl(painter);
BottomLeftControl(painter);
BottomRightControl(painter);
}
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPieceControls::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
m_rotationStartPoint = event->scenePos();
m_controlsVisible = false;
m_handleCorner = HandleCorner(event->scenePos());
prepareGeometryChange();
}
else
{
QGraphicsObject::mousePressEvent(event);
}
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPieceControls::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if((event->modifiers() & Qt::ShiftModifier) != 0U
&& static_cast<enum HandleCorner>(m_handleCorner) != HandleCorner::Invalid)
{
if (not m_originSaved)
{
VPSheet *sheet = m_layout->GetFocusedSheet();
if (sheet != nullptr)
{
m_savedOrigin = sheet->TransformationOrigin();
m_originSaved = true;
m_pieceRect = PiecesBoundingRect();
VPTransformationOrigon origin;
origin.custom = true;
if (static_cast<enum HandleCorner>(m_handleCorner) == HandleCorner::TopLeft)
{
origin.origin = m_pieceRect.topLeft();
}
else if (static_cast<enum HandleCorner>(m_handleCorner) == HandleCorner::TopRight)
{
origin.origin = m_pieceRect.topRight();
}
else if (static_cast<enum HandleCorner>(m_handleCorner) == HandleCorner::BottomRight)
{
origin.origin = m_pieceRect.bottomRight();
}
else if (static_cast<enum HandleCorner>(m_handleCorner) == HandleCorner::BottomLeft)
{
origin.origin = m_pieceRect.bottomLeft();
}
sheet->SetTransformationOrigin(origin);
emit TransformationOriginChanged();
}
}
}
else
{
if (m_originSaved)
{
VPSheet *sheet = m_layout->GetFocusedSheet();
if (sheet != nullptr)
{
if (not m_savedOrigin.custom)
{
m_pieceRect = PiecesBoundingRect();
m_savedOrigin.origin = m_pieceRect.center();
}
sheet->SetTransformationOrigin(m_savedOrigin);
emit TransformationOriginChanged();
}
m_originSaved = false;
}
}
QPointF rotationNewPoint = event->scenePos();
// get the angle from the center to the initial click point
QPointF rotationOrigin = TransformationOrigin(m_layout, m_pieceRect);
QLineF initPosition(rotationOrigin, m_rotationStartPoint);
QLineF initRotationPosition(rotationOrigin, rotationNewPoint);
qreal angle = initPosition.angleTo(initRotationPosition);
if (not qFuzzyIsNull(angle))
{
emit Rotate(rotationOrigin, angle);
}
if (m_originSaved && m_savedOrigin.custom)
{
QLineF line(rotationOrigin, m_savedOrigin.origin);
line.setAngle(line.angle()+angle);
m_savedOrigin.origin = line.p2();
}
m_rotationStartPoint = rotationNewPoint;
QGraphicsObject::mouseMoveEvent(event);
}
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPieceControls::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
m_controlsVisible = true;
if (m_originSaved)
{
VPSheet *sheet = m_layout->GetFocusedSheet();
if (sheet != nullptr)
{
if (not m_savedOrigin.custom)
{
m_pieceRect = PiecesBoundingRect();
m_savedOrigin.origin = m_pieceRect.center();
}
sheet->SetTransformationOrigin(m_savedOrigin);
emit TransformationOriginChanged();
}
m_originSaved = false;
}
on_UpdateControls();
}
QGraphicsObject::mouseReleaseEvent(event);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::TopLeftControl(QPainter *painter) const -> QPainterPath
{
return Controller(QTransform(), painter);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::TopRightControl(QPainter *painter) const -> QPainterPath
{
QTransform t;
t.scale(-1, 1);
t.translate(-(m_pieceRect.topLeft().x() * 2. + m_pieceRect.width()), 0);
return Controller(t, painter);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::BottomLeftControl(QPainter *painter) const -> QPainterPath
{
QTransform t;
t.scale(1, -1);
t.translate(0, -(m_pieceRect.topLeft().y() * 2. + m_pieceRect.height()));
return Controller(t, painter);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::BottomRightControl(QPainter *painter) const -> QPainterPath
{
QTransform t;
t.scale(-1, -1);
t.translate(-(m_pieceRect.topLeft().x() * 2. + m_pieceRect.width()),
-(m_pieceRect.topLeft().y() * 2. + m_pieceRect.height()));
return Controller(t, painter);
}
//---------------------------------------------------------------------------------------------------------------------
QPainterPath VPGraphicsPieceControls::Handles() const
{
QPainterPath path;
path.addPath(TopLeftControl());
path.addPath(TopRightControl());
path.addPath(BottomLeftControl());
path.addPath(BottomRightControl());
return path;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::Controller(const QTransform &t, QPainter *painter) const -> QPainterPath
{
if (painter != nullptr)
{
QPen pen = painter->pen();
pen.setColor(defaultColor);
painter->setPen(pen);
}
QBrush colorBrush(defaultColor);
QPainterPath controller = ArrowPath();
controller = t.map(controller);
if (painter != nullptr)
{
painter->save();
painter->setBrush(colorBrush);
painter->drawPath(controller);
painter->restore();
}
return controller;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::ControllersRect() const -> QRectF
{
const qreal scale = SceneScale(scene());
const qreal gap = 2;
QRectF rect = m_pieceRect;
const qreal minWidth = arcRadius/scale+gap;
const qreal minHeight = arcRadius/scale+gap;
if (m_pieceRect.width() < minWidth)
{
qreal diff = minWidth - m_pieceRect.width();
rect.adjust(-diff/2., 0, diff/2., 0);
}
if (m_pieceRect.height() < minHeight)
{
qreal diff = minHeight - m_pieceRect.height();
rect.adjust(0, -diff/2., 0, diff/2.);
}
return rect;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::ArrowPath() const -> QPainterPath
{
const qreal scale = SceneScale(scene());
QPainterPath arrow;
QRectF pieceRect = ControllersRect();
QLineF start(pieceRect.topLeft().x(), pieceRect.topLeft().y(),
pieceRect.topLeft().x(), pieceRect.topLeft().y() - (arcRadius+1)/scale);
start.setAngle(arcStartAngle);
arrow.moveTo(start.p2());
QLineF baseLine(start.p2(), QPointF(start.p2().x()+arrowTail/scale, start.p2().y()));
baseLine.setAngle(arrow1Angle);
arrow.lineTo(baseLine.p2());
QLineF leftSide = QLineF(baseLine.p2(), baseLine.p1());
leftSide.setLength(arrowSide/scale);
leftSide.setAngle(leftSide.angle()-90);
arrow.lineTo(leftSide.p2());
start = QLineF(pieceRect.topLeft().x(), pieceRect.topLeft().y(),
pieceRect.topLeft().x(), pieceRect.topLeft().y() - arcRadius/scale);
start.setAngle(arcStartAngle);
baseLine = QLineF(start.p2(), QPointF(start.p2().x()+(arrowTail+arrowLength)/scale, start.p2().y()));
baseLine.setAngle(arrow1Angle);
arrow.lineTo(baseLine.p2());
start = QLineF(pieceRect.topLeft().x(), pieceRect.topLeft().y(),
pieceRect.topLeft().x(), pieceRect.topLeft().y() - (arcRadius-1)/scale);
start.setAngle(arcStartAngle);
baseLine = QLineF(start.p2(), QPointF(start.p2().x()+arrowTail/scale, start.p2().y()));
baseLine.setAngle(arrow1Angle);
QLineF rightSide = QLineF(baseLine.p2(), baseLine.p1());
rightSide.setLength(arrowSide/scale);
rightSide.setAngle(rightSide.angle()+90);
arrow.lineTo(rightSide.p2());
arrow.lineTo(baseLine.p2());
arrow.lineTo(start.p2());
// arc 1
QRectF arc1Rect(pieceRect.topLeft().x()-(arcRadius-1)/scale, pieceRect.topLeft().y()-(arcRadius-1)/scale,
(arcRadius-1)/scale*2, (arcRadius-1)/scale*2);
arrow.arcTo(arc1Rect, arcStartAngle, arcAngle);
// arrow 2
start = QLineF(pieceRect.topLeft().x(), pieceRect.topLeft().y(),
pieceRect.topLeft().x(), pieceRect.topLeft().y() - (arcRadius-1)/scale);
start.setAngle(arcStartAngle+arcAngle);
baseLine = QLineF(start.p2(), QPointF(start.p2().x()+arrowTail/scale, start.p2().y()));
baseLine.setAngle(arrow2Angle+180);
arrow.lineTo(baseLine.p2());
leftSide = QLineF(baseLine.p2(), baseLine.p1());
leftSide.setLength(arrowSide/scale);
leftSide.setAngle(leftSide.angle()-90);
arrow.lineTo(leftSide.p2());
start = QLineF(pieceRect.topLeft().x(), pieceRect.topLeft().y(),
pieceRect.topLeft().x(), pieceRect.topLeft().y() - arcRadius/scale);
start.setAngle(arcStartAngle+arcAngle);
baseLine = QLineF(start.p2(), QPointF(start.p2().x()+(arrowTail+arrowLength)/scale, start.p2().y()));
baseLine.setAngle(arrow2Angle+180);
arrow.lineTo(baseLine.p2());
start = QLineF(pieceRect.topLeft().x(), pieceRect.topLeft().y(),
pieceRect.topLeft().x(), pieceRect.topLeft().y() - (arcRadius+1)/scale);
start.setAngle(arcStartAngle+arcAngle);
baseLine = QLineF(start.p2(), QPointF(start.p2().x()+arrowTail/scale, start.p2().y()));
baseLine.setAngle(arrow2Angle+180);
rightSide = QLineF(baseLine.p2(), baseLine.p1());
rightSide.setLength(arrowSide/scale);
rightSide.setAngle(rightSide.angle()+90);
arrow.lineTo(rightSide.p2());
arrow.lineTo(baseLine.p2());
arrow.lineTo(start.p2());
// arc 2
QRectF arc2Rect(pieceRect.topLeft().x()-(arcRadius+1)/scale, pieceRect.topLeft().y()-(arcRadius+1)/scale,
(arcRadius+1)/scale*2, (arcRadius+1)/scale*2);
QPainterPath arc;
start = QLineF(pieceRect.topLeft().x(), pieceRect.topLeft().y(),
pieceRect.topLeft().x(), pieceRect.topLeft().y() - (arcRadius+1)/scale);
start.setAngle(arcStartAngle);
arc.moveTo(start.p2());
arc.arcTo(arc2Rect, arcStartAngle, arcAngle);
arrow.addPath(arc.toReversed());
return arrow;
}
//---------------------------------------------------------------------------------------------------------------------
QRectF VPGraphicsPieceControls::PiecesBoundingRect() const
{
QRectF rect;
QGraphicsScene *scene = this->scene();
if (scene != nullptr)
{
QList<QGraphicsItem *> list = scene->selectedItems();
for (auto *item : list)
{
if (item->type() == UserType + static_cast<int>(PGraphicsItem::Piece))
{
rect = rect.united(item->sceneBoundingRect());
}
}
}
return rect;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPGraphicsPieceControls::HandleCorner(const QPointF &pos) const -> int
{
if (TopLeftControl().boundingRect().contains(pos))
{
return static_cast<int>(HandleCorner::BottomRight);
}
if (TopRightControl().boundingRect().contains(pos))
{
return static_cast<int>(HandleCorner::BottomLeft);
}
if (BottomLeftControl().boundingRect().contains(pos))
{
return static_cast<int>(HandleCorner::TopRight);
}
if (BottomRightControl().boundingRect().contains(pos))
{
return static_cast<int>(HandleCorner::TopLeft);
}
return static_cast<int>(HandleCorner::Invalid);
}