绘图区交互修正:悬停不改光标、图例联动、时间轴子刻度、边沿计数规则与光标区间联动
- 网格内信号悬停不再改变鼠标光标(维持原始箭头);只有真正按下拖动(MOVE_SIGNAL)
时才切换为抓握光标。
- 修复网格左上角图例的联动缺陷:
* 根因是图例的选中/悬停底色绘制在静态场景缓存里,而缓存指纹(SceneContentHash)
未包含「选中信号」→ 在左侧信号面板点选信号后缓存未失效,图例不会高亮。
现在把 SelectedRef() 折叠进指纹,选中变化会正确重绘。
* 图例悬停态随鼠标变化,不能进缓存:新增 m_hoverLegend 与 PaintLegendHighlight(),
在静态场景贴图之后用半透明强调色叠在本条目上形成“半高亮”,并在两条绘制路径
(缓存贴图快路径 / 整幅重绘路径)中都调用。
* 悬停图例时不再触发曲线拖拽提示(不画信号光晕、不弹“按住拖拽可移入其它网格”气泡),
仅半高亮;点选后参与联动。
- 主时间轴新增五等分子刻度线:在相邻主刻度之间插入 4 条短刻度(3px),
与主刻度重合的位置跳过。
- 「实验性功能 → 边沿计数」按用户明确的规则重做统计:
* 判定改为“穿越横向光标所在的 Y 值”:信号从下往上穿越 → 上升沿,
从上往下穿越 → 下降沿(原先按信号幅值中点判定,与光标无关,因此拖动光标
结果不变)。
* 缓存键随之扩展为「文件 + 信号 + 横向光标幅值 + 时间区间」,
拖动横向光标或拖动时间光标都会重算。
* 时间单调时二分定位采样区间,非单调时按区间过滤;NaN 仍视为断点。
* 面板宽度改为按文本实测宽度(EdgePanelWidth,使用不含省略号的
UiMeasureTextWidth)自适应,名称与“上升沿 N / 下降沿 M”一律完整显示,
数字不再被省略或截断。
* 点击 ✕ 现在同时关闭该网格的边沿计数功能(横向光标一并消失),
不再是仅收起面板。
- 边沿计数与光标功能联动:双光标模式下面板标题追加尾缀“(光标区间内)”,
统计只覆盖左右光标之间的时间区间;区间端点始终取两条光标时间的较小/较大值,
因此把任一光标拖到另一侧(左右交换)后区间与计数依然正确。
验证(TestData/VERY_BIG_DATA.CSV,编码器A相,125%、1725x996):
- 光标形态:悬停曲线 = ARROW(不改变);拖拽 = SIZEALL。
- 图例联动:在左侧面板点选信号后图例条目正确高亮;悬停图例显示半高亮、
无拖拽提示气泡。
- 时间轴五等分子刻度可见。
- 边沿计数(用独立脚本 .preflight/edgeprobe2.ps1 / edgeprobe3.ps1 按同一规则复算,
与界面显示逐位一致):
* 阈值 2.54415(全区间)→ 界面/复算均 上升沿 1333422 下降沿 1333421;
拖动横向光标到 5.08994 → 界面/复算均 上升沿 1843881 下降沿 1843881(计数随光标变化);
阈值 100 → 0 / 0。
* 双光标区间 [48.0211, 64.4051] → 界面/复算均 上升沿 193161 下降沿 193161;
区间 [64.4051, 79.8623] → 163647 / 163647。
* 光标交换:把光标 1 的时间改为 120(越过光标 2 的 79.8623)后,Δt 变为
40s 137.726ms,面板标题为“边沿计数(光标区间内)”,
计数 404668 / 404669 与复算的区间 [79.8623, 120] 完全一致。
- ✕:面板与横向光标同时消失。
- 回归:[1].CSV 双光标正常(Δt:151ms 200us),面板标题带尾缀;
make clean && make all 无警告无错误。
This commit is contained in:
1 parent
cd6e0ed058
commit
b884e7ecb4
2 files changed
+275
-39
No files matched your search
@@ -1348,15 +1348,18 @@ namespace jdi
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void PlotView::EdgeCountsFor(const DataFile& file, const SignalData& signal, std::size_t& rising,
|
||||
std::size_t& falling)
|
||||
void PlotView::EdgeCountsFor(const DataFile& file, const SignalData& signal, double edgeValue,
|
||||
bool hasRange, double rangeMin, double rangeMax, std::size_t& rising,
|
||||
std::size_t& falling) const
|
||||
{
|
||||
rising = 0;
|
||||
falling = 0;
|
||||
|
||||
// 缓存键必须包含横向光标幅值与时间区间:拖动光标或拖动时间光标都会改变统计结果。
|
||||
for (const EdgeCountEntry& entry : m_edgeCountCache)
|
||||
{
|
||||
if (entry.file == &file && entry.signal == &signal)
|
||||
if (entry.file == &file && entry.signal == &signal && entry.hasRange == hasRange &&
|
||||
entry.edgeValue == edgeValue && entry.rangeMin == rangeMin && entry.rangeMax == rangeMax)
|
||||
{
|
||||
rising = entry.rising;
|
||||
falling = entry.falling;
|
||||
@@ -1364,42 +1367,155 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 判定阈值取信号幅值中点:对数字/方波类信号等价于电平翻转,对模拟信号则是穿越中值。
|
||||
const double mid = (signal.minValue + signal.maxValue) * 0.5;
|
||||
bool hasPrevious = false;
|
||||
bool previousHigh = false;
|
||||
for (double value : signal.values)
|
||||
// 采样范围:时间单调时二分定位,否则按区间过滤(与波形绘制的处理一致)。
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = signal.values.size();
|
||||
if (hasRange && file.timeMonotonic)
|
||||
{
|
||||
const std::vector<double>& times = file.time;
|
||||
const std::size_t count = std::min(times.size(), signal.values.size());
|
||||
begin = static_cast<std::size_t>(
|
||||
std::lower_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(count),
|
||||
rangeMin) -
|
||||
times.begin());
|
||||
end = static_cast<std::size_t>(
|
||||
std::upper_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(count),
|
||||
rangeMax) -
|
||||
times.begin());
|
||||
if (begin > 0)
|
||||
{
|
||||
// 区间左侧补一个采样点:跨越区间边界的边沿也能被计入。
|
||||
--begin;
|
||||
}
|
||||
end = std::min(end, count);
|
||||
}
|
||||
else if (hasRange)
|
||||
{
|
||||
end = std::min(end, signal.values.size());
|
||||
}
|
||||
begin = std::min(begin, end);
|
||||
|
||||
// 边沿判定:以横向光标的幅值 edgeValue 为阈值。
|
||||
// 信号从下往上穿越 → 上升沿;从上往下穿越 → 下降沿。
|
||||
bool hasPrevious = false;
|
||||
double previous = 0.0;
|
||||
for (std::size_t index = begin; index < end; ++index)
|
||||
{
|
||||
const double value = signal.values[index];
|
||||
if (!std::isfinite(value))
|
||||
{
|
||||
// NaN 视为断点:不参与边沿判定,也不与前一点相连。
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
}
|
||||
const bool high = (value >= mid);
|
||||
if (hasPrevious && high != previousHigh)
|
||||
if (hasRange && !file.timeMonotonic && index < file.time.size())
|
||||
{
|
||||
if (high)
|
||||
const double time = file.time[index];
|
||||
if (time < rangeMin || time > rangeMax)
|
||||
{
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPrevious)
|
||||
{
|
||||
const double before = previous - edgeValue;
|
||||
const double after = value - edgeValue;
|
||||
if (before < 0.0 && after >= 0.0)
|
||||
{
|
||||
++rising;
|
||||
}
|
||||
else
|
||||
else if (before > 0.0 && after <= 0.0)
|
||||
{
|
||||
++falling;
|
||||
}
|
||||
}
|
||||
previousHigh = high;
|
||||
previous = value;
|
||||
hasPrevious = true;
|
||||
}
|
||||
|
||||
EdgeCountEntry entry;
|
||||
entry.file = &file;
|
||||
entry.signal = &signal;
|
||||
entry.edgeValue = edgeValue;
|
||||
entry.hasRange = hasRange;
|
||||
entry.rangeMin = rangeMin;
|
||||
entry.rangeMax = rangeMax;
|
||||
entry.rising = rising;
|
||||
entry.falling = falling;
|
||||
m_edgeCountCache.push_back(entry);
|
||||
}
|
||||
|
||||
float PlotView::EdgePanelWidth(int laneIndex) const
|
||||
{
|
||||
if (m_tab == nullptr || m_fonts == nullptr || laneIndex < 0 ||
|
||||
laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return kEdgePanelWidth * m_scale;
|
||||
}
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
|
||||
// 标题行 = “GX” 徽标 + “边沿计数(光标区间内)”。
|
||||
const std::wstring title = L"边沿计数" + EdgeRangeSuffix();
|
||||
float width = kEdgePanelPad * 2.0f * m_scale + kLaneBadgeWidth * m_scale +
|
||||
8.0f * m_scale + UiMeasureTextWidth(m_fonts->uiSmall, title) +
|
||||
kEdgePanelTitleHeight * m_scale;
|
||||
|
||||
// 数据行 = 名称与“上升沿 N / 下降沿 M”两行,取较宽者。
|
||||
// 用 UiMeasureTextWidth(无省略号)测量,保证数字完整显示。
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
if (resolved.data == nullptr || !resolved.visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float nameWidth = UiMeasureTextWidth(m_fonts->uiSmall, resolved.data->Title());
|
||||
std::size_t rising = 0;
|
||||
std::size_t falling = 0;
|
||||
EdgeCountsFor(*resolved.file, *resolved.data, lane.edgeValue, EdgeRangeActive(),
|
||||
EdgeRangeMin(), EdgeRangeMax(), rising, falling);
|
||||
const float countWidth =
|
||||
UiMeasureTextWidth(m_fonts->monoSmall,
|
||||
L"上升沿 " + std::to_wstring(rising) + L" 下降沿 " +
|
||||
std::to_wstring(falling));
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + std::max(nameWidth, countWidth));
|
||||
}
|
||||
|
||||
// 夹在绘图区宽度之内(过窄的网格也不至于把面板挤到外面)。
|
||||
return std::min(width, std::max(120.0f * m_scale, m_plotRect.Width - 8.0f * m_scale));
|
||||
}
|
||||
|
||||
bool PlotView::EdgeRangeActive() const
|
||||
{
|
||||
return m_tab != nullptr && Tab().CursorMode() == PlotCursorMode::DOUBLE;
|
||||
}
|
||||
|
||||
double PlotView::EdgeRangeMin() const
|
||||
{
|
||||
if (m_tab == nullptr)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
// 左右光标可能被拖动交换:始终取较小者为区间左端。
|
||||
return std::min(Tab().CursorTime(), Tab().CursorTimeAlt());
|
||||
}
|
||||
|
||||
double PlotView::EdgeRangeMax() const
|
||||
{
|
||||
if (m_tab == nullptr)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
return std::max(Tab().CursorTime(), Tab().CursorTimeAlt());
|
||||
}
|
||||
|
||||
std::wstring PlotView::EdgeRangeSuffix() const
|
||||
{
|
||||
return EdgeRangeActive() ? std::wstring(L"(光标区间内)") : std::wstring();
|
||||
}
|
||||
|
||||
bool PlotView::ToggleLaneAutoFitToSelection(int laneIndex)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
@@ -2348,11 +2464,9 @@ namespace jdi
|
||||
|
||||
if (UiPointInRect(m_plotRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
// 悬停命中可拖拽信号时给出"手掌"提示光标;按下拖拽过程中改用抓握光标。
|
||||
if (Tab().ZoomMode() == PlotZoomMode::NONE && HitTestSignal(x, y).IsValid())
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
// 悬停命中可拖拽信号时**不改变鼠标形状**(维持原始箭头);
|
||||
// 只有真正按下拖动(MOVE_SIGNAL)才切换为抓握光标。
|
||||
// 悬停图例时同样保持箭头,仅做半高亮提示。
|
||||
|
||||
// 选区放大模式下**未开始框选时保持默认箭头**(不改变鼠标形状);
|
||||
// 只有真正按下并拖动后才切成十字/方向箭头。
|
||||
@@ -2600,12 +2714,14 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 常规模式下精确检测鼠标是否贴近某条信号曲线,用于给出可拖拽提示。
|
||||
// 图例优先于曲线:图例条目本身就是信号的可点选目标(与左侧信号列表一致)。
|
||||
// 图例优先于曲线:图例条目是可点选目标(与左侧信号列表一致),
|
||||
// 但悬停图例时**只做半高亮提示**,不显示曲线拖拽提示(也不触发光晕)。
|
||||
SignalRef hoverSignal;
|
||||
SignalRef hoverLegend;
|
||||
if (Tab().ZoomMode() == PlotZoomMode::NONE && !m_cursorHover && !overEdgePanel)
|
||||
{
|
||||
hoverSignal = LegendAtPoint(x, y);
|
||||
if (!hoverSignal.IsValid())
|
||||
hoverLegend = LegendAtPoint(x, y);
|
||||
if (!hoverLegend.IsValid())
|
||||
{
|
||||
hoverSignal = HitTestSignal(x, y);
|
||||
}
|
||||
@@ -2615,6 +2731,11 @@ namespace jdi
|
||||
m_hoverSignal = hoverSignal;
|
||||
changed = true;
|
||||
}
|
||||
if (hoverLegend != m_hoverLegend)
|
||||
{
|
||||
m_hoverLegend = hoverLegend;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 双光标差值标签的悬停状态(用于区分"抓握/悬停"光标与普通状态)。
|
||||
const bool spanHover = IsOnCursorSpanLabel(x, y);
|
||||
@@ -2719,10 +2840,10 @@ namespace jdi
|
||||
}
|
||||
if (UiPointInRect(EdgePanelCloseRect(index), fx, fy))
|
||||
{
|
||||
// ✕ 只收起面板;横向光标仍保留(重新勾选菜单项可再次展开)。
|
||||
// ✕ 关闭面板的同时关闭该网格的边沿计数功能(横向光标一并消失)。
|
||||
if (m_tab != nullptr)
|
||||
{
|
||||
m_tab->Lanes()[static_cast<std::size_t>(index)].edgePanelOpen = false;
|
||||
SetLaneEdgeCount(index, false);
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
@@ -3336,6 +3457,7 @@ namespace jdi
|
||||
if (SceneCacheMatches() && BlitSceneCache(g))
|
||||
{
|
||||
PaintHoverHighlight(g);
|
||||
PaintLegendHighlight(g);
|
||||
PaintRubberBand(g);
|
||||
PaintCursor(g, m_lanes);
|
||||
PaintEdgeCursor(g, m_lanes);
|
||||
@@ -3382,6 +3504,7 @@ namespace jdi
|
||||
CaptureSceneCache(g);
|
||||
|
||||
PaintHoverHighlight(g);
|
||||
PaintLegendHighlight(g);
|
||||
PaintRubberBand(g);
|
||||
PaintCursor(g, m_lanes);
|
||||
PaintEdgeCursor(g, m_lanes);
|
||||
@@ -3479,6 +3602,40 @@ namespace jdi
|
||||
g.Restore(saveState);
|
||||
}
|
||||
|
||||
void PlotView::PaintLegendHighlight(gd::Graphics& g)
|
||||
{
|
||||
if (m_tab == nullptr || m_dragMode != PlotDragMode::NONE || !m_hoverLegend.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 图例条目的底色绘制在静态场景缓存里;悬停态随鼠标变化,不能进缓存,
|
||||
// 因此在贴图之后用半透明强调色在本条目范围内叠一层,形成“半高亮”提示。
|
||||
const int laneIndex = m_tab->LaneOfSignal(m_hoverLegend);
|
||||
if (laneIndex < 0 || laneIndex >= static_cast<int>(m_lanes.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const gd::RectF laneRect = m_lanes[static_cast<std::size_t>(laneIndex)].rect;
|
||||
if (laneRect.Y > m_plotRect.Y + m_plotRect.Height ||
|
||||
laneRect.Y + laneRect.Height < m_plotRect.Y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int saveState = g.Save();
|
||||
g.SetClip(m_area, gd::CombineModeIntersect);
|
||||
for (const LegendHit& hit : m_legendHits)
|
||||
{
|
||||
if (hit.ref == m_hoverLegend)
|
||||
{
|
||||
UiFillRoundRect(g, hit.rect, 3.0f * m_scale, WithAlpha(m_theme->accent, 0x38));
|
||||
break;
|
||||
}
|
||||
}
|
||||
g.Restore(saveState);
|
||||
}
|
||||
|
||||
PlotView::SceneCacheKey PlotView::MakeSceneCacheKey() const
|
||||
{
|
||||
SceneCacheKey key;
|
||||
@@ -3559,6 +3716,15 @@ namespace jdi
|
||||
mixFloat(m_laneContentHeight);
|
||||
mix(m_scrollBarVisible ? 1ull : 0ull);
|
||||
|
||||
// 选中信号会改变图例条目的底色与文字颜色(绘制在缓存的静态场景里),
|
||||
// 因此必须参与指纹:否则在左侧信号面板点选后图例不会高亮联动。
|
||||
{
|
||||
const SignalRef selected = SelectedRef();
|
||||
mix(selected.IsValid() ? 1ull : 0ull);
|
||||
mix(static_cast<std::uint64_t>(selected.fileIndex));
|
||||
mix(static_cast<std::uint64_t>(selected.signalIndex));
|
||||
}
|
||||
|
||||
for (std::size_t laneIndex = 0; laneIndex < m_lanes.size(); ++laneIndex)
|
||||
{
|
||||
const LaneVisual& visual = m_lanes[laneIndex];
|
||||
@@ -4211,14 +4377,15 @@ namespace jdi
|
||||
const float itemLeft = cursorX;
|
||||
const float swatchSize = kLegendSwatchSize * m_scale;
|
||||
|
||||
// 图例条目与被选中的信号保持一致的高亮反馈(与左侧列表选中态呼应)。
|
||||
// 选中态(与左侧列表呼应)绘制在静态场景里:选中信号参与场景指纹,
|
||||
// 因此选中变化会让缓存失效并重绘。**悬停半高亮不在此处**(随鼠标变化,
|
||||
// 画在缓存贴图之后,见 PaintLegendHighlight)。
|
||||
const bool selected = (SelectedRef() == ref);
|
||||
const bool hovered = (ref == m_hoverSignal) || (ref == m_dragSignal);
|
||||
if (selected || hovered)
|
||||
if (selected)
|
||||
{
|
||||
UiFillRoundRect(g, gd::RectF(itemLeft - 3.0f * m_scale, top - 1.0f * m_scale,
|
||||
itemWidth + 2.0f * m_scale, height + 2.0f * m_scale),
|
||||
3.0f * m_scale, selected ? m_theme->selection : m_theme->hover);
|
||||
3.0f * m_scale, m_theme->selection);
|
||||
}
|
||||
|
||||
UiDrawSwatch(g, gd::RectF(cursorX, top + (height - swatchSize) * 0.5f, swatchSize, swatchSize),
|
||||
@@ -4310,6 +4477,27 @@ namespace jdi
|
||||
m_theme->textMuted, TextAlign::LEFT);
|
||||
|
||||
const float labelWidth = 96.0f * m_scale;
|
||||
|
||||
// 五等分子刻度线:在相邻主刻度之间插入 4 条短刻度(更长的主刻度在最前绘制,
|
||||
// 短刻度与主刻度重合的位置跳过),便于目视读取中间时刻。
|
||||
const double minorStep = timeStep / static_cast<double>(kMinorDivisions);
|
||||
std::vector<double> minorTicks;
|
||||
BuildTicks(Tab().ViewMin(), Tab().ViewMax(), minorStep, minorTicks);
|
||||
for (const double tick : minorTicks)
|
||||
{
|
||||
const double majorIndex = tick / timeStep;
|
||||
if (std::fabs(majorIndex - std::floor(majorIndex + 0.5)) < 1.0e-6)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float x = TimeToX(tick);
|
||||
if (x < m_plotRect.X - 1.0f || x > m_plotRect.X + m_plotRect.Width + 1.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UiDrawLine(g, x, axisY, x, axisY + 3.0f * m_scale, m_theme->axisLine, 1.0f);
|
||||
}
|
||||
|
||||
for (const double tick : timeTicks)
|
||||
{
|
||||
const float x = TimeToX(tick);
|
||||
@@ -4714,7 +4902,7 @@ namespace jdi
|
||||
return gd::RectF();
|
||||
}
|
||||
|
||||
const float width = kEdgePanelWidth * m_scale;
|
||||
const float width = EdgePanelWidth(laneIndex);
|
||||
const float titleHeight = kEdgePanelTitleHeight * m_scale;
|
||||
const float rowHeight = kEdgePanelRowHeight * m_scale;
|
||||
const float pad = kEdgePanelPad * m_scale;
|
||||
@@ -4898,7 +5086,7 @@ namespace jdi
|
||||
UiDrawRoundRect(g, badgeRect, badgeHeight * 0.5f, m_theme->border, 1.0f);
|
||||
UiText(g, L"G" + std::to_wstring(index + 1), m_fonts->monoSmall, badgeRect, m_theme->text,
|
||||
TextAlign::CENTER);
|
||||
UiText(g, L"边沿计数", m_fonts->uiSmall,
|
||||
UiText(g, L"边沿计数" + EdgeRangeSuffix(), m_fonts->uiSmall,
|
||||
gd::RectF(badgeRect.X + badgeRect.Width + 8.0f * m_scale, panel.Y,
|
||||
panel.Width - badgeRect.Width - 8.0f * m_scale - titleHeight,
|
||||
titleHeight),
|
||||
@@ -4936,14 +5124,15 @@ namespace jdi
|
||||
}
|
||||
const std::uint32_t signalColor = StyleColor(*resolved.style);
|
||||
const float textWidth = panel.Width - pad * 2.0f;
|
||||
UiText(g, UiEllipsize(g, resolved.data->Title(), m_fonts->uiSmall, textWidth),
|
||||
m_fonts->uiSmall,
|
||||
// 名称与统计数字一律完整绘制(不省略):面板宽度已按文本实测宽度确定。
|
||||
UiText(g, resolved.data->Title(), m_fonts->uiSmall,
|
||||
gd::RectF(panel.X + pad, rowY, textWidth, rowHeight), signalColor,
|
||||
TextAlign::LEFT);
|
||||
|
||||
std::size_t rising = 0;
|
||||
std::size_t falling = 0;
|
||||
EdgeCountsFor(*resolved.file, *resolved.data, rising, falling);
|
||||
EdgeCountsFor(*resolved.file, *resolved.data, tabLane.edgeValue, EdgeRangeActive(),
|
||||
EdgeRangeMin(), EdgeRangeMax(), rising, falling);
|
||||
const std::wstring counts = L"上升沿 " + std::to_wstring(rising) + L" 下降沿 " +
|
||||
std::to_wstring(falling);
|
||||
UiText(g, counts, m_fonts->monoSmall,
|
||||
|
||||
Reference in new issue
Block a user