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:
1 parent
53768b8b25
commit
e3744f3eff
16 files changed
+462
-340
No files matched your search
@@ -12,8 +12,13 @@ namespace jdi
|
||||
namespace
|
||||
{
|
||||
const int kTimeTickTarget = 10; /**< 时间轴期望刻度数。 */
|
||||
const int kYTickTarget = 6; /**< 幅值轴期望刻度数。 */
|
||||
const int kYTickTarget = 6; /**< 幅值轴期望刻度数(网格较高时)。 */
|
||||
const int kYTickTargetMin = 3; /**< 幅值轴最少刻度数(网格很矮时也要保证可读数)。 */
|
||||
const int kYTickTargetMax = 8; /**< 幅值轴最多刻度数。 */
|
||||
const float kYTickSpacing = 28.0f; /**< 幅值刻度之间的理想间距(逻辑像素)。 */
|
||||
const float kYLabelMinGap = 15.0f; /**< 幅值标签的最小行距(逻辑像素)。 */
|
||||
const int kMinorDivisions = 5; /**< 相邻主刻度之间的等分数(用于估读)。 */
|
||||
const unsigned int kMinorLineAlpha = 0x9Au; /**< 辅助等分线的透明度(越高越清晰)。 */
|
||||
const float kScrollBarThickness = 9.0f; /**< 滚动条宽度(逻辑像素)。 */
|
||||
const float kLanePadding = 8.0f; /**< 网格内边距。 */
|
||||
const size_t kMaxPolylinePoints = 20000; /**< 单条折线最大顶点数。 */
|
||||
@@ -162,10 +167,8 @@ namespace jdi
|
||||
}
|
||||
result.data = m_dataSet->Resolve(ref);
|
||||
result.file = m_dataSet->FileAt(ref.fileIndex);
|
||||
if (m_tab != nullptr)
|
||||
{
|
||||
result.style = m_tab->Style(ref);
|
||||
}
|
||||
result.style = m_dataSet->Style(ref);
|
||||
result.visible = (m_tab != nullptr) && m_tab->IsVisible(ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -229,8 +232,8 @@ namespace jdi
|
||||
int firstFile = -1;
|
||||
for (const SignalRef& ref : m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].signals)
|
||||
{
|
||||
const SignalStyle* style = m_tab->Style(ref);
|
||||
if (style == nullptr || !style->visible)
|
||||
const SignalStyle* style = m_dataSet->Style(ref);
|
||||
if (style == nullptr || !m_tab->IsVisible(ref))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -888,7 +891,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1157,21 +1160,23 @@ namespace jdi
|
||||
{
|
||||
return EndEdit(true);
|
||||
}
|
||||
if (character == L'\b')
|
||||
{
|
||||
return m_edit.Backspace();
|
||||
}
|
||||
if (character == 0x7F)
|
||||
{
|
||||
return m_edit.DeleteForward();
|
||||
}
|
||||
if (character < 0x20)
|
||||
// 退格/删除已由 WM_KEYDOWN 的 OnEditKey 处理,此处忽略控制字符避免重复删除。
|
||||
if (character < 0x20 || character == 0x7F)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_edit.Insert(character);
|
||||
}
|
||||
|
||||
bool PlotView::CollapseEditSelectionToEnd()
|
||||
{
|
||||
if (m_editTarget == PlotEditTarget::NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_edit.DeselectToEnd();
|
||||
}
|
||||
|
||||
bool PlotView::OnEditKey(WPARAM key)
|
||||
{
|
||||
if (m_editTarget == PlotEditTarget::NONE)
|
||||
@@ -1893,7 +1898,11 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 幅值网格线(先画线,标尺稍后作为覆盖层绘制,避免被波形遮挡)。
|
||||
const double yStep = NiceStep(lane.yMax - lane.yMin, kYTickTarget);
|
||||
// 刻度密度随网格高度自适应:所有网格高度相同 → 各网格的刻度数量一致,
|
||||
// 不会出现“有的网格只有 1 条标签、有的有 3 条”的不一致观感。
|
||||
int yTickTarget = static_cast<int>(std::lround(lane.rect.Height / (kYTickSpacing * m_scale)));
|
||||
yTickTarget = std::max(kYTickTargetMin, std::min(yTickTarget, kYTickTargetMax));
|
||||
const double yStep = NiceStep(lane.yMax - lane.yMin, yTickTarget);
|
||||
std::vector<double> yTicks;
|
||||
BuildTicks(lane.yMin, lane.yMax, yStep, yTicks);
|
||||
|
||||
@@ -1916,7 +1925,7 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
UiDrawLine(g, lane.rect.X, y, lane.rect.X + lane.rect.Width, y,
|
||||
WithAlpha(m_theme->gridWeak, 0x70), 1.0f);
|
||||
WithAlpha(m_theme->gridWeak, kMinorLineAlpha), 1.0f);
|
||||
}
|
||||
|
||||
for (const double tick : yTicks)
|
||||
@@ -1952,7 +1961,7 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
UiDrawLine(g, x, lane.rect.Y, x, lane.rect.Y + lane.rect.Height,
|
||||
WithAlpha(m_theme->gridWeak, 0x70), 1.0f);
|
||||
WithAlpha(m_theme->gridWeak, kMinorLineAlpha), 1.0f);
|
||||
}
|
||||
|
||||
for (const double tick : timeTicks)
|
||||
@@ -1983,7 +1992,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2074,6 +2083,11 @@ namespace jdi
|
||||
const bool showLabels = (lane.rect.Height >= 34.0f * m_scale);
|
||||
float lastLabelY = -1.0e9f;
|
||||
|
||||
// 网格较矮时同步收紧标签行距,让密度随可用高度提升而不是整片被过滤掉。
|
||||
const float labelMinGap =
|
||||
std::min(kYLabelMinGap * m_scale,
|
||||
lane.rect.Height / static_cast<float>(yTickTarget + 1));
|
||||
|
||||
for (const double tick : yTicks)
|
||||
{
|
||||
if (!showLabels)
|
||||
@@ -2086,7 +2100,7 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (std::fabs(y - lastLabelY) < 15.0f * m_scale)
|
||||
if (std::fabs(y - lastLabelY) < labelMinGap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2150,7 +2164,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2223,7 +2237,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2266,7 +2280,7 @@ namespace jdi
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.style->visible)
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2370,7 +2384,7 @@ namespace jdi
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || resolved.file == nullptr ||
|
||||
!resolved.style->visible)
|
||||
!resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2455,6 +2469,12 @@ namespace jdi
|
||||
if (file != nullptr && !file->timeColumnName.empty())
|
||||
{
|
||||
axisName = file->timeColumnName;
|
||||
// CSV 中该列通常直接叫 "time",这里补上时间单位秒便于阅读;
|
||||
// 若列名自带单位描述(以右括号结尾)则不重复追加。
|
||||
if (axisName.back() != L')')
|
||||
{
|
||||
axisName += L" (s)";
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2532,7 +2552,7 @@ namespace jdi
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || resolved.style == nullptr || resolved.file == nullptr ||
|
||||
!resolved.style->visible)
|
||||
!resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user