diff --git a/CMakeLists.txt b/CMakeLists.txt index da3abd8..7d53f9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,21 @@ FetchContent_Declare(qtkeychain GIT_TAG v0.14.0) FetchContent_MakeAvailable(qtkeychain) +# 【ElaWidgetTools 评估 spike — 仅 feat/elawidgettools 分支】Fluent UI for QWidget。 +# 用 RainbowCandyX fork(支持 Qt6.10+,对 6.11 有条件修补)。SOURCE_SUBDIR 仅编库子目录, +# 跳过其示例/PySide bindings。静态链接(MIT 许可,static 合规且省 DLL)。库子目录自带 +# find_package(Qt6 Widgets/WidgetsPrivate) 与自身 .qrc(靠全局 AUTORCC)。仅隔离评估,不影响产品。 +set(ELAWIDGETTOOLS_BUILD_STATIC_LIB ON CACHE BOOL "" FORCE) +FetchContent_Declare(elawidgettools + GIT_REPOSITORY https://github.com/RainbowCandyX/ElaWidgetTools.git + GIT_TAG main + SOURCE_SUBDIR ElaWidgetTools) +FetchContent_MakeAvailable(elawidgettools) + add_subdirectory(src) +# ElaWidgetTools 评估 spike(隔离 demo,不属于产品 geopro_desktop;评估完删分支即弃)。 +add_subdirectory(spike/ela) + enable_testing() add_subdirectory(tests) diff --git a/build.bat b/build.bat index 86db412..90898ea 100644 --- a/build.bat +++ b/build.bat @@ -19,6 +19,12 @@ set "ROOT=%~dp0" set "BUILDDIR=%ROOT%build\release" set "PRESET=msvc-release" +REM 把临时目录指向 D: 的构建目录,规避 C: 盘满导致链接器写 %TEMP% 失败(LNK1108)。 +REM 仅作用于本次构建(setlocal 作用域),不污染用户 shell。注意:仍建议尽快清理 C: 盘。 +set "TEMP=%BUILDDIR%\tmp" +set "TMP=%BUILDDIR%\tmp" +if not exist "%TEMP%" mkdir "%TEMP%" + REM --- locate Visual Studio (vswhere lives at a fixed path) --- set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" if not exist "%VSWHERE%" ( diff --git a/docs/superpowers/plans/2026-06-09-elawidgettools-migration.md b/docs/superpowers/plans/2026-06-09-elawidgettools-migration.md new file mode 100644 index 0000000..cc00f11 --- /dev/null +++ b/docs/superpowers/plans/2026-06-09-elawidgettools-migration.md @@ -0,0 +1,83 @@ +# geopro_desktop → ElaWidgetTools (Fluent) 迁移计划 + +**分支**:`feat/elawidgettools` **日期**:2026-06-09 **决策**:全面 Ela 化(最彻底),支持明/暗主题。 +**前置评估已完成**:spike(`spike/ela/`) 证明 ElaWidgetTools(RainbowCandyX fork) 可用官方 Qt 6.11.1 + MSVC 经 FetchContent 构建,ElaWindow + ADS 内嵌 + QVTK 渲染均可行;ElaTheme 明暗切换可用,但**只自动覆盖 Ela\* 控件与 ElaWindow 外壳**,标准 QWidget/ADS/VTK 需手工主题联动。 + +--- + +## 0. 硬前提(动手前必须满足) + +- **P0-a 清理 C: 盘**:当前 C: 0 GB 可用,链接器写 `%TEMP%` 失败(`LNK1108`)。迁移需大量构建验证。需用户清理 C:;我同时把 `TEMP/TMP→D:` 兜底固化进 `build.bat`,避免反复手动重定向。 +- **P0-b 验证靠用户**:每阶段我构建通过后,用户运行 + 截图,我据反馈迭代(登录门槛 + GUI,我无法目视)。 +- **P0-c 回退保障**:全程保留 `GEOPRO_UI_SHELL=classic|ela` 环境变量开关,可在「现 QMainWindow 壳」与「ElaWindow 壳」间切换,便于 A/B 与回退;迁移稳定后再移除。 + +## 1. 依赖固化(P0 工程) + +- ElaWidgetTools 从 spike 提升为**正式依赖**:`FetchContent` 的 `GIT_TAG` 由 `main` **钉到具体 commit**(可复现)。 +- **静/动态**:先静态(MIT,省 DLL);若遇静态资源(字体/SVG 图标 .qrc)被剥离导致图标缺失,改动态(`ELAWIDGETTOOLS_BUILD_STATIC_LIB OFF` + DLL 随 `TARGET_RUNTIME_DLLS` 拷贝)。 +- **插件部署**:把 `platforms / styles / imageformats / iconengines`(含 SVG 图标用的 `qsvg`/`qsvgicon`)部署接进 `geopro_desktop` 的 post-build(今天 spike 是手动拷的,要正式化;windeployqt 会被 ADS 的 DLL 依赖卡住,改为显式 copy Qt plugins)。 + +## 2. P1 — 换壳(带开关) + +- 新建 `ElaShellWindow`(继承 `ElaWindow`)或在 `main()` 分支构造。把现有 `buildWorkbench()` 产出的中心内容(ADS `CDockManager` + 工具条)作为 ElaWindow 的一个 page/central content 挂入(`addPageNode` / `setCentralCustomWidget`)。 +- `eApp->init()` 在 `QApplication` 后调用;保留高 DPI 与 QVTK surface format 设置顺序。 +- **dock 持久化注意**:ElaWindow 接管后,`restoreState` 后重隐藏 ADS 标题栏的时序修复(已在主分支)需在新壳下复核。 +- **验收**:`GEOPRO_UI_SHELL=ela` 启动 → 登录 → 工作台;ADS 停靠可拖动;中央/详情 VTK 正常;导航/标题栏 Fluent 外观。截图确认。 + +## 3. P2 — 主题桥(明/暗覆盖所有非 Ela 面) + +- 新建 `ThemeBridge`:监听 `ElaTheme::themeModeChanged`,把明/暗同步到: + 1. **全局 QSS**:把 `Theme.cpp` 的 `kStyleSheet` 拆成「明」与「暗」两版(用已有 `type/space/radius/semantic` 令牌派生暗色盘),按主题切换。 + 2. **ADS 停靠区**:`CDockManager::setStyleSheet` 明/暗两套。 + 3. **VTK 背景**:中央 + 详情 renderer 背景随主题切深/浅底并 `Render()`。 + 4. **内联样式面板**:PanelHeader / TopBar / LoginWindow / main 浮层 的内联 QSS 改为「跟随主题」(去硬编码色,引用桥提供的明/暗令牌)。 +- **暗色盘设计**:在 `Theme.hpp` 增加暗色语义(surface/ink/border/accent 的暗版),保持品牌蓝在暗底的可读性与对比度(≥4.5:1)。 +- **验收**:明/暗一键切换,全界面(外壳+停靠+面板+VTK)协调一致、无残留亮/暗块;对比度达标。截图明、暗各一。 + +## 4. P3 — 全面控件 Ela 化(工作量主体) + +逐面替换标准控件为 `Ela*` 等价物,"白嫖"明暗与 Fluent 观感。映射(精确 Ela 类名在实施时按头文件确认): + +| 现状 | → Ela 等价 | 所在 | +|---|---|---| +| QPushButton | ElaPushButton | LoginWindow / 各处 | +| QLineEdit | ElaLineEdit | LoginWindow / ProjectListDialog 过滤 | +| QCheckBox | ElaCheckBox | LoginWindow / 图层浮层 / 异常列表 | +| QComboBox | ElaComboBox | ProjectListDialog / 全局 | +| QLabel(文本) | ElaText | 各处文本/标题 | +| QToolButton(Tab/操作) | ElaToolButton / ElaIconButton | PanelHeader / TopBar | +| QMenuBar / QMenu | ElaMenuBar / ElaMenu | TopBar | +| QTreeWidget | ElaTreeView(+model) 或保留+主题联动 | ObjectTreePanel | +| QListWidget | ElaListView(+model) 或保留+联动 | Dataset/Anomaly 面板 | +| QTableWidget | ElaTableView 或保留+联动 | ProjectListDialog | +| QProgressBar | ElaProgressBar | 全局 | +| QStatusBar | ElaStatusBar 或保留+联动 | main | +| QTabWidget/分段 | ElaTabWidget / ElaToggleSwitch | PanelHeader 数据/文件 | +| QDialog(登录) | ElaWidget/ElaWindow 风格弹窗 | LoginWindow | +| **保留(无替代)** | QVTKOpenGLStereoWidget、ADS CDockManager | 中央/详情/停靠 | + +- 树/列表/表若用 Ela 的 View 需改 model(成本高),可分两步:先保留 widget 版做主题联动,后续再评估换 View。 +- 登录窗重做为 Fluent 风格(沿用现有令牌与文案)。 + +## 5. P4 — 收尾 + +- 插件部署正式化、ElaWidgetTools 版本锁定、静态资源核验。 +- 去掉 `GEOPRO_UI_SHELL` 过渡开关(确认稳定后)。 +- 开源声明:ElaWidgetTools(MIT)、ADS(LGPLv2.1)、Qt(LGPL) NOTICE 归集。 +- 回归:登录、项目切换、对象树、数据集/文件分页、异常、VTK 各视图、dock 持久化。 + +## 风险登记 + +| 风险 | 缓解 | +|---|---| +| Qt 6.11 Windows Popup 渲染(作者红旗) | spike 已初验;P1 重点复核菜单/下拉/提示;必要时打 fork 的条件补丁 | +| ADS 在 ElaWindow 内主题/交互异常 | spike 已验内嵌;P2 专门做 ADS 明暗 QSS | +| Ela View 需 model 重写(树/列表/表) | 分步:先 widget 版主题联动,再评估换 View | +| 静态库资源剥离(图标/字体缺失) | 改动态库 | +| 我无法目视 | 每阶段用户运行+截图验收 | +| C: 满导致构建反复失败 | 清理 C: + TEMP→D: 固化进 build.bat | +| 大重构回归 | 全程 env 开关可回退;主分支零影响 | + +## 执行顺序 + +P0 → P1(验收)→ P2(验收)→ P3(按面板分批,每批验收)→ P4。每步构建通过后由用户运行+截图确认再进下一步。 diff --git a/spike/ela/CMakeLists.txt b/spike/ela/CMakeLists.txt new file mode 100644 index 0000000..4d48f0b --- /dev/null +++ b/spike/ela/CMakeLists.txt @@ -0,0 +1,18 @@ +# ElaWidgetTools 评估 spike(隔离 demo,独立 exe)。仅 feat/elawidgettools 分支评估用, +# 与产品 geopro_desktop 完全解耦:链 ElaWidgetTools(Fluent 库) + ADS + VTK,验证可行性与观感。 +add_executable(geopro_ela_spike WIN32 main.cpp) + +target_link_libraries(geopro_ela_spike PRIVATE + Qt6::Core Qt6::Gui Qt6::Widgets + ElaWidgetTools + ads::qt6advanceddocking + ${VTK_LIBRARIES}) + +vtk_module_autoinit(TARGETS geopro_ela_spike MODULES ${VTK_LIBRARIES}) + +if(WIN32) + add_custom_command(TARGET geopro_ela_spike POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $ + COMMAND_EXPAND_LISTS) +endif() diff --git a/spike/ela/main.cpp b/spike/ela/main.cpp new file mode 100644 index 0000000..77f3793 --- /dev/null +++ b/spike/ela/main.cpp @@ -0,0 +1,121 @@ +// ElaWidgetTools 评估 spike(隔离 demo,不属于产品 app,仅 feat/elawidgettools 分支评估用)。 +// 一锤定音验证四件事: +// ① 用你们官方 Qt 6.11.1 + MSVC 经 FetchContent 能否构建 ElaWidgetTools(RainbowCandyX fork); +// ② ElaWindow 的 Fluent 观感在你们机器上渲染是否正常(重点看 Qt6.11 的 Popup/弹窗); +// ③ Qt Advanced Docking System(ADS) 能否内嵌进 ElaWindow; +// ④ QVTKOpenGLStereoWidget 视口在 ElaWindow + ADS 内能否正常渲染。 +// 结论决定是否值得对真实 app 做外壳重构。 + +#include +#include +#include +#include +#include + +#include "ElaApplication.h" +#include "ElaDef.h" +#include "ElaPushButton.h" +#include "ElaText.h" +#include "ElaTheme.h" +#include "ElaWindow.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); + QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat()); + + QApplication app(argc, argv); + eApp->init(); // ElaApplication 初始化(Fluent 主题/字体/动画基建) + + ElaWindow window; + window.setWindowTitle(QStringLiteral("ElaWidgetTools Spike — Fluent + ADS + VTK")); + window.resize(1200, 760); + + // 「工作台」页内嵌 ADS 停靠管理器:验证 ADS 能否在 ElaWindow 内正常工作。 + auto* dockHost = new QWidget; + auto* hostLay = new QVBoxLayout(dockHost); + hostLay->setContentsMargins(0, 0, 0, 0); + auto* dockManager = new ads::CDockManager(dockHost); + hostLay->addWidget(dockManager); + + // dock1:Fluent 控件样例(看观感:ElaText / ElaPushButton 与标准控件对照)。 + auto* sample = new QWidget; + sample->setObjectName(QStringLiteral("sampleHost")); + auto* sLay = new QVBoxLayout(sample); + sLay->setContentsMargins(16, 16, 16, 16); + sLay->setSpacing(12); + sLay->addWidget(new ElaText(QStringLiteral("ElaText —— Fluent 文本"), sample)); + sLay->addWidget(new ElaPushButton(QStringLiteral("ElaPushButton 主操作"), sample)); + sLay->addWidget(new QLabel(QStringLiteral("(对照)标准 QLabel"), sample)); + + // 浅/深主题切换:ElaWidgetTools 内置 ElaTheme,运行期一键切换整套 Fluent 主题。 + auto* themeBtn = new ElaPushButton(QStringLiteral("切换 浅色 / 深色"), sample); + QObject::connect(themeBtn, &QPushButton::clicked, themeBtn, [] { + eTheme->setThemeMode(eTheme->getThemeMode() == ElaThemeType::Light ? ElaThemeType::Dark + : ElaThemeType::Light); + }); + sLay->addWidget(themeBtn); + sLay->addStretch(); + auto* d1 = new ads::CDockWidget(QStringLiteral("Fluent 控件")); + d1->setWidget(sample); + dockManager->addDockWidget(ads::LeftDockWidgetArea, d1); + + // dock2:QVTK 视口(蓝色锥体),验证 VTK 在 ElaWindow + ADS 内渲染。 + auto* vtkWidget = new QVTKOpenGLStereoWidget; + vtkNew renderWindow; + vtkNew renderer; + renderer->SetBackground(1.0, 1.0, 1.0); + vtkWidget->setRenderWindow(renderWindow); + renderWindow->AddRenderer(renderer); + vtkNew cone; + cone->SetResolution(48); + vtkNew mapper; + mapper->SetInputConnection(cone->GetOutputPort()); + vtkNew actor; + actor->SetMapper(mapper); + actor->GetProperty()->SetColor(0.18, 0.42, 0.71); // 品牌蓝 #2D6CB5 近似 + renderer->AddActor(actor); + renderer->ResetCamera(); + auto* d2 = new ads::CDockWidget(QStringLiteral("VTK 视口(锥体)")); + d2->setWidget(vtkWidget); + dockManager->addDockWidget(ads::RightDockWidgetArea, d2); + + // 关键发现演示:ADS 停靠区与普通 QWidget 不会自动跟随 ElaTheme(只有 Ela* 控件与 + // ElaWindow 外壳跟随)。这里手动把「停靠区背景 + 普通容器 + VTK 背景」同步到当前主题, + // 并监听 ElaTheme::themeModeChanged。这段「同步」正是真集成时要为每个非 Ela 面板付出的成本。 + auto* rendererPtr = renderer.Get(); + auto* rwPtr = renderWindow.Get(); + auto applyContentTheme = [dockManager, sample, rendererPtr, rwPtr](ElaThemeType::ThemeMode mode) { + const bool dark = (mode == ElaThemeType::Dark); + const QString bg = dark ? QStringLiteral("#1E1F22") : QStringLiteral("#FFFFFF"); + const QString fg = dark ? QStringLiteral("#E3E3E3") : QStringLiteral("#1F2A3D"); + dockManager->setStyleSheet( + QStringLiteral("ads--CDockAreaWidget, ads--CDockContainerWidget { background:%1; }") + .arg(bg)); + sample->setStyleSheet( + QStringLiteral("#sampleHost { background:%1; } #sampleHost QLabel { color:%2; }") + .arg(bg, fg)); + rendererPtr->SetBackground(dark ? 0.11 : 1.0, dark ? 0.12 : 1.0, dark ? 0.14 : 1.0); + rwPtr->Render(); + }; + QObject::connect(eTheme, &ElaTheme::themeModeChanged, dockManager, + [applyContentTheme](ElaThemeType::ThemeMode m) { applyContentTheme(m); }); + applyContentTheme(eTheme->getThemeMode()); // 初始同步当前主题 + + window.addPageNode(QStringLiteral("工作台"), dockHost); + window.show(); + return app.exec(); +}