修复缩放后平移的拖影/残留/错误元素与深放大区间卡顿:网格纵向滚动量纳入内容指纹、样式补丁增加视界校验、宽基底不再用含装饰的画面初始化、装饰缓存改为每帧重捕获并记录矩形与坐标原点、装饰底板改不透明以支持跨视界复用;交互帧 9~19ms 降至 4.5~6ms,纵向拖动残留 2 万+ 通道降至 72 通道,静止/结算帧像素精确;新增离屏诊断模式 6(逐帧计时 + 逐像素对比)
This commit is contained in:
1 parent
1c9c43233d
commit
e22749b57a
3 files changed
+224
-58
No files matched your search
@@ -2154,19 +2154,23 @@ namespace jdi
|
||||
*/
|
||||
bool m_sceneDegraded = false;
|
||||
|
||||
// ---- 「装饰层」像素缓存(水平平移时不变的内容:Y 轴刻度胶囊 + 图例)----
|
||||
// 平移会让视图每帧变化 → 静态场景缓存必然失效 → 每帧都要重画波形**以及**网格线、
|
||||
// Y 轴刻度文字、图例(实测后者占整幅重绘的一半以上)。但这些装饰内容**只取决于网格几何与
|
||||
// 信号外观,与时间轴视图无关**,平移时不变化。因此在每张「全质量帧」后把装饰区域的像素
|
||||
// 捕获下来,拖拽(快速帧)时直接贴回,省去每帧重绘文字与圆角底板的开销。
|
||||
// 仅在完全相同(`m_decorKey` 命中)时启用,避免几何/外观变化时用到过期像素。
|
||||
// ---- 「装饰层」像素缓存(Y 轴刻度胶囊 + 图例)----
|
||||
// 装饰(Y 轴刻度文字与其圆角底板、图例)只取决于网格布局 / 幅值范围 / 信号外观与
|
||||
// 图例文案,与时间视界无关;但它**不适合**放进「宽基底」:平移是从基底里截取子矩形,
|
||||
// 烘进基底的装饰会跟着一起移动 → 在网格左缘之外留下一排旧图例/刻度(重影)。
|
||||
// 因此单独缓存一帧**按当前视界绘制**的装饰像素,并在内容指纹完全一致时整块贴回。
|
||||
//
|
||||
// ⚠️ 关键不变量:**每一帧“真正绘制了装饰”都要重新捕获**(无论是否快速帧)。
|
||||
// 只要保证“缓存像素”与“缓存指纹”永远同源,贴回就不可能过期;
|
||||
// 反之(例如只在全质量帧捕获)会在几何/外观变化后贴回旧像素,
|
||||
// 表现为残留 / 黑块 / 错位等错误元素。
|
||||
/**
|
||||
* @brief 确保装饰缓存位图尺寸正确(不足则重建)。
|
||||
* @param [IN] width 位图宽度。 @param [IN] height 位图高度。
|
||||
* @return 可用返回 true。
|
||||
*/
|
||||
bool EnsureDecorCache(int width, int height);
|
||||
/** @brief 释放装饰缓存位图。 */
|
||||
/** @brief 释放装饰缓存位图并作废缓存。 */
|
||||
void ReleaseDecorCache();
|
||||
/**
|
||||
* @brief 把本帧记录下来的装饰矩形从后备缓冲区捕获进装饰缓存。
|
||||
@@ -2174,7 +2178,7 @@ namespace jdi
|
||||
*/
|
||||
bool CaptureDecorCache(gd::Graphics& g);
|
||||
/**
|
||||
* @brief 把装饰缓存里的装饰贴回后备缓冲区(快速帧用)。
|
||||
* @brief 把装饰缓存里的装饰贴回后备缓冲区(本帧跳过了装饰绘制时使用)。
|
||||
* @param [IN,OUT] g 绘图上下文。 @return 贴图成功返回 true。
|
||||
*/
|
||||
bool BlitDecorCache(gd::Graphics& g);
|
||||
@@ -2187,8 +2191,24 @@ namespace jdi
|
||||
mutable bool m_decorValid = false; /**< 装饰缓存内容是否有效。 */
|
||||
mutable std::uint64_t m_decorKey = 0; /**< 装饰缓存对应的内容指纹。 */
|
||||
mutable bool m_useDecorCache = false; /**< 本帧是否跳过绘制装饰、改用缓存贴回。 */
|
||||
/** 本帧需要捕获 / 贴回的装饰矩形(各网格的 Y 轴胶囊与图例带)。 */
|
||||
/** 本帧需要捕获的装饰矩形(各网格的 Y 轴胶囊与图例带)。 */
|
||||
mutable std::vector<gd::RectF> m_decorRects;
|
||||
/**
|
||||
* @brief 缓存里那批像素对应的装饰矩形(捕获时与像素同时保存)。
|
||||
*
|
||||
* ⚠️ 贴回必须用这一份,**不能**用当前的 `m_decorRects`:
|
||||
* 命中缓存时各网格会跳过绘制、根本不会重建矩形列表(为空),
|
||||
* 若用当前列表就会贴错位置或一个都不贴(表现为装饰消失 / 黑块 / 错位)。
|
||||
*/
|
||||
mutable std::vector<gd::RectF> m_decorCapturedRects;
|
||||
/**
|
||||
* @brief 捕获缓存像素时使用的绘图区原点(`m_area` 左上角)。
|
||||
*
|
||||
* ⚠️ 贴回时**必须用这一份**推算源偏移:宽缓冲区渲染会临时改写 `m_area`,
|
||||
* 若捕获与贴回两帧的 `m_area` 不同,直接用当前 `m_area` 作原点会让整块装饰错位。
|
||||
*/
|
||||
mutable int m_decorOriginX = 0;
|
||||
mutable int m_decorOriginY = 0;
|
||||
|
||||
// ---- 平移「宽缓冲区」:平移只改变绘制坐标、不改变画面内容 ----
|
||||
// 视界平移时整个波形并不改变,只是横向平移。因此以当前视界为中心,向左右各多渲染
|
||||
@@ -2258,6 +2278,16 @@ namespace jdi
|
||||
bool m_wideValid = false; /**< 宽缓冲区内容是否有效。 */
|
||||
std::uint64_t m_wideKey = 0; /**< 宽缓冲区对应的内容指纹(外观/派生参数等)。 */
|
||||
bool m_renderWide = false; /**< 正在渲染宽缓冲区(重入 `PaintLane` 时跳过装饰)。 */
|
||||
|
||||
/**
|
||||
* @brief 本次 `PaintLane` 只绘制「装饰」(Y 轴刻度胶囊 + 图例 + 网格分界线)。
|
||||
*
|
||||
* 宽缓冲区只存「基底」(底色 + 网格线 + 波形 + 派生曲线),**不含装饰**:
|
||||
* 因为装饰锚定在网格左缘、随视界横向移动,若烘进基底,平移截取时会产生重影。
|
||||
* 因此前台每帧用本标志重画一次装饰(复用与整幅渲染**完全相同**的绘制代码,
|
||||
* 保证逐像素一致),而不是去贴回任何缓存像素。
|
||||
*/
|
||||
bool m_renderDecorOnly = false;
|
||||
/** 上一次走"宽缓冲快路径"时的内容指纹(含视界)。当前帧指纹与之相同 → 视界已停止变化,
|
||||
* 此时改走一次整幅重绘("结算"),保证最终呈现的是**像素精确**的全质量帧。 */
|
||||
std::uint64_t m_wideLastKey = 0;
|
||||
|
||||
@@ -767,6 +767,7 @@ namespace jdi
|
||||
// 布局变化(窗口缩放、面板宽度调整、网格数量变化)后静态场景不再有效。
|
||||
InvalidateSceneCache();
|
||||
InvalidateWideBuffer();
|
||||
m_decorValid = false; // 装饰锚定在网格左缘,布局变了就必须重画装饰
|
||||
|
||||
m_labelBandWidth = 66.0f * m_scale;
|
||||
m_timeAxisHeight = 34.0f * m_scale;
|
||||
@@ -5601,8 +5602,7 @@ namespace jdi
|
||||
// ⚠️ **不要求"正在拖拽"**:非拖拽的视界变化(键盘方向键 / 工具栏)同样走此快路径,
|
||||
// 避免该放大区间"整幅重绘约 124ms"的卡顿。
|
||||
bool wideUsable = false;
|
||||
if (m_decorValid && HasDrawableContent() && m_sceneValid &&
|
||||
m_sceneGeometryHash == SceneGeometryHash())
|
||||
if (HasDrawableContent() && m_sceneValid && m_sceneGeometryHash == SceneGeometryHash())
|
||||
{
|
||||
std::vector<std::vector<SceneStyleEntry>> currentStyles;
|
||||
CollectSceneStyles(currentStyles);
|
||||
@@ -5623,14 +5623,12 @@ namespace jdi
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰);
|
||||
// 否则优先用"上一帧场景缓存"初始化基底(平移首帧:贴图 + 细条,避免从零重画
|
||||
// 2×视界造成的一次尖峰),再不行才整幅重建。
|
||||
// 已有可用基底(跨度与内容均匹配)→ 滚动:只补画新暴露的细条,恒为 O(细条),
|
||||
// 无周期性大尖峰;否则以当前视界为中心重建基底。
|
||||
// ⚠️ **不能用“上一帧的画面”初始化基底**:整幅渲染的画面里**含装饰**
|
||||
//(Y 轴刻度 + 图例),而基底不含;若直接拷贝,装饰像素会随平移一起移动,
|
||||
// 在网格左缘之外留下一排旧图例/刻度 —— 即用户看到的“拖影 / 残留”。
|
||||
bool ready = (m_wideValid && WideBufferMatches()) ? RollWideBuffer() : false;
|
||||
if (!ready && InitWideBufferFromScene())
|
||||
{
|
||||
ready = RollWideBuffer();
|
||||
}
|
||||
if (!ready)
|
||||
{
|
||||
ready = RebuildWideBuffer();
|
||||
@@ -5652,12 +5650,34 @@ namespace jdi
|
||||
std::vector<double> wideTimeTicks;
|
||||
BuildTicks(wideViewMin, wideViewMax, wideTimeStep, wideTimeTicks);
|
||||
PaintTimeAxis(g, wideTimeTicks, wideTimeStep);
|
||||
// 派生预览曲线作为叠加层重画(很轻);随后装饰层与其它叠加层。
|
||||
// 派生预览曲线:基底不含它们(拖参数滑块时基底不变、可直接复用像素)。
|
||||
for (std::size_t i = 0; i < m_lanes.size(); ++i)
|
||||
{
|
||||
PaintLaneDerivedOverlays(g, static_cast<int>(i), m_lanes[i]);
|
||||
}
|
||||
BlitDecorCache(g);
|
||||
// 装饰(Y 轴刻度 + 图例):基底不含(否则平移会重影)。
|
||||
// 内容指纹一致 → 直接整块贴回缓存(省掉占整幅渲染一半以上的文字绘制);
|
||||
// 否则用 `m_renderDecorOnly` 重画一遍并**立即重新捕获**。
|
||||
m_useDecorCache = m_decorValid && (m_decorKey == SceneDecorHash());
|
||||
if (m_useDecorCache)
|
||||
{
|
||||
// 贴回用**捕获时**的矩形(命中缓存时各网格不会重建矩形列表)。
|
||||
BlitDecorCache(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_decorRects.clear();
|
||||
m_renderDecorOnly = true;
|
||||
for (std::size_t i = 0; i < m_lanes.size(); ++i)
|
||||
{
|
||||
PaintLane(g, static_cast<int>(i), m_lanes[i], wideTimeTicks);
|
||||
}
|
||||
m_renderDecorOnly = false;
|
||||
if (CaptureDecorCache(g))
|
||||
{
|
||||
m_decorKey = SceneDecorHash();
|
||||
}
|
||||
}
|
||||
paintOverlays();
|
||||
return;
|
||||
}
|
||||
@@ -5700,10 +5720,10 @@ namespace jdi
|
||||
// 图例命中矩形每帧重建,避免残留已消失的信号。
|
||||
m_legendHits.clear();
|
||||
|
||||
// 「装饰层」缓存决策:快速帧(拖拽)期间、且装饰内容与上次捕获完全一致时,
|
||||
// 「装饰层」缓存决策:快速帧(拖拽/平移)期间、且装饰内容与上次捕获完全一致时,
|
||||
// 跳过 Y 轴胶囊与图例的绘制(约占整幅重绘的一半),改用上次捕获的像素贴回。
|
||||
m_decorRects.clear();
|
||||
m_useDecorCache = m_fastFrame && m_decorValid && (m_decorKey == SceneDecorHash());
|
||||
m_useDecorCache = m_decorValid && (m_decorKey == SceneDecorHash());
|
||||
|
||||
for (std::size_t index = 0; index < m_lanes.size(); ++index)
|
||||
{
|
||||
@@ -5712,18 +5732,16 @@ namespace jdi
|
||||
|
||||
PaintTimeAxis(g, timeTicks, timeStep);
|
||||
|
||||
// 装饰层:全质量帧捕获,快速帧贴回(见成员注释)。
|
||||
if (!m_fastFrame)
|
||||
{
|
||||
if (CaptureDecorCache(g))
|
||||
{
|
||||
m_decorKey = SceneDecorHash();
|
||||
}
|
||||
}
|
||||
else if (m_useDecorCache)
|
||||
// 装饰层:命中缓存则上一轮网格循环已经跳过绘制,这里整块贴回;
|
||||
// 否则本轮已经真正绘制了装饰,立即捕获(保证像素与指纹同源)。
|
||||
if (m_useDecorCache)
|
||||
{
|
||||
BlitDecorCache(g);
|
||||
}
|
||||
else if (CaptureDecorCache(g))
|
||||
{
|
||||
m_decorKey = SceneDecorHash();
|
||||
}
|
||||
|
||||
// 此刻画布上正好是"绘图区底色 + 全部网格 + 时间轴",尚未绘制任何随鼠标变化的内容,
|
||||
// 因此是捕获静态场景缓存的最佳时点(必须在裁剪区已全部恢复之后执行)。
|
||||
@@ -5958,6 +5976,10 @@ namespace jdi
|
||||
mixFloat(m_plotRect.Height);
|
||||
mixFloat(m_laneContentHeight);
|
||||
mix(m_scrollBarVisible ? 1ull : 0ull);
|
||||
// ⚠️ 网格纵向滚动量必须参与指纹:它只改变各网格的绘制位置。
|
||||
// 若不参与,平移宽缓冲区 / 静态场景缓存会把旧滚动位置的像素当成当前内容贴回,
|
||||
// 表现为“网格位置错位 / 拖影 / 残留”等错误元素。
|
||||
mixFloat(Tab().LaneScroll());
|
||||
|
||||
// 选中信号会改变图例条目的底色与文字颜色(绘制在缓存的静态场景里),
|
||||
// 因此必须参与指纹:否则在左侧信号面板点选后图例不会高亮联动。
|
||||
@@ -6091,6 +6113,23 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
// ⚠️ **必须额外确认“视图几何与缓存完全一致”**:`SceneGeometryHash()` 刻意不含视界,
|
||||
// 所以它相等**并不能**说明视图没变。若视界已经变了还走“只重画受影响网格”的局部补丁,
|
||||
// 就会把缓存里**旧视界**的画面(时间轴、其它网格、底色)留在屏上 —— 表现为用户反馈的
|
||||
// “拖影 / 残留 / 错误元素”。因此这里逐项比较除内容指纹之外的缓存键。
|
||||
{
|
||||
const SceneCacheKey key = MakeSceneCacheKey();
|
||||
if (key.viewMin != m_sceneKey.viewMin || key.viewMax != m_sceneKey.viewMax ||
|
||||
key.laneScroll != m_sceneKey.laneScroll || key.laneCount != m_sceneKey.laneCount ||
|
||||
key.areaX != m_sceneKey.areaX || key.areaY != m_sceneKey.areaY ||
|
||||
key.areaW != m_sceneKey.areaW || key.areaH != m_sceneKey.areaH ||
|
||||
key.scale != m_sceneKey.scale || key.haloFile != m_sceneKey.haloFile ||
|
||||
key.haloSignal != m_sceneKey.haloSignal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<SceneStyleEntry>> current;
|
||||
CollectSceneStyles(current);
|
||||
if (current.size() != m_sceneStyles.size())
|
||||
@@ -6687,6 +6726,7 @@ namespace jdi
|
||||
void PlotView::ReleaseDecorCache()
|
||||
{
|
||||
m_decorValid = false;
|
||||
m_decorCapturedRects.clear();
|
||||
if (m_decorDc != nullptr)
|
||||
{
|
||||
if (m_decorOldBitmap != nullptr)
|
||||
@@ -6745,12 +6785,19 @@ namespace jdi
|
||||
}
|
||||
g.ReleaseHDC(source);
|
||||
m_decorValid = any;
|
||||
if (any)
|
||||
{
|
||||
// 与像素同时保存矩形列表与**坐标原点**:贴回时必须用这一份(见成员注释)。
|
||||
m_decorCapturedRects = m_decorRects;
|
||||
m_decorOriginX = originX;
|
||||
m_decorOriginY = originY;
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
bool PlotView::BlitDecorCache(gd::Graphics& g)
|
||||
{
|
||||
if (!m_decorValid || m_decorDc == nullptr || m_decorRects.empty())
|
||||
if (!m_decorValid || m_decorDc == nullptr || m_decorCapturedRects.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -6759,19 +6806,21 @@ namespace jdi
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int originX = static_cast<int>(std::floor(m_area.X));
|
||||
const int originY = static_cast<int>(std::floor(m_area.Y));
|
||||
for (const gd::RectF& rect : m_decorRects)
|
||||
// 用**捕获时**的原点推算源偏移,但按**绝对坐标**写入:
|
||||
// 即使两帧的 `m_area` 不同(宽缓冲区渲染会临时改写它),装饰也不会错位。
|
||||
for (const gd::RectF& rect : m_decorCapturedRects)
|
||||
{
|
||||
const int x0 = static_cast<int>(std::floor(rect.X)) - originX;
|
||||
const int y0 = static_cast<int>(std::floor(rect.Y)) - originY;
|
||||
const int x1 = static_cast<int>(std::ceil(rect.X + rect.Width)) - originX;
|
||||
const int y1 = static_cast<int>(std::ceil(rect.Y + rect.Height)) - originY;
|
||||
if (x1 <= x0 || y1 <= y0)
|
||||
const int destX = static_cast<int>(std::floor(rect.X));
|
||||
const int destY = static_cast<int>(std::floor(rect.Y));
|
||||
const int srcX = destX - m_decorOriginX;
|
||||
const int srcY = destY - m_decorOriginY;
|
||||
const int w = static_cast<int>(std::ceil(rect.X + rect.Width)) - destX;
|
||||
const int h = static_cast<int>(std::ceil(rect.Y + rect.Height)) - destY;
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
BitBlt(target, x0 + originX, y0 + originY, x1 - x0, y1 - y0, m_decorDc, x0, y0, SRCCOPY);
|
||||
BitBlt(target, destX, destY, w, h, m_decorDc, srcX, srcY, SRCCOPY);
|
||||
}
|
||||
g.ReleaseHDC(target);
|
||||
return true;
|
||||
@@ -7351,22 +7400,29 @@ namespace jdi
|
||||
weakSegments.insert(weakSegments.end(), {x, lane.rect.Y, x, lane.rect.Y + lane.rect.Height});
|
||||
}
|
||||
|
||||
if (!minorSegments.empty())
|
||||
if (!m_renderDecorOnly)
|
||||
{
|
||||
UiDrawSolidLines(g, gridClip, minorSegments.data(), minorSegments.size() / 4, minorLineColor);
|
||||
}
|
||||
if (!weakSegments.empty())
|
||||
{
|
||||
UiDrawSolidLines(g, gridClip, weakSegments.data(), weakSegments.size() / 4, weakLineColor);
|
||||
}
|
||||
if (!strongSegments.empty())
|
||||
{
|
||||
UiDrawSolidLines(g, gridClip, strongSegments.data(), strongSegments.size() / 4, strongLineColor);
|
||||
if (!minorSegments.empty())
|
||||
{
|
||||
UiDrawSolidLines(g, gridClip, minorSegments.data(), minorSegments.size() / 4, minorLineColor);
|
||||
}
|
||||
if (!weakSegments.empty())
|
||||
{
|
||||
UiDrawSolidLines(g, gridClip, weakSegments.data(), weakSegments.size() / 4, weakLineColor);
|
||||
}
|
||||
if (!strongSegments.empty())
|
||||
{
|
||||
UiDrawSolidLines(g, gridClip, strongSegments.data(), strongSegments.size() / 4, strongLineColor);
|
||||
}
|
||||
}
|
||||
|
||||
// 波形绘制。
|
||||
const std::vector<SignalRef>& laneSignals = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].signals;
|
||||
if (laneSignals.empty())
|
||||
if (m_renderDecorOnly)
|
||||
{
|
||||
// 只画装饰:网格线与波形已由宽基底提供,这里跳过。
|
||||
}
|
||||
else if (laneSignals.empty())
|
||||
{
|
||||
const gd::RectF visibleRect(lane.rect.X, clipTop, lane.rect.Width, clipBottom - clipTop);
|
||||
UiText(g, L"(空网格)", m_fonts->uiSmall, visibleRect, m_theme->textMuted, TextAlign::CENTER);
|
||||
@@ -7519,7 +7575,8 @@ namespace jdi
|
||||
|
||||
// 网格分界线:网格之间不再留间隔,改用较重的横线明确分隔相邻网格。
|
||||
const float dividerY = lane.rect.Y;
|
||||
if (dividerY >= m_plotRect.Y - 1.0f && dividerY <= m_plotRect.Y + m_plotRect.Height)
|
||||
if (!m_renderDecorOnly && dividerY >= m_plotRect.Y - 1.0f &&
|
||||
dividerY <= m_plotRect.Y + m_plotRect.Height)
|
||||
{
|
||||
const float dividerSegments[4] = {lane.rect.X, dividerY, lane.rect.X + lane.rect.Width, dividerY};
|
||||
const std::uint32_t dividerColor =
|
||||
@@ -7599,12 +7656,14 @@ namespace jdi
|
||||
m_decorRects.push_back(gd::RectF(pill.X - 2.0f * m_scale, pill.Y - 1.0f * m_scale,
|
||||
pill.Width + 4.0f * m_scale, pill.Height + 2.0f * m_scale));
|
||||
}
|
||||
// 快速帧且装饰缓存命中时:跳过重绘(文字 + 圆角底板),改由装饰层贴回。
|
||||
// 命中装饰缓存:跳过重绘(文字 + 圆角底板),由上层整块贴回。
|
||||
if (!m_useDecorCache)
|
||||
{
|
||||
if (!pills.empty())
|
||||
{
|
||||
UiFillRoundRects(g, pills, 3.0f * m_scale, WithAlpha(m_theme->cardBg, 0xF0));
|
||||
// 胶囊底板**不透明**:装饰像素因此与时间视界无关,可以跨视界整块复用;
|
||||
// 半透明底板会把旧视界的波形混进缓存像素,平移贴回时在底板内部留下残影。
|
||||
UiFillRoundRects(g, pills, 3.0f * m_scale, m_theme->cardBg | 0xFF000000u);
|
||||
}
|
||||
for (std::size_t index = 0; index < pills.size(); ++index)
|
||||
{
|
||||
@@ -7760,21 +7819,21 @@ namespace jdi
|
||||
{
|
||||
// 底板从编号徽标开始一直盖到最后一个图例条目(编号徽标一并被掠开底下的网格线)。
|
||||
const float plateRight = cursorX + legendWidth + (legendWidth > 0.0f ? 2.0f * m_scale : 0.0f);
|
||||
// 记录图例装饰矩形(含编号徽标;供装饰层捕获/贴回):从徽标左缘到图例右缘。
|
||||
// 记录图例装饰矩形(含编号徽标):从徽标左缘到图例右缘。
|
||||
{
|
||||
const float left = std::min(badgeRect.X, legendBand.X - 4.0f * m_scale) - 2.0f * m_scale;
|
||||
const float right = std::max(badgeRect.X + badgeRect.Width, plateRight) + 2.0f * m_scale;
|
||||
m_decorRects.push_back(gd::RectF(left, top - 4.0f * m_scale, right - left,
|
||||
height + 8.0f * m_scale));
|
||||
}
|
||||
// 快速帧且装饰缓存命中:整块图例(底板 + 徽标 + 色块 + 文字)改由装饰层贴回,跳过重绘。
|
||||
// 命中装饰缓存:整块图例(底板 + 徽标 + 色块 + 文字)改由装饰层贴回,跳过重绘。
|
||||
if (m_useDecorCache)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UiFillRoundRect(g, gd::RectF(legendBand.X - 4.0f * m_scale, top - 2.0f * m_scale,
|
||||
plateRight - legendBand.X + 4.0f * m_scale, height + 4.0f * m_scale),
|
||||
3.0f * m_scale, WithAlpha(m_theme->cardBg, 0xE8));
|
||||
3.0f * m_scale, m_theme->cardBg | 0xFF000000u);
|
||||
}
|
||||
|
||||
// 编号徽标绘制在底板之上(与信号面板的 GX 徽标使用同一套配色与圆角)。
|
||||
|
||||
Reference in new issue
Block a user