fix: 统一输入框编辑行为、外观跨视图同步并优化标尺与网格可读性

输入框(TextEdit,全组件共用):
- 修复退格一次删除两个字符:WM_CHAR 与 WM_KEYDOWN 重复处理了退格/删除,
  现统一由 OnEditKey 处理,WM_CHAR 忽略全部控制字符。
- 进入编辑(整段选中)后按右键:取消全选并把插入符移到文本最右侧。
- 存在选区时松开 Shift 再按左右方向键,插入符收拢到选区的左/右端。
- 修复整段选中后按 Home/End 不收拢选区的问题。

标签页:
- 按加粗字体测量宽度,避免激活页长名称被提前省略(预览过短)。
- 重命名过程中按正在输入的文本测量并加宽上限(320px),标签随输入增长,
  解决长内容绘制错误;输入溢出时整体等比收缩,保证「+」始终可见。

信号显示外观跨标签页同步:
- 外观(颜色/自定义色记忆/透明度/线宽/样式)从 TabConfig 上移到 DataSet
  的 SignalData,成为信号的全局属性;可见性仍按标签页独立保存。
- 因此任一视图中修改某信号的颜色/线宽/透明度/样式,其它视图自动一致。

工具栏:
- 移除「全选信号」按钮;「清空勾选」改为「清空网格」,
  「拆分信号网格」改为「拆分网格」。

自适应:
- 无可见信号(未勾选/全部取消/清空网格)时复位 ViewInitialized,
  下次勾选第一个信号会重新自适应,而不是停留在旧视图范围。

绘图区标尺与网格:
- 幅值刻度密度按网格高度自适应(3-8 档),各网格刻度数量一致;
  网格较矮时同步收紧标签行距,提高可读密度。
- 网格线与五等分估读线加深,与网格边界(蓝灰)保持明显区分。
- 时间轴列名补上单位,显示为 time (s)。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-09-30 15:16:53 +08:00
1 parent 53768b8b25
commit e3744f3eff
16 files changed
+462 -340

No files matched your search

@@ -182,6 +182,26 @@ namespace jdi
*/
bool OnEditKey(WPARAM key);
/**
* @brief 编辑态下按下鼠标右键:取消选区并把插入符移到文本末尾。
* @return 发生改变返回 true;不在编辑态返回 false。
*/
bool CollapseEditSelectionToEnd();
/**
* @brief 获取指定信号的显示外观(只读,全局共享)。
* @param [IN] ref 信号引用。
* @return 外观指针,引用无效时返回 nullptr。
*/
const SignalStyle* StyleOf(const SignalRef& ref) const;
/**
* @brief 获取指定信号的显示外观(可写,全局共享)。
* @param [IN] ref 信号引用。
* @return 外观指针,引用无效时返回 nullptr。
*/
SignalStyle* StyleOf(const SignalRef& ref);
/**
* @brief 将浮动拾色器的取色结果应用到当前选中信号。
* @param [IN] color 0xAARRGGBB 颜色值。
+46 -31
View File
@@ -708,7 +708,7 @@ namespace jdi
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
const SignalRef ref{item.fileIndex, item.signalIndex};
const SignalData* signal = m_dataSet->Resolve(ref);
const SignalStyle* style = m_tab->Style(ref);
const SignalStyle* style = StyleOf(ref);
if (signal == nullptr || style == nullptr)
{
return;
@@ -734,18 +734,20 @@ namespace jdi
// 复选框。
const gd::RectF checkRect(baseX + kDisclosureWidth * m_scale, centerY - kCheckSize * m_scale * 0.5f,
kCheckSize * m_scale, kCheckSize * m_scale);
UiDrawCheckBox(g, checkRect, style->visible ? 1 : 0, 3.0f * m_scale);
// 可见性属于当前标签页,与全局共享的显示外观分开读取。
const bool visible = m_tab->IsVisible(ref);
UiDrawCheckBox(g, checkRect, visible ? 1 : 0, 3.0f * m_scale);
// 颜色块:叠加不透明度,透明度过低时自动补描边(与样式区色块规则一致)。
const gd::RectF swatchRect(checkRect.X + checkRect.Width + 6.0f * m_scale,
centerY - kSwatchSize * m_scale * 0.5f, kSwatchSize * m_scale,
kSwatchSize * m_scale);
UiDrawSwatch(g, swatchRect, style->color, style->visible ? style->opacity : 0.3f, 2.5f * m_scale,
UiDrawSwatch(g, swatchRect, style->color, visible ? style->opacity : 0.3f, 2.5f * m_scale,
m_scale);
// 网格徽标(可点击,用于调整所属网格)。
const int laneIndex = m_tab->LaneOfSignal(ref);
const bool badgeVisible = (laneIndex >= 0) && style->visible;
const bool badgeVisible = (laneIndex >= 0) && visible;
const gd::RectF badgeRect = SignalBadgeRect(row);
float nameRightLimit = rect.X + rect.Width - kPadding * m_scale;
@@ -763,7 +765,7 @@ namespace jdi
else
{
const float nameWidth = std::max(16.0f, nameRightLimit - nameLeft);
const std::uint32_t nameColor = style->visible ? m_theme->text : m_theme->textMuted;
const std::uint32_t nameColor = visible ? m_theme->text : m_theme->textMuted;
UiText(g, UiEllipsize(g, signal->Title(), m_fonts->ui, nameWidth), m_fonts->ui,
gd::RectF(nameLeft, rect.Y, nameWidth, rect.Height), nameColor, TextAlign::LEFT);
}
@@ -816,7 +818,7 @@ namespace jdi
UiFillRect(g, m_styleRect, m_theme->panelBg);
const SignalStyle* style = (m_tab == nullptr) ? nullptr : m_tab->Style(m_tab->Selected());
const SignalStyle* style = (m_tab == nullptr) ? nullptr : StyleOf(m_tab->Selected());
if (style == nullptr || m_tab->Selected().IsValid() == false)
{
UiText(g, L"请选择信号以编辑样式", m_fonts->uiSmall, m_styleRect, m_theme->textMuted,
@@ -962,7 +964,7 @@ namespace jdi
{
return false;
}
SignalStyle* style = m_tab->Style(m_tab->Selected());
SignalStyle* style = StyleOf(m_tab->Selected());
if (style == nullptr)
{
return false;
@@ -1041,7 +1043,7 @@ namespace jdi
{
case StyleEditField::WIDTH:
{
const SignalStyle* style = m_tab->Style(m_tab->Selected());
const SignalStyle* style = StyleOf(m_tab->Selected());
if (style == nullptr)
{
return false;
@@ -1051,7 +1053,7 @@ namespace jdi
}
case StyleEditField::OPACITY:
{
const SignalStyle* style = m_tab->Style(m_tab->Selected());
const SignalStyle* style = StyleOf(m_tab->Selected());
if (style == nullptr)
{
return false;
@@ -1138,7 +1140,7 @@ namespace jdi
{
break;
}
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
if (SignalStyle* style = StyleOf(m_tab->Selected()))
{
const float value = ClampFloat(static_cast<float>(parsed), kWidthMin, kWidthMax);
style->lineWidth = std::round(value * 10.0f) / 10.0f;
@@ -1157,7 +1159,7 @@ namespace jdi
{
break;
}
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
if (SignalStyle* style = StyleOf(m_tab->Selected()))
{
style->opacity = ClampFloat(static_cast<float>(parsed) / 100.0f, 0.0f, 1.0f);
}
@@ -1233,20 +1235,14 @@ namespace jdi
{
return EndEdit(true);
}
if (character == L'\b')
{
return m_edit.Backspace();
}
if (character == 0x7F)
{
return m_edit.DeleteForward();
}
// 百分比字段允许直接输入 % 但会被忽略,其余控制字符一律丢弃。
if (m_editField == StyleEditField::OPACITY && character == L'%')
// 退格/删除已由 WM_KEYDOWN 的 OnEditKey 处理:这里必须忽略控制字符,
// 否则同一次按键会被处理两次(一次删除两个字符)。
if (character < 0x20 || character == 0x7F)
{
return false;
}
if (character < 0x20)
// 百分比字段允许直接输入 % 但会被忽略,其余控制字符一律丢弃。
if (m_editField == StyleEditField::OPACITY && character == L'%')
{
return false;
}
@@ -1294,13 +1290,32 @@ namespace jdi
return true;
}
bool SignalPanel::CollapseEditSelectionToEnd()
{
if (m_editField == StyleEditField::NONE)
{
return false;
}
return m_edit.DeselectToEnd();
}
const SignalStyle* SignalPanel::StyleOf(const SignalRef& ref) const
{
return (m_dataSet != nullptr) ? m_dataSet->Style(ref) : nullptr;
}
SignalStyle* SignalPanel::StyleOf(const SignalRef& ref)
{
return (m_dataSetWritable != nullptr) ? m_dataSetWritable->Style(ref) : nullptr;
}
bool SignalPanel::ApplyColor(std::uint32_t color)
{
if (m_tab == nullptr)
{
return false;
}
SignalStyle* style = m_tab->Style(m_tab->Selected());
SignalStyle* style = StyleOf(m_tab->Selected());
if (style == nullptr)
{
return false;
@@ -1319,7 +1334,7 @@ namespace jdi
{
return false;
}
SignalStyle* style = m_tab->Style(m_tab->Selected());
SignalStyle* style = StyleOf(m_tab->Selected());
if (style == nullptr)
{
return false;
@@ -1400,7 +1415,7 @@ namespace jdi
{
hoverStyle = StyleHit::TOGGLE_ALL;
}
else if (m_tab != nullptr && m_tab->Style(m_tab->Selected()) != nullptr)
else if (m_tab != nullptr && StyleOf(m_tab->Selected()) != nullptr)
{
for (int index = 0; index < m_colorCount; ++index)
{
@@ -1513,7 +1528,7 @@ namespace jdi
}
// 样式编辑区。
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
if (SignalStyle* style = StyleOf(m_tab->Selected()))
{
int paletteCount = 0;
const std::uint32_t* palette = SignalPalette(paletteCount);
@@ -1641,7 +1656,7 @@ namespace jdi
const float fy = static_cast<float>(y);
if (UiPointInRect(m_customChipRect, fx, fy) && m_tab != nullptr)
{
if (const SignalStyle* style = m_tab->Style(m_tab->Selected()))
if (const SignalStyle* style = StyleOf(m_tab->Selected()))
{
changed = OpenColorPickerPopup(*style) || changed;
}
@@ -1789,7 +1804,7 @@ namespace jdi
return;
}
const SignalStyle* style = m_tab->Style(ref);
const SignalStyle* style = StyleOf(ref);
const SignalData* signal = m_dataSet->Resolve(ref);
if (style == nullptr || signal == nullptr)
{
@@ -1807,7 +1822,7 @@ namespace jdi
items.push_back(header);
MenuItem toggle;
toggle.label = style->visible ? L"隐藏该信号" : L"显示该信号";
toggle.label = m_tab->IsVisible(ref) ? L"隐藏该信号" : L"显示该信号";
toggle.id = CMD_SIGNAL_TOGGLE;
items.push_back(toggle);
@@ -2038,7 +2053,7 @@ namespace jdi
}
else if (result.id == CMD_COLOR_BASE && result.swatchIndex >= 0)
{
if (SignalStyle* style = m_tab->Style(m_menuTargetSignal))
if (SignalStyle* style = StyleOf(m_menuTargetSignal))
{
int paletteCount = 0;
const std::uint32_t* palette = SignalPalette(paletteCount);
@@ -2047,7 +2062,7 @@ namespace jdi
}
else if (result.id >= CMD_MODE_BASE && result.id < CMD_MODE_BASE + kModeOptionCount)
{
if (SignalStyle* style = m_tab->Style(m_menuTargetSignal))
if (SignalStyle* style = StyleOf(m_menuTargetSignal))
{
style->mode = kModeOptions[result.id - CMD_MODE_BASE];
}