Qt编程:QNodeEditor中的NodePainter

NodePainter 是 QtNodes 框架中负责绘制节点外观的类,它提供了一系列静态方法来绘制节点的各个部分。

主要功能

  1. 整体绘制:通过 paint() 方法协调绘制节点的所有组成部分

  2. 模块化绘制:将节点分解为多个部分分别绘制

  3. 样式支持:支持节点样式的自定义

#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&)

  • 功能:绘制整个节点的入口方法

  • 流程

    1. 重新计算节点几何尺寸

    2. 依次调用各部分的绘制方法

    3. 调用自定义绘制委托(如果有)

2. drawNodeRect()

  • 功能:绘制节点的主体矩形

  • 特性

    • 根据节点是否被选中或悬停使用不同样式

    • 使用渐变填充

    • 绘制圆角矩形边界

3. drawConnectionPoints()

  • 功能:绘制节点的连接点(端口)

  • 特性

    • 处理输入和输出端口

    • 根据连接状态和数据类型显示不同样式

    • 支持拖拽连接时的动态效果

4. drawFilledConnectionPoints()

  • 功能:绘制已连接的端口

  • 特性

    • 使用不同颜色标记已连接的端口

    • 支持数据类型定义的颜色

5. drawModelName()

  • 功能:绘制节点标题

  • 特性

    • 居中显示

    • 使用粗体字体

    • 可配置是否显示

6. drawEntryLabels()

  • 功能:绘制端口标签

  • 特性

    • 显示端口名称或数据类型

    • 输入端口标签左对齐,输出端口标签右对齐

    • 根据连接状态使用不同颜色

7. drawResizeRect()

  • 功能:绘制调整大小控制点

  • 特性

    • 仅当节点可调整大小时显示

    • 显示为灰色小圆点

8. drawValidationRect()

  • 功能:绘制验证状态区域

  • 特性

    • 显示验证错误或警告

    • 在节点底部显示

    • 包含验证消息文本

设计特点

  1. 静态方法设计:所有方法都是静态的,无需实例化即可使用

  2. 模块化:每个绘制部分有独立的方法,便于维护和扩展

  3. 样式分离:绘制逻辑与样式数据分离,通过 NodeStyle 和 ConnectionStyle 管理

  4. 状态感知:根据节点状态(选中、悬停、验证状态等)调整绘制效果

  5. 可扩展性:支持通过 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]
    |                  ↑             ↑
    └──────────────────┴─────────────┘

数据流动示例(以绘制连接点为例)

  1. NodeGraphicsObject::paint() 调用 NodePainter::paint()

  2. NodePainter 从 Node 获取 NodeState 检查连接状态

  3. 从 NodeDataModel 获取数据类型和连接策略

  4. 从 NodeGeometry 获取端口位置

  5. 从 FlowScene 检查类型转换可能性

  6. 从 StyleCollection 获取连接样式

  7. 根据以上所有信息绘制连接点

这种设计使得 NodePainter 能够集中处理所有绘制逻辑,同时保持与框架其他组件的松耦合关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值