添加项目文件。
This commit is contained in:
40
APP/APP_GlassCard.cpp
Normal file
40
APP/APP_GlassCard.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "APP/APP_GlassCard.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
namespace APP {
|
||||
|
||||
APP_GlassCard::APP_GlassCard(QWidget* parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
// 交给我们自己统一绘制卡片外观,不使用 QFrame 默认边框。
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
// 背景由 paintEvent 自绘,这里不走样式表背景。
|
||||
setAttribute(Qt::WA_StyledBackground, false);
|
||||
}
|
||||
|
||||
void APP_GlassCard::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
/*
|
||||
* 卡片外观刻意保持简单,方便教学时理解自绘流程:
|
||||
* 1. 先画一个带圆角的深色底板
|
||||
* 2. 再画一圈细边框
|
||||
*/
|
||||
const QRectF BodyRect = rect().adjusted(1.0, 1.0, -1.0, -1.0);
|
||||
const qreal Radius = 22.0;
|
||||
const QColor FillColor(30, 35, 43);
|
||||
const QColor BorderColor(82, 92, 104);
|
||||
|
||||
QPainter Painter(this);
|
||||
Painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// 先画卡片主体。
|
||||
Painter.setPen(QPen(BorderColor, 1.0));
|
||||
Painter.setBrush(FillColor);
|
||||
Painter.drawRoundedRect(BodyRect, Radius, Radius);
|
||||
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
27
APP/APP_GlassCard.h
Normal file
27
APP/APP_GlassCard.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QFrame>
|
||||
|
||||
namespace APP {
|
||||
|
||||
/*
|
||||
* 这是项目里所有“卡片容器”的基础控件。
|
||||
*
|
||||
* 它只负责统一外观,不负责任何业务逻辑:
|
||||
* 1. 统一圆角卡片风格
|
||||
* 2. 统一边框和暗色底板
|
||||
*
|
||||
* 上层像主页卡片、调试卡片都直接继承它。
|
||||
*/
|
||||
class APP_GlassCard : public QFrame
|
||||
{
|
||||
public:
|
||||
// 构造一个带统一外观的卡片容器。
|
||||
explicit APP_GlassCard(QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
// 卡片背景和圆角边框都在这里自绘。
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
177
APP/APP_KeyButton.cpp
Normal file
177
APP/APP_KeyButton.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "APP/APP_KeyButton.h"
|
||||
|
||||
#include "APP/APP_Theme.h"
|
||||
#include <QtCore/QtMath>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
namespace {
|
||||
|
||||
/*
|
||||
* 这个小工具函数用来把两种颜色按比例混合。
|
||||
* 当前项目里主要用它来根据按键状态生成不同深浅的背景色和边框色。
|
||||
*/
|
||||
QColor App_Func_MixColor(const QColor& Left, const QColor& Right, qreal Value)
|
||||
{
|
||||
const qreal Rate = qBound(0.0, Value, 1.0);
|
||||
|
||||
return QColor(
|
||||
qRound(Left.red() + (Right.red() - Left.red()) * Rate),
|
||||
qRound(Left.green() + (Right.green() - Left.green()) * Rate),
|
||||
qRound(Left.blue() + (Right.blue() - Left.blue()) * Rate),
|
||||
qRound(Left.alpha() + (Right.alpha() - Left.alpha()) * Rate));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace APP {
|
||||
|
||||
APP_KeyButton::APP_KeyButton(const APP_KeyInfo& KeyInfo, QWidget* parent)
|
||||
: QPushButton(parent),
|
||||
appKeyInfo(KeyInfo)
|
||||
{
|
||||
// 这里把按钮本身的交互属性定下来,后面就主要交给 paintEvent 自绘。
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setFlat(true);
|
||||
setMinimumSize(78, 78);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
void APP_KeyButton::App_Func_SetLatched(bool IsLatched)
|
||||
{
|
||||
// 状态没变时不重复刷新,避免无意义重绘。
|
||||
if (appIsLatched == IsLatched)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appIsLatched = IsLatched;
|
||||
update();
|
||||
}
|
||||
|
||||
void APP_KeyButton::App_Func_SetPressed(bool IsPressed)
|
||||
{
|
||||
// 状态没变时同样直接返回。
|
||||
if (appIsPressed == IsPressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appIsPressed = IsPressed;
|
||||
update();
|
||||
}
|
||||
|
||||
void APP_KeyButton::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
// 留一点内边距,让卡片式按键看起来不那么“顶满”。
|
||||
const QRectF ButtonRect = rect().adjusted(6.0, 6.0, -6.0, -6.0);
|
||||
const qreal Radius = 14.0;
|
||||
|
||||
QColor FillColor = App_Func_GetBackgroundColor();
|
||||
QColor OutlineColor = App_Func_GetBorderColor();
|
||||
|
||||
// 鼠标按下时再做一层轻微压暗,形成“压下去”的感觉。
|
||||
if (isDown())
|
||||
{
|
||||
FillColor = FillColor.darker(108);
|
||||
OutlineColor = OutlineColor.darker(112);
|
||||
}
|
||||
|
||||
QPainter Painter(this);
|
||||
Painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
Painter.setRenderHint(QPainter::TextAntialiasing, true);
|
||||
|
||||
// 先画背景和边框。
|
||||
Painter.setPen(QPen(OutlineColor, 1.2));
|
||||
Painter.setBrush(FillColor);
|
||||
Painter.drawRoundedRect(ButtonRect, Radius, Radius);
|
||||
|
||||
// hint 一般显示在左上角,比如 Num、Fn 这类短提示。
|
||||
if (!appKeyInfo.hint.isEmpty())
|
||||
{
|
||||
Painter.setFont(APP_Theme::App_Func_GetKeyHintFont());
|
||||
Painter.setPen(App_Func_GetTextColor().lighter(115));
|
||||
Painter.drawText(
|
||||
ButtonRect.adjusted(10.0, 8.0, -10.0, -8.0),
|
||||
Qt::AlignLeft | Qt::AlignTop,
|
||||
appKeyInfo.hint.toUpper());
|
||||
}
|
||||
|
||||
// 主文字放中间。文字长度不同就稍微缩一下字号,避免挤出按钮。
|
||||
QFont LabelFont = APP_Theme::App_Func_GetKeyLabelFont();
|
||||
if (appKeyInfo.label.size() > 2)
|
||||
{
|
||||
LabelFont.setPointSize(LabelFont.pointSize() - 4);
|
||||
}
|
||||
else if (appKeyInfo.label.size() == 2)
|
||||
{
|
||||
LabelFont.setPointSize(LabelFont.pointSize() - 2);
|
||||
}
|
||||
|
||||
Painter.setFont(LabelFont);
|
||||
Painter.setPen(App_Func_GetTextColor());
|
||||
Painter.drawText(ButtonRect, Qt::AlignCenter, appKeyInfo.label);
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetAccentColor() const
|
||||
{
|
||||
// 每个 tone 对应一组强调色,用来让不同类别的键略有区分。
|
||||
switch (appKeyInfo.tone)
|
||||
{
|
||||
case APP_KeyTone::Aqua:
|
||||
return QColor(72, 184, 162);
|
||||
case APP_KeyTone::Amber:
|
||||
return QColor(224, 172, 76);
|
||||
case APP_KeyTone::Blue:
|
||||
return QColor(103, 146, 224);
|
||||
case APP_KeyTone::Normal:
|
||||
default:
|
||||
return QColor(150, 168, 196);
|
||||
}
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetBackgroundColor() const
|
||||
{
|
||||
// 先以统一暗色为底,再按状态叠加强调色。
|
||||
const QColor BaseColor(55, 61, 70);
|
||||
|
||||
// 按下态最强。
|
||||
if (appIsPressed)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), 0.56);
|
||||
}
|
||||
|
||||
// 锁定态次之。
|
||||
if (appKeyInfo.tone != APP_KeyTone::Normal || appIsLatched)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), appIsLatched ? 0.35 : 0.18);
|
||||
}
|
||||
|
||||
return BaseColor;
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetBorderColor() const
|
||||
{
|
||||
const QColor BaseColor(104, 114, 126);
|
||||
|
||||
if (appIsPressed)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), 0.72);
|
||||
}
|
||||
|
||||
if (appKeyInfo.tone != APP_KeyTone::Normal || appIsLatched)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), appIsLatched ? 0.45 : 0.25);
|
||||
}
|
||||
|
||||
return BaseColor;
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetTextColor() const
|
||||
{
|
||||
// 当前项目固定用高亮浅色字,保证暗底上阅读清晰。
|
||||
return QColor(238, 242, 247);
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
51
APP/APP_KeyButton.h
Normal file
51
APP/APP_KeyButton.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "APP/APP_KeypadModel.h"
|
||||
#include <QtGui/QColor>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
namespace APP {
|
||||
|
||||
/*
|
||||
* 这是小键盘界面里的单个按键控件。
|
||||
*
|
||||
* 它的职责很单纯:
|
||||
* 1. 保存这颗键自己的显示信息
|
||||
* 2. 根据“锁定态 / 按下态”切换颜色
|
||||
* 3. 自己完成绘制
|
||||
*
|
||||
* 它不负责协议解析,也不直接参与 DRI / LGC 逻辑。
|
||||
*/
|
||||
class APP_KeyButton : public QPushButton
|
||||
{
|
||||
public:
|
||||
explicit APP_KeyButton(const APP_KeyInfo& KeyInfo, QWidget* parent = nullptr);
|
||||
|
||||
// 锁定态通常表示这颗键处于某种功能模式。
|
||||
void App_Func_SetLatched(bool IsLatched);
|
||||
|
||||
// 按下态表示实体键或逻辑键当前真的处于按下中。
|
||||
void App_Func_SetPressed(bool IsPressed);
|
||||
|
||||
protected:
|
||||
// 每颗键都自己画背景、边框、提示文字和主文字。
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
// 下面这些函数只负责给 paintEvent 提供颜色。
|
||||
QColor App_Func_GetAccentColor() const;
|
||||
QColor App_Func_GetBackgroundColor() const;
|
||||
QColor App_Func_GetBorderColor() const;
|
||||
QColor App_Func_GetTextColor() const;
|
||||
|
||||
// 这颗键对应的显示信息。
|
||||
APP_KeyInfo appKeyInfo;
|
||||
|
||||
// 是否处于锁定态。
|
||||
bool appIsLatched = false;
|
||||
|
||||
// 是否处于按下态。
|
||||
bool appIsPressed = false;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
135
APP/APP_KeypadModel.cpp
Normal file
135
APP/APP_KeypadModel.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "APP/APP_KeypadModel.h"
|
||||
|
||||
namespace APP {
|
||||
|
||||
APP_KeypadModel::APP_KeypadModel()
|
||||
{
|
||||
appKeyList = {
|
||||
{QStringLiteral("num"), QStringLiteral("Num"), QStringLiteral(""), 0x0053, 0, 0, 1, 1, APP_KeyTone::Aqua},
|
||||
{QStringLiteral("divide"), QStringLiteral("/"), QStringLiteral(""), 0x0054, 0, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("multiply"), QStringLiteral("*"), QStringLiteral(""), 0x0055, 0, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("minus"), QStringLiteral("-"), QStringLiteral(""), 0x0056, 0, 3, 1, 1, APP_KeyTone::Amber},
|
||||
{QStringLiteral("7"), QStringLiteral("7"), QStringLiteral("Home"), 0x005F, 1, 0, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("8"), QStringLiteral("8"), QStringLiteral("↑"), 0x0060, 1, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("9"), QStringLiteral("9"), QStringLiteral("PgUp"), 0x0061, 1, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("plus"), QStringLiteral("+"), QStringLiteral(""), 0x0057, 1, 3, 2, 1, APP_KeyTone::Aqua},
|
||||
{QStringLiteral("4"), QStringLiteral("4"), QStringLiteral("←"), 0x005C, 2, 0, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("5"), QStringLiteral("5"), QStringLiteral(""), 0x005D, 2, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("6"), QStringLiteral("6"), QStringLiteral("→"), 0x005E, 2, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("1"), QStringLiteral("1"), QStringLiteral("End"), 0x0059, 3, 0, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("2"), QStringLiteral("2"), QStringLiteral("↓"), 0x005A, 3, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("3"), QStringLiteral("3"), QStringLiteral("PgDn"), 0x005B, 3, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("enter"), QStringLiteral("Enter"), QStringLiteral(""), 0x0058, 3, 3, 2, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("Ins"), 0x0062, 4, 0, 1, 2, APP_KeyTone::Normal},
|
||||
{QStringLiteral("dot"), QStringLiteral("."), QStringLiteral("Del"), 0x0063, 4, 2, 1, 1, APP_KeyTone::Normal}
|
||||
};
|
||||
|
||||
appFunctionKeyList = {
|
||||
{QStringLiteral("7"), QStringLiteral("7"), QStringLiteral("功能"), 0x005F, 0, 0, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("8"), QStringLiteral("8"), QStringLiteral("功能"), 0x0060, 0, 1, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("9"), QStringLiteral("9"), QStringLiteral("功能"), 0x0061, 0, 2, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("minus"), QStringLiteral("-"), QStringLiteral("功能"), 0x0056, 0, 3, 1, 1, APP_KeyTone::Amber},
|
||||
{QStringLiteral("4"), QStringLiteral("4"), QStringLiteral("功能"), 0x005C, 1, 0, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("5"), QStringLiteral("5"), QStringLiteral("功能"), 0x005D, 1, 1, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("6"), QStringLiteral("6"), QStringLiteral("功能"), 0x005E, 1, 2, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("plus"), QStringLiteral("+"), QStringLiteral("功能"), 0x0057, 1, 3, 2, 1, APP_KeyTone::Aqua},
|
||||
{QStringLiteral("1"), QStringLiteral("1"), QStringLiteral("功能"), 0x0059, 2, 0, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("2"), QStringLiteral("2"), QStringLiteral("功能"), 0x005A, 2, 1, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("3"), QStringLiteral("3"), QStringLiteral("功能"), 0x005B, 2, 2, 1, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("功能"), 0x0062, 3, 0, 1, 3, APP_KeyTone::Blue}
|
||||
};
|
||||
}
|
||||
|
||||
const QVector<APP_KeyInfo>& APP_KeypadModel::App_Func_GetKeyList() const
|
||||
{
|
||||
return appKeyList;
|
||||
}
|
||||
|
||||
const QVector<APP_KeyInfo>& APP_KeypadModel::App_Func_GetFunctionKeyList() const
|
||||
{
|
||||
return appFunctionKeyList;
|
||||
}
|
||||
|
||||
bool APP_KeypadModel::App_Func_IsLatched(const QString& KeyId) const
|
||||
{
|
||||
return (KeyId == QStringLiteral("num")) && appNumLockOn;
|
||||
}
|
||||
|
||||
bool APP_KeypadModel::App_Func_IsPressed(const QString& KeyId) const
|
||||
{
|
||||
return appPressedKeyIdList.contains(KeyId);
|
||||
}
|
||||
|
||||
quint16 APP_KeypadModel::App_Func_GetUsageFromKeyId(const QString& KeyId) const
|
||||
{
|
||||
for (const APP_KeyInfo& KeyInfo : appKeyList)
|
||||
{
|
||||
if (KeyInfo.id == KeyId)
|
||||
{
|
||||
return KeyInfo.usage;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void APP_KeypadModel::App_Func_SetNumLockOn(bool IsOn)
|
||||
{
|
||||
appNumLockOn = IsOn;
|
||||
}
|
||||
|
||||
void APP_KeypadModel::App_Func_ClearPressedKeys()
|
||||
{
|
||||
appPressedKeyIdList.clear();
|
||||
}
|
||||
|
||||
void APP_KeypadModel::App_Func_SetPressedKeysFromUsageList(const QVector<quint16>& UsageList)
|
||||
{
|
||||
appPressedKeyIdList.clear();
|
||||
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
const QString KeyId = App_Func_GetKeyIdFromUsage(App_Func_MapUsageForUi(Usage));
|
||||
if (!KeyId.isEmpty() && !appPressedKeyIdList.contains(KeyId))
|
||||
{
|
||||
appPressedKeyIdList.append(KeyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APP_KeypadModel::App_Func_SetSwapUsagePair(quint16 UsageLeft, quint16 UsageRight, bool IsEnabled)
|
||||
{
|
||||
appIsSwapOn = IsEnabled;
|
||||
appSwapUsageLeft = UsageLeft;
|
||||
appSwapUsageRight = UsageRight;
|
||||
}
|
||||
|
||||
quint16 APP_KeypadModel::App_Func_MapUsageForUi(quint16 Usage) const
|
||||
{
|
||||
if (!appIsSwapOn || (appSwapUsageLeft == 0) || (appSwapUsageRight == 0) || (appSwapUsageLeft == appSwapUsageRight))
|
||||
{
|
||||
return Usage;
|
||||
}
|
||||
|
||||
if (Usage == appSwapUsageLeft)
|
||||
{
|
||||
return appSwapUsageRight;
|
||||
}
|
||||
|
||||
return (Usage == appSwapUsageRight) ? appSwapUsageLeft : Usage;
|
||||
}
|
||||
|
||||
QString APP_KeypadModel::App_Func_GetKeyIdFromUsage(quint16 Usage) const
|
||||
{
|
||||
for (const APP_KeyInfo& KeyInfo : appKeyList)
|
||||
{
|
||||
if (KeyInfo.usage == Usage)
|
||||
{
|
||||
return KeyInfo.id;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
58
APP/APP_KeypadModel.h
Normal file
58
APP/APP_KeypadModel.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
namespace APP {
|
||||
|
||||
enum class APP_KeyTone
|
||||
{
|
||||
Normal,
|
||||
Aqua,
|
||||
Amber,
|
||||
Blue
|
||||
};
|
||||
|
||||
struct APP_KeyInfo
|
||||
{
|
||||
QString id;
|
||||
QString label;
|
||||
QString hint;
|
||||
quint16 usage = 0;
|
||||
int row = 0;
|
||||
int column = 0;
|
||||
int rowSpan = 1;
|
||||
int columnSpan = 1;
|
||||
APP_KeyTone tone = APP_KeyTone::Normal;
|
||||
};
|
||||
|
||||
class APP_KeypadModel
|
||||
{
|
||||
public:
|
||||
APP_KeypadModel();
|
||||
|
||||
const QVector<APP_KeyInfo>& App_Func_GetKeyList() const;
|
||||
const QVector<APP_KeyInfo>& App_Func_GetFunctionKeyList() const;
|
||||
bool App_Func_IsLatched(const QString& KeyId) const;
|
||||
bool App_Func_IsPressed(const QString& KeyId) const;
|
||||
quint16 App_Func_GetUsageFromKeyId(const QString& KeyId) const;
|
||||
void App_Func_SetNumLockOn(bool IsOn);
|
||||
void App_Func_ClearPressedKeys();
|
||||
void App_Func_SetPressedKeysFromUsageList(const QVector<quint16>& UsageList);
|
||||
void App_Func_SetSwapUsagePair(quint16 UsageLeft, quint16 UsageRight, bool IsEnabled);
|
||||
|
||||
private:
|
||||
quint16 App_Func_MapUsageForUi(quint16 Usage) const;
|
||||
QString App_Func_GetKeyIdFromUsage(quint16 Usage) const;
|
||||
|
||||
QVector<APP_KeyInfo> appKeyList;
|
||||
QVector<APP_KeyInfo> appFunctionKeyList;
|
||||
QStringList appPressedKeyIdList;
|
||||
bool appNumLockOn = false;
|
||||
bool appIsSwapOn = false;
|
||||
quint16 appSwapUsageLeft = 0;
|
||||
quint16 appSwapUsageRight = 0;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
131
APP/APP_Theme.cpp
Normal file
131
APP/APP_Theme.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "APP/APP_Theme.h"
|
||||
|
||||
#include <QtGui/QFontDatabase>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace APP {
|
||||
|
||||
QPalette APP_Theme::App_Func_GetPalette()
|
||||
{
|
||||
/*
|
||||
* 不用样式表时,Qt 最稳妥的统一美化方式就是调色板:
|
||||
* 1. 先选 Fusion 风格
|
||||
* 2. 再给标准控件一组统一颜色
|
||||
*/
|
||||
QPalette Palette;
|
||||
|
||||
const QColor WindowColor(20, 25, 33);
|
||||
const QColor BaseColor(28, 34, 42);
|
||||
const QColor AltBaseColor(34, 40, 49);
|
||||
const QColor ButtonColor(38, 44, 54);
|
||||
const QColor BorderHintColor(86, 96, 108);
|
||||
const QColor HighlightColor(72, 184, 162);
|
||||
const QColor TextColor(238, 242, 247);
|
||||
const QColor DimTextColor(162, 170, 182);
|
||||
|
||||
Palette.setColor(QPalette::Window, WindowColor);
|
||||
Palette.setColor(QPalette::WindowText, TextColor);
|
||||
Palette.setColor(QPalette::Base, BaseColor);
|
||||
Palette.setColor(QPalette::AlternateBase, AltBaseColor);
|
||||
Palette.setColor(QPalette::Text, TextColor);
|
||||
Palette.setColor(QPalette::Button, ButtonColor);
|
||||
Palette.setColor(QPalette::ButtonText, TextColor);
|
||||
Palette.setColor(QPalette::BrightText, QColor(255, 255, 255));
|
||||
Palette.setColor(QPalette::Light, BorderHintColor.lighter(120));
|
||||
Palette.setColor(QPalette::Midlight, BorderHintColor);
|
||||
Palette.setColor(QPalette::Mid, BorderHintColor.darker(120));
|
||||
Palette.setColor(QPalette::Dark, WindowColor.darker(140));
|
||||
Palette.setColor(QPalette::Shadow, QColor(0, 0, 0, 140));
|
||||
Palette.setColor(QPalette::Highlight, HighlightColor);
|
||||
Palette.setColor(QPalette::HighlightedText, TextColor);
|
||||
Palette.setColor(QPalette::ToolTipBase, BaseColor);
|
||||
Palette.setColor(QPalette::ToolTipText, TextColor);
|
||||
Palette.setColor(QPalette::Link, QColor(103, 146, 224));
|
||||
Palette.setColor(QPalette::PlaceholderText, QColor(170, 178, 188, 170));
|
||||
Palette.setColor(QPalette::Disabled, QPalette::WindowText, DimTextColor);
|
||||
Palette.setColor(QPalette::Disabled, QPalette::Text, DimTextColor);
|
||||
Palette.setColor(QPalette::Disabled, QPalette::ButtonText, DimTextColor);
|
||||
|
||||
return Palette;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetBodyFont()
|
||||
{
|
||||
// 正文用相对稳妥、系统常见的字体候选。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Segoe UI Variable Text")
|
||||
<< QStringLiteral("Microsoft YaHei UI")
|
||||
<< QStringLiteral("Segoe UI")
|
||||
<< QStringLiteral("Bahnschrift")));
|
||||
Font.setPointSize(10);
|
||||
Font.setWeight(QFont::Medium);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetTitleFont()
|
||||
{
|
||||
// 页面标题字号更大、字重更高。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Segoe UI Variable Display Semibold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")
|
||||
<< QStringLiteral("Bahnschrift SemiBold")));
|
||||
Font.setPointSize(21);
|
||||
Font.setWeight(QFont::DemiBold);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetMetricFont()
|
||||
{
|
||||
// 指标类标题用比正文更有力度的字重。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Bahnschrift SemiBold")
|
||||
<< QStringLiteral("Segoe UI Semibold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")));
|
||||
Font.setPointSize(12);
|
||||
Font.setWeight(QFont::DemiBold);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetKeyLabelFont()
|
||||
{
|
||||
// 按键主文字字号较大,保证小键盘一眼能看清。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Bahnschrift SemiBold")
|
||||
<< QStringLiteral("Segoe UI Semibold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")));
|
||||
Font.setPointSize(22);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetKeyHintFont()
|
||||
{
|
||||
// 按键 hint 放左上角,所以字号更小。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Segoe UI Semibold")
|
||||
<< QStringLiteral("Bahnschrift SemiBold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")));
|
||||
Font.setPointSize(8);
|
||||
Font.setLetterSpacing(QFont::AbsoluteSpacing, 1.0);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QString APP_Theme::App_Func_PickFontFamily(const QStringList& FamilyList)
|
||||
{
|
||||
// 从候选字体里依次挑选系统真实存在的字体。
|
||||
const QFontDatabase Database;
|
||||
const QStringList AvailableFamilyList = Database.families();
|
||||
|
||||
for (int Index = 0; Index < FamilyList.size(); ++Index)
|
||||
{
|
||||
const QString& Family = FamilyList.at(Index);
|
||||
if (AvailableFamilyList.contains(Family))
|
||||
{
|
||||
return Family;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都不存在,就退回 Qt 当前默认字体。
|
||||
return QApplication::font().family();
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
38
APP/APP_Theme.h
Normal file
38
APP/APP_Theme.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtGui/QFont>
|
||||
#include <QtGui/QPalette>
|
||||
|
||||
namespace APP {
|
||||
|
||||
/*
|
||||
* 主题模块现在只保留一套固定暗色风格。
|
||||
*
|
||||
* - 不参与 DRI 枚举
|
||||
* - 不参与协议解析
|
||||
* - 不参与业务判断
|
||||
*/
|
||||
class APP_Theme
|
||||
{
|
||||
public:
|
||||
// 返回标准控件使用的统一调色板。
|
||||
static QPalette App_Func_GetPalette();
|
||||
|
||||
// 正文说明文字的默认字体。
|
||||
static QFont App_Func_GetBodyFont();
|
||||
// 页面标题字体。
|
||||
static QFont App_Func_GetTitleFont();
|
||||
// 指标、卡片主标题使用的强调字体。
|
||||
static QFont App_Func_GetMetricFont();
|
||||
// 键帽中央主文字字体。
|
||||
static QFont App_Func_GetKeyLabelFont();
|
||||
// 键帽角落提示文字字体。
|
||||
static QFont App_Func_GetKeyHintFont();
|
||||
|
||||
private:
|
||||
// 从候选字体列表中挑出当前系统真实存在的一项。
|
||||
static QString App_Func_PickFontFamily(const QStringList& FamilyList);
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
622
APP/APP_UIWindow.cpp
Normal file
622
APP/APP_UIWindow.cpp
Normal file
@@ -0,0 +1,622 @@
|
||||
#include "APP/APP_UIWindow.h"
|
||||
|
||||
#include "APP/APP_GlassCard.h"
|
||||
#include "APP/APP_KeyButton.h"
|
||||
#include "APP/APP_Theme.h"
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtWidgets/QButtonGroup>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QFormLayout>
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QHBoxLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QTabBar>
|
||||
#include <QtWidgets/QTabWidget>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
|
||||
namespace APP {
|
||||
|
||||
namespace {
|
||||
|
||||
class App_TabBar : public QTabBar
|
||||
{
|
||||
public:
|
||||
explicit App_TabBar(QWidget* parent = nullptr)
|
||||
: QTabBar(parent)
|
||||
{
|
||||
setDrawBase(false);
|
||||
setExpanding(true);
|
||||
setUsesScrollButtons(false);
|
||||
setElideMode(Qt::ElideNone);
|
||||
setMouseTracking(true);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
}
|
||||
|
||||
protected:
|
||||
QSize tabSizeHint(int index) const override
|
||||
{
|
||||
QSize Size = QTabBar::tabSizeHint(index);
|
||||
Size.rheight() = qMax(Size.height(), 38);
|
||||
Size.rwidth() += 18;
|
||||
return Size;
|
||||
}
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event) override
|
||||
{
|
||||
const int HoverIndex = tabAt(event->pos());
|
||||
if (appHoverIndex != HoverIndex)
|
||||
{
|
||||
appHoverIndex = HoverIndex;
|
||||
update();
|
||||
}
|
||||
|
||||
QTabBar::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void leaveEvent(QEvent* event) override
|
||||
{
|
||||
appHoverIndex = -1;
|
||||
update();
|
||||
QTabBar::leaveEvent(event);
|
||||
}
|
||||
|
||||
void paintEvent(QPaintEvent* event) override
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter Painter(this);
|
||||
Painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
Painter.setFont(APP_Theme::App_Func_GetBodyFont());
|
||||
|
||||
for (int Index = 0; Index < count(); ++Index)
|
||||
{
|
||||
QRect TabRect = tabRect(Index).adjusted(3, 5, -3, 0);
|
||||
const bool IsSelected = (Index == currentIndex());
|
||||
const bool IsHovered = (Index == appHoverIndex);
|
||||
|
||||
QColor FillColor(36, 40, 48);
|
||||
QColor BorderColor(92, 102, 114);
|
||||
QColor TextColor(214, 222, 232);
|
||||
|
||||
if (IsHovered)
|
||||
{
|
||||
FillColor = QColor(48, 54, 64);
|
||||
}
|
||||
|
||||
if (IsSelected)
|
||||
{
|
||||
FillColor = QColor(58, 64, 74);
|
||||
BorderColor = QColor(120, 132, 146);
|
||||
TextColor = QColor(238, 242, 247);
|
||||
}
|
||||
|
||||
Painter.setPen(QPen(BorderColor, 1.0));
|
||||
Painter.setBrush(FillColor);
|
||||
Painter.drawRoundedRect(TabRect, 8.0, 8.0);
|
||||
|
||||
Painter.setPen(TextColor);
|
||||
Painter.drawText(TabRect, Qt::AlignCenter, tabText(Index));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int appHoverIndex = -1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Qt 把 setTabBar() 设成 protected,
|
||||
* 所以这里保留一个最小子类,只负责注入我们自绘的页签栏。
|
||||
* 这层不是业务封装,而是 Qt API 访问限制带来的必要适配。
|
||||
*/
|
||||
class App_PageTabWidget : public QTabWidget
|
||||
{
|
||||
public:
|
||||
explicit App_PageTabWidget(QWidget* parent = nullptr)
|
||||
: QTabWidget(parent)
|
||||
{
|
||||
setTabBar(new App_TabBar(this));
|
||||
setDocumentMode(true);
|
||||
tabBar()->setExpanding(true);
|
||||
}
|
||||
};
|
||||
|
||||
QLabel* App_Func_CreateLabel(QWidget* parent, const QString& text, const QFont& font, bool wordWrap = false)
|
||||
{
|
||||
QLabel* label = new QLabel(text, parent);
|
||||
label->setFont(font);
|
||||
label->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
label->setWordWrap(wordWrap);
|
||||
return label;
|
||||
}
|
||||
|
||||
APP_GlassCard* App_Func_CreateCard(QWidget* parent, QVBoxLayout** pp_Layout)
|
||||
{
|
||||
APP_GlassCard* card = new APP_GlassCard(parent);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout(card);
|
||||
layout->setContentsMargins(20, 20, 20, 20);
|
||||
layout->setSpacing(14);
|
||||
*pp_Layout = layout;
|
||||
return card;
|
||||
}
|
||||
|
||||
void App_Func_SetGridStretch(QGridLayout* grid, int columnCount, int rowCount)
|
||||
{
|
||||
for (int column = 0; column < columnCount; ++column)
|
||||
{
|
||||
grid->setColumnStretch(column, 1);
|
||||
}
|
||||
for (int row = 0; row < rowCount; ++row)
|
||||
{
|
||||
grid->setRowStretch(row, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
App_UIWindow::App_UIWindow(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
App_Func_InitWindow();
|
||||
App_Func_InitUi();
|
||||
App_Func_InitConnect();
|
||||
App_Func_InitLogic();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
App_UIWindow::~App_UIWindow()
|
||||
{
|
||||
Lgc_Core_Func_Close(&appLgcState);
|
||||
}
|
||||
|
||||
void App_UIWindow::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter Painter(this);
|
||||
Painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
Painter.fillRect(rect(), palette().color(QPalette::Window));
|
||||
}
|
||||
|
||||
bool App_UIWindow::nativeEvent(const QByteArray& EventType, void* p_Message, long* p_Result)
|
||||
{
|
||||
/*
|
||||
* Windows 原生消息不在 APP 层解析。
|
||||
* APP 只做一件事:把消息往下转给 LGC / DRI。
|
||||
*/
|
||||
Lgc_Core_Func_HandleNativeMessage(&appLgcState, p_Message);
|
||||
return QWidget::nativeEvent(EventType, p_Message, p_Result);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitWindow()
|
||||
{
|
||||
setWindowTitle(QStringLiteral("数字键盘上位机"));
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
setMinimumSize(640, 960);
|
||||
resize(700, 1020);
|
||||
#else
|
||||
setMinimumSize(620, 760);
|
||||
resize(680, 820);
|
||||
#endif
|
||||
|
||||
setAttribute(Qt::WA_StyledBackground, true);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitUi()
|
||||
{
|
||||
QVBoxLayout* p_RootLayout = new QVBoxLayout(this);
|
||||
p_RootLayout->setContentsMargins(26, 22, 26, 24);
|
||||
p_RootLayout->setSpacing(14);
|
||||
|
||||
/*
|
||||
* 页面切换直接用 QTabWidget。
|
||||
* 这比自己维护一套切页按钮更短,也更适合教学。
|
||||
*/
|
||||
App_PageTabWidget* p_PageTab = new App_PageTabWidget(this);
|
||||
|
||||
p_PageTab->addTab(App_Func_CreatePadCard(), QStringLiteral("小键盘"));
|
||||
p_PageTab->addTab(App_Func_CreateFunctionRegisterCard(), QStringLiteral("功能注册"));
|
||||
p_PageTab->addTab(App_Func_CreateFunctionConfigCard(), QStringLiteral("功能配置"));
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
p_PageTab->addTab(App_Func_CreateDebugCard(), QStringLiteral("调试"));
|
||||
#endif
|
||||
|
||||
p_RootLayout->addWidget(p_PageTab, 1);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitConnect()
|
||||
{
|
||||
/*
|
||||
* 这份教学版刻意把所有 connect 放在同一个函数里,
|
||||
* 让学生顺着“谁发信号 -> 谁收信号”一眼就能看完。
|
||||
*/
|
||||
connect(&appTimerPoll, &QTimer::timeout, this, &App_UIWindow::App_Func_OnPollTimer);
|
||||
|
||||
/*
|
||||
* 这些控件都在 InitUi 阶段固定创建完成,
|
||||
* 所以这里直接 connect,不再加“防御性空指针判断”干扰阅读。
|
||||
*/
|
||||
const auto ConnectConfigSignal = [this](auto Sender, auto Signal)
|
||||
{
|
||||
connect(Sender, Signal, this, [this]()
|
||||
{
|
||||
App_Func_UpdateFunctionConfigFromUi();
|
||||
});
|
||||
};
|
||||
|
||||
ConnectConfigSignal(appFunctionEditMacroText, &QLineEdit::textChanged);
|
||||
ConnectConfigSignal(appFunctionEditWebsite, &QLineEdit::textChanged);
|
||||
ConnectConfigSignal(appFunctionComboSwapLeft, qOverload<int>(&QComboBox::currentIndexChanged));
|
||||
ConnectConfigSignal(appFunctionComboSwapRight, qOverload<int>(&QComboBox::currentIndexChanged));
|
||||
|
||||
/*
|
||||
* 功能键按钮统一交给 QButtonGroup 管,
|
||||
* 这样既能减少每颗按钮各写一段 lambda,
|
||||
* 也正好把 Qt 里“按钮分组 + id 分发”的知识点展示出来。
|
||||
*/
|
||||
connect(appFunctionButtonGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, [this](int ButtonId)
|
||||
{
|
||||
App_Func_OnFunctionKeyClicked(static_cast<quint16>(ButtonId));
|
||||
});
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
connect(appDebugPanel->Debug_Func_GetRefreshButton(), &QPushButton::clicked,
|
||||
this, &App_UIWindow::App_Func_OnRefreshDeviceClicked);
|
||||
|
||||
connect(appDebugPanel->Debug_Func_GetClearButton(), &QPushButton::clicked,
|
||||
this, &App_UIWindow::App_Func_OnClearLogClicked);
|
||||
|
||||
connect(appDebugPanel->Debug_Func_GetApplyConfigButton(), &QPushButton::clicked,
|
||||
this, &App_UIWindow::App_Func_OnApplyDeviceConfigClicked);
|
||||
#endif
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitLogic()
|
||||
{
|
||||
Lgc_Core_Func_Init(&appLgcState);
|
||||
Lgc_Core_Func_SetWindowHandle(&appLgcState, reinterpret_cast<void*>(winId()));
|
||||
|
||||
App_Func_UpdateFunctionConfigFromUi();
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
App_Func_RefreshDeviceConfigFromState();
|
||||
#endif
|
||||
|
||||
Lgc_Core_Func_Start(&appLgcState);
|
||||
|
||||
/*
|
||||
* 轮询不仅服务调试窗口,
|
||||
* 小键盘状态页和功能键页本身也要靠它持续拿最新状态。
|
||||
* 所以这里始终启动,不跟调试开关绑定。
|
||||
*/
|
||||
appTimerPoll.setInterval(30);
|
||||
appTimerPoll.start();
|
||||
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshUi()
|
||||
{
|
||||
App_Func_RefreshKeypadButtons();
|
||||
App_Func_RefreshFunctionButtons();
|
||||
App_Func_RefreshFunctionStatus();
|
||||
update();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshKeypadState()
|
||||
{
|
||||
appKeypadModel.App_Func_SetNumLockOn(appLgcState.IsSystemNumLockOn);
|
||||
appKeypadModel.App_Func_SetSwapUsagePair(
|
||||
appLgcState.SwapUsageLeft,
|
||||
appLgcState.SwapUsageRight,
|
||||
appLgcState.IsSwapModeOn);
|
||||
|
||||
const QVector<quint16>* p_UsageList = nullptr;
|
||||
if (appLgcState.IsPhysicalKeyStateValid)
|
||||
{
|
||||
p_UsageList = &appLgcState.PhysicalUsageList;
|
||||
}
|
||||
else if (appLgcState.IsVisibleKeyStateValid)
|
||||
{
|
||||
p_UsageList = &appLgcState.VisibleUsageList;
|
||||
}
|
||||
|
||||
if (p_UsageList != nullptr)
|
||||
{
|
||||
appKeypadModel.App_Func_SetPressedKeysFromUsageList(*p_UsageList);
|
||||
}
|
||||
else
|
||||
{
|
||||
appKeypadModel.App_Func_ClearPressedKeys();
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshKeypadButtons()
|
||||
{
|
||||
for (auto It = appKeypadButtonMap.begin(); It != appKeypadButtonMap.end(); ++It)
|
||||
{
|
||||
APP_KeyButton* p_Button = It.value();
|
||||
p_Button->App_Func_SetLatched(appKeypadModel.App_Func_IsLatched(It.key()));
|
||||
p_Button->App_Func_SetPressed(appKeypadModel.App_Func_IsPressed(It.key()));
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshFunctionButtons()
|
||||
{
|
||||
for (auto It = appFunctionButtonMap.begin(); It != appFunctionButtonMap.end(); ++It)
|
||||
{
|
||||
const QString KeyId = It.key();
|
||||
APP_KeyButton* p_Button = It.value();
|
||||
const quint16 Usage = appKeypadModel.App_Func_GetUsageFromKeyId(KeyId);
|
||||
const bool IsFunctionMode = Lgc_Core_Func_IsUsageFunctionMode(&appLgcState, Usage);
|
||||
|
||||
p_Button->App_Func_SetLatched(IsFunctionMode);
|
||||
p_Button->App_Func_SetPressed(appKeypadModel.App_Func_IsPressed(KeyId));
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshFunctionStatus()
|
||||
{
|
||||
appFunctionLabelStatus->setText(appLgcState.TextFunctionStatus.isEmpty()
|
||||
? QStringLiteral("等待功能键动作。")
|
||||
: appLgcState.TextFunctionStatus);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshDebugView()
|
||||
{
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
appDebugPanel->Debug_Func_SetConnectionText(appLgcState.TextConnection, appLgcState.IsConnected);
|
||||
appDebugPanel->Debug_Func_SetLogText(appLgcState.TextLog);
|
||||
#endif
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshAfterLogicChange()
|
||||
{
|
||||
/*
|
||||
* 逻辑层状态一旦变化,界面层真正需要做的事情其实只有这三步:
|
||||
* 1. 先把 LGC 状态同步到 APP 自己的显示模型
|
||||
* 2. 再把调试文本同步到调试页
|
||||
* 3. 最后统一刷新界面控件
|
||||
*/
|
||||
App_Func_RefreshKeypadState();
|
||||
App_Func_RefreshDebugView();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_UpdateFunctionConfigFromUi()
|
||||
{
|
||||
const quint16 OldSwapUsageLeft = appLgcState.FunctionButtonConfig.SwapUsageLeft;
|
||||
const quint16 OldSwapUsageRight = appLgcState.FunctionButtonConfig.SwapUsageRight;
|
||||
|
||||
appLgcState.FunctionButtonConfig.MacroText = appFunctionEditMacroText->text();
|
||||
appLgcState.FunctionButtonConfig.WebsiteUrl = appFunctionEditWebsite->text();
|
||||
appLgcState.FunctionButtonConfig.SwapUsageLeft =
|
||||
static_cast<quint16>(appFunctionComboSwapLeft->currentData().toUInt());
|
||||
appLgcState.FunctionButtonConfig.SwapUsageRight =
|
||||
static_cast<quint16>(appFunctionComboSwapRight->currentData().toUInt());
|
||||
|
||||
if (appLgcState.IsSwapModeOn &&
|
||||
((OldSwapUsageLeft != appLgcState.FunctionButtonConfig.SwapUsageLeft) ||
|
||||
(OldSwapUsageRight != appLgcState.FunctionButtonConfig.SwapUsageRight)))
|
||||
{
|
||||
Lgc_Core_Func_SetSwapMode(
|
||||
&appLgcState,
|
||||
appLgcState.FunctionButtonConfig.SwapUsageLeft,
|
||||
appLgcState.FunctionButtonConfig.SwapUsageRight,
|
||||
true);
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnFunctionKeyClicked(quint16 Usage)
|
||||
{
|
||||
const bool IsFunctionMode = Lgc_Core_Func_IsUsageFunctionMode(&appLgcState, Usage);
|
||||
Lgc_Core_Func_SetUsageFunctionMode(&appLgcState, Usage, !IsFunctionMode);
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnPollTimer()
|
||||
{
|
||||
if (Lgc_Core_Func_Poll(&appLgcState))
|
||||
{
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
}
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
void App_UIWindow::App_Func_RefreshDeviceConfigFromState()
|
||||
{
|
||||
appDebugPanel->Debug_Func_SetDeviceConfigText(
|
||||
appLgcState.DeviceConfig.VendorId,
|
||||
appLgcState.DeviceConfig.ProductId);
|
||||
|
||||
appDebugPanel->Debug_Func_SetConfigStatusText(
|
||||
QStringLiteral("当前目标 VID:PID = 0x%1:0x%2")
|
||||
.arg(appLgcState.DeviceConfig.VendorId, 4, 16, QLatin1Char('0'))
|
||||
.arg(appLgcState.DeviceConfig.ProductId, 4, 16, QLatin1Char('0'))
|
||||
.toUpper(),
|
||||
true);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnApplyDeviceConfigClicked()
|
||||
{
|
||||
quint16 VendorId = 0;
|
||||
quint16 ProductId = 0;
|
||||
|
||||
if (!appDebugPanel->Debug_Func_TryGetDeviceConfig(&VendorId, &ProductId))
|
||||
{
|
||||
appDebugPanel->Debug_Func_SetConfigStatusText(
|
||||
QStringLiteral("VID / PID 输入无效,请输入十六进制,例如 1209 和 0001。"),
|
||||
false);
|
||||
return;
|
||||
}
|
||||
|
||||
appLgcState.DeviceConfig.VendorId = VendorId;
|
||||
appLgcState.DeviceConfig.ProductId = ProductId;
|
||||
App_Func_OnRefreshDeviceClicked();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnRefreshDeviceClicked()
|
||||
{
|
||||
Lgc_Core_Func_RefreshDevice(&appLgcState);
|
||||
App_Func_RefreshDeviceConfigFromState();
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnClearLogClicked()
|
||||
{
|
||||
Lgc_Core_Func_ClearLog(&appLgcState);
|
||||
App_Func_RefreshDebugView();
|
||||
}
|
||||
#endif
|
||||
|
||||
QWidget* App_UIWindow::App_Func_CreatePadCard()
|
||||
{
|
||||
QVBoxLayout* p_Layout = nullptr;
|
||||
APP_GlassCard* p_Card = App_Func_CreateCard(this, &p_Layout);
|
||||
|
||||
p_Layout->addWidget(App_Func_CreateLabel(p_Card, QStringLiteral("小键盘状态页"),
|
||||
APP_Theme::App_Func_GetMetricFont()));
|
||||
p_Layout->addWidget(App_Func_CreateLabel(p_Card, QStringLiteral("这一页直接显示实体小键盘当前的按下状态,NumLock 也会同步点亮。"),
|
||||
APP_Theme::App_Func_GetBodyFont(), true));
|
||||
|
||||
QGridLayout* p_Grid = new QGridLayout();
|
||||
p_Grid->setSpacing(14);
|
||||
|
||||
const QVector<APP_KeyInfo>& KeyList = appKeypadModel.App_Func_GetKeyList();
|
||||
|
||||
for (const APP_KeyInfo& Key : KeyList)
|
||||
{
|
||||
APP_KeyButton* p_Button = new APP_KeyButton(Key, p_Card);
|
||||
appKeypadButtonMap.insert(Key.id, p_Button);
|
||||
p_Grid->addWidget(p_Button, Key.row, Key.column, Key.rowSpan, Key.columnSpan);
|
||||
}
|
||||
|
||||
App_Func_SetGridStretch(p_Grid, 4, 5);
|
||||
|
||||
p_Layout->addLayout(p_Grid, 1);
|
||||
return p_Card;
|
||||
}
|
||||
|
||||
QWidget* App_UIWindow::App_Func_CreateFunctionRegisterCard()
|
||||
{
|
||||
QVBoxLayout* p_Layout = nullptr;
|
||||
APP_GlassCard* p_Card = App_Func_CreateCard(this, &p_Layout);
|
||||
|
||||
p_Layout->addWidget(App_Func_CreateLabel(p_Card, QStringLiteral("功能注册"),
|
||||
APP_Theme::App_Func_GetMetricFont()));
|
||||
p_Layout->addWidget(App_Func_CreateLabel(p_Card, QStringLiteral("这一页只负责决定哪些小键盘键切到功能键模式。按钮点亮表示该键已经注册为功能键;再次点击即可取消。"),
|
||||
APP_Theme::App_Func_GetBodyFont(), true));
|
||||
|
||||
QGridLayout* p_Grid = new QGridLayout();
|
||||
p_Grid->setSpacing(14);
|
||||
|
||||
const QVector<APP_KeyInfo>& KeyList = appKeypadModel.App_Func_GetFunctionKeyList();
|
||||
appFunctionButtonGroup = new QButtonGroup(this);
|
||||
|
||||
for (const APP_KeyInfo& Key : KeyList)
|
||||
{
|
||||
APP_KeyButton* p_Button = new APP_KeyButton(Key, p_Card);
|
||||
appFunctionButtonMap.insert(Key.id, p_Button);
|
||||
appFunctionButtonGroup->addButton(p_Button, static_cast<int>(Key.usage));
|
||||
p_Grid->addWidget(p_Button, Key.row, Key.column, Key.rowSpan, Key.columnSpan);
|
||||
}
|
||||
|
||||
App_Func_SetGridStretch(p_Grid, 4, 4);
|
||||
p_Layout->addLayout(p_Grid, 1);
|
||||
return p_Card;
|
||||
}
|
||||
|
||||
QWidget* App_UIWindow::App_Func_CreateFunctionConfigCard()
|
||||
{
|
||||
QVBoxLayout* p_Layout = nullptr;
|
||||
APP_GlassCard* p_Card = App_Func_CreateCard(this, &p_Layout);
|
||||
|
||||
p_Layout->addWidget(App_Func_CreateLabel(p_Card, QStringLiteral("功能配置"),
|
||||
APP_Theme::App_Func_GetMetricFont()));
|
||||
p_Layout->addWidget(App_Func_CreateLabel(p_Card, QStringLiteral("这一页只放功能案例参数。当前教学版保留 0=文本输入、1=按键交换、2=打开网址,学生能更清楚看到“注册”和“配置”是两件事。"),
|
||||
APP_Theme::App_Func_GetBodyFont(), true));
|
||||
|
||||
QFormLayout* p_ConfigLayout = new QFormLayout();
|
||||
p_ConfigLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
p_ConfigLayout->setLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
p_ConfigLayout->setFormAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
p_ConfigLayout->setHorizontalSpacing(10);
|
||||
p_ConfigLayout->setVerticalSpacing(10);
|
||||
const auto AddConfigRow = [p_Card, p_ConfigLayout](const QString& text, QWidget* field)
|
||||
{
|
||||
p_ConfigLayout->addRow(
|
||||
App_Func_CreateLabel(p_Card, text, APP_Theme::App_Func_GetBodyFont()),
|
||||
field);
|
||||
};
|
||||
|
||||
appFunctionEditMacroText = new QLineEdit(p_Card);
|
||||
appFunctionEditMacroText->setText(QStringLiteral("HELLO WORLD!"));
|
||||
appFunctionEditMacroText->setPlaceholderText(QStringLiteral("例如:HELLO WORLD!"));
|
||||
AddConfigRow(QStringLiteral("功能键 0 文本"), appFunctionEditMacroText);
|
||||
|
||||
appFunctionComboSwapLeft = new QComboBox(p_Card);
|
||||
appFunctionComboSwapRight = new QComboBox(p_Card);
|
||||
|
||||
const QVector<APP_KeyInfo>& FunctionKeyList = appKeypadModel.App_Func_GetFunctionKeyList();
|
||||
for (const APP_KeyInfo& Key : FunctionKeyList)
|
||||
{
|
||||
appFunctionComboSwapLeft->addItem(Key.label, static_cast<uint>(Key.usage));
|
||||
appFunctionComboSwapRight->addItem(Key.label, static_cast<uint>(Key.usage));
|
||||
}
|
||||
|
||||
const auto SetComboIndex = [](QComboBox* Combo, quint16 Usage)
|
||||
{
|
||||
const int Index = Combo->findData(static_cast<uint>(Usage));
|
||||
if (Index >= 0)
|
||||
{
|
||||
Combo->setCurrentIndex(Index);
|
||||
}
|
||||
};
|
||||
|
||||
SetComboIndex(appFunctionComboSwapLeft, 0x005C);
|
||||
SetComboIndex(appFunctionComboSwapRight, 0x005D);
|
||||
|
||||
QWidget* p_SwapEditor = new QWidget(p_Card);
|
||||
QHBoxLayout* p_SwapLayout = new QHBoxLayout(p_SwapEditor);
|
||||
p_SwapLayout->setContentsMargins(0, 0, 0, 0);
|
||||
p_SwapLayout->setSpacing(8);
|
||||
p_SwapLayout->addWidget(appFunctionComboSwapLeft);
|
||||
p_SwapLayout->addWidget(App_Func_CreateLabel(p_SwapEditor, QStringLiteral("<->"),
|
||||
APP_Theme::App_Func_GetBodyFont()));
|
||||
p_SwapLayout->addWidget(appFunctionComboSwapRight);
|
||||
p_SwapLayout->addStretch(1);
|
||||
AddConfigRow(QStringLiteral("功能键 1 交换"), p_SwapEditor);
|
||||
|
||||
appFunctionEditWebsite = new QLineEdit(p_Card);
|
||||
appFunctionEditWebsite->setText(QStringLiteral("https://www.deepseek.com/"));
|
||||
appFunctionEditWebsite->setPlaceholderText(QStringLiteral("例如:https://www.deepseek.com/"));
|
||||
AddConfigRow(QStringLiteral("功能键 2 网址"), appFunctionEditWebsite);
|
||||
|
||||
appFunctionLabelStatus = App_Func_CreateLabel(
|
||||
p_Card, QStringLiteral("等待功能键动作。"),
|
||||
APP_Theme::App_Func_GetBodyFont(), true);
|
||||
AddConfigRow(QStringLiteral("最近一次动作"), appFunctionLabelStatus);
|
||||
|
||||
p_Layout->addLayout(p_ConfigLayout);
|
||||
p_Layout->addStretch(1);
|
||||
return p_Card;
|
||||
}
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
QWidget* App_UIWindow::App_Func_CreateDebugCard()
|
||||
{
|
||||
appDebugPanel = new DEBUG::Debug_Panel(this);
|
||||
appDebugPanel->Debug_Func_SetConnectionText(QStringLiteral("未连接,等待枚举设备。"), false);
|
||||
appDebugPanel->Debug_Func_SetLogText(QStringLiteral("等待收到输入包。"));
|
||||
return appDebugPanel;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace APP
|
||||
131
APP/APP_UIWindow.h
Normal file
131
APP/APP_UIWindow.h
Normal file
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "APP/APP_KeypadModel.h"
|
||||
#include "DEBUG/Debug_Config.h"
|
||||
#include "LOGIC/Lgc_Core.h"
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
#include "DEBUG/Debug_Panel.h"
|
||||
#endif
|
||||
|
||||
class QLabel;
|
||||
class QButtonGroup;
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
|
||||
namespace APP {
|
||||
|
||||
class APP_KeyButton;
|
||||
|
||||
/*
|
||||
* APP 主窗口负责把界面搭起来,并把 UI 事件转交给逻辑层。
|
||||
* 它只做三件事:
|
||||
* 1. 创建界面
|
||||
* 2. 周期性刷新 UI
|
||||
* 3. 把 Windows 原生消息转给 LGC / DRI
|
||||
*/
|
||||
class App_UIWindow : public QWidget
|
||||
{
|
||||
public:
|
||||
// 构造主窗口并完成 UI、信号和逻辑初始化。
|
||||
explicit App_UIWindow(QWidget* parent = nullptr);
|
||||
// 析构时负责收尾逻辑层资源。
|
||||
~App_UIWindow() override;
|
||||
|
||||
protected:
|
||||
// 自绘主窗口背景和简单装饰。
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
// 接收 Windows 原生消息,再转给 LGC / DRI 处理。
|
||||
bool nativeEvent(const QByteArray& EventType, void* p_Message, long* p_Result) override;
|
||||
|
||||
private:
|
||||
// 设置窗口标题、大小和基础属性。
|
||||
void App_Func_InitWindow();
|
||||
// 创建主界面控件和布局。
|
||||
void App_Func_InitUi();
|
||||
// 连接按钮、定时器等信号。
|
||||
void App_Func_InitConnect();
|
||||
// 初始化逻辑层状态和设备配置。
|
||||
void App_Func_InitLogic();
|
||||
// 执行一轮完整 UI 刷新。
|
||||
void App_Func_RefreshUi();
|
||||
// 刷新主键盘区状态摘要。
|
||||
void App_Func_RefreshKeypadState();
|
||||
// 刷新主键盘按钮显示。
|
||||
void App_Func_RefreshKeypadButtons();
|
||||
// 刷新功能键按钮显示。
|
||||
void App_Func_RefreshFunctionButtons();
|
||||
// 刷新功能配置区状态文字。
|
||||
void App_Func_RefreshFunctionStatus();
|
||||
// 刷新调试页显示内容。
|
||||
void App_Func_RefreshDebugView();
|
||||
// 逻辑状态改变后集中刷新必要区域。
|
||||
void App_Func_RefreshAfterLogicChange();
|
||||
// 把界面里的功能配置回写到逻辑层。
|
||||
void App_Func_UpdateFunctionConfigFromUi();
|
||||
// 处理功能键区按钮点击。
|
||||
void App_Func_OnFunctionKeyClicked(quint16 Usage);
|
||||
// 定时轮询设备输入与状态。
|
||||
void App_Func_OnPollTimer();
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
/*
|
||||
* 如果后续要整体删除调试功能,
|
||||
* 可以优先从这些入口和 APP_ENABLE_DEBUG_WINDOW 宏开始删。
|
||||
*/
|
||||
// 处理“应用 VID/PID”按钮点击。
|
||||
void App_Func_OnApplyDeviceConfigClicked();
|
||||
// 处理“刷新设备”按钮点击。
|
||||
void App_Func_OnRefreshDeviceClicked();
|
||||
// 处理“清空日志”按钮点击。
|
||||
void App_Func_OnClearLogClicked();
|
||||
// 用逻辑层当前状态刷新调试页输入框。
|
||||
void App_Func_RefreshDeviceConfigFromState();
|
||||
#endif
|
||||
|
||||
// 创建左侧主键盘卡片。
|
||||
QWidget* App_Func_CreatePadCard();
|
||||
// 创建右侧功能键配置卡片。
|
||||
QWidget* App_Func_CreateFunctionRegisterCard();
|
||||
QWidget* App_Func_CreateFunctionConfigCard();
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
// 创建调试卡片。
|
||||
QWidget* App_Func_CreateDebugCard();
|
||||
#endif
|
||||
|
||||
// 键盘布局模型,负责提供按键元数据。
|
||||
APP_KeypadModel appKeypadModel;
|
||||
// 主键盘按钮映射表:键名 -> 按钮对象。
|
||||
QHash<QString, APP_KeyButton*> appKeypadButtonMap;
|
||||
// 功能键按钮映射表:键名 -> 按钮对象。
|
||||
QHash<QString, APP_KeyButton*> appFunctionButtonMap;
|
||||
// 功能键按钮组,用来统一处理点击。
|
||||
QButtonGroup* appFunctionButtonGroup = nullptr;
|
||||
|
||||
// 功能区状态说明标签。
|
||||
QLabel* appFunctionLabelStatus = nullptr;
|
||||
// 功能键 0 的文本宏输入框。
|
||||
QLineEdit* appFunctionEditMacroText = nullptr;
|
||||
// 功能键 1 左侧交换键选择框。
|
||||
QComboBox* appFunctionComboSwapLeft = nullptr;
|
||||
// 功能键 1 右侧交换键选择框。
|
||||
QComboBox* appFunctionComboSwapRight = nullptr;
|
||||
// 功能键 2 的网址输入框。
|
||||
QLineEdit* appFunctionEditWebsite = nullptr;
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
// 调试面板实例。
|
||||
DEBUG::Debug_Panel* appDebugPanel = nullptr;
|
||||
#endif
|
||||
|
||||
// 周期轮询逻辑层的定时器。
|
||||
QTimer appTimerPoll;
|
||||
// APP 层持有的逻辑总状态。
|
||||
Lgc_Core_Struct_State appLgcState;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
Reference in New Issue
Block a user