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

285 lines
9.8 KiB
C++

#include "DefaultNodePainter.hpp"
#include <cmath>
#include <QtCore/QMargins>
#include "AbstractGraphModel.hpp"
#include "AbstractNodeGeometry.hpp"
#include "BasicGraphicsScene.hpp"
#include "ConnectionGraphicsObject.hpp"
#include "ConnectionIdUtils.hpp"
#include "NodeGraphicsObject.hpp"
#include "NodeState.hpp"
#include "StyleCollection.hpp"
namespace QtNodes {
void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const
{
// TODO?
//AbstractNodeGeometry & geometry = ngo.nodeScene()->nodeGeometry();
//geometry.recomputeSizeIfFontChanged(painter->font());
drawNodeRect(painter, ngo);
drawConnectionPoints(painter, ngo);
drawFilledConnectionPoints(painter, ngo);
drawNodeCaption(painter, ngo);
drawEntryLabels(painter, ngo);
drawResizeRect(painter, ngo);
}
void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const
{
AbstractGraphModel &model = ngo.graphModel();
NodeId const nodeId = ngo.nodeId();
AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry();
QSize size = geometry.size(nodeId);
QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style));
NodeStyle nodeStyle(json.object());
auto color = ngo.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor;
// if (ngo.nodeState().hovered()) {
// QPen p(color, nodeStyle.HoveredPenWidth);
// painter->setPen(p);
// } else {
// QPen p(color, nodeStyle.PenWidth);
// painter->setPen(p);
// }
QPen pen = painter->pen();
pen.setBrush(color);
pen.setWidth(2);
painter->setPen(pen);
painter->setBrush(nodeStyle.GradientColor0);
QRectF boundary(0, 0, size.width(), size.height());
painter->drawRect(boundary);
}
void DefaultNodePainter::drawConnectionPoints(QPainter *painter, NodeGraphicsObject &ngo) const
{
AbstractGraphModel &model = ngo.graphModel();
NodeId const nodeId = ngo.nodeId();
AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry();
QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style));
NodeStyle nodeStyle(json.object());
auto const &connectionStyle = StyleCollection::connectionStyle();
float diameter = nodeStyle.ConnectionPointDiameter;
auto reducedDiameter = diameter * 0.6;
for (PortType portType : {PortType::Out, PortType::In}) {
size_t const n = model
.nodeData(nodeId,
(portType == PortType::Out) ? NodeRole::OutPortCount
: NodeRole::InPortCount)
.toUInt();
for (PortIndex portIndex = 0; portIndex < n; ++portIndex) {
QPointF p = geometry.portPosition(nodeId, portType, portIndex);
auto const &dataType = model.portData(nodeId, portType, portIndex, PortRole::DataType)
.value<NodeDataType>();
double r = 1.0;
NodeState const &state = ngo.nodeState();
if (auto const *cgo = state.connectionForReaction()) {
PortType requiredPort = cgo->connectionState().requiredPort();
if (requiredPort == portType) {
ConnectionId possibleConnectionId = makeCompleteConnectionId(cgo->connectionId(),
nodeId,
portIndex);
bool const possible = model.connectionPossible(possibleConnectionId);
auto cp = cgo->sceneTransform().map(cgo->endPoint(requiredPort));
cp = ngo.sceneTransform().inverted().map(cp);
auto diff = cp - p;
double dist = std::sqrt(QPointF::dotProduct(diff, diff));
if (possible) {
double const thres = 40.0;
r = (dist < thres) ? (2.0 - dist / thres) : 1.0;
} else {
double const thres = 80.0;
r = (dist < thres) ? (dist / thres) : 1.0;
}
}
}
if (connectionStyle.useDataDefinedColors()) {
painter->setBrush(connectionStyle.normalColor(dataType.id));
} else {
painter->setBrush(nodeStyle.ConnectionPointColor);
}
painter->drawEllipse(p, reducedDiameter * r, reducedDiameter * r);
}
}
if (ngo.nodeState().connectionForReaction()) {
ngo.nodeState().resetConnectionForReaction();
}
}
void DefaultNodePainter::drawFilledConnectionPoints(QPainter *painter, NodeGraphicsObject &ngo) const
{
AbstractGraphModel &model = ngo.graphModel();
NodeId const nodeId = ngo.nodeId();
AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry();
QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style));
NodeStyle nodeStyle(json.object());
auto diameter = nodeStyle.ConnectionPointDiameter;
for (PortType portType : {PortType::Out, PortType::In}) {
size_t const n = model
.nodeData(nodeId,
(portType == PortType::Out) ? NodeRole::OutPortCount
: NodeRole::InPortCount)
.toUInt();
for (PortIndex portIndex = 0; portIndex < n; ++portIndex) {
QPointF p = geometry.portPosition(nodeId, portType, portIndex);
auto const &connected = model.connections(nodeId, portType, portIndex);
if (!connected.empty()) {
auto const &dataType = model
.portData(nodeId, portType, portIndex, PortRole::DataType)
.value<NodeDataType>();
auto const &connectionStyle = StyleCollection::connectionStyle();
if (connectionStyle.useDataDefinedColors()) {
QColor const c = connectionStyle.normalColor(dataType.id);
painter->setPen(c);
painter->setBrush(c);
} else {
painter->setPen(nodeStyle.FilledConnectionPointColor);
painter->setBrush(nodeStyle.FilledConnectionPointColor);
}
painter->drawEllipse(p, diameter * 0.4, diameter * 0.4);
}
}
}
}
void DefaultNodePainter::drawNodeCaption(QPainter *painter, NodeGraphicsObject &ngo) const
{
AbstractGraphModel &model = ngo.graphModel();
NodeId const nodeId = ngo.nodeId();
AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry();
if (!model.nodeData(nodeId, NodeRole::CaptionVisible).toBool())
return;
QString const name = model.nodeData(nodeId, NodeRole::Caption).toString();
QFont f({ "Arial", 10 });
f.setBold(true);
QPointF position = geometry.captionPosition(nodeId);
QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style));
NodeStyle nodeStyle(json.object());
painter->setBrush(QBrush("#f7aa1b"));
QFontMetrics metrics(f);
auto fontRect = metrics.boundingRect(name);
QSize sizeH = geometry.size(nodeId);
QRectF titleRect;
int w = sizeH.width();
// titleRect.setX(2);
// titleRect.setY(2);
titleRect.setWidth(w);
titleRect.setHeight(fontRect.height() + position.ry());
QPen pen = painter->pen();
pen.setWidth(0);
painter->setPen(pen);
painter->drawRect(titleRect);
painter->setFont(f);
painter->setPen(Qt::black);
painter->drawText(position, name);
f.setBold(false);
painter->setFont(f);
}
void DefaultNodePainter::drawEntryLabels(QPainter *painter, NodeGraphicsObject &ngo) const
{
AbstractGraphModel &model = ngo.graphModel();
NodeId const nodeId = ngo.nodeId();
AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry();
QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style));
NodeStyle nodeStyle(json.object());
for (PortType portType : {PortType::Out, PortType::In}) {
unsigned int n = model.nodeData<unsigned int>(nodeId,
(portType == PortType::Out)
? NodeRole::OutPortCount
: NodeRole::InPortCount);
for (PortIndex portIndex = 0; portIndex < n; ++portIndex) {
auto const &connected = model.connections(nodeId, portType, portIndex);
QPointF p = geometry.portTextPosition(nodeId, portType, portIndex);
if (connected.empty())
painter->setPen(nodeStyle.FontColorFaded);
else
painter->setPen(nodeStyle.FontColor);
QString s;
if (model.portData<bool>(nodeId, portType, portIndex, PortRole::CaptionVisible)) {
s = model.portData<QString>(nodeId, portType, portIndex, PortRole::Caption);
} else {
auto portData = model.portData(nodeId, portType, portIndex, PortRole::DataType);
s = portData.value<NodeDataType>().name;
}
painter->drawText(p, s);
}
}
}
void DefaultNodePainter::drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const
{
AbstractGraphModel &model = ngo.graphModel();
NodeId const nodeId = ngo.nodeId();
AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry();
if (model.nodeFlags(nodeId) & NodeFlag::Resizable) {
painter->setBrush(Qt::gray);
painter->drawEllipse(geometry.resizeHandleRect(nodeId));
}
}
} // namespace QtNodes