Push layered Qt host source files

This commit is contained in:
2026-04-17 16:25:19 +08:00
parent b576d3f19d
commit 89b23b2291
58 changed files with 10349 additions and 2461 deletions

View File

@@ -7,9 +7,7 @@ namespace APP {
APP_GlassCard::APP_GlassCard(QWidget* parent)
: QFrame(parent)
{
// 交给我们自己统一绘制卡片外观,不使用 QFrame 默认边框。
setFrameShape(QFrame::NoFrame);
// 背景由 paintEvent 自绘,这里不走样式表背景。
setAttribute(Qt::WA_StyledBackground, false);
}
@@ -17,11 +15,6 @@ 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);
@@ -29,12 +22,9 @@ void APP_GlassCard::paintEvent(QPaintEvent* event)
QPainter Painter(this);
Painter.setRenderHint(QPainter::Antialiasing, true);
// 先画卡片主体。
Painter.setPen(QPen(BorderColor, 1.0));
Painter.setBrush(FillColor);
Painter.drawRoundedRect(BodyRect, Radius, Radius);
}
} // namespace APP

View File

@@ -4,23 +4,12 @@
namespace APP {
/*
* 这是项目里所有“卡片容器”的基础控件。
*
* 它只负责统一外观,不负责任何业务逻辑:
* 1. 统一圆角卡片风格
* 2. 统一边框和暗色底板
*
* 上层像主页卡片、调试卡片都直接继承它。
*/
class APP_GlassCard : public QFrame
{
public:
// 构造一个带统一外观的卡片容器。
explicit APP_GlassCard(QWidget* parent = nullptr);
protected:
// 卡片背景和圆角边框都在这里自绘。
void paintEvent(QPaintEvent* event) override;
};

View File

@@ -4,12 +4,9 @@
#include <QtCore/QtMath>
#include <QtGui/QPainter>
namespace {
namespace
{
/*
* 这个小工具函数用来把两种颜色按比例混合。
* 当前项目里主要用它来根据按键状态生成不同深浅的背景色和边框色。
*/
QColor App_Func_MixColor(const QColor& Left, const QColor& Right, qreal Value)
{
const qreal Rate = qBound(0.0, Value, 1.0);
@@ -29,7 +26,7 @@ APP_KeyButton::APP_KeyButton(const APP_KeyInfo& KeyInfo, QWidget* parent)
: QPushButton(parent),
appKeyInfo(KeyInfo)
{
// 这里把按钮本身的交互属性定下来,后面就主要交给 paintEvent 自绘。
appHintText = appKeyInfo.hint;
setCursor(Qt::PointingHandCursor);
setFlat(true);
setMinimumSize(78, 78);
@@ -38,7 +35,6 @@ APP_KeyButton::APP_KeyButton(const APP_KeyInfo& KeyInfo, QWidget* parent)
void APP_KeyButton::App_Func_SetLatched(bool IsLatched)
{
// 状态没变时不重复刷新,避免无意义重绘。
if (appIsLatched == IsLatched)
{
return;
@@ -50,7 +46,6 @@ void APP_KeyButton::App_Func_SetLatched(bool IsLatched)
void APP_KeyButton::App_Func_SetPressed(bool IsPressed)
{
// 状态没变时同样直接返回。
if (appIsPressed == IsPressed)
{
return;
@@ -60,18 +55,27 @@ void APP_KeyButton::App_Func_SetPressed(bool IsPressed)
update();
}
void APP_KeyButton::App_Func_SetHintText(const QString& HintText)
{
if (appHintText == HintText)
{
return;
}
appHintText = HintText;
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);
@@ -81,24 +85,20 @@ void APP_KeyButton::paintEvent(QPaintEvent* event)
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())
if (!appHintText.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());
appHintText.toUpper());
}
// 主文字放中间。文字长度不同就稍微缩一下字号,避免挤出按钮。
QFont LabelFont = APP_Theme::App_Func_GetKeyLabelFont();
if (appKeyInfo.label.size() > 2)
{
@@ -116,7 +116,6 @@ void APP_KeyButton::paintEvent(QPaintEvent* event)
QColor APP_KeyButton::App_Func_GetAccentColor() const
{
// 每个 tone 对应一组强调色,用来让不同类别的键略有区分。
switch (appKeyInfo.tone)
{
case APP_KeyTone::Aqua:
@@ -133,16 +132,13 @@ QColor APP_KeyButton::App_Func_GetAccentColor() const
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);
@@ -170,7 +166,6 @@ QColor APP_KeyButton::App_Func_GetBorderColor() const
QColor APP_KeyButton::App_Func_GetTextColor() const
{
// 当前项目固定用高亮浅色字,保证暗底上阅读清晰。
return QColor(238, 242, 247);
}

View File

@@ -6,45 +6,27 @@
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);
void App_Func_SetHintText(const QString& HintText);
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;
// 是否处于锁定态。
QString appHintText;
bool appIsLatched = false;
// 是否处于按下态。
bool appIsPressed = false;
};

View File

@@ -5,39 +5,24 @@ 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("num"), QStringLiteral("Num"), QString(), 0x0053, 0, 0, 1, 1, APP_KeyTone::Aqua},
{QStringLiteral("divide"), QStringLiteral("/"), QString(), 0x0054, 0, 1, 1, 1, APP_KeyTone::Normal},
{QStringLiteral("multiply"), QStringLiteral("*"), QString(), 0x0055, 0, 2, 1, 1, APP_KeyTone::Normal},
{QStringLiteral("minus"), QStringLiteral("-"), QString(), 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("8"), QStringLiteral("8"), QStringLiteral("Up"), 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("plus"), QStringLiteral("+"), QString(), 0x0057, 1, 3, 2, 1, APP_KeyTone::Aqua},
{QStringLiteral("4"), QStringLiteral("4"), QStringLiteral("Left"), 0x005C, 2, 0, 1, 1, APP_KeyTone::Normal},
{QStringLiteral("5"), QStringLiteral("5"), QString(), 0x005D, 2, 1, 1, 1, APP_KeyTone::Normal},
{QStringLiteral("6"), QStringLiteral("6"), QStringLiteral("Right"), 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("2"), QStringLiteral("2"), QStringLiteral("Down"), 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("enter"), QStringLiteral("Enter"), QString(), 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
@@ -45,11 +30,6 @@ 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;
@@ -73,6 +53,32 @@ quint16 APP_KeypadModel::App_Func_GetUsageFromKeyId(const QString& KeyId) const
return 0;
}
QString APP_KeypadModel::App_Func_GetKeyIdFromUsage(quint16 Usage) const
{
for (const APP_KeyInfo& KeyInfo : appKeyList)
{
if (KeyInfo.usage == Usage)
{
return KeyInfo.id;
}
}
return QString();
}
QString APP_KeypadModel::App_Func_GetDefaultHint(const QString& KeyId) const
{
for (const APP_KeyInfo& KeyInfo : appKeyList)
{
if (KeyInfo.id == KeyId)
{
return KeyInfo.hint;
}
}
return QString();
}
void APP_KeypadModel::App_Func_SetNumLockOn(bool IsOn)
{
appNumLockOn = IsOn;
@@ -89,7 +95,7 @@ void APP_KeypadModel::App_Func_SetPressedKeysFromUsageList(const QVector<quint16
for (quint16 Usage : UsageList)
{
const QString KeyId = App_Func_GetKeyIdFromUsage(App_Func_MapUsageForUi(Usage));
const QString KeyId = App_Func_GetKeyIdFromUsage(Usage);
if (!KeyId.isEmpty() && !appPressedKeyIdList.contains(KeyId))
{
appPressedKeyIdList.append(KeyId);
@@ -97,39 +103,4 @@ void APP_KeypadModel::App_Func_SetPressedKeysFromUsageList(const QVector<quint16
}
}
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

View File

@@ -33,26 +33,19 @@ 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;
QString App_Func_GetKeyIdFromUsage(quint16 Usage) const;
QString App_Func_GetDefaultHint(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

View File

@@ -7,11 +7,6 @@ namespace APP {
QPalette APP_Theme::App_Func_GetPalette()
{
/*
* 不用样式表时Qt 最稳妥的统一美化方式就是调色板:
* 1. 先选 Fusion 风格
* 2. 再给标准控件一组统一颜色
*/
QPalette Palette;
const QColor WindowColor(20, 25, 33);
@@ -45,13 +40,140 @@ QPalette APP_Theme::App_Func_GetPalette()
Palette.setColor(QPalette::Disabled, QPalette::WindowText, DimTextColor);
Palette.setColor(QPalette::Disabled, QPalette::Text, DimTextColor);
Palette.setColor(QPalette::Disabled, QPalette::ButtonText, DimTextColor);
Palette.setColor(QPalette::Disabled, QPalette::Base, QColor(30, 35, 43));
Palette.setColor(QPalette::Disabled, QPalette::Button, QColor(34, 40, 49));
Palette.setColor(QPalette::Disabled, QPalette::Window, QColor(24, 29, 36));
Palette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(60, 68, 78));
Palette.setColor(QPalette::Disabled, QPalette::HighlightedText, DimTextColor);
return Palette;
}
QString APP_Theme::App_Func_GetStyleSheet()
{
return QStringLiteral(R"(
QWidget {
color: #eef2f7;
background-color: transparent;
}
QTabWidget::pane {
border: 0;
background: transparent;
}
QTabBar::tab {
background: rgba(56, 64, 76, 0.78);
color: #b9c3cf;
border: 1px solid #5d6a78;
border-bottom: none;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding: 8px 18px;
margin-right: 6px;
min-width: 92px;
}
QTabBar::tab:selected {
background: #2d3742;
color: #f7fbff;
}
QTabBar::tab:hover:!selected {
background: #394553;
color: #eff4f8;
}
QPushButton {
background: #303947;
color: #eff4f8;
border: 1px solid #667587;
border-radius: 8px;
padding: 8px 14px;
min-height: 18px;
}
QPushButton:hover {
background: #384354;
border-color: #7b8da1;
}
QPushButton:pressed {
background: #25303b;
}
QPushButton:disabled {
background: #252c35;
color: #7d8794;
border-color: #414b58;
}
QLineEdit,
QPlainTextEdit,
QTableWidget,
QComboBox,
QSpinBox {
background: #1d242d;
color: #edf3f8;
border: 1px solid #4f5d6d;
border-radius: 8px;
selection-background-color: #48b8a2;
selection-color: #102022;
}
QLineEdit,
QComboBox,
QSpinBox {
padding: 6px 8px;
}
QPlainTextEdit,
QTableWidget {
border-radius: 10px;
}
QHeaderView::section {
background: #313b49;
color: #d8e0e8;
border: none;
border-bottom: 1px solid #4f5d6d;
padding: 7px 8px;
}
QTableCornerButton::section {
background: #313b49;
border: none;
border-bottom: 1px solid #4f5d6d;
}
QTableWidget {
alternate-background-color: #232b35;
gridline-color: #2f3945;
}
QTableWidget::item {
padding: 4px 6px;
}
QScrollBar:vertical {
background: transparent;
width: 10px;
margin: 4px 0 4px 0;
}
QScrollBar::handle:vertical {
background: #53606f;
min-height: 28px;
border-radius: 5px;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical,
QScrollBar::add-page:vertical,
QScrollBar::sub-page:vertical {
background: transparent;
height: 0;
}
QMenu {
background: #1f262f;
color: #eef2f7;
border: 1px solid #53606f;
padding: 6px;
}
QMenu::item {
padding: 7px 14px;
border-radius: 6px;
}
QMenu::item:selected {
background: #33404d;
}
)");
}
QFont APP_Theme::App_Func_GetBodyFont()
{
// 正文用相对稳妥、系统常见的字体候选。
QFont Font(App_Func_PickFontFamily(QStringList()
<< QStringLiteral("Segoe UI Variable Text")
<< QStringLiteral("Microsoft YaHei UI")
@@ -62,21 +184,8 @@ QFont APP_Theme::App_Func_GetBodyFont()
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")
@@ -88,7 +197,6 @@ QFont APP_Theme::App_Func_GetMetricFont()
QFont APP_Theme::App_Func_GetKeyLabelFont()
{
// 按键主文字字号较大,保证小键盘一眼能看清。
QFont Font(App_Func_PickFontFamily(QStringList()
<< QStringLiteral("Bahnschrift SemiBold")
<< QStringLiteral("Segoe UI Semibold")
@@ -99,7 +207,6 @@ QFont APP_Theme::App_Func_GetKeyLabelFont()
QFont APP_Theme::App_Func_GetKeyHintFont()
{
// 按键 hint 放左上角,所以字号更小。
QFont Font(App_Func_PickFontFamily(QStringList()
<< QStringLiteral("Segoe UI Semibold")
<< QStringLiteral("Bahnschrift SemiBold")
@@ -111,20 +218,17 @@ QFont APP_Theme::App_Func_GetKeyHintFont()
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)
for (const QString& Family : FamilyList)
{
const QString& Family = FamilyList.at(Index);
if (AvailableFamilyList.contains(Family))
{
return Family;
}
}
// 如果都不存在,就退回 Qt 当前默认字体。
return QApplication::font().family();
}

View File

@@ -6,32 +6,17 @@
namespace APP {
/*
* 主题模块现在只保留一套固定暗色风格。
*
* - 不参与 DRI 枚举
* - 不参与协议解析
* - 不参与业务判断
*/
class APP_Theme
{
public:
// 返回标准控件使用的统一调色板。
static QPalette App_Func_GetPalette();
// 正文说明文字的默认字体。
static QString App_Func_GetStyleSheet();
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);
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,130 +1,117 @@
#pragma once
#include "APP/APP_KeypadModel.h"
#include "DEBUG/Debug_Config.h"
#include "LOGIC/Lgc_Core.h"
#include <QtCore/QHash>
#include <QtCore/QSet>
#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;
class QPlainTextEdit;
class QPushButton;
class QResizeEvent;
class QSpinBox;
class QStackedWidget;
class QTabWidget;
class QTableWidget;
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 处理。
void resizeEvent(QResizeEvent* event) override;
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_RefreshFeatureTable();
void App_Func_RefreshFunctionStatus();
// 刷新调试页显示内容。
void App_Func_RefreshDebugView();
// 逻辑状态改变后集中刷新必要区域。
void App_Func_RefreshPacketTestPanel();
void App_Func_RefreshPacketTestTable();
void App_Func_RefreshUi();
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
void App_Func_SelectFeature(int FeatureId, bool SwitchToFeaturePage = false);
void App_Func_SaveFeatureFromUi();
void App_Func_UpdateFeatureEditorState();
void App_Func_UpdateFeatureEditorHeight();
void App_Func_AddFeature();
void App_Func_DeleteFeature();
void App_Func_ShowKeyMenu(quint16 Usage, const QPoint& GlobalPos);
void App_Func_StartSequenceRecording();
void App_Func_StopSequenceRecording();
void App_Func_UpdateSequenceRecordingUi();
bool App_Func_HandleSequenceRecordMessage(void* p_Message);
// 创建左侧主键盘卡片。
QWidget* App_Func_CreatePadCard();
// 创建右侧功能键配置卡片。
QWidget* App_Func_CreateFunctionRegisterCard();
QWidget* App_Func_CreateFunctionConfigCard();
QWidget* App_Func_CreatePacketTestCard();
#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;
QTabWidget* appPageTab = nullptr;
int appFeaturePageIndex = 0;
int appPacketTestPageIndex = 0;
// 功能区状态说明标签。
QLabel* appFunctionLabelStatus = nullptr;
// 功能键 0 的文本宏输入框。
QLineEdit* appFunctionEditMacroText = nullptr;
// 功能键 1 左侧交换键选择框。
QComboBox* appFunctionComboSwapLeft = nullptr;
// 功能键 1 右侧交换键选择框。
QComboBox* appFunctionComboSwapRight = nullptr;
// 功能键 2 的网址输入框。
QLineEdit* appFunctionEditWebsite = nullptr;
QTableWidget* appFeatureTable = nullptr;
QPushButton* appFeatureAddButton = nullptr;
QPushButton* appFeatureDeleteButton = nullptr;
QPushButton* appFeatureTimeSyncButton = nullptr;
QPushButton* appFeatureThemeSwitchButton = nullptr;
QLabel* appFeatureBindingSummaryLabel = nullptr;
QLineEdit* appFeatureNameEdit = nullptr;
QLineEdit* appFeatureDescriptionEdit = nullptr;
QComboBox* appFeatureTypeCombo = nullptr;
QStackedWidget* appFeatureEditorStack = nullptr;
QLineEdit* appFeatureSequenceEdit = nullptr;
QLineEdit* appFeatureWebsiteEdit = nullptr;
QPushButton* appFeatureSequenceRecordStartButton = nullptr;
QPushButton* appFeatureSequenceRecordStopButton = nullptr;
QLabel* appPacketTestTransportLabel = nullptr;
QLabel* appPacketTestProtocolLabel = nullptr;
QLabel* appPacketTestHelloLabel = nullptr;
QLabel* appPacketTestAckLabel = nullptr;
QLabel* appPacketTestErrorLabel = nullptr;
QLabel* appPacketTestBitmapLabel = nullptr;
QTableWidget* appPacketTestTable = nullptr;
QPlainTextEdit* appPacketTestLogEdit = nullptr;
QPushButton* appPacketTestRefreshButton = nullptr;
QComboBox* appPacketTestTargetCombo = nullptr;
QPushButton* appPacketTestSendHelloButton = nullptr;
QPushButton* appPacketTestSendBitmapCurrentButton = nullptr;
QPushButton* appPacketTestSendBitmapAllOnButton = nullptr;
QPushButton* appPacketTestSendBitmapAllOffButton = nullptr;
QPushButton* appPacketTestSendTimeSyncButton = nullptr;
QSpinBox* appPacketTestThemeRedSpin = nullptr;
QSpinBox* appPacketTestThemeGreenSpin = nullptr;
QSpinBox* appPacketTestThemeBlueSpin = nullptr;
QPushButton* appPacketTestSendThemeButton = nullptr;
QPushButton* appPacketTestClearLogButton = nullptr;
bool appIsUpdatingFeatureUi = false;
bool appIsSequenceRecording = false;
int appSelectedFeatureId = 0;
QSet<QString> appSequenceRecordingPressedKeySet;
#if APP_ENABLE_DEBUG_WINDOW
// 调试面板实例。
DEBUG::Debug_Panel* appDebugPanel = nullptr;
#endif
// 周期轮询逻辑层的定时器。
QTimer appTimerPoll;
// APP 层持有的逻辑总状态。
QTimer appTimerAutoRefreshDevice;
Lgc_Core_Struct_State appLgcState;
};

View File

@@ -0,0 +1,291 @@
#include "APP/APP_UIWindow.h"
#include "APP/APP_KeyButton.h"
#include "APP/APP_UIWindow_Private.h"
#include <QtCore/QSignalBlocker>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QTableWidget>
#include <QtWidgets/QTableWidgetItem>
namespace APP {
namespace
{
QString App_Func_GetDefaultKeyDescription(quint16 Usage)
{
return QStringLiteral(
"Default numpad key: %1. Left click simulates a real press/release. "
"Right click binds a function.")
.arg(Lgc_Core_GetUsageShortText(Usage));
}
} // namespace
void App_UIWindow::App_Func_RefreshKeypadState()
{
const Lgc_Core_Struct_View View = Lgc_Core_GetView(&appLgcState);
appKeypadModel.App_Func_SetNumLockOn(View.IsSystemNumLockOn);
if (View.IsPhysicalKeyStateValid)
{
appKeypadModel.App_Func_SetPressedKeysFromUsageList(View.PhysicalUsageList);
return;
}
if (View.IsVisibleKeyStateValid)
{
appKeypadModel.App_Func_SetPressedKeysFromUsageList(View.VisibleUsageList);
return;
}
appKeypadModel.App_Func_ClearPressedKeys();
}
void App_UIWindow::App_Func_RefreshKeypadButtons()
{
for (auto It = appKeypadButtonMap.begin(); It != appKeypadButtonMap.end(); ++It)
{
const QString KeyId = It.key();
const quint16 Usage = appKeypadModel.App_Func_GetUsageFromKeyId(KeyId);
const int FeatureId = Lgc_Core_GetUsageFeatureId(&appLgcState, Usage);
APP_KeyButton* p_Button = It.value();
p_Button->App_Func_SetLatched(
appKeypadModel.App_Func_IsLatched(KeyId) || (FeatureId > 0));
p_Button->App_Func_SetPressed(appKeypadModel.App_Func_IsPressed(KeyId));
p_Button->App_Func_SetHintText(
FeatureId > 0
? Lgc_Core_GetFeatureNameById(&appLgcState, FeatureId)
: appKeypadModel.App_Func_GetDefaultHint(KeyId));
p_Button->setToolTip(
FeatureId > 0
? Lgc_Core_GetFeatureDescriptionById(&appLgcState, FeatureId)
: App_Func_GetDefaultKeyDescription(Usage));
}
}
void App_UIWindow::App_Func_RefreshFeatureTable()
{
const QVector<int> FeatureIdList = Lgc_Core_GetFeatureIdList(&appLgcState);
const bool HasFeatures = !FeatureIdList.isEmpty();
QSignalBlocker Blocker(appFeatureTable);
appFeatureTable->clearContents();
appFeatureTable->setRowCount(FeatureIdList.size());
int TargetRow = -1;
for (int Row = 0; Row < FeatureIdList.size(); ++Row)
{
const int FeatureId = FeatureIdList.at(Row);
const Lgc_FunctionFeature_Definition Feature =
Lgc_Core_GetFeature(&appLgcState, FeatureId);
QTableWidgetItem* p_NameItem =
new QTableWidgetItem(Lgc_Core_GetFeatureNameById(&appLgcState, FeatureId));
p_NameItem->setData(Qt::UserRole, FeatureId);
appFeatureTable->setItem(Row, 0, p_NameItem);
appFeatureTable->setItem(
Row,
1,
new QTableWidgetItem(Lgc_Core_GetFeatureDescriptionById(&appLgcState, FeatureId)));
appFeatureTable->setItem(
Row,
2,
new QTableWidgetItem(Lgc_Core_GetFeatureTypeText(Feature.Type)));
if (FeatureId == appSelectedFeatureId)
{
TargetRow = Row;
}
}
appSelectedFeatureId =
TargetRow >= 0 ? appSelectedFeatureId : (HasFeatures ? FeatureIdList.first() : 0);
TargetRow = TargetRow >= 0 ? TargetRow : (HasFeatures ? 0 : -1);
if (TargetRow >= 0)
{
appFeatureTable->selectRow(TargetRow);
}
else
{
appFeatureTable->clearSelection();
}
App_Func_UpdateFeatureEditorState();
}
void App_UIWindow::App_Func_RefreshFunctionStatus()
{
const Lgc_Core_Struct_View View = Lgc_Core_GetView(&appLgcState);
appFunctionLabelStatus->setText(
View.TextFunctionStatus.isEmpty()
? QStringLiteral("Waiting for function-key activity.")
: View.TextFunctionStatus);
}
void App_UIWindow::App_Func_SelectFeature(int FeatureId, bool SwitchToFeaturePage)
{
if (appIsSequenceRecording && (FeatureId != appSelectedFeatureId))
{
App_Func_StopSequenceRecording();
}
appSelectedFeatureId =
Lgc_Core_GetFeature(&appLgcState, FeatureId).Id > 0 ? FeatureId : 0;
if (appSelectedFeatureId > 0)
{
for (int Row = 0; Row < appFeatureTable->rowCount(); ++Row)
{
QTableWidgetItem* p_Item = appFeatureTable->item(Row, 0);
if ((p_Item != nullptr) && (p_Item->data(Qt::UserRole).toInt() == appSelectedFeatureId))
{
QSignalBlocker Blocker(appFeatureTable);
appFeatureTable->selectRow(Row);
break;
}
}
}
App_Func_UpdateFeatureEditorState();
if (SwitchToFeaturePage)
{
appPageTab->setCurrentIndex(appFeaturePageIndex);
}
}
void App_UIWindow::App_Func_SaveFeatureFromUi()
{
if (appSelectedFeatureId <= 0)
{
return;
}
Lgc_FunctionFeature_Definition Feature =
Lgc_Core_GetFeature(&appLgcState, appSelectedFeatureId);
if (Feature.Id <= 0)
{
return;
}
Feature.Name = appFeatureNameEdit->text();
Feature.Description = appFeatureDescriptionEdit->text();
Feature.Type =
static_cast<Lgc_FunctionFeature_Type>(appFeatureTypeCombo->currentData().toInt());
Feature.SequenceText = appFeatureSequenceEdit->text();
Feature.WebsiteUrl = appFeatureWebsiteEdit->text();
if ((Feature.Type == Lgc_FunctionFeature_Type::Website) &&
Feature.WebsiteUrl.trimmed().isEmpty())
{
Feature.WebsiteUrl = QStringLiteral("https://");
QSignalBlocker Blocker(appFeatureWebsiteEdit);
appFeatureWebsiteEdit->setText(Feature.WebsiteUrl);
}
Lgc_Core_UpdateFeature(&appLgcState, Feature);
appFeatureEditorStack->setCurrentIndex(App_Func_GetFeatureStackIndex(Feature.Type));
App_Func_UpdateFeatureEditorHeight();
Lgc_Core_SaveFunctionConfig(&appLgcState);
App_Func_RefreshAfterLogicChange(); // FIX: keep Device Test and status text aligned after feature edits.
}
void App_UIWindow::App_Func_UpdateFeatureEditorHeight()
{
if ((appFeatureEditorStack == nullptr) || (appFeatureEditorStack->currentWidget() == nullptr))
{
return;
}
const int Height = appFeatureEditorStack->currentWidget()->sizeHint().height();
if (appFeatureEditorStack->height() != Height)
{
appFeatureEditorStack->setFixedHeight(Height);
}
}
void App_UIWindow::App_Func_UpdateFeatureEditorState()
{
const Lgc_FunctionFeature_Definition Feature =
Lgc_Core_GetFeature(&appLgcState, appSelectedFeatureId);
const bool HasFeature = Feature.Id > 0;
if (appIsSequenceRecording && (!HasFeature || !App_Func_IsKeyRecordFeatureType(Feature.Type)))
{
App_Func_StopSequenceRecording();
}
appFeatureNameEdit->setEnabled(HasFeature);
appFeatureDescriptionEdit->setEnabled(HasFeature);
appFeatureTypeCombo->setEnabled(HasFeature);
appFeatureEditorStack->setEnabled(HasFeature);
appFeatureDeleteButton->setEnabled(HasFeature);
appIsUpdatingFeatureUi = true;
if (!HasFeature)
{
appFeatureNameEdit->clear();
appFeatureDescriptionEdit->clear();
appFeatureSequenceEdit->clear();
appFeatureWebsiteEdit->clear();
appFeatureTypeCombo->setCurrentIndex(0);
appFeatureEditorStack->setCurrentIndex(0);
App_Func_UpdateFeatureEditorHeight();
appFeatureBindingSummaryLabel->setText(QStringLiteral("No function selected yet."));
appIsUpdatingFeatureUi = false;
App_Func_UpdateSequenceRecordingUi();
return;
}
appFeatureNameEdit->setText(Feature.Name);
appFeatureDescriptionEdit->setText(Feature.Description);
const int TypeIndex = appFeatureTypeCombo->findData(static_cast<int>(Feature.Type));
if (TypeIndex >= 0)
{
appFeatureTypeCombo->setCurrentIndex(TypeIndex);
}
appFeatureSequenceEdit->setText(Feature.SequenceText);
appFeatureWebsiteEdit->setText(App_Func_GetWebsiteEditText(Feature.WebsiteUrl));
appFeatureEditorStack->setCurrentIndex(App_Func_GetFeatureStackIndex(Feature.Type));
appFeatureSequenceEdit->setPlaceholderText(
Feature.Type == Lgc_FunctionFeature_Type::KeySequence
? QStringLiteral("Example: Ctrl+C -> Ctrl+A")
: QStringLiteral("Example: Ctrl+C"));
App_Func_UpdateFeatureEditorHeight();
appFeatureBindingSummaryLabel->setText(
Lgc_Core_GetFeatureBindingSummary(&appLgcState, Feature.Id));
appIsUpdatingFeatureUi = false;
App_Func_UpdateSequenceRecordingUi();
}
void App_UIWindow::App_Func_AddFeature()
{
const int FeatureId = Lgc_Core_AddFeature(&appLgcState);
Lgc_Core_SaveFunctionConfig(&appLgcState);
App_Func_RefreshFeatureTable(); // FIX: the feature table must update before reselection.
App_Func_RefreshAfterLogicChange(); // FIX: refresh Device Test and current config state after add.
App_Func_SelectFeature(FeatureId, true);
}
void App_UIWindow::App_Func_DeleteFeature()
{
if (appSelectedFeatureId <= 0)
{
return;
}
if (appIsSequenceRecording)
{
App_Func_StopSequenceRecording();
}
Lgc_Core_DeleteFeature(&appLgcState, appSelectedFeatureId);
appSelectedFeatureId = 0;
Lgc_Core_SaveFunctionConfig(&appLgcState);
App_Func_RefreshFeatureTable(); // FIX: rebuild the feature table after delete.
App_Func_RefreshAfterLogicChange(); // FIX: refresh Device Test and current config state after delete.
}
} // namespace APP

View File

@@ -0,0 +1,12 @@
#pragma once
#include "LOGIC/Lgc_Func_Button.h"
#include <QtCore/QString>
namespace APP {
int App_Func_GetFeatureStackIndex(Lgc_FunctionFeature_Type Type);
bool App_Func_IsKeyRecordFeatureType(Lgc_FunctionFeature_Type Type);
QString App_Func_GetWebsiteEditText(const QString& UrlText);
} // namespace APP

427
APP/APP_UIWindow_Record.cpp Normal file
View File

@@ -0,0 +1,427 @@
#include "APP/APP_UIWindow.h"
#include "APP/APP_UIWindow_Private.h"
#include <QtCore/QByteArray>
#include <QtCore/QSignalBlocker>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenu>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTableWidget>
#include <Windows.h>
namespace APP {
namespace
{
bool App_Func_IsModifierRecordToken(const QString& Token)
{
return (Token == QStringLiteral("Ctrl")) ||
(Token == QStringLiteral("Shift")) ||
(Token == QStringLiteral("Alt")) ||
(Token == QStringLiteral("Win"));
}
QStringList App_Func_GetOrderedRecordedModifierTokens(const QSet<QString>& PressedKeySet)
{
const QStringList ModifierOrder = {
QStringLiteral("Ctrl"),
QStringLiteral("Shift"),
QStringLiteral("Alt"),
QStringLiteral("Win")
};
QStringList Result;
for (const QString& ModifierToken : ModifierOrder)
{
if (PressedKeySet.contains(ModifierToken))
{
Result.append(ModifierToken);
}
}
return Result;
}
QString App_Func_GetRecordedCombinationText(
const QSet<QString>& PressedKeySet,
const QString& TriggerToken)
{
QStringList TokenList = App_Func_GetOrderedRecordedModifierTokens(PressedKeySet);
if (!TriggerToken.isEmpty() && !App_Func_IsModifierRecordToken(TriggerToken))
{
TokenList.append(TriggerToken);
}
return TokenList.join(QStringLiteral("+"));
}
bool App_Func_TryGetRawKeyboard(void* p_Message, RAWKEYBOARD& Keyboard)
{
if (p_Message == nullptr)
{
return false;
}
MSG* p_Msg = reinterpret_cast<MSG*>(p_Message);
if (p_Msg->message != WM_INPUT)
{
return false;
}
UINT NeedSize = 0;
GetRawInputData(
reinterpret_cast<HRAWINPUT>(p_Msg->lParam),
RID_INPUT,
nullptr,
&NeedSize,
sizeof(RAWINPUTHEADER));
if (NeedSize == 0)
{
return false;
}
QByteArray Buffer(static_cast<int>(NeedSize), 0);
if (GetRawInputData(
reinterpret_cast<HRAWINPUT>(p_Msg->lParam),
RID_INPUT,
Buffer.data(),
&NeedSize,
sizeof(RAWINPUTHEADER)) == static_cast<UINT>(-1))
{
return false;
}
const RAWINPUT* p_Input = reinterpret_cast<const RAWINPUT*>(Buffer.constData());
if (p_Input->header.dwType != RIM_TYPEKEYBOARD)
{
return false;
}
Keyboard = p_Input->data.keyboard;
return true;
}
QString App_Func_GetRecordedKeyToken(const RAWKEYBOARD& Keyboard)
{
const bool IsE0 = (Keyboard.Flags & RI_KEY_E0) != 0;
const USHORT VirtualKey = Keyboard.VKey;
if ((VirtualKey >= 'A') && (VirtualKey <= 'Z'))
{
return QString(QChar(static_cast<char16_t>(VirtualKey)));
}
if ((VirtualKey >= '0') && (VirtualKey <= '9'))
{
return QString(QChar(static_cast<char16_t>(VirtualKey)));
}
if ((VirtualKey >= VK_F1) && (VirtualKey <= VK_F24))
{
return QStringLiteral("F%1").arg(VirtualKey - VK_F1 + 1);
}
switch (VirtualKey)
{
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
return QStringLiteral("Ctrl");
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
return QStringLiteral("Shift");
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
return QStringLiteral("Alt");
case VK_LWIN:
case VK_RWIN:
return QStringLiteral("Win");
case VK_RETURN:
return IsE0 ? QStringLiteral("NumEnter") : QStringLiteral("Enter");
case VK_SPACE:
return QStringLiteral("Space");
case VK_TAB:
return QStringLiteral("Tab");
case VK_ESCAPE:
return QStringLiteral("Esc");
case VK_BACK:
return QStringLiteral("Backspace");
case VK_DELETE:
return QStringLiteral("Delete");
case VK_INSERT:
return QStringLiteral("Insert");
case VK_HOME:
return QStringLiteral("Home");
case VK_END:
return QStringLiteral("End");
case VK_PRIOR:
return QStringLiteral("PageUp");
case VK_NEXT:
return QStringLiteral("PageDown");
case VK_LEFT:
return QStringLiteral("Left");
case VK_RIGHT:
return QStringLiteral("Right");
case VK_UP:
return QStringLiteral("Up");
case VK_DOWN:
return QStringLiteral("Down");
case VK_CAPITAL:
return QStringLiteral("CapsLock");
case VK_SNAPSHOT:
return QStringLiteral("PrintScreen");
case VK_SCROLL:
return QStringLiteral("ScrollLock");
case VK_PAUSE:
return QStringLiteral("Pause");
case VK_NUMPAD0:
return QStringLiteral("Num0");
case VK_NUMPAD1:
return QStringLiteral("Num1");
case VK_NUMPAD2:
return QStringLiteral("Num2");
case VK_NUMPAD3:
return QStringLiteral("Num3");
case VK_NUMPAD4:
return QStringLiteral("Num4");
case VK_NUMPAD5:
return QStringLiteral("Num5");
case VK_NUMPAD6:
return QStringLiteral("Num6");
case VK_NUMPAD7:
return QStringLiteral("Num7");
case VK_NUMPAD8:
return QStringLiteral("Num8");
case VK_NUMPAD9:
return QStringLiteral("Num9");
case VK_DIVIDE:
return QStringLiteral("Num/");
case VK_MULTIPLY:
return QStringLiteral("Num*");
case VK_SUBTRACT:
return QStringLiteral("Num-");
case VK_ADD:
return QStringLiteral("Num+");
case VK_DECIMAL:
return QStringLiteral("Num.");
case VK_OEM_COMMA:
return QStringLiteral("Comma");
case VK_OEM_PERIOD:
return QStringLiteral("Period");
case VK_OEM_1:
return QStringLiteral("Semicolon");
case VK_OEM_2:
return QStringLiteral("Slash");
case VK_OEM_3:
return QStringLiteral("Grave");
case VK_OEM_4:
return QStringLiteral("LeftBracket");
case VK_OEM_5:
return QStringLiteral("Backslash");
case VK_OEM_6:
return QStringLiteral("RightBracket");
case VK_OEM_7:
return QStringLiteral("Quote");
case VK_OEM_MINUS:
return QStringLiteral("Minus");
case VK_OEM_PLUS:
return QStringLiteral("Equal");
default:
return QString();
}
}
} // namespace
void App_UIWindow::App_Func_StartSequenceRecording()
{
const Lgc_FunctionFeature_Definition Feature =
Lgc_Core_GetFeature(&appLgcState, appSelectedFeatureId);
if ((Feature.Id <= 0) || !App_Func_IsKeyRecordFeatureType(Feature.Type))
{
return;
}
{
QSignalBlocker Blocker(appFeatureSequenceEdit);
appFeatureSequenceEdit->setText(QString());
}
App_Func_SaveFeatureFromUi();
if (!Lgc_Core_BeginSequenceRecording(&appLgcState, appSelectedFeatureId))
{
return;
}
appIsSequenceRecording = true;
appSequenceRecordingPressedKeySet.clear();
App_Func_UpdateSequenceRecordingUi();
App_Func_RefreshKeypadButtons();
App_Func_RefreshFunctionStatus();
update();
appFeatureSequenceEdit->setFocus(Qt::OtherFocusReason);
}
void App_UIWindow::App_Func_StopSequenceRecording()
{
Lgc_Core_EndSequenceRecording(&appLgcState);
appIsSequenceRecording = false;
appSequenceRecordingPressedKeySet.clear();
App_Func_UpdateSequenceRecordingUi();
App_Func_RefreshKeypadButtons();
App_Func_RefreshFunctionStatus();
update();
}
void App_UIWindow::App_Func_UpdateSequenceRecordingUi()
{
const Lgc_FunctionFeature_Definition Feature =
Lgc_Core_GetFeature(&appLgcState, appSelectedFeatureId);
const bool HasFeature = Feature.Id > 0;
const bool CanRecord = HasFeature && App_Func_IsKeyRecordFeatureType(Feature.Type);
const bool IsLocked = appIsSequenceRecording;
appFeatureTable->setEnabled(!IsLocked);
appFeatureAddButton->setEnabled(!IsLocked);
appFeatureDeleteButton->setEnabled(HasFeature && !IsLocked);
appFeatureNameEdit->setEnabled(HasFeature && !IsLocked);
appFeatureDescriptionEdit->setEnabled(HasFeature && !IsLocked);
appFeatureTypeCombo->setEnabled(HasFeature && !IsLocked);
appFeatureWebsiteEdit->setEnabled(HasFeature && !IsLocked);
appFeatureSequenceEdit->setReadOnly(IsLocked);
appFeatureSequenceRecordStartButton->setEnabled(CanRecord && !IsLocked);
appFeatureSequenceRecordStopButton->setEnabled(CanRecord && IsLocked);
}
bool App_UIWindow::App_Func_HandleSequenceRecordMessage(void* p_Message)
{
if (!appIsSequenceRecording)
{
return false;
}
RAWKEYBOARD Keyboard = {};
if (!App_Func_TryGetRawKeyboard(p_Message, Keyboard))
{
return false;
}
const QString Token = App_Func_GetRecordedKeyToken(Keyboard);
if (Token.isEmpty())
{
return false;
}
const bool IsPressed = (Keyboard.Flags & RI_KEY_BREAK) == 0;
if (IsPressed)
{
if (appSequenceRecordingPressedKeySet.contains(Token))
{
return false;
}
appSequenceRecordingPressedKeySet.insert(Token);
if (!App_Func_IsModifierRecordToken(Token))
{
const Lgc_FunctionFeature_Definition Feature =
Lgc_Core_GetFeature(&appLgcState, appSelectedFeatureId);
const QString CombinationText =
App_Func_GetRecordedCombinationText(appSequenceRecordingPressedKeySet, Token);
if ((Feature.Id <= 0) ||
!App_Func_IsKeyRecordFeatureType(Feature.Type) ||
CombinationText.isEmpty())
{
return false;
}
const QString OldText = appFeatureSequenceEdit->text().trimmed();
const QString NewText =
Feature.Type == Lgc_FunctionFeature_Type::KeySequence
? (OldText.isEmpty()
? CombinationText
: QStringLiteral("%1 -> %2").arg(OldText, CombinationText))
: CombinationText;
{
QSignalBlocker Blocker(appFeatureSequenceEdit);
appFeatureSequenceEdit->setText(NewText);
appFeatureSequenceEdit->setCursorPosition(NewText.size());
}
App_Func_SaveFeatureFromUi();
Lgc_Core_UpdateSequenceRecordingStatus(&appLgcState, NewText);
App_Func_RefreshKeypadButtons();
App_Func_RefreshFunctionStatus();
update();
return true;
}
return false;
}
appSequenceRecordingPressedKeySet.remove(Token);
return false;
}
void App_UIWindow::App_Func_ShowKeyMenu(quint16 Usage, const QPoint& GlobalPos)
{
const int CurrentFeatureId = Lgc_Core_GetUsageFeatureId(&appLgcState, Usage);
QMenu Menu(this);
QAction* p_NoneAction = Menu.addAction(QStringLiteral("No Function"));
p_NoneAction->setCheckable(true);
p_NoneAction->setChecked(CurrentFeatureId <= 0);
p_NoneAction->setData(0);
const QVector<int> FeatureIdList = Lgc_Core_GetFeatureIdList(&appLgcState);
if (!FeatureIdList.isEmpty())
{
Menu.addSeparator();
for (int FeatureId : FeatureIdList)
{
QAction* p_Action =
Menu.addAction(QStringLiteral("Bind to %1")
.arg(Lgc_Core_GetFeatureNameById(&appLgcState, FeatureId)));
p_Action->setCheckable(true);
p_Action->setChecked(FeatureId == CurrentFeatureId);
p_Action->setData(FeatureId);
p_Action->setToolTip(Lgc_Core_GetFeatureDescriptionById(&appLgcState, FeatureId));
}
}
else
{
Menu.addSeparator();
QAction* p_EmptyAction = Menu.addAction(QStringLiteral("No function yet. Please add one first."));
p_EmptyAction->setEnabled(false);
}
Menu.addSeparator();
QAction* p_OpenFeaturePageAction = Menu.addAction(QStringLiteral("Open Function List"));
p_OpenFeaturePageAction->setData(-1);
QAction* p_SelectedAction = Menu.exec(GlobalPos);
if (p_SelectedAction == nullptr)
{
return;
}
const int ActionData = p_SelectedAction->data().toInt();
if (ActionData == -1)
{
App_Func_SelectFeature(CurrentFeatureId, true);
return;
}
if (!Lgc_Core_BindUsageToFeature(&appLgcState, Usage, ActionData))
{
return;
}
Lgc_Core_SaveFunctionConfig(&appLgcState);
App_Func_RefreshFeatureTable(); // FIX: keep the feature summary in sync after right-click binding.
App_Func_RefreshAfterLogicChange(); // FIX: refresh Device Test logs and auto-sync state immediately.
const int FeatureToSelect = ActionData > 0 ? ActionData : CurrentFeatureId; // FIX
if (FeatureToSelect > 0)
{
App_Func_SelectFeature(FeatureToSelect);
}
}
} // namespace APP