颜色映射图例,规格尺寸修改
This commit is contained in:
@@ -9,6 +9,8 @@ set(CMAKE_AUTOUIC ON)
|
|||||||
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
add_subdirectory(3rdpart/QXlsx/QXlsx)
|
||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
|
||||||
find_package(Qt6 COMPONENTS
|
find_package(Qt6 COMPONENTS
|
||||||
@@ -22,6 +24,7 @@ find_package(Qt6 COMPONENTS
|
|||||||
Quick
|
Quick
|
||||||
QuickControls2
|
QuickControls2
|
||||||
QuickLayouts
|
QuickLayouts
|
||||||
|
QuickDialogs2
|
||||||
LinguistTools
|
LinguistTools
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,6 +73,8 @@ target_link_libraries(TactileIpc3D
|
|||||||
Qt6::Quick
|
Qt6::Quick
|
||||||
Qt6::QuickControls2
|
Qt6::QuickControls2
|
||||||
Qt6::QuickLayouts
|
Qt6::QuickLayouts
|
||||||
|
Qt6::QuickDialogs2
|
||||||
|
QXlsx::QXlsx
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TS_FILES
|
set(TS_FILES
|
||||||
|
|||||||
@@ -10,8 +10,18 @@ classDiagram
|
|||||||
+bool connected
|
+bool connected
|
||||||
+SerialBackend* serial
|
+SerialBackend* serial
|
||||||
+DataBackend* data
|
+DataBackend* data
|
||||||
|
+int rangeMin
|
||||||
|
+int rangeMax
|
||||||
|
+QColor colorLow
|
||||||
|
+QColor colorMid
|
||||||
|
+QColor colorHigh
|
||||||
+setLightMode(bool)
|
+setLightMode(bool)
|
||||||
+setLanguage(string)
|
+setLanguage(string)
|
||||||
|
+setRangeMin(int)
|
||||||
|
+setRangeMax(int)
|
||||||
|
+setColorLow(QColor)
|
||||||
|
+setColorMid(QColor)
|
||||||
|
+setColorHigh(QColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SerialConfig {
|
class SerialConfig {
|
||||||
@@ -60,6 +70,10 @@ classDiagram
|
|||||||
+setTransport(transport)
|
+setTransport(transport)
|
||||||
}
|
}
|
||||||
class GLWidget {
|
class GLWidget {
|
||||||
|
+setRange(int, int)
|
||||||
|
+setColorLow(QColor)
|
||||||
|
+setColorMid(QColor)
|
||||||
|
+setColorHigh(QColor)
|
||||||
+dotClicked(index, row, col, value)
|
+dotClicked(index, row, col, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +174,7 @@ classDiagram
|
|||||||
|
|
||||||
AppBackend --> SerialBackend
|
AppBackend --> SerialBackend
|
||||||
AppBackend --> DataBackend
|
AppBackend --> DataBackend
|
||||||
|
AppBackend ..> GLWidget : render config
|
||||||
SerialBackend --> SerialConfig
|
SerialBackend --> SerialConfig
|
||||||
SerialBackend --> SensorRequest
|
SerialBackend --> SensorRequest
|
||||||
SerialBackend --> SensorSpec
|
SerialBackend --> SensorSpec
|
||||||
@@ -248,6 +263,15 @@ flowchart TD
|
|||||||
SB -->|updateProtocolBindings_| SW3[SerialSendWorker.setBuildRequestFunc]
|
SB -->|updateProtocolBindings_| SW3[SerialSendWorker.setBuildRequestFunc]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 渲染/颜色映射流程 (Mermaid)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
UI[LeftPanel 颜色映射] -->|rangeMin/rangeMax<br/>colorLow/Mid/High| AB[AppBackend]
|
||||||
|
AB -->|rangeChanged/colorChanged| GL[GLWidget]
|
||||||
|
GL -->|uMinV/uMaxV/uColorLow/Mid/High| SH[dots.frag]
|
||||||
|
```
|
||||||
|
|
||||||
## 配置接口与步骤说明
|
## 配置接口与步骤说明
|
||||||
|
|
||||||
- 设置协议:
|
- 设置协议:
|
||||||
@@ -272,6 +296,11 @@ flowchart TD
|
|||||||
- `SerialSendWorker::setBuildRequestFunc(codec->buildRequest)`
|
- `SerialSendWorker::setBuildRequestFunc(codec->buildRequest)`
|
||||||
- 打开串口:
|
- 打开串口:
|
||||||
- `SerialBackend::open()` -> `SerialSendWorker::openTransport(config)`,成功后在从站模式启动轮询发送。
|
- `SerialBackend::open()` -> `SerialSendWorker::openTransport(config)`,成功后在从站模式启动轮询发送。
|
||||||
|
- 渲染/颜色映射配置:
|
||||||
|
- QML 绑定 `AppBackend::rangeMin/rangeMax` 与 `colorLow/Mid/High`(`LeftPanel` 中的颜色映射面板)。
|
||||||
|
- `setRangeMin/Max` 发出 `rangeChanged(min, max)`,由 `GLWidget::setRange` 同步到 `uMinV/uMaxV`。
|
||||||
|
- `setColorLow/Mid/High` 发出 `color*Changed`,由 `GLWidget::setColor*` 同步到 `uColorLow/Mid/High`。
|
||||||
|
- `dots.frag` 归一化公式:`value01 = clamp((v - min) / (max - min))`,并用 low->mid->high 线性插值。
|
||||||
|
|
||||||
## 分层设计概述
|
## 分层设计概述
|
||||||
|
|
||||||
@@ -279,6 +308,7 @@ flowchart TD
|
|||||||
- 串口采集层:`Transport + Format + Codec + Decoder + Manager` 分层,独立于业务逻辑。
|
- 串口采集层:`Transport + Format + Codec + Decoder + Manager` 分层,独立于业务逻辑。
|
||||||
- 串口线程化:读取/解码/发送三线程 + Packet/Frame 队列,降低 UI 卡顿风险。
|
- 串口线程化:读取/解码/发送三线程 + Packet/Frame 队列,降低 UI 卡顿风险。
|
||||||
- 数据驱动层:负责帧缓存、数据导入导出与回放,提供渲染回调。
|
- 数据驱动层:负责帧缓存、数据导入导出与回放,提供渲染回调。
|
||||||
|
- 渲染配置层:`AppBackend` 提供范围/颜色参数,`GLWidget` 将其转为 shader uniform 进行颜色映射。
|
||||||
- UI 层:`NavBar + LeftPanel + OpenGL View + RightPanel` 的 1+3 布局。
|
- UI 层:`NavBar + LeftPanel + OpenGL View + RightPanel` 的 1+3 布局。
|
||||||
|
|
||||||
## 类接口与成员说明(C++)
|
## 类接口与成员说明(C++)
|
||||||
@@ -289,11 +319,16 @@ flowchart TD
|
|||||||
- `lightMode` / `language` / `connected`:UI 基础状态。
|
- `lightMode` / `language` / `connected`:UI 基础状态。
|
||||||
- `serial()` / `data()`:暴露子系统实例给 QML。
|
- `serial()` / `data()`:暴露子系统实例给 QML。
|
||||||
- `setLightMode(bool)` / `setLanguage(string)`。
|
- `setLightMode(bool)` / `setLanguage(string)`。
|
||||||
|
- `rangeMin` / `rangeMax`:颜色映射的数值范围。
|
||||||
|
- `colorLow` / `colorMid` / `colorHigh`:颜色映射的三个基准颜色。
|
||||||
|
- `setRangeMin(int)` / `setRangeMax(int)` / `setColorLow(QColor)` / `setColorMid(QColor)` / `setColorHigh(QColor)`。
|
||||||
- 成员变量:
|
- 成员变量:
|
||||||
- `m_serial`:串口采集层对象。
|
- `m_serial`:串口采集层对象。
|
||||||
- `m_data`:数据驱动层对象。
|
- `m_data`:数据驱动层对象。
|
||||||
- `m_lightMode` / `m_language`:全局 UI 状态。
|
- `m_lightMode` / `m_language`:全局 UI 状态。
|
||||||
|
- `m_rangeMin` / `m_rangeMax` / `m_colorLow` / `m_colorMid` / `m_colorHigh`:颜色映射参数。
|
||||||
- 备注:串口连接成功后会清空历史数据缓存,避免旧数据残留。
|
- 备注:串口连接成功后会清空历史数据缓存,避免旧数据残留。
|
||||||
|
- `rangeChanged(min, max)` 用于驱动 `GLWidget::setRange`,颜色变更通过 `color*Changed` 同步。
|
||||||
|
|
||||||
### SerialBackend (`src/serial/serial_backend.h`)
|
### SerialBackend (`src/serial/serial_backend.h`)
|
||||||
- 作用:串口采集层的统一控制器,负责协议选择、三线程调度与数据分发。
|
- 作用:串口采集层的统一控制器,负责协议选择、三线程调度与数据分发。
|
||||||
@@ -395,9 +430,14 @@ flowchart TD
|
|||||||
|
|
||||||
### GLWidget (`src/glwidget.h`)
|
### GLWidget (`src/glwidget.h`)
|
||||||
- 作用:OpenGL 渲染窗口,显示传感器点阵与背景。
|
- 作用:OpenGL 渲染窗口,显示传感器点阵与背景。
|
||||||
|
- 接口:
|
||||||
|
- `setRange(int minV, int maxV)`:设置 `uMinV/uMaxV`。
|
||||||
|
- `setColorLow(QColor)` / `setColorMid(QColor)` / `setColorHigh(QColor)`:设置 `uColorLow/uColorMid/uColorHigh`。
|
||||||
- 信号:
|
- 信号:
|
||||||
- `dotClicked(index, row, col, value)`:鼠标点击某个点时发出索引与数据值。
|
- `dotClicked(index, row, col, value)`:鼠标点击某个点时发出索引与数据值。
|
||||||
- 备注:拾取使用屏幕投影 + 半径阈值,便于 RightPanel 订阅点击事件做曲线展示。
|
- 备注:
|
||||||
|
- 颜色映射在 `dots.frag` 内完成,低/中/高三段线性插值。
|
||||||
|
- 拾取使用屏幕投影 + 半径阈值,便于 RightPanel 订阅点击事件做曲线展示。
|
||||||
|
|
||||||
### DataFrame (`src/data_frame.h`)
|
### DataFrame (`src/data_frame.h`)
|
||||||
- 字段:
|
- 字段:
|
||||||
@@ -440,6 +480,9 @@ flowchart TD
|
|||||||
- 采样周期(从站模式可用)。
|
- 采样周期(从站模式可用)。
|
||||||
- 采样参数:功能码、起始地址、读取长度。
|
- 采样参数:功能码、起始地址、读取长度。
|
||||||
- 传感器规格:协议名、型号、网格规格占位。
|
- 传感器规格:协议名、型号、网格规格占位。
|
||||||
|
- 颜色映射:
|
||||||
|
- 数值范围(min/max)。
|
||||||
|
- 低/中/高三色选择(`ColorDialog`)。
|
||||||
- 显示控制:显示网络/坐标轴、回放与导出入口。
|
- 显示控制:显示网络/坐标轴、回放与导出入口。
|
||||||
|
|
||||||
### RightPanel(`qml/content/RightPanel.qml`)
|
### RightPanel(`qml/content/RightPanel.qml`)
|
||||||
@@ -518,6 +561,7 @@ flowchart TD
|
|||||||
## 更新记录
|
## 更新记录
|
||||||
|
|
||||||
- 2026-01-11:新增 `QtSerialTransport`(`QSerialPort` 传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。
|
- 2026-01-11:新增 `QtSerialTransport`(`QSerialPort` 传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。
|
||||||
|
- 2026-01-12:`LeftPanel` 新增颜色映射参数;`AppBackend`/`GLWidget` 增加颜色与范围接口,shader 使用三色渐变映射数据值。
|
||||||
- 2026-01-11:补充串口配置流程图与配置接口说明(协议/参数/解码器绑定/打开流程)。
|
- 2026-01-11:补充串口配置流程图与配置接口说明(协议/参数/解码器绑定/打开流程)。
|
||||||
- 2026-01-05:新增串口三线程流水线(读/解码/发送)与 Packet/Frame 队列,更新协议起始符说明,补充队列溢出 TODO 与线程组件文档。
|
- 2026-01-05:新增串口三线程流水线(读/解码/发送)与 Packet/Frame 队列,更新协议起始符说明,补充队列溢出 TODO 与线程组件文档。
|
||||||
- 2026-01-05:CollapsiblePanel 组件改为跟随 `backend.lightMode` 切换暗色主题配色。
|
- 2026-01-05:CollapsiblePanel 组件改为跟随 `backend.lightMode` 切换暗色主题配色。
|
||||||
|
|||||||
@@ -96,6 +96,14 @@
|
|||||||
<source>采样周期</source>
|
<source>采样周期</source>
|
||||||
<translation>Sample Interval</translation>
|
<translation>Sample Interval</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>宽</source>
|
||||||
|
<translation>Width</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>高</source>
|
||||||
|
<translation>Height</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>连接</source>
|
<source>连接</source>
|
||||||
<translation>Connect</translation>
|
<translation>Connect</translation>
|
||||||
@@ -140,6 +148,34 @@
|
|||||||
<source>重新识别</source>
|
<source>重新识别</source>
|
||||||
<translation>Rescan</translation>
|
<translation>Rescan</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>颜色映射</source>
|
||||||
|
<translation>Color Mapping</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>最小值</source>
|
||||||
|
<translation>Min Value</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>最大值</source>
|
||||||
|
<translation>Max Value</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>低色</source>
|
||||||
|
<translation>Low Color</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>选择</source>
|
||||||
|
<translation>Select</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>中色</source>
|
||||||
|
<translation>Mid Color</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>高色</source>
|
||||||
|
<translation>High Color</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>显示控制</source>
|
<source>显示控制</source>
|
||||||
<translation>Display</translation>
|
<translation>Display</translation>
|
||||||
@@ -160,6 +196,45 @@
|
|||||||
<source>导出数据</source>
|
<source>导出数据</source>
|
||||||
<translation>Export Data</translation>
|
<translation>Export Data</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>选择低色</source>
|
||||||
|
<translation>Select Low Color</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>选择中色</source>
|
||||||
|
<translation>Select Mid Color</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>选择高色</source>
|
||||||
|
<translation>Select High Color</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>OpenFileDialog</name>
|
||||||
|
<message>
|
||||||
|
<source>导入数据</source>
|
||||||
|
<translation>Import Data</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>位置</source>
|
||||||
|
<translation>Locations</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>此电脑</source>
|
||||||
|
<translation>This PC</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>桌面</source>
|
||||||
|
<translation>Desktop</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>文档</source>
|
||||||
|
<translation>Documents</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>下载</source>
|
||||||
|
<translation>Downloads</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>RightPanel</name>
|
<name>RightPanel</name>
|
||||||
@@ -171,6 +246,10 @@
|
|||||||
<source>Live Trend</source>
|
<source>Live Trend</source>
|
||||||
<translation>Live Trend</translation>
|
<translation>Live Trend</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Legend</source>
|
||||||
|
<translation>Legend</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Metrics</source>
|
<source>Metrics</source>
|
||||||
<translation>Metrics</translation>
|
<translation>Metrics</translation>
|
||||||
|
|||||||
@@ -171,6 +171,10 @@
|
|||||||
<source>Live Trend</source>
|
<source>Live Trend</source>
|
||||||
<translation>实时趋势</translation>
|
<translation>实时趋势</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Legend</source>
|
||||||
|
<translation>图例</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Metrics</source>
|
<source>Metrics</source>
|
||||||
<translation>指标</translation>
|
<translation>指标</translation>
|
||||||
|
|||||||
85
main.cpp
85
main.cpp
@@ -44,7 +44,8 @@ int main(int argc, char *argv[]) {
|
|||||||
QOpenGLContext probeCtx;
|
QOpenGLContext probeCtx;
|
||||||
probeCtx.setFormat(QSurfaceFormat::defaultFormat());
|
probeCtx.setFormat(QSurfaceFormat::defaultFormat());
|
||||||
if (!probeCtx.create()) {
|
if (!probeCtx.create()) {
|
||||||
qCritical().noquote() << "Failed to create an OpenGL context (required: OpenGL 3.3 Core).";
|
qCritical().noquote() << "Failed to create an OpenGL context "
|
||||||
|
"(required: OpenGL 3.3 Core).";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,17 +55,22 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if (!probeCtx.makeCurrent(&probeSurface)) {
|
if (!probeCtx.makeCurrent(&probeSurface)) {
|
||||||
qCritical().noquote()
|
qCritical().noquote()
|
||||||
<< "Failed to make the OpenGL context current. This usually means the requested format is unsupported by the current graphics driver.";
|
<< "Failed to make the OpenGL context current. This usually "
|
||||||
|
"means the requested format is unsupported by the current "
|
||||||
|
"graphics driver.";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QSurfaceFormat actual = probeCtx.format();
|
const QSurfaceFormat actual = probeCtx.format();
|
||||||
const bool versionOk = (actual.majorVersion() > 3) || (actual.majorVersion() == 3 && actual.minorVersion() >= 3);
|
const bool versionOk =
|
||||||
|
(actual.majorVersion() > 3) ||
|
||||||
|
(actual.majorVersion() == 3 && actual.minorVersion() >= 3);
|
||||||
if (!versionOk || actual.profile() != QSurfaceFormat::CoreProfile) {
|
if (!versionOk || actual.profile() != QSurfaceFormat::CoreProfile) {
|
||||||
probeCtx.doneCurrent();
|
probeCtx.doneCurrent();
|
||||||
qCritical().noquote()
|
qCritical().noquote()
|
||||||
<< "OpenGL context is not OpenGL 3.3 Core (got: "
|
<< "OpenGL context is not OpenGL 3.3 Core (got: "
|
||||||
<< actual.majorVersion() << "." << actual.minorVersion() << ", profile=" << actual.profile() << ").";
|
<< actual.majorVersion() << "." << actual.minorVersion()
|
||||||
|
<< ", profile=" << actual.profile() << ").";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,12 +90,12 @@ int main(int argc, char *argv[]) {
|
|||||||
qmlRegisterSingletonInstance("TactileIPC", 1, 0, "I18n", &i18n);
|
qmlRegisterSingletonInstance("TactileIPC", 1, 0, "I18n", &i18n);
|
||||||
qmlRegisterType<SparklinePlotItem>("LiveTrend", 1, 0, "SparklinePlot");
|
qmlRegisterType<SparklinePlotItem>("LiveTrend", 1, 0, "SparklinePlot");
|
||||||
i18n.setLanguage(backend.language());
|
i18n.setLanguage(backend.language());
|
||||||
QObject::connect(&backend, &AppBackend::languageChanged, &i18n, [&backend, &i18n]() {
|
QObject::connect(
|
||||||
i18n.setLanguage(backend.language());
|
&backend, &AppBackend::languageChanged, &i18n,
|
||||||
});
|
[&backend, &i18n]() { i18n.setLanguage(backend.language()); });
|
||||||
auto *qmlEngine = new QQmlEngine(root);
|
auto *qmlEngine = new QQmlEngine(root);
|
||||||
|
|
||||||
auto createQuickWidget = [&](const QUrl& sourceUrl) -> QQuickWidget* {
|
auto createQuickWidget = [&](const QUrl &sourceUrl) -> QQuickWidget * {
|
||||||
auto *view = new QQuickWidget(qmlEngine, root);
|
auto *view = new QQuickWidget(qmlEngine, root);
|
||||||
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||||
view->setSource(sourceUrl);
|
view->setSource(sourceUrl);
|
||||||
@@ -107,26 +113,30 @@ int main(int argc, char *argv[]) {
|
|||||||
leftView->setFixedWidth(350);
|
leftView->setFixedWidth(350);
|
||||||
|
|
||||||
auto *glw = new GLWidget;
|
auto *glw = new GLWidget;
|
||||||
glw->setSpec(8, 11, 0.1f, 0.03f);
|
glw->setSpec(12, 7, 0.1f, 0.03f);
|
||||||
glw->setPanelThickness(0.08f);
|
glw->setPanelThickness(0.08f);
|
||||||
glw->setRange(0, 1000);
|
glw->setRange(backend.rangeMin(), backend.rangeMax());
|
||||||
|
glw->setColorLow(backend.colorLow());
|
||||||
|
glw->setColorMid(backend.colorMid());
|
||||||
|
glw->setColorHigh(backend.colorHigh());
|
||||||
|
|
||||||
/* backend.data()->setLiveRenderCallback([glw](const DataFrame& frame) {
|
/* backend.data()->setLiveRenderCallback([glw](const DataFrame& frame) {
|
||||||
if (frame.data.size() != glw->dotCount())
|
if (frame.data.size() != glw->dotCount())
|
||||||
return;
|
return;
|
||||||
glw->submitValues(frame.data);
|
glw->submitValues(frame.data);
|
||||||
}); */
|
}); */
|
||||||
backend.data()->setLiveRenderCallback([](const DataFrame& frame) {
|
backend.data()->setLiveRenderCallback([](const DataFrame &frame) {
|
||||||
if (frame.data.size() != 0) {
|
if (frame.data.size() != 0) {
|
||||||
// AA 55 1A 00 34 00 FB 00 1C 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F1
|
// AA 55 1A 00 34 00 FB 00 1C 00 00 10 00 00 00 00 00 00 00 00 00 00
|
||||||
// aa 55 1a 00 34 00 fb 00 1c 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1
|
// 00 00 00 00 00 00 00 00 F1 aa 55 1a 00 34 00 fb 00 1c 00 00 10 00
|
||||||
|
// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1
|
||||||
qDebug() << "data size: " << frame.data.size();
|
qDebug() << "data size: " << frame.data.size();
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// TODO:待实现内容(将frame数据分发给右侧曲线/指标的QML接口)
|
// TODO:待实现内容(将frame数据分发给右侧曲线/指标的QML接口)
|
||||||
|
|
||||||
auto *rightView = createQuickWidget(QUrl("qrc:/qml/content/RightPanel.qml"));
|
auto *rightView =
|
||||||
|
createQuickWidget(QUrl("qrc:/qml/content/RightPanel.qml"));
|
||||||
|
|
||||||
splitter->addWidget(leftView);
|
splitter->addWidget(leftView);
|
||||||
splitter->addWidget(glw);
|
splitter->addWidget(glw);
|
||||||
@@ -136,33 +146,52 @@ int main(int argc, char *argv[]) {
|
|||||||
splitter->setStretchFactor(2, 0);
|
splitter->setStretchFactor(2, 0);
|
||||||
splitter->setSizes({320, 640, 320});
|
splitter->setSizes({320, 640, 320});
|
||||||
auto applySplitterStyle = [&backend, splitter]() {
|
auto applySplitterStyle = [&backend, splitter]() {
|
||||||
const QString handleColor = backend.lightMode() ? QStringLiteral("#E0E0E0") : QStringLiteral("#2C2C2C");
|
const QString handleColor = backend.lightMode()
|
||||||
splitter->setStyleSheet(QStringLiteral("QSplitter::handle { background: %1; }").arg(handleColor));
|
? QStringLiteral("#E0E0E0")
|
||||||
|
: QStringLiteral("#2C2C2C");
|
||||||
|
splitter->setStyleSheet(
|
||||||
|
QStringLiteral("QSplitter::handle { background: %1; }")
|
||||||
|
.arg(handleColor));
|
||||||
};
|
};
|
||||||
applySplitterStyle();
|
applySplitterStyle();
|
||||||
QObject::connect(&backend, &AppBackend::lightModeChanged, splitter, [applySplitterStyle]() {
|
QObject::connect(&backend, &AppBackend::lightModeChanged, splitter,
|
||||||
applySplitterStyle();
|
[applySplitterStyle]() { applySplitterStyle(); });
|
||||||
});
|
|
||||||
|
|
||||||
auto applyQuickTheme = [&backend, navView, leftView, rightView]() {
|
auto applyQuickTheme = [&backend, navView, leftView, rightView]() {
|
||||||
const QColor navColor = backend.lightMode() ? QColor(QStringLiteral("#F5F7F5")) : QColor(QStringLiteral("#2B2F2B"));
|
const QColor navColor = backend.lightMode()
|
||||||
const QColor panelColor = backend.lightMode() ? QColor(QStringLiteral("#F5F5F5")) : QColor(QStringLiteral("#2C2C2C"));
|
? QColor(QStringLiteral("#F5F7F5"))
|
||||||
|
: QColor(QStringLiteral("#2B2F2B"));
|
||||||
|
const QColor panelColor = backend.lightMode()
|
||||||
|
? QColor(QStringLiteral("#F5F5F5"))
|
||||||
|
: QColor(QStringLiteral("#2C2C2C"));
|
||||||
navView->setClearColor(navColor);
|
navView->setClearColor(navColor);
|
||||||
leftView->setClearColor(panelColor);
|
leftView->setClearColor(panelColor);
|
||||||
rightView->setClearColor(panelColor);
|
rightView->setClearColor(panelColor);
|
||||||
};
|
};
|
||||||
applyQuickTheme();
|
applyQuickTheme();
|
||||||
QObject::connect(&backend, &AppBackend::lightModeChanged, navView, [applyQuickTheme]() {
|
QObject::connect(&backend, &AppBackend::lightModeChanged, navView,
|
||||||
applyQuickTheme();
|
[applyQuickTheme]() { applyQuickTheme(); });
|
||||||
});
|
|
||||||
|
|
||||||
|
QObject::connect(&backend, &AppBackend::lightModeChanged, glw,
|
||||||
QObject::connect(&backend, &AppBackend::lightModeChanged, glw, [&backend, glw]() {
|
[&backend, glw]() {
|
||||||
bool m = backend.lightMode() ? true : false;
|
bool m = backend.lightMode() ? true : false;
|
||||||
glw->setLightMode(m);
|
glw->setLightMode(m);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(&backend, &AppBackend::showGridChanged, glw, &GLWidget::setShowGrid);
|
QObject::connect(&backend, &AppBackend::showGridChanged, glw,
|
||||||
|
&GLWidget::setShowGrid);
|
||||||
|
QObject::connect(&backend, &AppBackend::sensorRowChanged, glw,
|
||||||
|
&GLWidget::setRow);
|
||||||
|
QObject::connect(&backend, &AppBackend::sensorColChanged, glw,
|
||||||
|
&GLWidget::setCol);
|
||||||
|
QObject::connect(&backend, &AppBackend::rangeChanged, glw,
|
||||||
|
&GLWidget::setRange);
|
||||||
|
QObject::connect(&backend, &AppBackend::colorLowChanged, glw,
|
||||||
|
&GLWidget::setColorLow);
|
||||||
|
QObject::connect(&backend, &AppBackend::colorMidChanged, glw,
|
||||||
|
&GLWidget::setColorMid);
|
||||||
|
QObject::connect(&backend, &AppBackend::colorHighChanged, glw,
|
||||||
|
&GLWidget::setColorHigh);
|
||||||
|
|
||||||
rootLayout->addWidget(navView);
|
rootLayout->addWidget(navView);
|
||||||
rootLayout->addWidget(splitter);
|
rootLayout->addWidget(splitter);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import TactileIPC 1.0
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
width: 350
|
implicitWidth: 350
|
||||||
|
|
||||||
property alias title: titleText.text
|
property alias title: titleText.text
|
||||||
property bool expanded: true
|
property bool expanded: true
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls.Material
|
import QtQuick.Controls.Material
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
import "."
|
import "."
|
||||||
import TactileIPC 1.0
|
import TactileIPC 1.0
|
||||||
|
|
||||||
@@ -208,6 +209,42 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("宽")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
SpinBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 1
|
||||||
|
to: 20
|
||||||
|
value: Backend.sensorCol
|
||||||
|
enabled: Backend.serial.connected === false
|
||||||
|
onValueModified: Backend.sensorCol = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("高")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
SpinBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 1
|
||||||
|
to: 20
|
||||||
|
value: Backend.sensorRow
|
||||||
|
enabled: Backend.serial.connected === false
|
||||||
|
onValueModified: Backend.sensorRow = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 12
|
spacing: 12
|
||||||
@@ -372,6 +409,150 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CollapsiblePanel {
|
||||||
|
title: root.tr("颜色映射")
|
||||||
|
expanded: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("最小值")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
SpinBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: -999999
|
||||||
|
to: 999999
|
||||||
|
editable: true
|
||||||
|
value: Backend.rangeMin
|
||||||
|
onValueModified: Backend.rangeMin = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("最大值")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
SpinBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: -999999
|
||||||
|
to: 999999
|
||||||
|
editable: true
|
||||||
|
value: Backend.rangeMax
|
||||||
|
onValueModified: Backend.rangeMax = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("低色")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: 4
|
||||||
|
color: Backend.colorLow
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(0, 0, 0, 0.2)
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
lowColorDialog.selectedColor = Backend.colorLow
|
||||||
|
lowColorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: root.tr("选择")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onClicked: {
|
||||||
|
lowColorDialog.selectedColor = Backend.colorLow
|
||||||
|
lowColorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("中色")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: 4
|
||||||
|
color: Backend.colorMid
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(0, 0, 0, 0.2)
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
midColorDialog.selectedColor = Backend.colorMid
|
||||||
|
midColorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: root.tr("选择")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onClicked: {
|
||||||
|
midColorDialog.selectedColor = Backend.colorMid
|
||||||
|
midColorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("高色")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: 4
|
||||||
|
color: Backend.colorHigh
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(0, 0, 0, 0.2)
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
highColorDialog.selectedColor = Backend.colorHigh
|
||||||
|
highColorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: root.tr("选择")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onClicked: {
|
||||||
|
highColorDialog.selectedColor = Backend.colorHigh
|
||||||
|
highColorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CollapsiblePanel {
|
CollapsiblePanel {
|
||||||
title: root.tr("显示控制")
|
title: root.tr("显示控制")
|
||||||
expanded: true
|
expanded: true
|
||||||
@@ -399,7 +580,14 @@ Rectangle {
|
|||||||
Button {
|
Button {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: root.tr("导出数据")
|
text: root.tr("导出数据")
|
||||||
onClicked: exportDlg.open()
|
onClicked: {
|
||||||
|
if (Backend.data.frameCount != 0) {
|
||||||
|
exportDlg.open()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Backend.data.frameCount() === 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,6 +595,25 @@ Rectangle {
|
|||||||
Item { Layout.fillHeight: true }
|
Item { Layout.fillHeight: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColorDialog {
|
||||||
|
id: lowColorDialog
|
||||||
|
title: root.tr("选择低色")
|
||||||
|
onAccepted: Backend.colorLow = selectedColor
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorDialog {
|
||||||
|
id: midColorDialog
|
||||||
|
title: root.tr("选择中色")
|
||||||
|
onAccepted: Backend.colorMid = selectedColor
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorDialog {
|
||||||
|
id: highColorDialog
|
||||||
|
title: root.tr("选择高色")
|
||||||
|
onAccepted: Backend.colorHigh = selectedColor
|
||||||
|
}
|
||||||
|
|
||||||
SaveAsExportDialog {
|
SaveAsExportDialog {
|
||||||
id: exportDlg
|
id: exportDlg
|
||||||
/* onSaveTo: (folder, filename, format, method) => {
|
/* onSaveTo: (folder, filename, format, method) => {
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
property int minValue: 0
|
property int minValue: 0
|
||||||
property int maxValue: 100
|
property int maxValue: 100
|
||||||
|
property color colorLow: Qt.rgba(0.10, 0.75, 1.00, 1.0)
|
||||||
|
property color colorMid: Qt.rgba(0.10, 0.95, 0.35, 1.0)
|
||||||
|
property color colorHigh: Qt.rgba(1.00, 0.22, 0.10, 1.0)
|
||||||
|
property int barWidth: 34
|
||||||
|
property int barRadius: 8
|
||||||
|
|
||||||
implicitWidth: 90
|
implicitWidth: barWidth + 48
|
||||||
implicitHeight: 220
|
implicitHeight: 240
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -25,17 +30,16 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
width: 26
|
width: root.barWidth
|
||||||
radius: 6
|
radius: root.barRadius
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: Qt.rgba(1, 1, 1, 0.18)
|
border.color: Qt.rgba(1, 1, 1, 0.18)
|
||||||
|
|
||||||
gradient: Gradient {
|
gradient: Gradient {
|
||||||
// must match shaders/dots.frag:dataColorRamp (high at top)
|
// must match shaders/dots.frag:dataColorRamp (high at top)
|
||||||
GradientStop { position: 0.00; color: Qt.rgba(1.00, 0.22, 0.10, 1.0) } // c3
|
GradientStop { position: 0.00; color: root.colorHigh }
|
||||||
GradientStop { position: 0.34; color: Qt.rgba(1.00, 0.92, 0.22, 1.0) } // c2
|
GradientStop { position: 0.50; color: root.colorMid }
|
||||||
GradientStop { position: 0.67; color: Qt.rgba(0.10, 0.95, 0.35, 1.0) } // c1
|
GradientStop { position: 1.00; color: root.colorLow }
|
||||||
GradientStop { position: 1.00; color: Qt.rgba(0.10, 0.75, 1.00, 1.0) } // c0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,4 +51,3 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ Rectangle {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 8
|
anchors.leftMargin: 8
|
||||||
Image {
|
Image {
|
||||||
width: 18
|
width: 16
|
||||||
height: 12
|
height: 16
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
source: modelData.icon
|
source: modelData.icon
|
||||||
}
|
}
|
||||||
@@ -107,10 +107,13 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
Row {
|
Row {
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 24
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
Image {
|
Image {
|
||||||
width: 18
|
width: 16
|
||||||
height: 12
|
height: 16
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
source: langBox.model[langBox.currentIndex]
|
source: langBox.model[langBox.currentIndex]
|
||||||
? langBox.model[langBox.currentIndex].icon
|
? langBox.model[langBox.currentIndex].icon
|
||||||
|
|||||||
291
qml/content/OpenFileDialog.qml
Normal file
291
qml/content/OpenFileDialog.qml
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Controls.Material 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
import Qt.labs.folderlistmodel 2.15
|
||||||
|
import QtCore 6.2
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
import TactileIPC 1.0
|
||||||
|
|
||||||
|
Window {
|
||||||
|
id: root
|
||||||
|
width: 980
|
||||||
|
height: 640
|
||||||
|
minimumWidth: 880
|
||||||
|
minimumHeight: 560
|
||||||
|
visible: false
|
||||||
|
modality: Qt.ApplicationModal
|
||||||
|
flags: Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
|
||||||
|
title: root.tr("导入数据")
|
||||||
|
color: windowBg
|
||||||
|
|
||||||
|
readonly property bool isDark: !Backend.lightMode
|
||||||
|
readonly property color windowBg: isDark ? "#1B1F1B" : "#F7F8F9"
|
||||||
|
Material.accent: root.accent
|
||||||
|
Material.primary: root.accent
|
||||||
|
Material.theme: root.isDark ? Material.Dark : Material.Light
|
||||||
|
|
||||||
|
readonly property color accent: "#21A453"
|
||||||
|
readonly property color accentSoft: root.isDark ? "#1F3A2A" : "#E6F6EC"
|
||||||
|
readonly property color panel: root.isDark ? "#242924" : "#FFFFFF"
|
||||||
|
readonly property color border: root.isDark ? "#343A35" : "#E1E5EA"
|
||||||
|
readonly property color text: root.isDark ? "#E6ECE7" : "#1E2A32"
|
||||||
|
readonly property color subText: root.isDark ? "#9AA5A0" : "#6E7A86"
|
||||||
|
readonly property color fieldBg: root.isDark ? "#1E221E" : "#FFFFFF"
|
||||||
|
readonly property color surfaceAlt: root.isDark ? "#202520" : "#F9FAFB"
|
||||||
|
readonly property color hoverBg: root.isDark ? "#2C322D" : "#F3F6F8"
|
||||||
|
readonly property color iconBg: root.isDark ? "#25362B" : "#E8F3EA"
|
||||||
|
readonly property color iconBgAlt: root.isDark ? "#2A302A" : "#EFF2F5"
|
||||||
|
readonly property color disabledBg: root.isDark ? "#4B544E" : "#C9D2D8"
|
||||||
|
readonly property string uiFont: "Microsoft YaHei UI"
|
||||||
|
|
||||||
|
property url currentFolder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) + "/"
|
||||||
|
property string chosenFilename: ""
|
||||||
|
property string importFormat: ""
|
||||||
|
property string importMethod: ""
|
||||||
|
|
||||||
|
signal importIn(url filename, string format, string method)
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function accept() {
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function reject() {
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function centerOnScreen_() {
|
||||||
|
x = Math.round((Screen.width - width) / 2)
|
||||||
|
y = Math.round((Screen.height - height) / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFolder_(path) {
|
||||||
|
if (!path)
|
||||||
|
return path
|
||||||
|
if (path.endsWith("/"))
|
||||||
|
return path
|
||||||
|
return path + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function tr(text) {
|
||||||
|
I18n.retranslateToken
|
||||||
|
return qsTr(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: if (visible) centerOnScreen_()
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 54
|
||||||
|
radius: 6
|
||||||
|
color: root.panel
|
||||||
|
border.color: root.border
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 8
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
id: backBtn
|
||||||
|
text: "<"
|
||||||
|
font.family: root.uiFont
|
||||||
|
onClicked: {
|
||||||
|
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 4
|
||||||
|
color: backBtn.hovered ? root.accentSoft : "transparent"
|
||||||
|
border.color: backBtn.hovered ? root.accent : root.border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
id: forwardBtn
|
||||||
|
text: ">"
|
||||||
|
font.family: root.uiFont
|
||||||
|
onClicked: {
|
||||||
|
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 4
|
||||||
|
color: backBtn.hovered ? root.accentSoft : "transparent"
|
||||||
|
border.color: backBtn.hovered ? root.accent : root.border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
id: upBtn
|
||||||
|
text: "^"
|
||||||
|
font.family: root.uiFont
|
||||||
|
onClicked: {
|
||||||
|
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 4
|
||||||
|
color: backBtn.hovered ? root.accentSoft : "transparent"
|
||||||
|
border.color: backBtn.hovered ? root.accent : root.border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: breadcrumb
|
||||||
|
Layout.fillWidth: true
|
||||||
|
readOnly: true
|
||||||
|
font.family: root.uiFont
|
||||||
|
color: root.text
|
||||||
|
text: root.currentFolder.toString()
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 4
|
||||||
|
color: root.surfaceAlt
|
||||||
|
border.color: root.border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 220
|
||||||
|
Layout.fillHeight: true
|
||||||
|
radius: 6
|
||||||
|
color: root.panel
|
||||||
|
border.color: root.border
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.tr("位置")
|
||||||
|
font.bold: true
|
||||||
|
font.family: root.uiFont
|
||||||
|
color: root.text
|
||||||
|
}
|
||||||
|
ListView {
|
||||||
|
id: places
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
model: [
|
||||||
|
{ name: root.tr("此电脑"), url: "file:///", icon: root.isDark ? "qrc:/images/computer_dark.png" : "qrc:/images/computer_light.png" },
|
||||||
|
{ name: root.tr("桌面"), url: StandardPaths.writableLocation(StandardPaths.DesktopLocation) + "/", icon: root.isDark ? "qrc:/images/desktop_dark.png" : "qrc:/images/desktop_light.png" },
|
||||||
|
{ name: root.tr("文档"), url: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) + "/", icon: root.isDark ? "qrc:/images/docs_dark.png" : "qrc:/images/docs_light.png" },
|
||||||
|
{ name: root.tr("下载"), url: StandardPaths.writableLocation(StandardPaths.DownloadLocation) + "/", icon: root.isDark ? "qrc:/images/download_dark.png" : "qrc:/images/download_light.png" }
|
||||||
|
]
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
width: ListView.view.width
|
||||||
|
onClicked: {
|
||||||
|
places.currentIndex = index
|
||||||
|
root.currentFolder = normalizeFolder_(modeData.url)
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 4
|
||||||
|
color: places.currentIndex === index ? root.accentSoft : "transparent"
|
||||||
|
border.color: places.currentIndex === index ? root.accent : "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: 8
|
||||||
|
Image {
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
source: modelData.icon
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: modelData.name
|
||||||
|
font.family: root.uiFont
|
||||||
|
color: root.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
radius: 6
|
||||||
|
color: root.panel
|
||||||
|
border.color: root.border
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Lable {
|
||||||
|
// TODO table title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: root.border
|
||||||
|
}
|
||||||
|
FolderListModel {
|
||||||
|
id: fileModel
|
||||||
|
folder: root.currentFolder
|
||||||
|
showDotAndDotDot: false
|
||||||
|
showDirs: true
|
||||||
|
showFiles: true
|
||||||
|
sortField: FolderListModel.Name
|
||||||
|
}
|
||||||
|
ListView {
|
||||||
|
id: fileList
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
model: fileModel
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
id: fileRow
|
||||||
|
width: ListView.view.width
|
||||||
|
onDoubleClicked: {
|
||||||
|
const isDir = fileModel.get(index, "fileIsDir")
|
||||||
|
if (isDir) {
|
||||||
|
root.currentFolder = normalizeFolder_(fileModel.get(index, "filePath"))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO import file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
fileList.currentIndex = index
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 4
|
||||||
|
color: fileRow.hovered ? root.hoverBg : "transparent"
|
||||||
|
}
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: 8
|
||||||
|
Rectangle {
|
||||||
|
width+:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,9 +22,19 @@ Rectangle {
|
|||||||
Material.accent: Material.Green
|
Material.accent: Material.Green
|
||||||
Material.primary: Material.Green
|
Material.primary: Material.Green
|
||||||
|
|
||||||
ColumnLayout {
|
ScrollView {
|
||||||
|
id: scrollView
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 12
|
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
contentWidth: scrollView.availableWidth
|
||||||
|
contentHeight: contentLayout.implicitHeight + 24
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: contentLayout
|
||||||
|
x: 12
|
||||||
|
y: 12
|
||||||
|
width: scrollView.availableWidth - 24
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
LiveTrendCard {
|
LiveTrendCard {
|
||||||
@@ -193,6 +203,24 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CollapsiblePanel {
|
||||||
|
title: root.tr("Legend")
|
||||||
|
expanded: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Legend {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.preferredHeight: 200
|
||||||
|
barWidth: 36
|
||||||
|
minValue: Backend.rangeMin
|
||||||
|
maxValue: Backend.rangeMax
|
||||||
|
colorLow: Backend.colorLow
|
||||||
|
colorMid: Backend.colorMid
|
||||||
|
colorHigh: Backend.colorHigh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item { Layout.fillHeight: true }
|
Item { Layout.fillHeight: true }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ out vec4 FragColor;
|
|||||||
|
|
||||||
uniform float uMinV;
|
uniform float uMinV;
|
||||||
uniform float uMaxV;
|
uniform float uMaxV;
|
||||||
|
uniform vec3 uColorLow;
|
||||||
|
uniform vec3 uColorMid;
|
||||||
|
uniform vec3 uColorHigh;
|
||||||
uniform sampler2D uDotTex;
|
uniform sampler2D uDotTex;
|
||||||
uniform int uHasData; // 0 = no data, 1 = has data
|
uniform int uHasData; // 0 = no data, 1 = has data
|
||||||
uniform vec3 uCameraPos;
|
uniform vec3 uCameraPos;
|
||||||
@@ -18,14 +21,8 @@ float saturate(float x) { return clamp(x, 0.0, 1.0); }
|
|||||||
|
|
||||||
vec3 dataColorRamp(float t) {
|
vec3 dataColorRamp(float t) {
|
||||||
t = saturate(t);
|
t = saturate(t);
|
||||||
vec3 c0 = vec3(0.10, 0.75, 1.00); // cyan-blue (low)
|
if (t < 0.5) return mix(uColorLow, uColorMid, t / 0.5);
|
||||||
vec3 c1 = vec3(0.10, 0.95, 0.35); // green
|
return mix(uColorMid, uColorHigh, (t - 0.5) / 0.5);
|
||||||
vec3 c2 = vec3(1.00, 0.92, 0.22); // yellow
|
|
||||||
vec3 c3 = vec3(1.00, 0.22, 0.10); // red (high)
|
|
||||||
|
|
||||||
if (t < 0.33) return mix(c0, c1, t / 0.33);
|
|
||||||
if (t < 0.66) return mix(c1, c2, (t - 0.33) / 0.33);
|
|
||||||
return mix(c2, c3, (t - 0.66) / 0.34);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
|
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
|
||||||
@@ -117,9 +114,8 @@ void main() {
|
|||||||
float value01 = clamp((vValue - uMinV) / max(1e-6, (uMaxV - uMinV)), 0.0, 1.0);
|
float value01 = clamp((vValue - uMinV) / max(1e-6, (uMaxV - uMinV)), 0.0, 1.0);
|
||||||
vec3 dataCol = dataColorRamp(value01);
|
vec3 dataCol = dataColorRamp(value01);
|
||||||
|
|
||||||
// bool hasData = (uHasData != 0);
|
bool hasData = (uHasData != 0);
|
||||||
// vec3 baseColor = hasData ? dataCol : metalBase;
|
vec3 baseColor = hasData ? dataCol : metalBase;
|
||||||
vec3 baseColor = metalBase;
|
|
||||||
|
|
||||||
// dataViz: flat/unlit, no lighting modulation (keep pure baseColor)
|
// dataViz: flat/unlit, no lighting modulation (keep pure baseColor)
|
||||||
if (uRenderMode == 1) {
|
if (uRenderMode == 1) {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ AppBackend::AppBackend(QObject* parent)
|
|||||||
m_data->clear();
|
m_data->clear();
|
||||||
emit connectedChanged();
|
emit connectedChanged();
|
||||||
});
|
});
|
||||||
|
connect(this, &AppBackend::sensorRowChanged, m_serial, &SerialBackend::setSensorHeight);
|
||||||
|
connect(this, &AppBackend::sensorColChanged, m_serial, &SerialBackend::setSensorWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AppBackend::connected() const {
|
bool AppBackend::connected() const {
|
||||||
@@ -48,3 +50,66 @@ void AppBackend::setShowGrid(bool on) {
|
|||||||
m_showGrid = on;
|
m_showGrid = on;
|
||||||
emit showGridChanged(on);
|
emit showGridChanged(on);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppBackend::setSensorCol(int c) {
|
||||||
|
if (m_serial->connected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_sensorCol = c;
|
||||||
|
qInfo() << "sensorColChanged: " << c;
|
||||||
|
emit sensorColChanged(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppBackend::setSensorRow(int r) {
|
||||||
|
if (m_serial->connected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_sensorRow = r;
|
||||||
|
qInfo() << "sensorRowChanged: " << r;
|
||||||
|
emit sensorRowChanged(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppBackend::setRangeMin(int v) {
|
||||||
|
if (m_rangeMin == v)
|
||||||
|
return;
|
||||||
|
m_rangeMin = v;
|
||||||
|
if (m_rangeMin > m_rangeMax) {
|
||||||
|
m_rangeMax = m_rangeMin;
|
||||||
|
emit rangeMaxChanged(m_rangeMax);
|
||||||
|
}
|
||||||
|
emit rangeMinChanged(m_rangeMin);
|
||||||
|
emit rangeChanged(m_rangeMin, m_rangeMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppBackend::setRangeMax(int v) {
|
||||||
|
if (m_rangeMax == v)
|
||||||
|
return;
|
||||||
|
m_rangeMax = v;
|
||||||
|
if (m_rangeMax < m_rangeMin) {
|
||||||
|
m_rangeMin = m_rangeMax;
|
||||||
|
emit rangeMinChanged(m_rangeMin);
|
||||||
|
}
|
||||||
|
emit rangeMaxChanged(m_rangeMax);
|
||||||
|
emit rangeChanged(m_rangeMin, m_rangeMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppBackend::setColorLow(const QColor& color) {
|
||||||
|
if (m_colorLow == color)
|
||||||
|
return;
|
||||||
|
m_colorLow = color;
|
||||||
|
emit colorLowChanged(m_colorLow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppBackend::setColorMid(const QColor& color) {
|
||||||
|
if (m_colorMid == color)
|
||||||
|
return;
|
||||||
|
m_colorMid = color;
|
||||||
|
emit colorMidChanged(m_colorMid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppBackend::setColorHigh(const QColor& color) {
|
||||||
|
if (m_colorHigh == color)
|
||||||
|
return;
|
||||||
|
m_colorHigh = color;
|
||||||
|
emit colorHighChanged(m_colorHigh);
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#define TACTILEIPC3D_BACKEND_H
|
#define TACTILEIPC3D_BACKEND_H
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QColor>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "data_backend.h"
|
#include "data_backend.h"
|
||||||
@@ -19,6 +20,13 @@ class AppBackend : public QObject {
|
|||||||
Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged);
|
Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged);
|
||||||
Q_PROPERTY(SerialBackend* serial READ serial CONSTANT)
|
Q_PROPERTY(SerialBackend* serial READ serial CONSTANT)
|
||||||
Q_PROPERTY(DataBackend* data READ data CONSTANT)
|
Q_PROPERTY(DataBackend* data READ data CONSTANT)
|
||||||
|
Q_PROPERTY(int sensorCol READ sensorCol WRITE setSensorCol NOTIFY sensorColChanged);
|
||||||
|
Q_PROPERTY(int sensorRow READ sensorRow WRITE setSensorRow NOTIFY sensorRowChanged);
|
||||||
|
Q_PROPERTY(int rangeMin READ rangeMin WRITE setRangeMin NOTIFY rangeMinChanged);
|
||||||
|
Q_PROPERTY(int rangeMax READ rangeMax WRITE setRangeMax NOTIFY rangeMaxChanged);
|
||||||
|
Q_PROPERTY(QColor colorLow READ colorLow WRITE setColorLow NOTIFY colorLowChanged);
|
||||||
|
Q_PROPERTY(QColor colorMid READ colorMid WRITE setColorMid NOTIFY colorMidChanged);
|
||||||
|
Q_PROPERTY(QColor colorHigh READ colorHigh WRITE setColorHigh NOTIFY colorHighChanged);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AppBackend(QObject* parent=nullptr);
|
explicit AppBackend(QObject* parent=nullptr);
|
||||||
@@ -35,12 +43,34 @@ public:
|
|||||||
|
|
||||||
bool showGrid() const { return m_showGrid; }
|
bool showGrid() const { return m_showGrid; }
|
||||||
void setShowGrid(bool on);
|
void setShowGrid(bool on);
|
||||||
|
int sensorCol() const { qInfo() << "col:" << m_sensorCol; return m_sensorCol; }
|
||||||
|
int sensorRow() const { qInfo() << "row:" << m_sensorRow; return m_sensorRow; }
|
||||||
|
void setSensorRow(int r);
|
||||||
|
void setSensorCol(int c);
|
||||||
|
int rangeMin() const { return m_rangeMin; }
|
||||||
|
int rangeMax() const { return m_rangeMax; }
|
||||||
|
void setRangeMin(int v);
|
||||||
|
void setRangeMax(int v);
|
||||||
|
QColor colorLow() const { return m_colorLow; }
|
||||||
|
QColor colorMid() const { return m_colorMid; }
|
||||||
|
QColor colorHigh() const { return m_colorHigh; }
|
||||||
|
void setColorLow(const QColor& color);
|
||||||
|
void setColorMid(const QColor& color);
|
||||||
|
void setColorHigh(const QColor& color);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void lightModeChanged();
|
void lightModeChanged();
|
||||||
void languageChanged();
|
void languageChanged();
|
||||||
void connectedChanged();
|
void connectedChanged();
|
||||||
void showGridChanged(bool on);
|
void showGridChanged(bool on);
|
||||||
|
void sensorColChanged(int c);
|
||||||
|
void sensorRowChanged(int r);
|
||||||
|
void rangeMinChanged(int v);
|
||||||
|
void rangeMaxChanged(int v);
|
||||||
|
void rangeChanged(int minV, int maxV);
|
||||||
|
void colorLowChanged(const QColor& color);
|
||||||
|
void colorMidChanged(const QColor& color);
|
||||||
|
void colorHighChanged(const QColor& color);
|
||||||
private:
|
private:
|
||||||
SerialBackend* m_serial = nullptr;
|
SerialBackend* m_serial = nullptr;
|
||||||
DataBackend* m_data = nullptr;
|
DataBackend* m_data = nullptr;
|
||||||
@@ -48,6 +78,13 @@ private:
|
|||||||
QString m_language = QStringLiteral("zh_CN");
|
QString m_language = QStringLiteral("zh_CN");
|
||||||
|
|
||||||
bool m_showGrid = true;
|
bool m_showGrid = true;
|
||||||
|
int m_sensorRow = 12;
|
||||||
|
int m_sensorCol = 7;
|
||||||
|
int m_rangeMin = 0;
|
||||||
|
int m_rangeMax = 1000;
|
||||||
|
QColor m_colorLow = QColor::fromRgbF(0.10, 0.75, 1.00);
|
||||||
|
QColor m_colorMid = QColor::fromRgbF(0.10, 0.95, 0.35);
|
||||||
|
QColor m_colorHigh = QColor::fromRgbF(1.00, 0.22, 0.10);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //TACTILEIPC3D_BACKEND_H
|
#endif //TACTILEIPC3D_BACKEND_H
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ DataBackend::DataBackend(QObject* parent)
|
|||||||
emitFrame_(m_frames[m_playbackIndex], cb);
|
emitFrame_(m_frames[m_playbackIndex], cb);
|
||||||
m_playbackIndex++;
|
m_playbackIndex++;
|
||||||
});
|
});
|
||||||
seedDebugFrames_();
|
// seedDebugFrames_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataBackend::ingestFrame(const DataFrame& frame) {
|
void DataBackend::ingestFrame(const DataFrame& frame) {
|
||||||
@@ -56,6 +56,14 @@ bool DataBackend::exportCsv(const QString& path) const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DataBackend::exportXlsx(const QString& path) const {
|
||||||
|
/* QFile file(path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly))
|
||||||
|
return false;
|
||||||
|
*/
|
||||||
|
return buildXlsx_(path);
|
||||||
|
}
|
||||||
|
|
||||||
bool DataBackend::importJson(const QString& path) {
|
bool DataBackend::importJson(const QString& path) {
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
@@ -70,6 +78,57 @@ bool DataBackend::importCsv(const QString& path) {
|
|||||||
return loadCsv_(file.readAll());
|
return loadCsv_(file.readAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DataBackend::importXlsx(const QString& path) {
|
||||||
|
QXlsx::Document doc(path);
|
||||||
|
if (!doc.isLoadPackage()) {
|
||||||
|
qCritical() << "failed to open file: " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopPlayback();
|
||||||
|
clear();
|
||||||
|
doc.selectSheet(1);
|
||||||
|
QXlsx::CellRange range = doc.dimension();
|
||||||
|
int firstRow = range.firstRow();
|
||||||
|
int lastRow = range.lastRow();
|
||||||
|
int firstCol = range.firstColumn();
|
||||||
|
int lastCol = range.lastColumn();
|
||||||
|
|
||||||
|
if (firstRow == 0 && lastRow == 0 && firstCol == 0 && lastCol == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int row_count = lastRow - firstRow + 1;
|
||||||
|
int col_count = lastCol - firstCol + 1;
|
||||||
|
// TODO 完善xlsx数据导入
|
||||||
|
struct SpanInfo {
|
||||||
|
int row = 0;
|
||||||
|
int column = 0;
|
||||||
|
int rowSpan = 1;
|
||||||
|
int colSpan = 1;
|
||||||
|
};
|
||||||
|
QVector<QVector<QVariant>> cells;
|
||||||
|
QVector<SpanInfo> spans;
|
||||||
|
for (int r = 0; r < row_count; ++r) {
|
||||||
|
cells.resize(col_count);
|
||||||
|
for (int c = 0; c < col_count; ++c) {
|
||||||
|
int excel_row = firstRow + r;
|
||||||
|
int excel_col = firstCol + c;
|
||||||
|
|
||||||
|
std::shared_ptr<QXlsx::Cell> cell_obj = doc.cellAt(excel_row, excel_col);
|
||||||
|
QVariant v;
|
||||||
|
|
||||||
|
if (cell_obj) {
|
||||||
|
v = cell_obj->readValue();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
v = doc.read(excel_row, excel_col);
|
||||||
|
}
|
||||||
|
|
||||||
|
cells[r][c] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DataBackend::startPlayback(int intervalMs) {
|
void DataBackend::startPlayback(int intervalMs) {
|
||||||
if (m_frames.isEmpty())
|
if (m_frames.isEmpty())
|
||||||
return;
|
return;
|
||||||
@@ -273,6 +332,47 @@ QByteArray DataBackend::buildCsv_() const {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DataBackend::buildXlsx_(const QString& path) const {
|
||||||
|
QXlsx::Document xlsx;
|
||||||
|
int current_sheet_index = 1;
|
||||||
|
int current_sheet_row_start = 1;
|
||||||
|
|
||||||
|
int col_count = m_frames.at(0).data.size();
|
||||||
|
auto ensure_sheet_for_row = [&](int row){
|
||||||
|
if (m_frames.size() <= 0) {
|
||||||
|
if (xlsx.currentWorksheet() == nullptr)
|
||||||
|
xlsx.addSheet("Sheet1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xlsx.currentWorksheet() == nullptr) {
|
||||||
|
xlsx.addSheet(QStringLiteral("Sheet%1").arg(current_sheet_index));
|
||||||
|
current_sheet_row_start = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (row - current_sheet_row_start >= m_frames.size()) {
|
||||||
|
++current_sheet_index;
|
||||||
|
xlsx.addSheet(QStringLiteral("Sheet%1").arg(current_sheet_index));
|
||||||
|
current_sheet_row_start = row;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t row = 1; row <= m_frames.size(); row++) {
|
||||||
|
ensure_sheet_for_row(row);
|
||||||
|
xlsx.write(1, 1, m_frames.at(row - 1).pts);
|
||||||
|
int col_index = 2;
|
||||||
|
for (auto data : m_frames.at(row - 1).data) {
|
||||||
|
xlsx.write(row, col_index, data);
|
||||||
|
col_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!xlsx.saveAs(path)) {
|
||||||
|
qCritical() << "failed to save file: " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void DataBackend::seedDebugFrames_() {
|
void DataBackend::seedDebugFrames_() {
|
||||||
if (!m_frames.isEmpty())
|
if (!m_frames.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <QSerialPort>
|
#include <QSerialPort>
|
||||||
#include <QSerialPortInfo>
|
#include <QSerialPortInfo>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
#include "xlsxdocument.h"
|
||||||
|
#include "xlsxformat.h"
|
||||||
#include "data_frame.h"
|
#include "data_frame.h"
|
||||||
|
|
||||||
class DataBackend : public QObject {
|
class DataBackend : public QObject {
|
||||||
@@ -42,13 +44,14 @@ public:
|
|||||||
Q_INVOKABLE void clear();
|
Q_INVOKABLE void clear();
|
||||||
Q_INVOKABLE bool exportJson(const QString& path) const;
|
Q_INVOKABLE bool exportJson(const QString& path) const;
|
||||||
Q_INVOKABLE bool exportCsv(const QString& path) const;
|
Q_INVOKABLE bool exportCsv(const QString& path) const;
|
||||||
|
Q_INVOKABLE bool exportXlsx(const QString& path) const;
|
||||||
Q_INVOKABLE bool importJson(const QString& path);
|
Q_INVOKABLE bool importJson(const QString& path);
|
||||||
Q_INVOKABLE bool importCsv(const QString& path);
|
Q_INVOKABLE bool importCsv(const QString& path);
|
||||||
|
Q_INVOKABLE bool importXlsx(const QString& path);
|
||||||
Q_INVOKABLE void startPlayback(int intervalMs);
|
Q_INVOKABLE void startPlayback(int intervalMs);
|
||||||
Q_INVOKABLE void stopPlayback();
|
Q_INVOKABLE void stopPlayback();
|
||||||
Q_INVOKABLE void exportHandler(const QUrl& folder, const QString& filename,
|
Q_INVOKABLE void exportHandler(const QUrl& folder, const QString& filename,
|
||||||
const QString& format, const QString& method);
|
const QString& format, const QString& method);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void frameCountChanged();
|
void frameCountChanged();
|
||||||
void playbackRunningChanged();
|
void playbackRunningChanged();
|
||||||
@@ -59,6 +62,7 @@ private:
|
|||||||
bool loadCsv_(const QByteArray& data);
|
bool loadCsv_(const QByteArray& data);
|
||||||
QByteArray buildJson_() const;
|
QByteArray buildJson_() const;
|
||||||
QByteArray buildCsv_() const;
|
QByteArray buildCsv_() const;
|
||||||
|
bool buildXlsx_(const QString& path) const;
|
||||||
|
|
||||||
void seedDebugFrames_();
|
void seedDebugFrames_();
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ static void matIdentity(float m[16]) {
|
|||||||
m[0] = m[5] = m[10] = m[15] = 1;
|
m[0] = m[5] = m[10] = m[15] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QVector3D toColorVec(const QColor& color) {
|
||||||
|
return QVector3D(color.redF(), color.greenF(), color.blueF());
|
||||||
|
}
|
||||||
|
|
||||||
GLWidget::GLWidget(QWidget *parent)
|
GLWidget::GLWidget(QWidget *parent)
|
||||||
: QOpenGLWidget(parent) {
|
: QOpenGLWidget(parent) {
|
||||||
setMinimumSize(640, 480);
|
setMinimumSize(640, 480);
|
||||||
@@ -191,6 +195,30 @@ void GLWidget::setShowBg(bool on = true) {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLWidget::setColorLow(const QColor& color) {
|
||||||
|
const QVector3D next = toColorVec(color);
|
||||||
|
if (m_colorLow == next)
|
||||||
|
return;
|
||||||
|
m_colorLow = next;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLWidget::setColorMid(const QColor& color) {
|
||||||
|
const QVector3D next = toColorVec(color);
|
||||||
|
if (m_colorMid == next)
|
||||||
|
return;
|
||||||
|
m_colorMid = next;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLWidget::setColorHigh(const QColor& color) {
|
||||||
|
const QVector3D next = toColorVec(color);
|
||||||
|
if (m_colorHigh == next)
|
||||||
|
return;
|
||||||
|
m_colorHigh = next;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
void GLWidget::initializeGL() {
|
void GLWidget::initializeGL() {
|
||||||
initializeOpenGLFunctions();
|
initializeOpenGLFunctions();
|
||||||
|
|
||||||
@@ -211,6 +239,14 @@ void GLWidget::initializeGL() {
|
|||||||
matIdentity(m_proj);
|
matIdentity(m_proj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLWidget::initGeometry_() {
|
||||||
|
initDotTexture_();
|
||||||
|
initBackgroundGeometry_();
|
||||||
|
initPanelGeometry_();
|
||||||
|
initDotGeometry_();
|
||||||
|
initRoomGeometry_();
|
||||||
|
}
|
||||||
|
|
||||||
void GLWidget::resizeGL(int w, int h) {
|
void GLWidget::resizeGL(int w, int h) {
|
||||||
glViewport(0, 0, w, h);
|
glViewport(0, 0, w, h);
|
||||||
}
|
}
|
||||||
@@ -293,8 +329,11 @@ void GLWidget::paintGL() {
|
|||||||
// uMinV/uMaxV: 传感值范围,用于 fragment shader 把 value 映射成颜色
|
// uMinV/uMaxV: 传感值范围,用于 fragment shader 把 value 映射成颜色
|
||||||
m_dotsProg->setUniformValue("uMinV", float(m_min));
|
m_dotsProg->setUniformValue("uMinV", float(m_min));
|
||||||
m_dotsProg->setUniformValue("uMaxV", float(m_max));
|
m_dotsProg->setUniformValue("uMaxV", float(m_max));
|
||||||
|
m_dotsProg->setUniformValue("uColorLow", m_colorLow);
|
||||||
|
m_dotsProg->setUniformValue("uColorMid", m_colorMid);
|
||||||
|
m_dotsProg->setUniformValue("uColorHigh", m_colorHigh);
|
||||||
const int hasData = (m_hasData.load() || m_dotTex == 0) ? 1 : 0;
|
const int hasData = (m_hasData.load() || m_dotTex == 0) ? 1 : 0;
|
||||||
m_dotsProg->setUniformValue("uHasData", 0);
|
m_dotsProg->setUniformValue("uHasData", hasData);
|
||||||
m_dotsProg->setUniformValue("uCameraPos", m_cameraPos);
|
m_dotsProg->setUniformValue("uCameraPos", m_cameraPos);
|
||||||
m_dotsProg->setUniformValue("uDotTex", 0);
|
m_dotsProg->setUniformValue("uDotTex", 0);
|
||||||
if (m_dotTex) {
|
if (m_dotTex) {
|
||||||
@@ -427,6 +466,7 @@ void GLWidget::mousePressEvent(QMouseEvent *event) {
|
|||||||
if (event->button() == Qt::LeftButton) {
|
if (event->button() == Qt::LeftButton) {
|
||||||
QVector3D world;
|
QVector3D world;
|
||||||
const int index = pickDotIndex_(event->pos(), &world);
|
const int index = pickDotIndex_(event->pos(), &world);
|
||||||
|
qInfo() << "clicked index: " << index;
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
float value = 0.0f;
|
float value = 0.0f;
|
||||||
int row = 0;
|
int row = 0;
|
||||||
@@ -1098,3 +1138,17 @@ void GLWidget::setShowGrid(bool on) {
|
|||||||
m_showGrid = on;
|
m_showGrid = on;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLWidget::setRow(int row) {
|
||||||
|
row = qMax(0, row);
|
||||||
|
if (m_rows == row)
|
||||||
|
return;
|
||||||
|
setSpec(row, m_cols, m_pitch, m_dotRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLWidget::setCol(int col) {
|
||||||
|
col = qMax(0, col);
|
||||||
|
if (m_cols == col)
|
||||||
|
return;
|
||||||
|
setSpec(m_rows, col, m_pitch, m_dotRadius);
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <QMatrix4x4>
|
#include <QMatrix4x4>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QColor>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qvectornd.h>
|
#include <qvectornd.h>
|
||||||
@@ -26,6 +27,8 @@ struct Ray {
|
|||||||
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core {
|
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(float yaw READ yaw WRITE setYaw NOTIFY yawChanged)
|
Q_PROPERTY(float yaw READ yaw WRITE setYaw NOTIFY yawChanged)
|
||||||
|
Q_PROPERTY(int row READ row WRITE setRow)
|
||||||
|
Q_PROPERTY(int col READ col WRITE setCol)
|
||||||
// Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged)
|
// Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged)
|
||||||
public:
|
public:
|
||||||
enum RenderMode {
|
enum RenderMode {
|
||||||
@@ -59,7 +62,8 @@ public:
|
|||||||
|
|
||||||
bool showGrid() const { return m_showGrid; }
|
bool showGrid() const { return m_showGrid; }
|
||||||
|
|
||||||
|
int row() const { return m_rows; }
|
||||||
|
int col() const { return m_cols; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// 值域范围,用于 shader 里把 value 映射到颜色(绿->红)
|
// 值域范围,用于 shader 里把 value 映射到颜色(绿->红)
|
||||||
@@ -70,6 +74,11 @@ public slots:
|
|||||||
void setLightMode(bool on);
|
void setLightMode(bool on);
|
||||||
void setShowBg(bool on);
|
void setShowBg(bool on);
|
||||||
void setShowGrid(bool on);
|
void setShowGrid(bool on);
|
||||||
|
void setCol(int col);
|
||||||
|
void setRow(int row);
|
||||||
|
void setColorLow(const QColor& color);
|
||||||
|
void setColorMid(const QColor& color);
|
||||||
|
void setColorHigh(const QColor& color);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void yawChanged();
|
void yawChanged();
|
||||||
@@ -85,6 +94,7 @@ protected:
|
|||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void initGeometry_();
|
||||||
void initPanelGeometry_();
|
void initPanelGeometry_();
|
||||||
void initDotGeometry_();
|
void initDotGeometry_();
|
||||||
void initBackgroundGeometry_();
|
void initBackgroundGeometry_();
|
||||||
@@ -154,6 +164,9 @@ private:
|
|||||||
unsigned int m_bgVbo = 0;
|
unsigned int m_bgVbo = 0;
|
||||||
bool m_lightMode = true;
|
bool m_lightMode = true;
|
||||||
bool m_showBg = true;
|
bool m_showBg = true;
|
||||||
|
QVector3D m_colorLow{0.10f, 0.75f, 1.00f};
|
||||||
|
QVector3D m_colorMid{0.10f, 0.95f, 0.35f};
|
||||||
|
QVector3D m_colorHigh{1.00f, 0.22f, 0.10f};
|
||||||
|
|
||||||
// MVP = Projection * View * Model。
|
// MVP = Projection * View * Model。
|
||||||
// 这里 panel/dots 顶点基本已经是“世界坐标”,所以我们用 proj*view 即可(model 先省略)。
|
// 这里 panel/dots 顶点基本已经是“世界坐标”,所以我们用 proj*view 即可(model 先省略)。
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ SerialBackend::SerialBackend(QObject* parent)
|
|||||||
, m_decodeThread(&m_packetQueue, &m_frameQueue) {
|
, m_decodeThread(&m_packetQueue, &m_frameQueue) {
|
||||||
m_request.dataLength = 24;
|
m_request.dataLength = 24;
|
||||||
m_spec.model = QStringLiteral("PZR-A");
|
m_spec.model = QStringLiteral("PZR-A");
|
||||||
m_spec.rows = 3;
|
|
||||||
m_spec.cols = 4;
|
|
||||||
|
|
||||||
auto codec = std::make_shared<PiezoresistiveACodec>();
|
auto codec = std::make_shared<PiezoresistiveACodec>();
|
||||||
auto decoder = std::make_shared<PiezoresistiveADecoder>();
|
auto decoder = std::make_shared<PiezoresistiveADecoder>();
|
||||||
@@ -270,6 +268,16 @@ void SerialBackend::feedBytes(const QByteArray& data) {
|
|||||||
m_readThread.enqueueBytes(data);
|
m_readThread.enqueueBytes(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SerialBackend::setSensorWidth(int w) {
|
||||||
|
m_spec.cols = w;
|
||||||
|
syncSendConfig_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialBackend::setSensorHeight(int h) {
|
||||||
|
m_spec.rows = h;
|
||||||
|
syncSendConfig_();
|
||||||
|
}
|
||||||
|
|
||||||
void SerialBackend::drainFrames_() {
|
void SerialBackend::drainFrames_() {
|
||||||
if (!m_frameCallback)
|
if (!m_frameCallback)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ public:
|
|||||||
Q_INVOKABLE void requestOnce();
|
Q_INVOKABLE void requestOnce();
|
||||||
Q_INVOKABLE void feedBytes(const QByteArray& data);
|
Q_INVOKABLE void feedBytes(const QByteArray& data);
|
||||||
|
|
||||||
|
int sensorWidth() const { return m_spec.cols; }
|
||||||
|
int sensorHeight() const { return m_spec.rows; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setSensorWidth(int w);
|
||||||
|
void setSensorHeight(int h);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void portNameChanged();
|
void portNameChanged();
|
||||||
void baudRateChanged();
|
void baudRateChanged();
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ struct SensorRequest {
|
|||||||
struct SensorSpec {
|
struct SensorSpec {
|
||||||
QString model;
|
QString model;
|
||||||
QString version;
|
QString version;
|
||||||
int rows = 0;
|
int rows = 12;
|
||||||
int cols = 0;
|
int cols = 7;
|
||||||
float pitch = 0.0f;
|
float pitch = 0.0f;
|
||||||
float dotRadius = 0.0f;
|
float dotRadius = 0.0f;
|
||||||
float rangeMin = 0.0f;
|
float rangeMin = 0.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user