重构(UI): 统一组件协议 + 去手写链路 + 大文件按职责拆分 + 代码审查 + 剥离 Tools
## 1. 新增 UiComponent 顶层组件协议(不是负优化)
Components/Shared/Inc/UiComponent.hpp:纯接口、无状态,8 个方法
SetScale / Layout / Area / Paint / HitTest / OnMouseLeave / IsEditing / QueryCursor。
Toolbar / TabBar / SignalPanel / PlotView 本来就各自拥有完全同名同签名的成员,
因此改造 = 加 override + 补 Area()(PlotView 另加一个非 const 的 QueryCursor 转发,
因为它自己的查询会惰性建立行缓存),实现代码零改动、静态派发、体积无增长。
刻意不把 UiWidget 当作它的基类:UiWidget 是"树节点"(父给尺寸、父下探脏区、
命中沿树链派发),而 PlotView(4 层位图缓存)/ SignalPanel(行位图缓存)是
"地盘宿主"。强行上树会推翻"父脏则子全脏"的前提,历史上正是这类耦合导致过
"后台渲染覆盖平移结果""命中矩形错位"的事故。头文件里已写明这条理由。
Toolbar/TabBar 是两者的交集:对外实现 UiComponent,对内持有一棵 UiWidget 树。
## 2. 宿主侧:四条手写链 -> 组件表
Application 新增 ComponentEntry{ component, target, chrome } 与 m_components
(底->顶:plot -> panel -> toolbar -> tabBar),BuildComponentTable() 装配一次。
- ApplyDpi 6 行逐个 SetScale -> 1 个循环
- Relayout 手算三列 x 坐标 -> UiStackRects(内容区, HORIZONTAL, {panelW, splitW, AUTO})
- WM_MOUSELEAVE 逐个 OnMouseLeave -> 循环累积 chromeChanged / plotChanged
- WM_SETCURSOR if/else if 链 -> 从表尾向前找首个命中者,取其 QueryCursor
顺带兑现历史缺口:UiLayout::UiStackRects 自引入以来第一次有了生产调用点。
有意保留显式的三条链(WM_LBUTTONDOWN / WM_LBUTTONUP / WM_MOUSEMOVE):它们各自带
组件专属后置动作(提交重命名、构建派生信号、面板联动滚动、更新拖拽光标、
plotOnlyMove 决定局部失效还是整帧失效)。塞进表里只能靠 lambda 字段,
可读性反而下降 —— 属于用户明确禁止的"过度抽象",因此不做。
## 3. 大文件按职责拆分(纯整函数搬移,逐像素 0 差异)
SignalPanelInteraction.cpp 2329 -> 831 行,拆出:
SignalPanelEdit.cpp 616 内联编辑(筛选框、组名/信号名、数值、颜色与透明度)
SignalPanelScroll.cpp 205 动画与滚动(折起、联动滚动、滚动到目标信号)
SignalPanelMenu.cpp 527 弹出菜单(系数/偏移、镜像、右键菜单与命令回执)
Shared/PopupMenu.cpp 1901 -> 706 行,拆出:
PopupMenuLayout.cpp 753 尺寸测量、项矩形排版、滚动可见性
PopupMenuPaint.cpp 397 背景/阴影/项绘制
Inc/PopupMenuInternal.hpp 103 pmdetail 常量 + inline 辅助(跨 TU 安全)
拆分判据是"职责"而非行数;共享常量一律收敛到 *Internal.hpp。
## 4. 代码审查(修 5 处)
- SignalPanelInteraction.cpp 头注释仍写"4 片",实际 6 片 -> 改为完整清单
- PopupMenuLayout.cpp 机械搬移时把构造函数一起搬走 -> 重复定义,只保留在交互分片
- PopupMenu.cpp 的 #include "PopupMenuInternal.hpp" 落进 namespace jdi 内 -> 移到文件头
- PopupMenuPaint.cpp 缺 UiControls.hpp(UiDrawEditFieldContent 未声明)-> 补 include
- 全仓复查历史删除符号残留引用(m_paintPlot / ClampCursorsAfterPan /
PaintFreeMarkerTooltip / CaptureDecorCache / FitSinglePointSignal / CsvTable /
DataFile / .preflight / structcheck)-> 零命中
## 5. Tools/ 剥离
git rm -r --cached Tools + .gitignore 新增 Tools/(本地文件保留,可随时复跑)。
ui_refactor 分支为全本地分支,另做 filter-branch 清除历史中的该目录。
## 6. 验证
- make release / make debug:零 error 零 warning
- rendercheck:A-H 八个场景哈希与改造前逐一相同
(A 2166933039626F66 B C444ECFEB18FD73D C 7984446D8E7A4418 D BF855879CA12F289
E 1C663E4118616016 F 0535292D8E07AE18 G 7A58172C46577729 H 23F390DD76141113)
FUNC 7/7 PASS
- uicheck:69 passed, 0 failed
- TargetCheck.exe 891,392 B(< 900 KB 阈值)
## 7. 文档
Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md
新增第五阶段章节;Documents/agent_mem.md 同步变更日志与「第七轮」硬约束。
This commit is contained in:
1 parent
675c2a5f54
commit
0ec8055e95
20 files changed
+3287
-2678
No files matched your search
@@ -0,0 +1,585 @@
|
||||
// SignalPanel 实现分片 —— 弹出菜单(系数/偏移、镜像、信号/分组/网格右键菜单、命令回执)。
|
||||
//
|
||||
// 本文件只包含 SignalPanel 的成员函数定义(机械搬移,行为不变)。
|
||||
// ⚠️ 菜单是"打开即模态"的浮层:`CloseTransientUi` 必须在点击别处时收起它,
|
||||
// 否则菜单会一直挂在那里吃掉后续点击。
|
||||
|
||||
#include "SignalPanelInternal.hpp"
|
||||
|
||||
namespace jdi
|
||||
{
|
||||
bool SignalPanel::OpenGainMenu()
|
||||
{
|
||||
if (m_menu == nullptr || m_tab == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const SignalStyle* style = StyleOf(SelectedRef());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 二级面板:两张“标题(带还原图标)+ 输入框”的组合:
|
||||
// 1. 「系数调整」:一整行系数输入框;
|
||||
// 2. 「偏移调整」:下方两行分别为「Y轴」与「时间轴」的偏移输入框。
|
||||
//(与网格 Y 轴上下限的菜单内联编辑同一套做法:提交后面板保持打开,可连续微调。)
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
MenuItem gainHeader;
|
||||
gainHeader.kind = MenuItemKind::HEADER_ACTION;
|
||||
gainHeader.label = L"系数调整";
|
||||
gainHeader.id = CMD_GAIN_RESET; // 点击标题栏右侧的还原图标 → 系数重置为 1
|
||||
items.push_back(gainHeader);
|
||||
|
||||
// 非编辑态带正号(用户明确要求);进入编辑后的初始文本用不带符号的纯数值。
|
||||
const std::wstring gainText = FormatGainText(style->gain);
|
||||
const std::wstring gainEditText = FormatNumberText(static_cast<double>(style->gain));
|
||||
MenuItem gainEdit;
|
||||
gainEdit.kind = MenuItemKind::EDIT;
|
||||
gainEdit.label.clear(); // 不显示说明文字:输入框独占整行
|
||||
gainEdit.id = CMD_GAIN_EDIT;
|
||||
gainEdit.value = gainText;
|
||||
gainEdit.editValue = gainEditText;
|
||||
gainEdit.filter = EditFilter::NUMBER;
|
||||
items.push_back(gainEdit);
|
||||
|
||||
MenuItem offsetHeader;
|
||||
offsetHeader.kind = MenuItemKind::HEADER_ACTION;
|
||||
offsetHeader.label = L"偏移调整";
|
||||
offsetHeader.id = CMD_OFFSET_RESET; // 点击还原图标 → 两个偏移都恢复默认值
|
||||
items.push_back(offsetHeader);
|
||||
|
||||
// 「Y轴」:对绘制值加上一个常数偏移(波形上下移动),非编辑态带正负号。
|
||||
MenuItem offsetY;
|
||||
offsetY.kind = MenuItemKind::EDIT;
|
||||
offsetY.label = L"Y轴";
|
||||
offsetY.id = CMD_OFFSET_Y;
|
||||
offsetY.value = FormatSignedNumber(static_cast<double>(style->offsetY));
|
||||
offsetY.editValue = FormatNumberText(static_cast<double>(style->offsetY));
|
||||
offsetY.filter = EditFilter::NUMBER;
|
||||
items.push_back(offsetY);
|
||||
|
||||
// 「时间轴」:把采样沿时间轴平移(画在 t + offsetTime)。
|
||||
// 非编辑态显示与双光标差值同一套复合单位文本,并**带正负号**(用户明确要求);
|
||||
// 编辑态显示以秒为单位的数值,并在输入框最右侧提示单位 `s`。
|
||||
MenuItem offsetTime;
|
||||
offsetTime.kind = MenuItemKind::EDIT;
|
||||
offsetTime.label = L"时间轴";
|
||||
offsetTime.id = CMD_OFFSET_TIME;
|
||||
offsetTime.value = FormatOffsetTimeText(style->offsetTime);
|
||||
offsetTime.editValue = FormatNumberText(style->offsetTime);
|
||||
offsetTime.editSuffix = L"s";
|
||||
offsetTime.filter = EditFilter::NUMBER;
|
||||
items.push_back(offsetTime);
|
||||
|
||||
// 锚定在按钮**左上角的上方一点**:二级面板**向上展开**,其左下角对准按钮左上角,
|
||||
// 并再往上让出 `kAdjustMenuGap`(用户明确要求)。
|
||||
//(旧实现在按钮下沿向右下展开,面板会盖住下方内容。)
|
||||
const gd::RectF& button = m_adjustRects[kAdjustGain];
|
||||
const gd::RectF bounds = (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area;
|
||||
const int anchorY =
|
||||
static_cast<int>(std::lround(button.Y - kAdjustMenuGap * m_scale));
|
||||
m_menu->Open(MenuScope::SIGNAL_PANEL, bounds, static_cast<int>(button.X), anchorY, items,
|
||||
true);
|
||||
// ⚠️ 菜单一旦打开就会接管鼠标事件,样式区不会再收到 WM_MOUSEMOVE →
|
||||
// 必须在这里主动清掉按钮的悬停态,否则按钮会"像鼠标一直按着"一样持续高亮。
|
||||
ClearStyleHover();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SignalPanel::OpenMirrorMenu()
|
||||
{
|
||||
if (m_menu == nullptr || m_tab == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const SignalStyle* style = StyleOf(SelectedRef());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 两行带复选框的选项:点击即切换、且**保持菜单打开**(可连续勾选两项)。
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
MenuItem horizontal;
|
||||
horizontal.kind = MenuItemKind::NORMAL;
|
||||
horizontal.label = L"水平镜像";
|
||||
horizontal.id = CMD_MIRROR_HORIZONTAL;
|
||||
horizontal.checked = style->mirrorHorizontal;
|
||||
horizontal.keepOpen = true;
|
||||
items.push_back(horizontal);
|
||||
|
||||
MenuItem vertical;
|
||||
vertical.kind = MenuItemKind::NORMAL;
|
||||
vertical.label = L"垂直镜像";
|
||||
vertical.id = CMD_MIRROR_VERTICAL;
|
||||
vertical.checked = style->mirrorVertical;
|
||||
vertical.keepOpen = true;
|
||||
items.push_back(vertical);
|
||||
|
||||
const gd::RectF& button = m_adjustRects[kAdjustMirror];
|
||||
const gd::RectF bounds = (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area;
|
||||
// 与「系数/偏移」面板同一套锛定:向上展开,左下角 = 按钮左上角再上移一点。
|
||||
const int anchorY =
|
||||
static_cast<int>(std::lround(button.Y - kAdjustMenuGap * m_scale));
|
||||
m_menu->Open(MenuScope::SIGNAL_PANEL, bounds, static_cast<int>(button.X), anchorY, items,
|
||||
true);
|
||||
ClearStyleHover();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SignalPanel::ClearStyleHover()
|
||||
{
|
||||
if (m_hoverStyle == StyleHit::NONE && m_hoverStyleIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_hoverStyle = StyleHit::NONE;
|
||||
m_hoverStyleIndex = -1;
|
||||
}
|
||||
|
||||
bool SignalPanel::CloseTransientUi()
|
||||
{
|
||||
bool changed = false;
|
||||
if (m_editField != StyleEditField::NONE)
|
||||
{
|
||||
changed = EndEdit(true) || changed;
|
||||
}
|
||||
// 点击面板之外也要结束「筛选信号」的编辑状态。
|
||||
if (m_filterEditing)
|
||||
{
|
||||
changed = EndFilterEdit() || changed;
|
||||
}
|
||||
if (m_colorPicker != nullptr && m_colorPicker->IsOpen())
|
||||
{
|
||||
m_colorPicker->Close();
|
||||
changed = true;
|
||||
}
|
||||
// 面板外点击:取消尚未抬起确认的关闭操作。
|
||||
m_pendingClosePart = RowPart::NONE;
|
||||
m_pendingCloseRow = -1;
|
||||
m_pendingFilterClear = false;
|
||||
m_pendingGainMenu = false;
|
||||
m_pendingMirrorMenu = false;
|
||||
m_pendingTargetLaneMenu = false;
|
||||
return changed;
|
||||
}
|
||||
|
||||
void SignalPanel::OpenSignalMenu(const SignalRef& ref, int x, int y)
|
||||
{
|
||||
if (m_menu == nullptr || m_tab == nullptr || m_dataSet == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const SignalStyle* style = StyleOf(ref);
|
||||
const SignalData* signal = m_dataSet->Resolve(ref);
|
||||
if (style == nullptr || signal == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_menuTargetSignal = ref;
|
||||
m_menuTargetGroup = -1;
|
||||
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
MenuItem header;
|
||||
header.kind = MenuItemKind::HEADER;
|
||||
header.label = signal->name;
|
||||
items.push_back(header);
|
||||
|
||||
MenuItem toggle;
|
||||
toggle.label = m_tab->IsVisible(ref) ? L"隐藏该信号" : L"显示该信号";
|
||||
toggle.id = CMD_SIGNAL_TOGGLE;
|
||||
items.push_back(toggle);
|
||||
|
||||
MenuItem solo;
|
||||
solo.label = L"仅显示该信号";
|
||||
solo.id = CMD_SIGNAL_SOLO;
|
||||
items.push_back(solo);
|
||||
|
||||
MenuItem showAll;
|
||||
showAll.label = L"显示全部信号";
|
||||
showAll.id = CMD_SIGNAL_SHOW_ALL;
|
||||
items.push_back(showAll);
|
||||
|
||||
MenuItem hideAll;
|
||||
hideAll.label = L"隐藏全部信号";
|
||||
hideAll.id = CMD_SIGNAL_HIDE_ALL;
|
||||
items.push_back(hideAll);
|
||||
|
||||
MenuItem separator;
|
||||
separator.kind = MenuItemKind::SEPARATOR;
|
||||
items.push_back(separator);
|
||||
|
||||
int paletteCount = 0;
|
||||
const std::uint32_t* palette = SignalPalette(paletteCount);
|
||||
MenuItem colors;
|
||||
colors.kind = MenuItemKind::SWATCHES;
|
||||
colors.id = CMD_COLOR_BASE;
|
||||
colors.swatchCount = std::min(paletteCount, 16);
|
||||
colors.checkedSwatch = -1;
|
||||
for (int index = 0; index < colors.swatchCount; ++index)
|
||||
{
|
||||
colors.swatches[index] = palette[index];
|
||||
if (style->color == palette[index])
|
||||
{
|
||||
colors.checkedSwatch = index;
|
||||
}
|
||||
}
|
||||
items.push_back(colors);
|
||||
|
||||
MenuItem modeHeader;
|
||||
modeHeader.kind = MenuItemKind::HEADER;
|
||||
modeHeader.label = L"样式";
|
||||
items.push_back(modeHeader);
|
||||
|
||||
for (int index = 0; index < kModeOptionCount; ++index)
|
||||
{
|
||||
MenuItem mode;
|
||||
mode.label = DrawModeName(kModeOptions[index]);
|
||||
mode.id = CMD_MODE_BASE + index;
|
||||
mode.checked = (style->mode == kModeOptions[index]);
|
||||
items.push_back(mode);
|
||||
}
|
||||
|
||||
const gd::RectF bounds = (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area;
|
||||
m_menu->Open(MenuScope::SIGNAL_PANEL, bounds, x, y, items);
|
||||
}
|
||||
|
||||
void SignalPanel::OpenGroupMenu(int groupIndex, int x, int y)
|
||||
{
|
||||
if (m_menu == nullptr || m_tab == nullptr || m_dataSet == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const SignalGroup* group = m_dataSet->GroupAt(groupIndex);
|
||||
if (group == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_menuTargetGroup = groupIndex;
|
||||
m_menuTargetSignal = SignalRef();
|
||||
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
MenuItem header;
|
||||
header.kind = MenuItemKind::HEADER;
|
||||
header.label = group->name;
|
||||
items.push_back(header);
|
||||
|
||||
MenuItem showAll;
|
||||
showAll.label = L"选中该分组全部信号";
|
||||
showAll.id = CMD_GROUP_SHOW_ALL;
|
||||
items.push_back(showAll);
|
||||
|
||||
MenuItem hideAll;
|
||||
hideAll.label = L"取消该分组全部信号";
|
||||
hideAll.id = CMD_GROUP_HIDE_ALL;
|
||||
items.push_back(hideAll);
|
||||
|
||||
MenuItem separator;
|
||||
separator.kind = MenuItemKind::SEPARATOR;
|
||||
items.push_back(separator);
|
||||
|
||||
MenuItem remove;
|
||||
remove.label = L"关闭该分组(从工作区移除)";
|
||||
remove.id = CMD_GROUP_REMOVE;
|
||||
items.push_back(remove);
|
||||
|
||||
const gd::RectF bounds = (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area;
|
||||
m_menu->Open(MenuScope::SIGNAL_PANEL, bounds, x, y, items);
|
||||
}
|
||||
|
||||
void SignalPanel::OpenLaneMenu(const SignalRef& ref, int x, int y)
|
||||
{
|
||||
if (m_menu == nullptr || m_tab == nullptr || m_dataSet == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const SignalData* signal = m_dataSet->Resolve(ref);
|
||||
if (signal == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_menuTargetSignal = ref;
|
||||
m_menuTargetGroup = -1;
|
||||
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
MenuItem header;
|
||||
header.kind = MenuItemKind::HEADER;
|
||||
header.label = L"所属网格";
|
||||
items.push_back(header);
|
||||
|
||||
const std::vector<Lane>& lanes = m_tab->Lanes();
|
||||
const int currentLane = m_tab->LaneOfSignal(ref);
|
||||
for (std::size_t index = 0; index < lanes.size(); ++index)
|
||||
{
|
||||
MenuItem lane;
|
||||
lane.label = L"网格 " + std::to_wstring(index + 1) + L"(" +
|
||||
std::to_wstring(lanes[index].signals.size()) + L" 条信号)";
|
||||
lane.id = CMD_LANE_BASE + static_cast<int>(index);
|
||||
lane.checked = (currentLane == static_cast<int>(index));
|
||||
items.push_back(lane);
|
||||
}
|
||||
|
||||
MenuItem separator;
|
||||
separator.kind = MenuItemKind::SEPARATOR;
|
||||
items.push_back(separator);
|
||||
|
||||
MenuItem create;
|
||||
create.label = L"新建网格";
|
||||
create.id = CMD_LANE_NEW;
|
||||
items.push_back(create);
|
||||
|
||||
const gd::RectF bounds = (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area;
|
||||
// ⚠️ 网格可能是**上百个**(例如把 MF4 的全部信号各自放进独立网格):
|
||||
// 不限制高度时菜单会长到屏幕之外、且逐条绘制上百行会明显卡顿。
|
||||
// 这里限制可滚动条目区**最多显示 10 行**(用户 2026-10-16 要求):
|
||||
// 超出部分用滚轮 / 右侧滚动条翻动,而顶部「所属网格」标题与底部的
|
||||
// 「新建网格」**始终固定不动**。
|
||||
MenuLayoutConstraint constraint;
|
||||
constraint.maxScrollRows = kLaneMenuVisibleRows;
|
||||
// 额外加一道硬上限:窗口很矮时也不得超出客户区(与行数上限取更严格者)。
|
||||
constraint.maxHeight = std::max(180.0f, bounds.Height * 0.8f);
|
||||
m_menu->Open(MenuScope::SIGNAL_PANEL, bounds, x, y, items, false, constraint);
|
||||
}
|
||||
|
||||
bool SignalPanel::OnMenuCommand(const MenuResult& result)
|
||||
{
|
||||
if (m_tab == nullptr || m_dataSet == nullptr || !result.valid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 「系数」/「偏移」二级面板里的输入框提交:就地更新,面板保持打开
|
||||
//(与网格 Y 轴上下限的菜单内联编辑一致),并把夹取后的真实值回写到输入框上。
|
||||
if (result.editCommit)
|
||||
{
|
||||
SignalStyle* style = StyleOf(SelectedRef());
|
||||
if (style != nullptr)
|
||||
{
|
||||
const std::wstring trimmed = Trim(result.editText);
|
||||
wchar_t* stop = nullptr;
|
||||
const double parsed =
|
||||
trimmed.empty() ? 0.0 : std::wcstod(trimmed.c_str(), &stop);
|
||||
// 非法/空输入保持原值(只把真实值回写到输入框,避免用户看到错误的数字)。
|
||||
const bool valid = !trimmed.empty() && stop != trimmed.c_str() && std::isfinite(parsed);
|
||||
switch (result.id)
|
||||
{
|
||||
case CMD_GAIN_EDIT:
|
||||
{
|
||||
if (valid)
|
||||
{
|
||||
style->gain =
|
||||
static_cast<float>(std::max(-kGainLimit, std::min(parsed, kGainLimit)));
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_GAIN_EDIT, FormatGainText(style->gain),
|
||||
FormatNumberText(static_cast<double>(style->gain)));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case CMD_OFFSET_Y:
|
||||
{
|
||||
if (valid)
|
||||
{
|
||||
style->offsetY = static_cast<float>(
|
||||
std::max(-static_cast<double>(kGainLimit), std::min(parsed, kGainLimit)));
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(
|
||||
CMD_OFFSET_Y, FormatSignedNumber(static_cast<double>(style->offsetY)),
|
||||
FormatNumberText(static_cast<double>(style->offsetY)));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case CMD_OFFSET_TIME:
|
||||
{
|
||||
if (valid)
|
||||
{
|
||||
style->offsetTime =
|
||||
std::max(-kGainLimit, std::min(parsed, kGainLimit));
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_OFFSET_TIME,
|
||||
FormatOffsetTimeText(style->offsetTime),
|
||||
FormatNumberText(style->offsetTime));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 标题栏右侧的「还原」图标:系数重置为默认值 1(面板保持打开,输入框立即回显)。
|
||||
if (result.id == CMD_GAIN_RESET)
|
||||
{
|
||||
if (SignalStyle* style = StyleOf(SelectedRef()))
|
||||
{
|
||||
style->gain = 1.0f;
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_GAIN_EDIT, FormatGainText(style->gain),
|
||||
FormatNumberText(static_cast<double>(style->gain)));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 「偏移调整」的「还原」图标:Y 与时间偏移都恢复为默认值 0。
|
||||
if (result.id == CMD_OFFSET_RESET)
|
||||
{
|
||||
if (SignalStyle* style = StyleOf(SelectedRef()))
|
||||
{
|
||||
style->offsetY = 0.0f;
|
||||
style->offsetTime = 0.0;
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_OFFSET_Y, FormatSignedNumber(0.0),
|
||||
FormatNumberText(0.0));
|
||||
m_menu->UpdateItemValue(CMD_OFFSET_TIME, FormatOffsetTimeText(0.0),
|
||||
FormatNumberText(0.0));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 「镜像」二级面板:两行带复选框的切换项(面板保持打开,勾选状态就地回写)。
|
||||
if (result.id == CMD_MIRROR_HORIZONTAL || result.id == CMD_MIRROR_VERTICAL)
|
||||
{
|
||||
if (SignalStyle* style = StyleOf(SelectedRef()))
|
||||
{
|
||||
const bool horizontal = (result.id == CMD_MIRROR_HORIZONTAL);
|
||||
bool& flag = horizontal ? style->mirrorHorizontal : style->mirrorVertical;
|
||||
flag = !flag;
|
||||
if (!horizontal)
|
||||
{
|
||||
// ⚠️ Y 偏移以**未镜像空间**为准:垂直镜像开关变化时同步取反偏移量,
|
||||
// 使绘制等价于「先加上偏移再镜像」(用户明确要求)。
|
||||
style->offsetY = -style->offsetY;
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(
|
||||
CMD_OFFSET_Y, FormatSignedNumber(static_cast<double>(style->offsetY)),
|
||||
FormatNumberText(static_cast<double>(style->offsetY)));
|
||||
}
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemLabel(result.id, horizontal ? L"水平镜像" : L"垂直镜像", flag, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool needRelayout = false;
|
||||
|
||||
switch (result.id)
|
||||
{
|
||||
case CMD_SIGNAL_TOGGLE:
|
||||
if (m_menuTargetSignal.IsValid())
|
||||
{
|
||||
m_tab->ToggleVisible(m_menuTargetSignal);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
}
|
||||
break;
|
||||
case CMD_SIGNAL_SOLO:
|
||||
if (m_menuTargetSignal.IsValid())
|
||||
{
|
||||
m_tab->SoloSignal(m_menuTargetSignal);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
}
|
||||
break;
|
||||
case CMD_SIGNAL_SHOW_ALL:
|
||||
m_tab->SetAllVisible(true);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
break;
|
||||
case CMD_SIGNAL_HIDE_ALL:
|
||||
m_tab->SetAllVisible(false);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
break;
|
||||
case CMD_GROUP_SHOW_ALL:
|
||||
if (m_menuTargetGroup >= 0)
|
||||
{
|
||||
m_tab->SetGroupVisible(m_menuTargetGroup, true);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
}
|
||||
break;
|
||||
case CMD_GROUP_HIDE_ALL:
|
||||
if (m_menuTargetGroup >= 0)
|
||||
{
|
||||
m_tab->SetGroupVisible(m_menuTargetGroup, false);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
}
|
||||
break;
|
||||
case CMD_GROUP_REMOVE:
|
||||
if (m_menuTargetGroup >= 0 && m_host != nullptr)
|
||||
{
|
||||
PostMessageW(m_host, WM_APP + 1, static_cast<WPARAM>(m_menuTargetGroup), 0);
|
||||
}
|
||||
break;
|
||||
case CMD_LANE_NEW:
|
||||
if (m_menuTargetSignal.IsValid())
|
||||
{
|
||||
m_tab->MoveSignalToLane(*m_dataSet, m_menuTargetSignal, -1);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (result.id >= CMD_LANE_BASE && result.id < CMD_LANE_NEW)
|
||||
{
|
||||
if (m_menuTargetSignal.IsValid())
|
||||
{
|
||||
m_tab->MoveSignalToLane(*m_dataSet, m_menuTargetSignal, result.id - CMD_LANE_BASE);
|
||||
m_rowsDirty = true;
|
||||
needRelayout = true;
|
||||
}
|
||||
}
|
||||
else if (result.id == CMD_COLOR_BASE && result.swatchIndex >= 0)
|
||||
{
|
||||
if (SignalStyle* style = StyleOf(m_menuTargetSignal))
|
||||
{
|
||||
int paletteCount = 0;
|
||||
const std::uint32_t* palette = SignalPalette(paletteCount);
|
||||
style->color = palette[result.swatchIndex % std::max(1, paletteCount)];
|
||||
}
|
||||
}
|
||||
else if (result.id >= CMD_MODE_BASE && result.id < CMD_MODE_BASE + kModeOptionCount)
|
||||
{
|
||||
if (SignalStyle* style = StyleOf(m_menuTargetSignal))
|
||||
{
|
||||
style->mode = kModeOptions[result.id - CMD_MODE_BASE];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return needRelayout;
|
||||
}
|
||||
|
||||
} // namespace jdi
|
||||
Reference in new issue
Block a user