diff --git a/devel/0185.md b/devel/0185.md new file mode 100644 index 0000000000..8123abdf06 --- /dev/null +++ b/devel/0185.md @@ -0,0 +1,108 @@ +# [0185] Chat 侧边栏欢迎内容居中偏上调整 + +## 相关文档 + +- [dddd.md](dddd.md) - 任务文档模板 + +## 任务相关的代码文件 + +- `src/Plugins/Qt/qt_chat_tab_widget.cpp` +- `src/Plugins/Qt/qt_chat_tab_widget.hpp` +- `tests/Plugins/Qt/qt_chat_tab_widget_test.cpp` + +## 如何测试 + +### 确定性测试(单元测试) + +编译并运行 Chat Tab Widget 单元测试: + +```bash +xmake b qt_chat_tab_widget_test +xmake r qt_chat_tab_widget_test +``` + +预期结果:全部通过。 + +### 非确定性测试(UI 验证) + +#### A. Chat 标签页欢迎页位置 + +1. 切换到 Chat 标签页(确保没有活跃会话,显示欢迎页) +2. 观察:"欢迎使用 Liii STEM" 和输入框应位于整个软件界面的竖直方向中心稍稍偏上 +3. 调整窗口高度(放大/缩小) +4. 观察:欢迎内容应随窗口高度自适应,始终保持中心偏上 + +#### B. Chat 侧边栏(dock 模式)欢迎页位置 + +1. 打开一个 TMU 文档或 PDF 文档 +2. 点击右上角 AI 对话浮动按钮,打开右侧 dock +3. 确保 dock 中显示的是欢迎页(无活跃会话或新建空白会话) +4. 观察:"欢迎使用 Liii STEM" 和输入框应位于整个软件界面的竖直方向中心稍稍偏上 +5. 调整主窗口高度 +6. 观察:欢迎内容应随窗口高度自适应,始终保持中心偏上 + +#### C. 会话模式不受影响 + +1. 在 Chat 标签页或 dock 中发送一条消息 +2. 观察:消息区域应贴顶显示(仅有少量顶部偏移),输入框在底部 +3. 观察:不应出现大片空白区域挤压消息区 + +## 如何提交 + +提交前编译项目: + +```bash +xmake b stem +``` + +提交前运行单元测试: + +```bash +xmake b qt_chat_tab_widget_test +xmake r qt_chat_tab_widget_test +``` + +## What + +调整 `ChatConversationPanel` 欢迎模式的竖直布局策略,使其在 Chat 标签页和 Chat 侧边栏(dock)模式下都能将"欢迎使用 Liii STEM"和输入框置于整个软件界面的中心偏上位置。 + +具体修改: +1. 将 `contentLayout` 顶部固定偏移(240px)改为 0,消除硬编码偏移。 +2. 重写 `ChatConversationPanel::resizeEvent()`,在欢迎模式下根据面板高度动态计算顶部 spacer 高度,使内容位于容器高度的 2/7 处(中心稍稍偏上)。 +3. 进入会话模式时,顶部 spacer 恢复为固定小偏移(8px),不影响消息区域展开。 + +## Why + +当前实现使用固定的 240px 顶部 spacer(`kWelcomeTopOffsetY`): +- 在 Chat 标签页模式下,由于标签页容器高度相对较小,240px 偏移使内容看起来偏上。 +- 在 Chat 侧边栏(dock)模式下,dock 高度通常等于主窗口高度,240px 偏移不足以将内容推到偏上位置,导致内容落在 dock 的竖直方向中心,与 Chat 标签页的视觉效果不一致。 + +用户期望两种模式下的欢迎页具有统一的视觉位置:整个软件界面的中心稍稍偏上。 + +## How + +### 布局策略调整 + +原方案: +``` +contentLayout: [topSpacer_ 240px Fixed] -> [topPanel stretch 1, AlignTop] +``` + +新方案(欢迎模式): +``` +contentLayout: [topSpacer_ height*2/7 Fixed] -> [topPanel stretch 1, AlignTop] +topLayout: [welcomeTitle_] + -> [sessionTitle_] + -> [messageFrame_ hidden] + -> [inputWrap] +``` + +新方案(会话模式): +``` +contentLayout: [topSpacer_ 8px Fixed] -> [topPanel stretch 1, AlignTop] +topLayout: [sessionTitle_] + -> [messageFrame_ stretch 1 visible] + -> [inputWrap] +``` + +`resizeEvent` 中按 `height() * 2 / 7 - kContentMarginY` 计算顶部 spacer 高度,确保内容始终位于容器高度的 2/7 处(中心稍稍偏上)。窗口大小变化时自动重新计算,无需内部弹性空间。进入会话模式后,`topSpacer_` 收缩为固定小偏移,不影响消息区域展开。 diff --git a/src/Plugins/Qt/qt_chat_tab_widget.cpp b/src/Plugins/Qt/qt_chat_tab_widget.cpp index 5b94e30fa8..3148eb859c 100644 --- a/src/Plugins/Qt/qt_chat_tab_widget.cpp +++ b/src/Plugins/Qt/qt_chat_tab_widget.cpp @@ -137,8 +137,7 @@ ChatConversationPanel::setup_ui () { contentLayout->setContentsMargins (0, DpiUtils::scaled (kContentMarginY), 0, DpiUtils::scaled (kContentMarginY)); contentLayout->setSpacing (DpiUtils::scaled (kContentSpacing)); - topSpacer_= new QSpacerItem (0, DpiUtils::scaled (kWelcomeTopOffsetY), - QSizePolicy::Minimum, QSizePolicy::Fixed); + topSpacer_= new QSpacerItem (0, 0, QSizePolicy::Minimum, QSizePolicy::Fixed); contentLayout->addSpacerItem (topSpacer_); QWidget* topPanel = new QWidget (this); @@ -315,9 +314,8 @@ void ChatConversationPanel::enterConversationMode () { if (conversationMode_) return; - conversationMode_ = true; - const int startOffset= DpiUtils::scaled (kWelcomeTopOffsetY); - const int endOffset = DpiUtils::scaled (kConversationTopOffsetY); + conversationMode_ = true; + const int endOffset= DpiUtils::scaled (kConversationTopOffsetY); if (messageFrame_) { QGraphicsOpacityEffect* messageEffect= @@ -359,6 +357,20 @@ ChatConversationPanel::enterConversationMode () { } } +void +ChatConversationPanel::resizeEvent (QResizeEvent* event) { + QWidget::resizeEvent (event); + if (conversationMode_ || !topSpacer_) return; + int targetOffset= height () * 2 / 7 - DpiUtils::scaled (kContentMarginY); + if (targetOffset < 0) targetOffset= 0; + topSpacer_->changeSize (0, targetOffset, QSizePolicy::Minimum, + QSizePolicy::Fixed); + if (layout ()) { + layout ()->invalidate (); + layout ()->activate (); + } +} + void ChatConversationPanel::focusInput () { url inBufUrl= ChatSessionManager::inputBufferUrl (sessionId_); diff --git a/src/Plugins/Qt/qt_chat_tab_widget.hpp b/src/Plugins/Qt/qt_chat_tab_widget.hpp index 0533a71307..ba142870b7 100644 --- a/src/Plugins/Qt/qt_chat_tab_widget.hpp +++ b/src/Plugins/Qt/qt_chat_tab_widget.hpp @@ -114,6 +114,8 @@ class ChatConversationPanel : public QWidget { protected: /// 事件过滤器:拦截 Enter 键触发发送 bool eventFilter (QObject* watched, QEvent* event) override; + /// 欢迎模式下按容器高度比例调整顶部偏移 + void resizeEvent (QResizeEvent* event) override; private: /// 构建面板 UI 布局 diff --git a/tests/Plugins/Qt/qt_chat_tab_widget_test.cpp b/tests/Plugins/Qt/qt_chat_tab_widget_test.cpp index c7443eebce..db0b8e7228 100644 --- a/tests/Plugins/Qt/qt_chat_tab_widget_test.cpp +++ b/tests/Plugins/Qt/qt_chat_tab_widget_test.cpp @@ -9,12 +9,14 @@ #include "Qt/qt_utilities.hpp" #include "base.hpp" #include +#include #include #include #include #include #include #include +#include #include using namespace moebius;