NodePainter
是 QtNodes 框架中负责绘制节点外观的类,它提供了一系列静态方法来绘制节点的各个部分。
主要功能
-
整体绘制:通过
paint()
方法协调绘制节点的所有组成部分 -
模块化绘制:将节点分解为多个部分分别绘制
-
样式支持:支持节点样式的自定义
#pragma once
#include <QtGui/QPainter>
namespace QtNodes
{
class Node;
class NodeState;
class NodeGeometry;
class NodeGraphicsObject;
class NodeDataModel;
class FlowItemEntry;
class FlowScene;
class NodePainter
{
public:
NodePainter();
public:
static void paint(QPainter *painter, Node &node, FlowScene const &scene);
static void drawNodeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model,
NodeGraphicsObject const &graphicsObject);
static void drawModelName(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model);
static void drawEntryLabels(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model);
static void drawConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model,
FlowScene const &scene);
static void drawFilledConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model);
static void drawResizeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model);
static void drawValidationRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model,
NodeGraphicsObject const &graphicsObject);
};
} // namespace QtNodes
#include "NodePainter.hpp"
#include "FlowScene.hpp"
#include "Node.hpp"
#include "NodeDataModel.hpp"
#include "NodeGeometry.hpp"
#include "NodeGraphicsObject.hpp"
#include "NodeState.hpp"
#include "PortType.hpp"
#include "StyleCollection.hpp"
#include <QtCore/QMargins>
#include <cmath>
using QtNodes::FlowScene;
using QtNodes::Node;
using QtNodes::NodeDataModel;
using QtNodes::NodeGeometry;
using QtNodes::NodeGraphicsObject;
using QtNodes::NodePainter;
using QtNodes::NodeState;
void NodePainter::paint(QPainter *painter, Node &node, FlowScene const &scene)
{
NodeGeometry const &geom = node.nodeGeometry();
NodeState const &state = node.nodeState();
NodeGraphicsObject const &graphicsObject = node.nodeGraphicsObject();
geom.recalculateSize(painter->font());
//--------------------------------------------
NodeDataModel const *model = node.nodeDataModel();
drawNodeRect(painter, geom, model, graphicsObject);
drawConnectionPoints(painter, geom, state, model, scene);
drawFilledConnectionPoints(painter, geom, state, model);
drawModelName(painter, geom, state, model);
drawEntryLabels(painter, geom, state, model);
drawResizeRect(painter, geom, model);
drawValidationRect(painter, geom, model, graphicsObject);
/// call custom painter
if (auto painterDelegate = model->painterDelegate())
{
painterDelegate->paint(painter, geom, model);
}
}
void NodePainter::drawNodeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model, NodeGraphicsObject const &graphicsObject)
{
NodeStyle const &nodeStyle = model->nodeStyle();
auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor;
if (geom.hovered())
{
QPen p(color, nodeStyle.HoveredPenWidth);
painter->setPen(p);
}
else
{
QPen p(color, nodeStyle.PenWidth);
painter->setPen(p);
}
QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(2.0, geom.height()));
gradient.setColorAt(0.0, nodeStyle.GradientColor0);
gradient.setColorAt(0.03, nodeStyle.GradientColor1);
gradient.setColorAt(0.97, nodeStyle.GradientColor2);
gradient.setColorAt(1.0, nodeStyle.GradientColor3);
painter->setBrush(gradient);
float diam = nodeStyle.ConnectionPointDiameter;
QRectF boundary(-diam, -diam, 2.0 * diam + geom.width(), 2.0 * diam + geom.height());
double const radius = 3.0;
painter->drawRoundedRect(boundary, radius, radius);
}
void NodePainter::drawConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model,
FlowScene const &scene)
{
NodeStyle const &nodeStyle = model->nodeStyle();
auto const &connectionStyle = StyleCollection::connectionStyle();
float diameter = nodeStyle.ConnectionPointDiameter;
auto reducedDiameter = diameter * 0.6;
for (PortType portType : { PortType::Out, PortType::In })
{
size_t n = state.getEntries(portType).size();
for (unsigned int i = 0; i < n; ++i)
{
QPointF p = geom.portScenePosition(i, portType);
auto const &dataType = model->dataType(portType, i);
bool canConnect =
(state.getEntries(portType)[i].empty() || model->portConnectionPolicy(portType, i) == NodeDataModel::ConnectionPolicy::Many);
double r = 1.0;
if (state.isReacting() && canConnect && portType == state.reactingPortType())
{
auto diff = geom.draggingPos() - p;
double dist = std::sqrt(QPointF::dotProduct(diff, diff));
bool typeConvertable = false;
{
if (portType == PortType::In)
{
typeConvertable = scene.registry().getTypeConverter(state.reactingDataType()->id(), dataType->id()) != nullptr;
}
else
{
typeConvertable = scene.registry().getTypeConverter(dataType->id(), state.reactingDataType()->id()) != nullptr;
}
}
if (*state.reactingDataType() == *dataType || typeConvertable)
{
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);
}
};
}
void NodePainter::drawFilledConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model)
{
NodeStyle const &nodeStyle = model->nodeStyle();
auto const &connectionStyle = StyleCollection::connectionStyle();
auto diameter = nodeStyle.ConnectionPointDiameter;
for (PortType portType : { PortType::Out, PortType::In })
{
size_t n = state.getEntries(portType).size();
for (size_t i = 0; i < n; ++i)
{
QPointF p = geom.portScenePosition(i, portType);
if (!state.getEntries(portType)[i].empty())
{
auto const &dataType = model->dataType(portType, i);
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 NodePainter::drawModelName(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model)
{
NodeStyle const &nodeStyle = model->nodeStyle();
Q_UNUSED(state);
if (!model->captionVisible())
return;
QString const &name = model->caption();
QFont f = painter->font();
f.setBold(true);
QFontMetrics metrics(f);
auto rect = metrics.boundingRect(name);
QPointF position((geom.width() - rect.width()) / 2.0, (geom.spacing() + geom.entryHeight()) / 3.0);
painter->setFont(f);
painter->setPen(nodeStyle.FontColor);
painter->drawText(position, name);
f.setBold(false);
painter->setFont(f);
}
void NodePainter::drawEntryLabels(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model)
{
QFontMetrics const &metrics = painter->fontMetrics();
for (PortType portType : { PortType::Out, PortType::In })
{
auto const &nodeStyle = model->nodeStyle();
auto &entries = state.getEntries(portType);
size_t n = entries.size();
for (size_t i = 0; i < n; ++i)
{
QPointF p = geom.portScenePosition(i, portType);
if (entries[i].empty())
painter->setPen(nodeStyle.FontColorFaded);
else
painter->setPen(nodeStyle.FontColor);
QString s;
if (model->portCaptionVisible(portType, i))
{
s = model->portCaption(portType, i);
}
else
{
s = model->dataType(portType, i)->name();
}
auto rect = metrics.boundingRect(s);
p.setY(p.y() + rect.height() / 4.0);
switch (portType)
{
case PortType::In: p.setX(5.0); break;
case PortType::Out: p.setX(geom.width() - 5.0 - rect.width()); break;
default: break;
}
painter->drawText(p, s);
}
}
}
void NodePainter::drawResizeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model)
{
if (model->resizable())
{
painter->setBrush(Qt::gray);
painter->drawEllipse(geom.resizeRect());
}
}
void NodePainter::drawValidationRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model,
NodeGraphicsObject const &graphicsObject)
{
auto modelValidationState = model->validationState();
if (modelValidationState != NodeValidationState::Valid)
{
NodeStyle const &nodeStyle = model->nodeStyle();
auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor;
if (geom.hovered())
{
QPen p(color, nodeStyle.HoveredPenWidth);
painter->setPen(p);
}
else
{
QPen p(color, nodeStyle.PenWidth);
painter->setPen(p);
}
// Drawing the validation message background
if (modelValidationState == NodeValidationState::Error)
{
painter->setBrush(nodeStyle.ErrorColor);
}
else
{
painter->setBrush(nodeStyle.WarningColor);
}
double const radius = 3.0;
float diam = nodeStyle.ConnectionPointDiameter;
QRectF boundary(-diam, -diam + geom.height() - geom.validationHeight(), 2.0 * diam + geom.width(), 2.0 * diam + geom.validationHeight());
painter->drawRoundedRect(boundary, radius, radius);
painter->setBrush(Qt::gray);
// Drawing the validation message itself
QString const &errorMsg = model->validationMessage();
QFont f = painter->font();
QFontMetrics metrics(f);
auto rect = metrics.boundingRect(errorMsg);
QPointF position((geom.width() - rect.width()) / 2.0, geom.height() - (geom.validationHeight() - diam) / 2.0);
painter->setFont(f);
painter->setPen(nodeStyle.FontColor);
painter->drawText(position, errorMsg);
}
}
主要方法
1. paint(QPainter*, Node&, FlowScene const&)
-
功能:绘制整个节点的入口方法
-
流程:
-
重新计算节点几何尺寸
-
依次调用各部分的绘制方法
-
调用自定义绘制委托(如果有)
-
2. drawNodeRect()
-
功能:绘制节点的主体矩形
-
特性:
-
根据节点是否被选中或悬停使用不同样式
-
使用渐变填充
-
绘制圆角矩形边界
-
3. drawConnectionPoints()
-
功能:绘制节点的连接点(端口)
-
特性:
-
处理输入和输出端口
-
根据连接状态和数据类型显示不同样式
-
支持拖拽连接时的动态效果
-
4. drawFilledConnectionPoints()
-
功能:绘制已连接的端口
-
特性:
-
使用不同颜色标记已连接的端口
-
支持数据类型定义的颜色
-
5. drawModelName()
-
功能:绘制节点标题
-
特性:
-
居中显示
-
使用粗体字体
-
可配置是否显示
-
6. drawEntryLabels()
-
功能:绘制端口标签
-
特性:
-
显示端口名称或数据类型
-
输入端口标签左对齐,输出端口标签右对齐
-
根据连接状态使用不同颜色
-
7. drawResizeRect()
-
功能:绘制调整大小控制点
-
特性:
-
仅当节点可调整大小时显示
-
显示为灰色小圆点
-
8. drawValidationRect()
-
功能:绘制验证状态区域
-
特性:
-
显示验证错误或警告
-
在节点底部显示
-
包含验证消息文本
-
设计特点
-
静态方法设计:所有方法都是静态的,无需实例化即可使用
-
模块化:每个绘制部分有独立的方法,便于维护和扩展
-
样式分离:绘制逻辑与样式数据分离,通过
NodeStyle
和ConnectionStyle
管理 -
状态感知:根据节点状态(选中、悬停、验证状态等)调整绘制效果
-
可扩展性:支持通过
painterDelegate
进行自定义绘制
使用场景
NodePainter
主要在 NodeGraphicsObject
的 paint()
方法中被调用,用于渲染节点的可视化表示。它处理了节点在正常状态、交互状态(如拖拽连接)和错误状态下的所有可视化表现。
这个类的设计使得节点的外观可以高度定制化,同时保持核心绘制逻辑的集中管理。
NodePainter 与其他核心类的关联
NodePainter
在 QtNodes 框架中不是孤立存在的,它与多个核心类有紧密的协作关系。以下是主要的关联关系:
1. 与 Node 类的关联
核心关系:
-
NodePainter::paint()
直接接受Node&
参数作为绘制目标 -
通过 Node 对象获取其他组件:
-
node.nodeGeometry()
- 获取几何信息 -
node.nodeState()
- 获取状态信息 -
node.nodeGraphicsObject()
- 获取图形对象 -
node.nodeDataModel()
- 获取数据模型
-
交互流程:
NodeGraphicsObject::paint() → NodePainter::paint() → 使用 Node 的各个组件进行绘制
2. 与 NodeGeometry 类的关联
数据依赖:
-
几乎所有绘制方法都接收
NodeGeometry const&
参数 -
依赖 NodeGeometry 提供的信息:
-
节点尺寸和位置
-
端口位置计算
-
验证区域高度
-
调整大小控制点位置
-
关键方法:
-
portScenePosition()
- 获取端口绘制位置 -
width()/height()
- 获取节点尺寸 -
resizeRect()
- 获取调整大小控制点区域
3. 与 NodeState 类的关联
状态依赖:
-
使用 NodeState 获取当前连接状态
-
检查反应状态(拖拽连接时)
-
获取端口条目信息
关键数据:
-
getEntries()
- 获取端口连接信息 -
reactingPortType()
- 获取正在交互的端口类型 -
reactingDataType()
- 获取正在交互的数据类型
4. 与 NodeDataModel 类的关联
模型依赖:
-
几乎所有绘制方法都接收
NodeDataModel const*
参数 -
依赖模型提供的信息:
-
节点样式 (
nodeStyle()
) -
数据类型信息
-
验证状态和消息
-
端口连接策略
-
标题和标签文本
-
关键方法:
-
nodeStyle()
- 获取节点视觉样式 -
dataType()
- 获取端口数据类型 -
validationState()
- 获取验证状态 -
portCaption()
- 获取端口标签文本
5. 与 NodeGraphicsObject 类的关联
交互依赖:
-
获取选中状态 (
isSelected()
) -
获取悬停状态 (
hovered()
) -
支持自定义绘制委托 (
painterDelegate()
)
关键作用:
-
影响节点边框颜色(选中/未选中)
-
提供悬停状态反馈
-
允许模型扩展绘制逻辑
6. 与 FlowScene 类的关联
场景依赖:
-
主要在
drawConnectionPoints()
中使用 -
用于类型转换检查:
-
scene.registry().getTypeConverter()
-
关键作用:
-
确定不同类型之间是否可以转换
-
影响拖拽连接时的视觉反馈
7. 与 StyleCollection 类的关联
样式依赖:
-
获取连接样式:
StyleCollection::connectionStyle()
关键作用:
-
确定连接点的颜色和样式
-
支持数据类型特定的颜色
类关系图
[FlowScene] ←─ [NodePainter] → [StyleCollection] ↑ ↑ [NodeGraphicsObject] ← [Node] → [NodeDataModel] | | | | [NodeGeometry] [NodeState] | ↑ ↑ └──────────────────┴─────────────┘
数据流动示例(以绘制连接点为例)
-
NodeGraphicsObject::paint()
调用NodePainter::paint()
-
NodePainter
从Node
获取NodeState
检查连接状态 -
从
NodeDataModel
获取数据类型和连接策略 -
从
NodeGeometry
获取端口位置 -
从
FlowScene
检查类型转换可能性 -
从
StyleCollection
获取连接样式 -
根据以上所有信息绘制连接点
这种设计使得 NodePainter
能够集中处理所有绘制逻辑,同时保持与框架其他组件的松耦合关系。