新版布局
This commit is contained in:
@@ -79,13 +79,15 @@ bool ConfirmDialog::confirm(QWidget* parent, const QString& title, const QString
|
||||
return dlg.exec() == QDialog::Accepted;
|
||||
}
|
||||
|
||||
void ConfirmDialog::message(QWidget* parent, const QString& title, const QString& text)
|
||||
void ConfirmDialog::message(QWidget* parent, const QString& title, const QString& text,
|
||||
int width, int fontPx)
|
||||
{
|
||||
ConfirmDialog dlg(parent);
|
||||
dlg.m_panel->setFixedSize(900, 600); // 正文行数少 → 面板收窄
|
||||
dlg.m_cancel->hide(); // 只保留“确认”
|
||||
dlg.m_title->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;font-weight:700;").arg(C::TEXT));
|
||||
dlg.m_body->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;").arg(C::TEXT));
|
||||
// 正文单独给字号:文案长的调用点可以调小它,让固定的字数在更窄的面板里排成预期行数
|
||||
dlg.m_body->setStyleSheet(QString("background:transparent;color:%1;font-size:%2px;")
|
||||
.arg(C::TEXT).arg(fontPx));
|
||||
dlg.m_title->setWordWrap(true);
|
||||
dlg.m_title->setContentsMargins(0, 20, 0, 0); // 标题文字距该行顶部 10px
|
||||
dlg.m_body->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -97,6 +99,14 @@ void ConfirmDialog::message(QWidget* parent, const QString& title, const QString
|
||||
}
|
||||
dlg.setTitle(title);
|
||||
dlg.setText(text);
|
||||
|
||||
// 面板宽度由调用点给定,高度按内容算:标题/正文都开了 wordWrap,宽度先定下来,
|
||||
// layout 才能用 heightForWidth 量出准确高度(QLabel 的 sizeHint 对换行文本不准,
|
||||
// 所以不能靠 adjustSize)。宽度以前是写死的常量、三个调用点共用,一处的长文案
|
||||
// 会把别处的弹窗一起撑宽,故改成逐调用点传入。
|
||||
const int kW = width;
|
||||
dlg.m_panel->ensurePolished(); // 先让 QSS 的 50px 字号落到控件上,量出来的高度才对
|
||||
dlg.m_panel->setFixedSize(kW, dlg.m_panel->heightForWidth(kW));
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,12 @@ public:
|
||||
// 模态确认,返回是否“确认”
|
||||
static bool confirm(QWidget* parent, const QString& title, const QString& text);
|
||||
|
||||
// 独立信息弹窗(仅“确认”按钮,替代系统 QMessageBox;正文 50px)
|
||||
static void message(QWidget* parent, const QString& title, const QString& text);
|
||||
// 独立信息弹窗(仅“确认”按钮,替代系统 QMessageBox)。
|
||||
// width = 面板宽度,fontPx = **正文字号**(标题固定 50px);正文按宽度换行、高度按内容算。
|
||||
// 两个参数都是**各调用点各给各的**——以前写死共用,一处的长文案会把别处的弹窗一起撑宽,
|
||||
// 想让它窄一点就只能靠截断,现在可以靠缩字号。
|
||||
static void message(QWidget* parent, const QString& title, const QString& text,
|
||||
int width = 1200, int fontPx = 50);
|
||||
|
||||
// 轻提示:显示 text,ms 毫秒后自动消失(用于“修改成功”等)
|
||||
static void toast(QWidget* parent, const QString& text, int ms = 2000);
|
||||
|
||||
+194
-12
@@ -2,9 +2,40 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#include <QFontMetrics>
|
||||
#include <QEvent>
|
||||
#include "global.h"
|
||||
#include <QStyle>
|
||||
|
||||
namespace {
|
||||
// 行高的上限。行按 stretch 长到这个高度就停,多出来的空间还给卡片末尾的弹性——
|
||||
// 否则卡片一高(首页 2×2 那种高卡)就把两三行拉成稀稀拉拉一大片。
|
||||
// 数值行的最大字号是 50px(行高约 60px),72 给足了余量,想调松紧改这个数。
|
||||
const int kRowMaxH = 72;
|
||||
|
||||
// 字号:数值行标签 32(数值字号随卡片走,见 DataCard::setValueFontPx)
|
||||
const int kLabelFontPx = 32;
|
||||
|
||||
// 故障行单独一号,且**全应用统一**。放不下时不再截断,而是整条从右往左自动滑动
|
||||
// (见 tickFaultScroll),所以字号可以比数值行小一点也依然读得全。
|
||||
const int kFaultFontPx = 40;
|
||||
const int kFaultScrollMs = 30; // 滑动节拍
|
||||
const int kFaultScrollStep = 3; // 每拍左移像素 -> 约 100px/s
|
||||
|
||||
// 故障文字的裁剪容器至少要有一行高,否则没有布局时 QWidget::sizeHint() 是 (-1,-1),
|
||||
// 这一行会被压成 0 高
|
||||
const int kFaultBoxMinH = kFaultFontPx + 8;
|
||||
|
||||
// 给一行的所有控件设最大高度,从而把整行的高度卡住(QHBoxLayout 的高度取子项最大值)
|
||||
void capRow(QHBoxLayout* row, int h)
|
||||
{
|
||||
for (int i = 0; i < row->count(); ++i)
|
||||
if (QWidget* w = row->itemAt(i)->widget())
|
||||
w->setMaximumHeight(h);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DataCard::DataCard(const QString& title, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
@@ -23,7 +54,11 @@ DataCard::DataCard(const QString& title, QWidget* parent)
|
||||
.arg(C::TEXT, C::BORDER));
|
||||
lay->addWidget(m_title);
|
||||
|
||||
// 字段行容器
|
||||
// 故障行:构造时就建好,永远占着最后一行——有无故障都不改变卡片布局。
|
||||
ensureFaultRow();
|
||||
|
||||
// 末尾弹性:各行长到自己的上限后,剩下的高度全归它。
|
||||
// 顺序因此固定为 [标题][数值行…][故障行][弹性],addField 插在倒数第二项之前。
|
||||
lay->addStretch(1);
|
||||
}
|
||||
|
||||
@@ -41,18 +76,23 @@ int DataCard::addField(const QString& label, const QString& value, bool big)
|
||||
|
||||
QLabel* lab = new QLabel(label + ":", this);
|
||||
lab->setProperty("sub", true);
|
||||
lab->setStyleSheet(QString("color:%1;font-size:32px;").arg(C::TEXT_SUB));
|
||||
lab->setStyleSheet(QString("color:%1;font-size:%2px;").arg(C::TEXT_SUB).arg(kLabelFontPx));
|
||||
|
||||
// 数值标签只在行内样式里给字号/字重,颜色交给应用级样式表的 [cardValue] / [alert] 规则:
|
||||
// 控件自身的样式表优先级高于应用级样式表,行内一写 color,告警的红字与暗红闪烁就全被盖掉,
|
||||
// 属性照样翻转、界面上却看不出任何变化。AlarmBanner 就是这么写的,这里保持一致。
|
||||
QLabel* val = new QLabel(value, this);
|
||||
val->setStyleSheet(QString("color:%1;%2").arg(C::TEXT,
|
||||
big ? "font-size:50px;font-weight:700;" : "font-size:42px;font-weight:700;"));
|
||||
val->setProperty("cardValue", true);
|
||||
val->setStyleSheet(QString("font-size:%1px;font-weight:700;").arg(big ? 50 : m_valueFontPx));
|
||||
|
||||
row->addWidget(lab);
|
||||
row->addWidget(val, 1);
|
||||
|
||||
// 插入到标题栏之后(stretch 之前)
|
||||
// 插到故障行之前(末尾两项固定是 [故障行][弹性]);
|
||||
// stretch = 1 让行在有富余高度时长到 kRowMaxH 为止,再高就不长了
|
||||
capRow(row, kRowMaxH);
|
||||
QVBoxLayout* lay = qobject_cast<QVBoxLayout*>(layout());
|
||||
lay->insertLayout(lay->count() - 1, row);
|
||||
lay->insertLayout(lay->count() - 2, row, 1);
|
||||
|
||||
m_values.append(val);
|
||||
m_labels.append(label);
|
||||
@@ -71,17 +111,159 @@ void DataCard::setAlarm(int index, bool on)
|
||||
if (index < 0 || index >= m_values.size())
|
||||
return;
|
||||
QLabel* val = m_values[index];
|
||||
if (on) {
|
||||
val->setProperty("alert", true);
|
||||
if (val->property("alert").toBool() == on)
|
||||
return; // 每次遥测刷新都会调进来,状态没变就别重算样式
|
||||
val->setProperty("alert", on);
|
||||
if (on)
|
||||
startBlink(val, "blink");
|
||||
} else {
|
||||
else
|
||||
stopBlink(val, "blink");
|
||||
val->setProperty("alert", false);
|
||||
val->style()->unpolish(val);
|
||||
val->style()->polish(val);
|
||||
// 置属性不会自动重算样式,不补这一次重算,红字要等下一次 blink 翻转(最多 500ms)才出现
|
||||
val->style()->unpolish(val);
|
||||
val->style()->polish(val);
|
||||
val->update();
|
||||
}
|
||||
|
||||
// 故障行:构造时就建好,永远占着最后一行,高度与位置都不随有无故障变化。
|
||||
// 不放“故障:”标签——那要吃掉一百多像素,卡片本来就窄;整行宽度都给文字。
|
||||
// 文字比整行还宽时不截断,改成在裁剪容器里从右往左自动滑动。
|
||||
void DataCard::ensureFaultRow()
|
||||
{
|
||||
if (m_faultRow)
|
||||
return;
|
||||
|
||||
m_faultRow = new QHBoxLayout();
|
||||
m_faultRow->setSpacing(0);
|
||||
|
||||
// 裁剪容器:子控件超出它的范围会被裁掉,正好当滑动窗口用。
|
||||
// 里面不放布局,标签的宽度和位置由 positionFaultLabel() 手动给。
|
||||
m_faultBox = new QWidget(this);
|
||||
m_faultBox->setMinimumHeight(kFaultBoxMinH);
|
||||
m_faultBox->installEventFilter(this); // 宽度定了才知道要不要滑
|
||||
|
||||
// 与字段行同规矩:行内只写字号/字重,颜色交给应用级样式表的 [cardValue] / [alert]
|
||||
m_faultValue = new QLabel(m_faultBox);
|
||||
m_faultValue->setProperty("cardValue", true);
|
||||
m_faultValue->setStyleSheet(QString("font-size:%1px;font-weight:700;").arg(kFaultFontPx));
|
||||
|
||||
m_faultRow->addWidget(m_faultBox, 1);
|
||||
capRow(m_faultRow, kRowMaxH); // 与数值行同一个上限
|
||||
|
||||
// 无故障时用**空文字**占位,而不是 hide():隐藏的子控件会让这一项被判成“空”,
|
||||
// QBoxLayout 连它那份高度一起跳过,卡片照样会跳一下。
|
||||
|
||||
QVBoxLayout* lay = qobject_cast<QVBoxLayout*>(layout());
|
||||
if (lay)
|
||||
lay->addLayout(m_faultRow, 1); // 固定在数值行之后,高度同样受 kRowMaxH 限制
|
||||
|
||||
m_faultTimer = new QTimer(this);
|
||||
m_faultTimer->setInterval(1000); // 多条故障按 1s 轮流显示
|
||||
connect(m_faultTimer, &QTimer::timeout, this, &DataCard::rotateFault);
|
||||
|
||||
m_scrollTimer = new QTimer(this);
|
||||
m_scrollTimer->setInterval(kFaultScrollMs);
|
||||
connect(m_scrollTimer, &QTimer::timeout, this, &DataCard::tickFaultScroll);
|
||||
}
|
||||
|
||||
// 设文字、按字体量出像素宽,再决定停着显示还是滑动
|
||||
void DataCard::applyFaultText()
|
||||
{
|
||||
QFont f = m_faultValue->font();
|
||||
f.setPixelSize(kFaultFontPx); // QSS 的字号未必已经反映到 QLabel::font() 上
|
||||
const QFontMetrics fm(f);
|
||||
|
||||
m_faultValue->setText(m_faultText);
|
||||
m_textW = m_faultText.isEmpty() ? 0 : fm.horizontalAdvance(m_faultText) + 2;
|
||||
m_faultValue->setFixedWidth(m_textW > 0 ? m_textW : 1);
|
||||
m_offset = 0;
|
||||
|
||||
updateFaultScroll();
|
||||
positionFaultLabel();
|
||||
}
|
||||
|
||||
// 放得下就停着显示;放不下才滑动。首次布局前 m_faultBox->width() 还是默认值,
|
||||
// 这里会先当成"放得下",等拿到真宽度后由 eventFilter 再判一次。
|
||||
void DataCard::updateFaultScroll()
|
||||
{
|
||||
const bool need = m_textW > 0 && m_faultBox->width() > 0 && m_textW > m_faultBox->width();
|
||||
if (need) {
|
||||
if (!m_scrollTimer->isActive()) m_scrollTimer->start();
|
||||
} else {
|
||||
m_scrollTimer->stop();
|
||||
m_offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DataCard::positionFaultLabel()
|
||||
{
|
||||
m_faultValue->resize(m_textW > 0 ? m_textW : 1, m_faultBox->height());
|
||||
m_faultValue->move(-m_offset, 0); // 负偏移 = 往左移出裁剪容器
|
||||
}
|
||||
|
||||
// 左移一拍;整条完全移出左边界后从头再来
|
||||
void DataCard::tickFaultScroll()
|
||||
{
|
||||
if (m_textW <= 0)
|
||||
return;
|
||||
m_offset += kFaultScrollStep;
|
||||
if (m_offset > m_textW)
|
||||
m_offset = 0;
|
||||
positionFaultLabel();
|
||||
}
|
||||
|
||||
bool DataCard::eventFilter(QObject* obj, QEvent* ev)
|
||||
{
|
||||
if (obj == m_faultBox && ev->type() == QEvent::Resize) {
|
||||
// 真宽度下来了,这一刻才判得准要不要滑
|
||||
updateFaultScroll();
|
||||
positionFaultLabel();
|
||||
}
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
}
|
||||
|
||||
void DataCard::setFaults(const QStringList& faults)
|
||||
{
|
||||
// 每次遥测刷新都会调进来。内容没变就直接返回:既省掉重复重算样式,
|
||||
// 也避免把 m_faultIdx 清零——否则多条故障永远停在第一条上。
|
||||
if (faults == m_faults)
|
||||
return;
|
||||
m_faults = faults;
|
||||
m_faultIdx = 0;
|
||||
|
||||
// 行本身一直占着位(见 ensureFaultRow),这里只切内容:
|
||||
// 无故障清空文字、有故障填内容并红闪——显隐规则与以前一致。
|
||||
if (m_faults.isEmpty()) {
|
||||
m_faultTimer->stop();
|
||||
stopBlink(m_faultValue, "blink");
|
||||
m_faultValue->setProperty("alert", false);
|
||||
m_faultText.clear();
|
||||
applyFaultText(); // 清空文字:停滑、宽度归 1,这一行仍然占位
|
||||
return;
|
||||
}
|
||||
|
||||
m_faultText = m_faults.at(0);
|
||||
applyFaultText();
|
||||
m_faultValue->setProperty("alert", true);
|
||||
startBlink(m_faultValue, "blink");
|
||||
m_faultValue->style()->unpolish(m_faultValue);
|
||||
m_faultValue->style()->polish(m_faultValue);
|
||||
m_faultValue->update();
|
||||
|
||||
if (m_faults.size() > 1)
|
||||
m_faultTimer->start();
|
||||
else
|
||||
m_faultTimer->stop();
|
||||
}
|
||||
|
||||
void DataCard::rotateFault()
|
||||
{
|
||||
if (m_faults.size() < 2)
|
||||
return;
|
||||
m_faultIdx = (m_faultIdx + 1) % m_faults.size();
|
||||
m_faultText = m_faults.at(m_faultIdx);
|
||||
applyFaultText();
|
||||
}
|
||||
|
||||
void DataCard::setHighlight(bool on)
|
||||
{
|
||||
if (on)
|
||||
|
||||
+38
-2
@@ -1,13 +1,21 @@
|
||||
#ifndef DATACARD_H
|
||||
#define DATACARD_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
class QLabel;
|
||||
class QHBoxLayout;
|
||||
class QTimer;
|
||||
class QEvent;
|
||||
|
||||
// ===================== 数据卡片 =====================
|
||||
// 标题栏 + 若干“标签:值”行;支持指定字段红色闪烁告警
|
||||
// 标题栏 + 若干“标签:值”行 + 一条常驻的“故障”行。
|
||||
// 每行有最大高度、长到上限就停,多出来的高度归末尾弹性,所以有无故障都不改变布局;
|
||||
// 字段行支持红色闪烁告警;故障行没有标签、字号 kFaultFontPx,有故障时红闪、
|
||||
// 多条按 1s 轮流显示,文字比整行宽时自动滑动,无故障时内容为空(行仍占位)。
|
||||
class DataCard : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -18,13 +26,41 @@ public:
|
||||
int addField(const QString& label, const QString& value = "--", bool big = false);
|
||||
void setField(int index, const QString& value);
|
||||
void setAlarm(int index, bool on);
|
||||
void setFaults(const QStringList& faults); // 故障行内容(空 = 清空文字,行仍占位)
|
||||
// 数值行字号。要在 addField 之前调——字号是建标签时就写进行内样式表的,改不了已建的。
|
||||
// big 字段不走这个值(固定 50)。
|
||||
void setValueFontPx(int px) { m_valueFontPx = px; }
|
||||
void setHighlight(bool on); // 主色描边突出
|
||||
void indentRows(); // 字段行整体缩进到「中间偏左」(行首 : 行尾 = 2 : 3),只需调用一次
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* ev) override; // 盯故障数值标签的宽度变化
|
||||
|
||||
private slots:
|
||||
void rotateFault();
|
||||
void tickFaultScroll(); // 故障文字滑动一拍
|
||||
|
||||
private:
|
||||
void ensureFaultRow();
|
||||
void applyFaultText(); // 设文字、量宽度、决定要不要滑动
|
||||
void updateFaultScroll(); // 放得下就停着显示,放不下才开滑动
|
||||
void positionFaultLabel(); // 按当前偏移摆放故障标签
|
||||
|
||||
QLabel* m_title = nullptr;
|
||||
QHBoxLayout* m_faultRow = nullptr; // 构造函数里就建好,永远占着最后一行
|
||||
QWidget* m_faultBox = nullptr; // 裁剪容器:文字超宽时标签在这里面左移
|
||||
QLabel* m_faultValue = nullptr; // 无“故障:”标签;宽度按文字自然宽度,位置手动给
|
||||
QTimer* m_faultTimer = nullptr; // 多条故障 1s 轮换
|
||||
QTimer* m_scrollTimer = nullptr; // 滑动节拍
|
||||
QStringList m_faults; // 当前故障列表,空 = 无故障
|
||||
QString m_faultText; // 当前该显示的完整文字
|
||||
int m_faultIdx = 0;
|
||||
int m_textW = 0; // 当前故障文字的像素宽
|
||||
int m_offset = 0; // 已左移的像素
|
||||
|
||||
QVector<QLabel*> m_values;
|
||||
QVector<QString> m_labels;
|
||||
int m_valueFontPx = 42; // 数值行字号;big 字段固定 50,不受它影响
|
||||
};
|
||||
|
||||
#endif // DATACARD_H
|
||||
|
||||
+16
-16
@@ -26,21 +26,17 @@ LedIndicator::LedIndicator(const QString& text, QWidget* parent)
|
||||
void LedIndicator::setState(State s)
|
||||
{
|
||||
m_state = s;
|
||||
bool needBlink = (s == RedBlink || s == YellowBlink);
|
||||
if (needBlink && !m_blink) {
|
||||
m_blink = new QTimer(this);
|
||||
m_blink->setInterval(500);
|
||||
connect(m_blink, &QTimer::timeout, this, [this]() {
|
||||
m_on = !m_on;
|
||||
update();
|
||||
});
|
||||
m_blink->start();
|
||||
m_on = true;
|
||||
} else if (!needBlink && m_blink) {
|
||||
m_blink->stop();
|
||||
delete m_blink;
|
||||
m_blink = nullptr;
|
||||
m_on = true;
|
||||
const bool needBlink = (s == RedBlink || s == YellowBlink);
|
||||
if (needBlink && !m_blinkConn) {
|
||||
// 挂全局节拍,而不是本控件自己起定时器:各自起的话相位互不相同,
|
||||
// 同一屏上的几个指示灯(顶部通信状态、底栏、自检结果…)会各闪各的。
|
||||
m_blinkConn = connect(BlinkBeat::instance(), &BlinkBeat::toggled, this,
|
||||
[this](bool on) { m_on = on; update(); });
|
||||
m_on = BlinkBeat::instance()->on(); // 立刻对齐当前相位,别等下一拍
|
||||
} else if (!needBlink && m_blinkConn) {
|
||||
disconnect(m_blinkConn);
|
||||
m_blinkConn = QMetaObject::Connection();
|
||||
m_on = true; // 停闪=常亮
|
||||
}
|
||||
update();
|
||||
}
|
||||
@@ -103,7 +99,11 @@ void LedIndicator::paintEvent(QPaintEvent*)
|
||||
p.drawEllipse(c, m_radius, m_radius);
|
||||
|
||||
// 文字(圆点在文字后时,文字从控件左边缘开始)
|
||||
p.setPen(QColor(C::TEXT));
|
||||
QColor textColor = m_textColor.isValid() ? m_textColor : QColor(C::TEXT);
|
||||
// 顶部状态条这类要“整条闪烁”的场合:文字与圆点同步降到 40% 透明度
|
||||
if (m_blinkText && (m_state == RedBlink || m_state == YellowBlink) && !m_on)
|
||||
textColor.setAlpha(102);
|
||||
p.setPen(textColor);
|
||||
const QRectF textRect(m_dotAfter ? 0 : textLeft(), 0,
|
||||
m_dotAfter ? textW : width() - textLeft() - 10, height());
|
||||
p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, m_text);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#ifndef LEDINDICATOR_H
|
||||
#define LEDINDICATOR_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QMetaObject>
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
|
||||
// ===================== 指示灯(自绘圆形渐变 + 外发光) =====================
|
||||
// 状态:灰=未运行/关闭/正常,绿=运行/开启/到位,红=故障/液位低,
|
||||
@@ -20,6 +21,9 @@ public:
|
||||
void setTextPixelSize(int px) { m_textPx = px; updateGeometry(); update(); } // 文字字号
|
||||
void setRadius(int r) { m_radius = r; updateGeometry(); update(); } // 圆形指示点半径
|
||||
void setDotAfterText(bool after) { m_dotAfter = after; updateGeometry(); update(); } // 圆点画在文字后面
|
||||
void setTextColor(const QColor& c) { m_textColor = c; update(); } // 文字颜色,默认 C::TEXT
|
||||
// 文字是否随闪烁状态一起明暗(默认只闪圆点,文字常亮)
|
||||
void setBlinkText(bool on) { m_blinkText = on; update(); }
|
||||
State state() const { return m_state; }
|
||||
|
||||
QSize sizeHint() const override; // 宽度按文字实际宽度计算,避免文字被裁
|
||||
@@ -32,11 +36,13 @@ private:
|
||||
|
||||
State m_state = Off;
|
||||
QString m_text;
|
||||
QTimer* m_blink = nullptr;
|
||||
QMetaObject::Connection m_blinkConn; // 订阅全局闪烁节拍(见 BlinkBeat)
|
||||
bool m_on = true;
|
||||
int m_radius = 16; // 圆形指示点 32×32
|
||||
int m_textPx = 24; // 文字字号
|
||||
bool m_dotAfter = false; // false=圆点在文字前,true=圆点在文字后
|
||||
QColor m_textColor; // 无效色 = 用默认 C::TEXT
|
||||
bool m_blinkText = false; // true=文字随闪烁明暗,false=文字常亮
|
||||
};
|
||||
|
||||
#endif // LEDINDICATOR_H
|
||||
|
||||
@@ -18,32 +18,8 @@ StatusStrip::StatusStrip(AppContext* ctx, QWidget* parent)
|
||||
lay->setContentsMargins(36, 0, 24, 0);
|
||||
lay->setSpacing(40);
|
||||
|
||||
// 底部不再显示底盘/液位数据(改由各页面卡片展示),只保留模式徽标 + 返回按钮
|
||||
//auto makeVal = [this](const QString& label) {
|
||||
// QLabel* l = new QLabel(this);
|
||||
// l->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT_SUB));
|
||||
// l->setProperty("label", label);
|
||||
// return l;
|
||||
//};
|
||||
//m_battery = makeVal("底盘电量");
|
||||
//m_voltage = makeVal("底盘电压");
|
||||
//m_levelA = makeVal("清水液位");
|
||||
//m_levelFoam = makeVal("泡沫液位");
|
||||
//lay->addWidget(m_battery);
|
||||
//lay->addWidget(m_voltage);
|
||||
//lay->addWidget(m_levelA);
|
||||
//lay->addWidget(m_levelFoam);
|
||||
|
||||
lay->addStretch(1);
|
||||
|
||||
// 模式徽标(仅显示:行车/机械臂由遥控器拨钮经串口回传,界面不参与切换)
|
||||
m_mode = new QLabel("行车模式", this);
|
||||
m_mode->setFixedSize(260, 80);
|
||||
m_mode->setAlignment(Qt::AlignCenter);
|
||||
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:40px;font-weight:700;")
|
||||
.arg(C::SUCCESS));
|
||||
lay->addWidget(m_mode);
|
||||
|
||||
// 返回按钮
|
||||
m_back = new QPushButton("返回", this);
|
||||
m_back->setFixedSize(200, 80);
|
||||
@@ -52,11 +28,6 @@ StatusStrip::StatusStrip(AppContext* ctx, QWidget* parent)
|
||||
lay->addWidget(m_back);
|
||||
|
||||
connect(m_ctx->comm(), &CarCommunication::telemetryUpdated, this, &StatusStrip::refresh);
|
||||
connect(m_ctx, &AppContext::modeChanged, this, [this](Mode m) {
|
||||
m_mode->setText(m == Mode::Drive ? "行车模式" : "机械臂模式");
|
||||
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:40px;font-weight:700;")
|
||||
.arg(m == Mode::Drive ? C::SUCCESS : C::WARNING));
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -70,17 +41,7 @@ void StatusStrip::setBackEnabled(bool enabled)
|
||||
m_back->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void StatusStrip::setModeVisible(bool visible)
|
||||
{
|
||||
m_mode->setVisible(visible);
|
||||
}
|
||||
|
||||
void StatusStrip::refresh()
|
||||
{
|
||||
// 底部已不显示遥测数据,保留空实现(仍连着 telemetryUpdated,方便以后加显示项)
|
||||
//const Telemetry& t = m_ctx->telemetry;
|
||||
//m_battery->setText(QString("底盘电量:%1%").arg(t.soc));
|
||||
//m_voltage->setText(QString("底盘电压:%1V").arg(t.volt, 0, 'f', 0));
|
||||
//m_levelA->setText(QString("清水液位:%1L").arg(t.levelA, 0, 'f', 0));
|
||||
//m_levelFoam->setText(QString("泡沫液位:%1L").arg(t.levelFoam, 0, 'f', 0));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class QPushButton;
|
||||
class AppContext;
|
||||
|
||||
// ===================== 底部状态条 =====================
|
||||
// 只保留:模式徽标(仅显示)+ 返回按钮。行车/机械臂由遥控器拨钮决定,界面不参与切换。
|
||||
// 只保留「返回」按钮。行车/机械臂模式徽标已撤——底部不再展示模式。
|
||||
class StatusStrip : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -17,7 +17,6 @@ public:
|
||||
|
||||
void setBackVisible(bool visible);
|
||||
void setBackEnabled(bool enabled);
|
||||
void setModeVisible(bool visible); // 固定行车模式的页面(如底盘页)可隐藏模式徽标
|
||||
|
||||
signals:
|
||||
void backClicked();
|
||||
@@ -27,11 +26,6 @@ private slots:
|
||||
|
||||
private:
|
||||
AppContext* m_ctx;
|
||||
//QLabel* m_battery = nullptr; // 底部不再显示数据
|
||||
//QLabel* m_voltage = nullptr;
|
||||
//QLabel* m_levelA = nullptr;
|
||||
//QLabel* m_levelFoam = nullptr;
|
||||
QLabel* m_mode = nullptr; // 模式徽标(只显示,不参与切换)
|
||||
QPushButton* m_back = nullptr;
|
||||
};
|
||||
|
||||
|
||||
+68
-26
@@ -2,10 +2,22 @@
|
||||
#include "ledindicator.h"
|
||||
#include "appcontext.h"
|
||||
#include "car.h"
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include "global.h"
|
||||
|
||||
namespace {
|
||||
// 首页/作业选择“中央卡片区”的左边缘:主区左内边距 24 + 左列 300 + 间距 24
|
||||
const int kCardLeft = 300;
|
||||
// LedIndicator 的文字起点(圆点区宽度):radius(16)×2 + 左侧留白 18,见 LedIndicator::textLeft()
|
||||
const int kCommTextLeft = 50;
|
||||
// 顶栏左右区宽度。左区要容下最长的一条异常“气体检测仪通信异常”
|
||||
// (9 个全角字 × 43px = 387):274(左侧留白) + 50 + 387 + 10 = 721;
|
||||
// 上限 726——居中标题的左边缘在 750,再宽就压上去了。
|
||||
const int kSideW = 726;
|
||||
} // namespace
|
||||
|
||||
TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
|
||||
: QWidget(parent), m_ctx(ctx)
|
||||
{
|
||||
@@ -14,16 +26,18 @@ TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
|
||||
setStyleSheet(QString("QWidget#TitleBar{background-color:%1;border-bottom:1px solid %2;}")
|
||||
.arg(C::PANEL, C::BORDER));
|
||||
|
||||
// 左侧区(车载端通信状态)
|
||||
// 左侧区(车载端通信状态,固定一行,不轮播)
|
||||
QWidget* leftBox = new QWidget(this);
|
||||
QHBoxLayout* leftLay = new QHBoxLayout(leftBox);
|
||||
leftLay->setContentsMargins(260, 0, 0, 0);
|
||||
leftLay->setSpacing(20);
|
||||
m_comm = new LedIndicator("通信正常", leftBox);
|
||||
m_comm->setFixedHeight(80); // 容纳放大后的文字,避免上下裁切
|
||||
m_comm->setTextPixelSize(45);
|
||||
leftLay->setSpacing(0); // 只有指示器 + 弹簧,留间距只会白白吃掉文字宽度
|
||||
m_comm = new LedIndicator("系统通信正常", leftBox);
|
||||
// 高度沿用 LedIndicator 默认的 52:43px 字与 48px 直径的圆点外发光刚好容得下
|
||||
m_comm->setTextPixelSize(45); // 与右侧“遥控器正常”同字号
|
||||
m_comm->setBlinkText(true); // 异常时文字跟着圆点一起闪
|
||||
m_comm->setState(LedIndicator::Green);
|
||||
leftLay->addWidget(m_comm);
|
||||
leftLay->setContentsMargins(kCardLeft - 24 - kCommTextLeft, 0, 0, 0);
|
||||
|
||||
leftLay->addWidget(m_comm, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
leftLay->addStretch(1);
|
||||
|
||||
// 右侧区(遥控器状态:链路 + 电量)
|
||||
@@ -37,35 +51,37 @@ TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
|
||||
m_rc->setTextPixelSize(45);
|
||||
m_rc->setState(LedIndicator::RedBlink);
|
||||
rightLay->addWidget(m_rc);
|
||||
m_rcBat = new QLabel("--%", rightBox);
|
||||
m_rcBat->setStyleSheet(QString("color:%1;font-size:60px;").arg(C::TEXT_SUB));
|
||||
m_rcBat = new QLabel(rightBox);
|
||||
m_rcBat->setAlignment(Qt::AlignCenter);
|
||||
rightLay->addWidget(m_rcBat);
|
||||
setRcBattery(-1);
|
||||
|
||||
// 左右两侧等宽 → 中间标题恰好居中于屏幕
|
||||
// 左区需 260(左边距)+ 240(圆点区与留白 60 + 文字 4×45)= 500
|
||||
// 右区需 285(圆点区与留白 60 + 文字 5×45)+ 30 + 130(电量)+ 20 = 465
|
||||
const int sideW = 520;
|
||||
leftBox->setFixedWidth(sideW);
|
||||
rightBox->setFixedWidth(sideW);
|
||||
// 左右两侧区等宽(宽度见 kSideW:左区要放得下最长的一条异常)
|
||||
leftBox->setFixedWidth(kSideW);
|
||||
rightBox->setFixedWidth(kSideW);
|
||||
|
||||
// 标题(居中)
|
||||
m_title = new QLabel("", this);
|
||||
m_title->setAlignment(Qt::AlignCenter);
|
||||
m_title->setStyleSheet(QString("color:%1;font-size:60px;font-weight:700;").arg(C::TEXT));
|
||||
|
||||
QHBoxLayout* lay = new QHBoxLayout(this);
|
||||
// 用 QGridLayout 让标题横跨整条顶栏居中,而不是夹在左右两区之间拉伸:
|
||||
// 后者下标题的可用宽度 = 1920 - 24×2 - 两侧区宽×2,左区为了放下通信文字一加宽,
|
||||
// 标题就被挤窄甚至裁字;横跨三列后标题始终居中于屏幕,两侧区多宽都不影响它。
|
||||
QGridLayout* lay = new QGridLayout(this);
|
||||
lay->setContentsMargins(24, 6, 24, 6);
|
||||
lay->setSpacing(0);
|
||||
lay->addWidget(leftBox);
|
||||
lay->addWidget(m_title, 1);
|
||||
lay->addWidget(rightBox);
|
||||
lay->addWidget(leftBox, 0, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
lay->addWidget(rightBox, 0, 2, Qt::AlignRight | Qt::AlignVCenter);
|
||||
lay->addWidget(m_title, 0, 0, 1, 3, Qt::AlignCenter);
|
||||
|
||||
// 车载端通信状态刷新
|
||||
connect(m_ctx->comm(), &CarCommunication::commOkChanged, this, [this](bool ok) {
|
||||
if (ok) { m_comm->setText("通信正常"); m_comm->setState(LedIndicator::Green); }
|
||||
else { m_comm->setText("通信异常"); m_comm->setState(LedIndicator::RedBlink); }
|
||||
});
|
||||
// 车载端通信状态:只在文案变化时由通信层通知,这里不做任何轮播。
|
||||
connect(m_ctx->comm(), &CarCommunication::commTextChanged,
|
||||
this, &TitleBar::onCommTextChanged);
|
||||
|
||||
// 每个页面各有自己的顶条实例,构造时通信层可能早就报过状态了;而 commTextChanged
|
||||
// 只在内容变化时才发,所以这里必须先取一次当前值,否则新页面的顶条会一直停在构造时的初值。
|
||||
updateComm();
|
||||
|
||||
// 遥控器链路与电量刷新
|
||||
connect(m_ctx->rc(), &RcCommunication::linkChanged, this, &TitleBar::onRcLink);
|
||||
@@ -73,6 +89,22 @@ TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
|
||||
onRcLink(m_ctx->rc()->linkOk());
|
||||
}
|
||||
|
||||
void TitleBar::onCommTextChanged()
|
||||
{
|
||||
updateComm();
|
||||
}
|
||||
|
||||
// 左区固定显示通信状态:链路断或带 All_Comm_FaultCode 就红色闪烁,其余绿灯。
|
||||
// 文案由通信层渲染(Telemetry::commText),这里只决定配色。
|
||||
void TitleBar::updateComm()
|
||||
{
|
||||
const Telemetry& t = m_ctx->telemetry;
|
||||
const bool warn = !t.commOk || t.commCode != 0;
|
||||
m_comm->setText(t.commText);
|
||||
m_comm->setState(warn ? LedIndicator::RedBlink : LedIndicator::Green);
|
||||
m_comm->setTextColor(QColor(warn ? C::DANGER : C::TEXT)); // 只有异常才用红色警示
|
||||
}
|
||||
|
||||
void TitleBar::setTitle(const QString& title)
|
||||
{
|
||||
m_title->setText(title);
|
||||
@@ -86,11 +118,21 @@ void TitleBar::onRcLink(bool ok)
|
||||
} else {
|
||||
m_rc->setText("遥控器断开");
|
||||
m_rc->setState(LedIndicator::RedBlink);
|
||||
m_rcBat->setText("--%");
|
||||
setRcBattery(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void TitleBar::setRcBattery(int battery)
|
||||
{
|
||||
// battery < 0 表示链路断开,无电量可显示
|
||||
const bool valid = battery >= 0;
|
||||
const QString color = !valid ? C::TEXT_SUB
|
||||
: (battery <= 20 ? C::DANGER : C::SUCCESS);
|
||||
m_rcBat->setText(valid ? QString("%1%").arg(battery) : QString("--%"));
|
||||
m_rcBat->setStyleSheet(QString("color:%1;font-size:50px;").arg(color));
|
||||
}
|
||||
|
||||
void TitleBar::onRcFrame(const RcFrame& f)
|
||||
{
|
||||
m_rcBat->setText(QString("%1%").arg(f.battery));
|
||||
setRcBattery(f.battery);
|
||||
}
|
||||
|
||||
+10
-1
@@ -9,7 +9,9 @@ class AppContext;
|
||||
class LedIndicator;
|
||||
|
||||
// ===================== 顶条组件(高 98px) =====================
|
||||
// 左侧“车载端通信:●正常/异常”,中央页面标题,右侧“遥控器:●正常/断开 + 电量”
|
||||
// 左侧“车载端通信:●系统通信正常/xx通信异常”,中央页面标题,右侧“遥控器:●正常/断开 + 电量”。
|
||||
// 左区**固定**显示通信状态一行,不再轮播故障明细——各项子系统的故障明细分别进对应卡片的
|
||||
// “故障”行(见 DataCard::setFaults)。文案由 CarCommunication 渲染进 Telemetry::commText。
|
||||
class TitleBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -21,8 +23,15 @@ public:
|
||||
private slots:
|
||||
void onRcLink(bool ok);
|
||||
void onRcFrame(const RcFrame& f);
|
||||
void onCommTextChanged();
|
||||
|
||||
private:
|
||||
// 按 Telemetry 的通信状态刷新左区指示灯
|
||||
void updateComm();
|
||||
|
||||
// 电量文字与配色:>20% 绿色,≤20% 红色,无数据时用次文字色
|
||||
void setRcBattery(int battery);
|
||||
|
||||
AppContext* m_ctx;
|
||||
LedIndicator* m_comm = nullptr;
|
||||
LedIndicator* m_rc = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user