From 5d4bbfc758a41246a4388117872c2de471c8126b Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Wed, 12 Aug 2026 10:52:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=A2=AF=E5=BD=A2?= =?UTF-8?q?=E5=9B=BE=E7=BC=96=E8=BE=91=E5=99=A8=E7=94=BB=E5=B8=83=E5=91=88?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/ui/logic_editor_widget.cpp | 245 ++++++++++++++++++++++------- 1 file changed, 189 insertions(+), 56 deletions(-) diff --git a/app/src/ui/logic_editor_widget.cpp b/app/src/ui/logic_editor_widget.cpp index 99043c9..2bc38b4 100644 --- a/app/src/ui/logic_editor_widget.cpp +++ b/app/src/ui/logic_editor_widget.cpp @@ -2,7 +2,9 @@ #include #include +#include #include +#include #include #include @@ -15,9 +17,20 @@ namespace { constexpr qreal kMinimumSceneWidth = 1280.0; constexpr qreal kLeftRailX = 72.0; constexpr qreal kStageWidth = 184.0; -constexpr qreal kNodeWidth = 118.0; -constexpr qreal kBranchSpacing = 58.0; -constexpr qreal kRungGap = 30.0; +constexpr qreal kNodeWidth = 128.0; +constexpr qreal kNodeTerminalX = kNodeWidth / 2.0; +constexpr qreal kBranchSpacing = 96.0; +constexpr qreal kRungGap = 18.0; +constexpr qreal kRungBaseHeight = 132.0; +constexpr qreal kMainLineOffset = 78.0; +constexpr qreal kLadderLineWidth = 1.6; +constexpr qreal kPlaceholderWidth = 112.0; +constexpr qreal kPlaceholderHeight = 30.0; + +const QColor kLadderColor(QStringLiteral("#26343d")); +const QColor kSelectionColor(QStringLiteral("#dcecf4")); +const QColor kSelectionBorderColor(QStringLiteral("#1677a8")); +const QColor kPlaceholderColor(QStringLiteral("#82919b")); QString comparisonText(ComparisonOperator comparison) { @@ -33,21 +46,46 @@ QString comparisonText(ComparisonOperator comparison) } } -QString nodeAddressText(const LogicNodeConfig &config) +QString registerAddressText(const RegisterAddress &address) +{ + return QStringLiteral("%1%2") + .arg(address.area() == RegisterArea::M ? QStringLiteral("M") + : QStringLiteral("D")) + .arg(address.index()); +} + +QString nodeToolTip(const LogicNodeConfig &config) { return std::visit( [](const auto &value) -> QString { using Config = std::decay_t; - if constexpr (std::is_same_v - || std::is_same_v) + if constexpr (std::is_same_v) + { + return value.mode == ContactMode::NormallyOpen + ? LogicEditorWidget::tr("常开触点 %1").arg( + registerAddressText(value.address)) + : LogicEditorWidget::tr("常闭触点 %1").arg( + registerAddressText(value.address)); + } + else if constexpr (std::is_same_v) { - return QStringLiteral("M%1").arg(value.address.index()); + QString mode = LogicEditorWidget::tr("普通线圈"); + if (value.mode == CoilMode::Set) + { + mode = LogicEditorWidget::tr("置位线圈"); + } + else if (value.mode == CoilMode::Reset) + { + mode = LogicEditorWidget::tr("复位线圈"); + } + return QStringLiteral("%1 %2").arg( + mode, registerAddressText(value.address)); } else { - return QStringLiteral("D%1 %2 %3") - .arg(value.address.index()) + return LogicEditorWidget::tr("比较条件:%1 %2 %3") + .arg(registerAddressText(value.address)) .arg(comparisonText(value.comparison)) .arg(value.value); } @@ -62,7 +100,30 @@ qreal rungHeight(const LadderRung &rung) { maximum_branches = std::max(maximum_branches, stage.branches.size()); } - return 100.0 + static_cast(maximum_branches - 1U) * kBranchSpacing; + return kRungBaseHeight + + static_cast(maximum_branches - 1U) * kBranchSpacing; +} + +void addPlaceholder( + QGraphicsScene &scene, + const QPointF ¢er, + const QString &text, + qreal width = kPlaceholderWidth) +{ + const QRectF bounds( + center.x() - width / 2.0, + center.y() - kPlaceholderHeight / 2.0, + width, + kPlaceholderHeight); + scene.addRect( + bounds, + QPen(kPlaceholderColor, 1.2, Qt::DashLine), + QBrush(Qt::white)); + QGraphicsTextItem *label = scene.addText(text); + label->setDefaultTextColor(kPlaceholderColor); + label->setPos( + center.x() - label->boundingRect().width() / 2.0, + center.y() - label->boundingRect().height() / 2.0); } } // namespace @@ -82,11 +143,13 @@ public: { setPos(center); setFlag(ItemIsSelectable, true); + setZValue(1.0); + setToolTip(nodeToolTip(config_)); } QRectF boundingRect() const override { - return {-kNodeWidth / 2.0, -27.0, kNodeWidth, 54.0}; + return {-kNodeWidth / 2.0, -46.0, kNodeWidth, 92.0}; } void paint( @@ -98,27 +161,45 @@ public: const bool selected = (option->state & QStyle::State_Selected) != 0; if (selected) { - painter->fillRect( - boundingRect(), QColor(QStringLiteral("#dceef7"))); + painter->fillRect(boundingRect(), kSelectionColor); } - painter->setPen(QPen( - selected ? QColor(QStringLiteral("#1677a8")) - : QColor(QStringLiteral("#1f2b33")), - selected ? 2.0 : 1.6)); + const QColor symbol_color = selected ? kSelectionBorderColor : kLadderColor; + painter->setPen(QPen(symbol_color, selected ? 2.0 : kLadderLineWidth)); + + QFont text_font = painter->font(); + text_font.setPointSizeF(9.5); + painter->setFont(text_font); if (const auto *contact = std::get_if(&config_)) { + painter->fillRect(QRectF(-24, -21, 48, 42), Qt::white); + painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0)); + painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0)); painter->drawLine(QPointF(-18, -15), QPointF(-18, 15)); painter->drawLine(QPointF(18, -15), QPointF(18, 15)); if (contact->mode == ContactMode::NormallyClosed) { painter->drawLine(QPointF(-23, 18), QPointF(23, -18)); } + painter->setPen(kLadderColor); + painter->drawText( + QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18), + Qt::AlignCenter, + registerAddressText(contact->address)); } else if (const auto *coil = std::get_if(&config_)) { - painter->drawArc(QRectF(-29, -20, 30, 40), -80 * 16, 160 * 16); - painter->drawArc(QRectF(-1, -20, 30, 40), 100 * 16, 160 * 16); + painter->fillRect(QRectF(-34, -23, 68, 46), Qt::white); + painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-16, 0)); + painter->drawLine(QPointF(16, 0), QPointF(kNodeTerminalX, 0)); + QPainterPath left_coil; + left_coil.moveTo(-2, -20); + left_coil.cubicTo(-24, -16, -24, 16, -2, 20); + painter->drawPath(left_coil); + QPainterPath right_coil; + right_coil.moveTo(2, -20); + right_coil.cubicTo(24, -16, 24, 16, 2, 20); + painter->drawPath(right_coil); if (coil->mode == CoilMode::Set || coil->mode == CoilMode::Reset) { painter->drawText( @@ -127,16 +208,36 @@ public: coil->mode == CoilMode::Set ? QStringLiteral("S") : QStringLiteral("R")); } + painter->setPen(kLadderColor); + painter->drawText( + QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18), + Qt::AlignCenter, + registerAddressText(coil->address)); } - else + else if (const auto *comparison = std::get_if(&config_)) { - painter->drawRect(QRectF(-43, -18, 86, 36)); + constexpr qreal box_half_width = 47.0; + const QRectF instruction_box(-box_half_width, -17, 94, 34); + painter->fillRect(instruction_box.adjusted(-1, -1, 1, 1), Qt::white); + painter->drawLine( + QPointF(-kNodeTerminalX, 0), QPointF(-box_half_width, 0)); + painter->drawLine( + QPointF(box_half_width, 0), QPointF(kNodeTerminalX, 0)); + painter->drawRect(instruction_box); + painter->drawText( + instruction_box, + Qt::AlignCenter, + QStringLiteral("%1 INT").arg(comparisonText(comparison->comparison))); + painter->setPen(kLadderColor); + painter->drawText( + QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18), + Qt::AlignCenter, + registerAddressText(comparison->address)); + painter->drawText( + QRectF(-kNodeWidth / 2.0, 22, kNodeWidth, 18), + Qt::AlignCenter, + QString::number(comparison->value)); } - painter->setPen(QColor(QStringLiteral("#263640"))); - painter->drawText( - QRectF(-kNodeWidth / 2.0, -42, kNodeWidth, 18), - Qt::AlignCenter, - nodeAddressText(config_)); } const std::string &nodeId() const { return node_id_; } @@ -186,10 +287,15 @@ public: boundingRect(), QColor(QStringLiteral("#f0f7fa"))); } painter->setPen(QColor(QStringLiteral("#62717b"))); + QString title = QStringLiteral("网络 %1").arg(number_); + if (!name_.isEmpty() && !name_.startsWith(QStringLiteral("网络 "))) + { + title += QStringLiteral(":") + name_; + } painter->drawText( QRectF(28, 6, 260, 22), Qt::AlignLeft | Qt::AlignVCenter, - QStringLiteral("%1 %2").arg(number_).arg(name_)); + title); painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1)); painter->drawLine( QPointF(28, height_ - 1), QPointF(scene_width_ - 28, height_ - 1)); @@ -267,14 +373,14 @@ void LogicEditorWidget::reloadLogic() { const qreal height = rungHeight(rung); scene_->addItem(new RungItem(rung, rung_number, top, height, scene_width)); - const qreal main_y = top + 62.0; + const qreal main_y = top + kMainLineOffset; scene_->addLine( - kLeftRailX, top + 32.0, kLeftRailX, top + height - 18.0, - QPen(QColor(QStringLiteral("#202a31")), 3)); + kLeftRailX, top + 38.0, kLeftRailX, top + height - 16.0, + QPen(kLadderColor, 2.4)); scene_->addLine( - right_rail_x, top + 32.0, right_rail_x, top + height - 18.0, - QPen(QColor(QStringLiteral("#202a31")), 3)); + right_rail_x, top + 38.0, right_rail_x, top + height - 16.0, + QPen(kLadderColor, 2.4)); qreal cursor_x = kLeftRailX; for (const LadderStage &stage : rung.stages) @@ -284,27 +390,27 @@ void LogicEditorWidget::reloadLogic() const qreal right_join = next_x - 18.0; scene_->addLine( cursor_x, main_y, left_join, main_y, - QPen(QColor(QStringLiteral("#202a31")), 2)); + QPen(kLadderColor, kLadderLineWidth)); scene_->addLine( right_join, main_y, next_x, main_y, - QPen(QColor(QStringLiteral("#202a31")), 2)); + QPen(kLadderColor, kLadderLineWidth)); if (stage.branches.size() > 1U) { const qreal bottom_y = main_y + static_cast(stage.branches.size() - 1U) * kBranchSpacing; scene_->addLine( left_join, main_y, left_join, bottom_y, - QPen(QColor(QStringLiteral("#202a31")), 2)); + QPen(kLadderColor, kLadderLineWidth)); scene_->addLine( right_join, main_y, right_join, bottom_y, - QPen(QColor(QStringLiteral("#202a31")), 2)); + QPen(kLadderColor, kLadderLineWidth)); } for (std::size_t branch = 0; branch < stage.branches.size(); ++branch) { const qreal branch_y = main_y + static_cast(branch) * kBranchSpacing; scene_->addLine( left_join, branch_y, right_join, branch_y, - QPen(QColor(QStringLiteral("#202a31")), 2)); + QPen(kLadderColor, kLadderLineWidth)); scene_->addItem(new NodeItem( stage.branches.at(branch), rung.id, @@ -314,32 +420,58 @@ void LogicEditorWidget::reloadLogic() cursor_x = next_x; } - const qreal output_x = right_rail_x - 116.0; - scene_->addLine( - cursor_x, main_y, output_x, main_y, - QPen(QColor(QStringLiteral("#202a31")), 2)); - scene_->addLine( - output_x, main_y, right_rail_x, main_y, - QPen(QColor(QStringLiteral("#202a31")), 2)); + const qreal output_center_x = right_rail_x - 68.0; + if (rung.stages.empty()) + { + const qreal condition_center_x = kLeftRailX + kStageWidth / 2.0; + scene_->addLine( + kLeftRailX, + main_y, + condition_center_x - kPlaceholderWidth / 2.0, + main_y, + QPen(kLadderColor, kLadderLineWidth)); + scene_->addLine( + condition_center_x + kPlaceholderWidth / 2.0, + main_y, + output_center_x, + main_y, + QPen(kLadderColor, kLadderLineWidth)); + addPlaceholder( + *scene_, + QPointF(condition_center_x, main_y), + tr("添加条件")); + } + else + { + scene_->addLine( + cursor_x, main_y, output_center_x, main_y, + QPen(kLadderColor, kLadderLineWidth)); + } if (rung.output.has_value()) { + scene_->addLine( + output_center_x, main_y, right_rail_x, main_y, + QPen(kLadderColor, kLadderLineWidth)); scene_->addItem(new NodeItem( *rung.output, rung.id, {}, - QPointF(output_x + 50.0, main_y))); + QPointF(output_center_x, main_y))); } else { - auto *placeholder = scene_->addText(QStringLiteral("< 输出线圈 >")); - placeholder->setDefaultTextColor(QColor(QStringLiteral("#87949c"))); - placeholder->setPos(output_x + 4.0, main_y - 14.0); - } - if (rung.stages.empty()) - { - auto *placeholder = scene_->addText(QStringLiteral("< 添加条件 >")); - placeholder->setDefaultTextColor(QColor(QStringLiteral("#87949c"))); - placeholder->setPos(kLeftRailX + 32.0, main_y - 14.0); + constexpr qreal output_placeholder_width = 96.0; + scene_->addLine( + output_center_x + output_placeholder_width / 2.0, + main_y, + right_rail_x, + main_y, + QPen(kLadderColor, kLadderLineWidth)); + addPlaceholder( + *scene_, + QPointF(output_center_x, main_y), + tr("输出线圈"), + output_placeholder_width); } top += height + kRungGap; ++rung_number; @@ -450,7 +582,7 @@ LogicEditorResult LogicEditorWidget::addParallelCondition( { result = {false, LogicEditorError::InvalidOperation, - "select a condition before adding a parallel branch", + "请先选择要添加并联分支的触点或比较条件", {}}; } else @@ -501,7 +633,8 @@ LogicEditorResult LogicEditorWidget::deleteSelected() const std::string rung_id = selectedRungId(); if (rung_id.empty()) { - return {false, LogicEditorError::InvalidOperation, "no ladder object is selected", {}}; + return {false, LogicEditorError::InvalidOperation, + "请先选择要删除的逻辑节点或网络", {}}; } result = editor_service_.removeRung(logic_id_, rung_id); if (result.succeeded && current_rung_id_ == rung_id)