mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-07 09:19:57 +01:00
381 lines
9.7 KiB
C++
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
|