open-story-teller/story-editor/nodeeditor/src/ConnectionGraphicsObject.cpp
2023-05-28 14:50:48 +02:00

381 lines
9.7 KiB
C++

#include "ConnectionGraphicsObject.hpp"
#include "AbstractGraphModel.hpp"
#include "AbstractNodeGeometry.hpp"
#include "BasicGraphicsScene.hpp"
#include "ConnectionIdUtils.hpp"
#include "ConnectionPainter.hpp"
#include "ConnectionState.hpp"
#include "ConnectionStyle.hpp"
#include "NodeConnectionInteraction.hpp"
#include "NodeGraphicsObject.hpp"
#include "StyleCollection.hpp"
#include "locateNode.hpp"
#include <QtWidgets/QGraphicsBlurEffect>
#include <QtWidgets/QGraphicsDropShadowEffect>
#include <QtWidgets/QGraphicsSceneMouseEvent>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets/QStyleOptionGraphicsItem>
#include <QtCore/QDebug>
#include <stdexcept>
namespace QtNodes {
ConnectionGraphicsObject::ConnectionGraphicsObject(BasicGraphicsScene &scene,
ConnectionId const connectionId)
: _connectionId(connectionId)
, _graphModel(scene.graphModel())
, _connectionState(*this)
, _out{0, 0}
, _in{0, 0}
{
scene.addItem(this);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsFocusable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setAcceptHoverEvents(true);
//addGraphicsEffect();
setZValue(-1.0);
initializePosition();
}
void ConnectionGraphicsObject::initializePosition()
{
// This function is only called when the ConnectionGraphicsObject
// is newly created. At this moment both end coordinates are (0, 0)
// in Connection G.O. coordinates. The position of the whole
// Connection G. O. in scene coordinate system is also (0, 0).
// By moving the whole object to the Node Port position
// we position both connection ends correctly.
if (_connectionState.requiredPort() != PortType::None) {
PortType attachedPort = oppositePort(_connectionState.requiredPort());
PortIndex portIndex = getPortIndex(attachedPort, _connectionId);
NodeId nodeId = getNodeId(attachedPort, _connectionId);
NodeGraphicsObject *ngo = nodeScene()->nodeGraphicsObject(nodeId);
if (ngo) {
QTransform nodeSceneTransform = ngo->sceneTransform();
AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry();
QPointF pos = geometry.portScenePosition(nodeId,
attachedPort,
portIndex,
nodeSceneTransform);
this->setPos(pos);
}
}
move();
}
AbstractGraphModel &ConnectionGraphicsObject::graphModel() const
{
return _graphModel;
}
BasicGraphicsScene *ConnectionGraphicsObject::nodeScene() const
{
return dynamic_cast<BasicGraphicsScene *>(scene());
}
ConnectionId const &ConnectionGraphicsObject::connectionId() const
{
return _connectionId;
}
QRectF ConnectionGraphicsObject::boundingRect() const
{
auto points = pointsC1C2();
// `normalized()` fixes inverted rects.
QRectF basicRect = QRectF(_out, _in).normalized();
QRectF c1c2Rect = QRectF(points.first, points.second).normalized();
QRectF commonRect = basicRect.united(c1c2Rect);
auto const &connectionStyle = StyleCollection::connectionStyle();
float const diam = connectionStyle.pointDiameter();
QPointF const cornerOffset(diam, diam);
// Expand rect by port circle diameter
commonRect.setTopLeft(commonRect.topLeft() - cornerOffset);
commonRect.setBottomRight(commonRect.bottomRight() + 2 * cornerOffset);
return commonRect;
}
QPainterPath ConnectionGraphicsObject::shape() const
{
#ifdef DEBUG_DRAWING
//QPainterPath path;
//path.addRect(boundingRect());
//return path;
#else
return ConnectionPainter::getPainterStroke(*this);
#endif
}
QPointF const &ConnectionGraphicsObject::endPoint(PortType portType) const
{
Q_ASSERT(portType != PortType::None);
return (portType == PortType::Out ? _out : _in);
}
void ConnectionGraphicsObject::setEndPoint(PortType portType, QPointF const &point)
{
if (portType == PortType::In)
_in = point;
else
_out = point;
}
void ConnectionGraphicsObject::move()
{
auto moveEnd = [this](ConnectionId cId, PortType portType) {
NodeId nodeId = getNodeId(portType, cId);
if (nodeId == InvalidNodeId)
return;
NodeGraphicsObject *ngo = nodeScene()->nodeGraphicsObject(nodeId);
if (ngo) {
AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry();
QPointF scenePos = geometry.portScenePosition(nodeId,
portType,
getPortIndex(portType, cId),
ngo->sceneTransform());
QPointF connectionPos = sceneTransform().inverted().map(scenePos);
setEndPoint(portType, connectionPos);
}
};
moveEnd(_connectionId, PortType::Out);
moveEnd(_connectionId, PortType::In);
prepareGeometryChange();
update();
}
ConnectionState const &ConnectionGraphicsObject::connectionState() const
{
return _connectionState;
}
ConnectionState &ConnectionGraphicsObject::connectionState()
{
return _connectionState;
}
void ConnectionGraphicsObject::paint(QPainter *painter,
QStyleOptionGraphicsItem const *option,
QWidget *)
{
if (!scene())
return;
painter->setClipRect(option->exposedRect);
ConnectionPainter::paint(painter, *this);
}
void ConnectionGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem::mousePressEvent(event);
}
void ConnectionGraphicsObject::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
prepareGeometryChange();
auto view = static_cast<QGraphicsView *>(event->widget());
auto ngo = locateNodeAt(event->scenePos(), *nodeScene(), view->transform());
if (ngo) {
ngo->reactToConnection(this);
_connectionState.setLastHoveredNode(ngo->nodeId());
} else {
_connectionState.resetLastHoveredNode();
}
//-------------------
auto requiredPort = _connectionState.requiredPort();
if (requiredPort != PortType::None) {
setEndPoint(requiredPort, event->pos());
}
//-------------------
update();
event->accept();
}
void ConnectionGraphicsObject::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem::mouseReleaseEvent(event);
ungrabMouse();
event->accept();
auto view = static_cast<QGraphicsView *>(event->widget());
Q_ASSERT(view);
auto ngo = locateNodeAt(event->scenePos(), *nodeScene(), view->transform());
bool wasConnected = false;
if (ngo) {
NodeConnectionInteraction interaction(*ngo, *this, *nodeScene());
wasConnected = interaction.tryConnect();
}
// If connection attempt was unsuccessful
if (!wasConnected) {
// Resulting unique_ptr is not used and automatically deleted.
nodeScene()->resetDraftConnection();
}
}
void ConnectionGraphicsObject::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
_connectionState.setHovered(true);
update();
// Signal
nodeScene()->connectionHovered(connectionId(), event->screenPos());
event->accept();
}
void ConnectionGraphicsObject::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
_connectionState.setHovered(false);
update();
// Signal
nodeScene()->connectionHoverLeft(connectionId());
event->accept();
}
std::pair<QPointF, QPointF> ConnectionGraphicsObject::pointsC1C2() const
{
switch (nodeScene()->orientation()) {
case Qt::Horizontal:
return pointsC1C2Horizontal();
break;
case Qt::Vertical:
return pointsC1C2Vertical();
break;
}
throw std::logic_error("Unreachable code after switch statement");
}
void ConnectionGraphicsObject::addGraphicsEffect()
{
auto effect = new QGraphicsBlurEffect;
effect->setBlurRadius(5);
setGraphicsEffect(effect);
//auto effect = new QGraphicsDropShadowEffect;
//auto effect = new ConnectionBlurEffect(this);
//effect->setOffset(4, 4);
//effect->setColor(QColor(Qt::gray).darker(800));
}
std::pair<QPointF, QPointF> ConnectionGraphicsObject::pointsC1C2Horizontal() const
{
double const defaultOffset = 200;
double xDistance = _in.x() - _out.x();
double horizontalOffset = qMin(defaultOffset, std::abs(xDistance));
double verticalOffset = 0;
double ratioX = 0.5;
if (xDistance <= 0) {
double yDistance = _in.y() - _out.y() + 20;
double vector = yDistance < 0 ? -1.0 : 1.0;
verticalOffset = qMin(defaultOffset, std::abs(yDistance)) * vector;
ratioX = 1.0;
}
horizontalOffset *= ratioX;
QPointF c1(_out.x() + horizontalOffset, _out.y() + verticalOffset);
QPointF c2(_in.x() - horizontalOffset, _in.y() - verticalOffset);
return std::make_pair(c1, c2);
}
std::pair<QPointF, QPointF> ConnectionGraphicsObject::pointsC1C2Vertical() const
{
double const defaultOffset = 200;
double yDistance = _in.y() - _out.y();
double verticalOffset = qMin(defaultOffset, std::abs(yDistance));
double horizontalOffset = 0;
double ratioY = 0.5;
if (yDistance <= 0) {
double xDistance = _in.x() - _out.x() + 20;
double vector = xDistance < 0 ? -1.0 : 1.0;
horizontalOffset = qMin(defaultOffset, std::abs(xDistance)) * vector;
ratioY = 1.0;
}
verticalOffset *= ratioY;
QPointF c1(_out.x() + horizontalOffset, _out.y() + verticalOffset);
QPointF c2(_in.x() - horizontalOffset, _in.y() - verticalOffset);
return std::make_pair(c1, c2);
}
} // namespace QtNodes