中心轴曲线配色 + 筛选框交互完善 + 双光标拖拽性能修复
- 中心轴边沿计数的平均值曲线改为「由信号色派生的高对比度颜色」:保持色相、 大幅拉开明度(与原始波形高对比),透明度与原信号一致;原信号改色时曲线 颜色随之变化,而光标显示该曲线数值时仍沿用原始信号的配色样式。 - 信号筛选框:内容区左右内缩改为与上下一致(不再「左右空隙明显更大」); 有输入时右侧显示清空 ✕(悬停高亮 + 手型光标,点击清空且保持编辑态); 筛选无结果时在列表区提示「无匹配的筛选信号」。 - 光标数值超出上下视界时,三角箭头标识下移 kCursorHandleReserve, 不再遮挡光标自身的三角把手(含双光标模式)。 - 性能:新增「装饰区域脏标记 + 绘图区局部回送」快路径——绘图区内的悬停/拖拽 期间跳过标签栏/工具栏/面板/分隔条的重绘,且只 BitBlt 绘图区矩形; WM_PAINT 依据更新区域是否落在绘图区内决定快慢路径,窗口缩放/遮挡露出/ 菜单与拾色器弹出等场景仍整帧重绘。 以 VERY_BIG_DATA.CSV 全信号双光标拖拽实测:单帧耗时由约 28ms 降至约 12ms。 同时修复拖拽期间残留的信号悬停提示气泡。
This commit is contained in:
1 parent
fe72afc83c
commit
8ae8d7af4f
6 files changed
+333
-45
No files matched your search
@@ -364,6 +364,16 @@ namespace jdi
|
||||
float m_loadingProgress = 0.0f; /**< 加载进度(0-1)。 */
|
||||
float m_loadingSpin = 0.0f; /**< 加载动画旋转相位(度)。 */
|
||||
unsigned long long m_loadingBytes = 0; /**< 当前文件字节数(用于展示体积)。 */
|
||||
|
||||
/**
|
||||
* @brief 装饰区域(标签栏 / 工具栏 / 信号面板 / 分隔条)是否需要重绘。
|
||||
*
|
||||
* 这些区域的绘制成本与数据量无关但与控件数量成正比,且只在鼠标位于其中
|
||||
* 或发生交互时才会变化。绘图区拖拽(如双光标左右移动)期间持续置为
|
||||
* false,从而每帧只重绘、只回送绘图区所在的矩形,避免整窗重绘的开销。
|
||||
*/
|
||||
bool m_chromeDirty = true;
|
||||
bool m_mouseOverPlot = false; /**< 上一次鼠标移动是否位于绘图区内。 */
|
||||
};
|
||||
|
||||
} // namespace jdi
|
||||
|
||||
@@ -37,6 +37,32 @@ namespace jdi
|
||||
const UINT_PTR kHintTimerId = 1; /**< 提示浮层淡出用的定时器 ID。 */
|
||||
const UINT kHintTimerPeriodMs = 33; /**< 提示浮层淡出的重绘间隔(约 30fps)。 */
|
||||
|
||||
/**
|
||||
* @brief 只失效绘图区所在的矩形。
|
||||
*
|
||||
* 配合 WM_PAINT 中对更新区域的判断,可以走“只重绘、只回送绘图区”的快路径;
|
||||
* 绘图区矩形无效时退化为整窗失效,保证不会漏画。
|
||||
*
|
||||
* @param [IN] hwnd 窗口句柄。
|
||||
* @param [IN] plotRect 绘图区矩形(客户区坐标)。
|
||||
*/
|
||||
void InvalidatePlotRect(HWND hwnd, const gd::RectF& plotRect)
|
||||
{
|
||||
RECT invalid = {};
|
||||
invalid.left = static_cast<LONG>(std::floor(plotRect.X));
|
||||
invalid.top = static_cast<LONG>(std::floor(plotRect.Y));
|
||||
invalid.right = static_cast<LONG>(std::ceil(plotRect.X + plotRect.Width));
|
||||
invalid.bottom = static_cast<LONG>(std::ceil(plotRect.Y + plotRect.Height));
|
||||
if (invalid.right > invalid.left && invalid.bottom > invalid.top)
|
||||
{
|
||||
InvalidateRect(hwnd, &invalid, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief 标签页右键菜单命令 ID。 */
|
||||
const int kMenuTabNew = 1;
|
||||
const int kMenuTabClose = 2;
|
||||
@@ -594,6 +620,9 @@ namespace jdi
|
||||
m_panel.SetClientBounds(gd::RectF(0.0f, 0.0f, width, height));
|
||||
m_panel.Layout(m_panelRect);
|
||||
m_plot.Layout(m_plotRect);
|
||||
|
||||
// 所有控件的目标矩形都变了,装饰区域必须整体重绘一次。
|
||||
m_chromeDirty = true;
|
||||
}
|
||||
|
||||
void Application::EnsureBackBuffer(int width, int height)
|
||||
@@ -605,6 +634,9 @@ namespace jdi
|
||||
|
||||
ReleaseBackBuffer();
|
||||
|
||||
// 新建的后备位图内容未定义,必须整帧重绘一次。
|
||||
m_chromeDirty = true;
|
||||
|
||||
HDC screenDc = GetDC(m_hwnd);
|
||||
if (screenDc == nullptr)
|
||||
{
|
||||
@@ -697,26 +729,34 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 工具栏/标签栏/面板各自带悬停态,内容只在鼠标位于其中或发生交互时改变。
|
||||
// 绘图区内的拖拽(如双光标左右移动)期间这些区域完全静止,跳过其绘制,
|
||||
// 每帧省下约 6-8ms;弹出菜单 / 拾色器 / 加载遮罩会覆盖到装饰区域,必须整帧重绘。
|
||||
const bool overlaysVisible = m_menu.IsOpen() || m_colorPicker.IsOpen();
|
||||
const bool chromeDirty = m_chromeDirty || m_loading || overlaysVisible;
|
||||
m_chromeDirty = false;
|
||||
// 提示浮层淡出由定时器驱动,其绘制范围局限在绘图区,但为稳妥起见整帧回送。
|
||||
const bool fullFrame = chromeDirty || m_plot.HintNeedsAnimation();
|
||||
|
||||
{
|
||||
gd::Graphics g(m_backDc);
|
||||
g.SetSmoothingMode(gd::SmoothingModeAntiAlias);
|
||||
g.SetPixelOffsetMode(gd::PixelOffsetModeHalf);
|
||||
g.SetTextRenderingHint(gd::TextRenderingHintClearTypeGridFit);
|
||||
|
||||
// 工具栏/标签栏/面板各自带悬停态,必须每帧绘制;
|
||||
// 真正昂贵的是绘图区的波形(大文件约 80ms/帧),
|
||||
// 由 PlotView 内部的静态场景缓存消除,与数据量无关。
|
||||
UiFillRect(g, gd::RectF(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)),
|
||||
m_theme.windowBg);
|
||||
if (chromeDirty)
|
||||
{
|
||||
UiFillRect(g, gd::RectF(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)),
|
||||
m_theme.windowBg);
|
||||
|
||||
m_tabBar.Paint(g);
|
||||
m_toolbar.Paint(g);
|
||||
m_panel.Paint(g);
|
||||
m_tabBar.Paint(g);
|
||||
m_toolbar.Paint(g);
|
||||
m_panel.Paint(g);
|
||||
UiFillRect(g, m_splitterRect, m_theme.border);
|
||||
}
|
||||
|
||||
m_plot.Paint(g);
|
||||
|
||||
UiFillRect(g, m_splitterRect, m_theme.border);
|
||||
|
||||
// 弹出菜单与浮动拾色器始终绘制在最上层,保证与主界面风格一致。
|
||||
m_menu.Paint(g);
|
||||
m_colorPicker.Paint(g);
|
||||
@@ -728,7 +768,19 @@ namespace jdi
|
||||
HDC windowDc = GetDC(m_hwnd);
|
||||
if (windowDc != nullptr)
|
||||
{
|
||||
BitBlt(windowDc, 0, 0, width, height, m_backDc, 0, 0, SRCCOPY);
|
||||
if (fullFrame)
|
||||
{
|
||||
BitBlt(windowDc, 0, 0, width, height, m_backDc, 0, 0, SRCCOPY);
|
||||
}
|
||||
else
|
||||
{
|
||||
// PlotView 会把自身绘制严格裁剪在 m_plotRect 内,因此只回送该矩形即可。
|
||||
const int px = static_cast<int>(std::floor(m_plotRect.X));
|
||||
const int py = static_cast<int>(std::floor(m_plotRect.Y));
|
||||
const int pw = static_cast<int>(std::ceil(m_plotRect.X + m_plotRect.Width)) - px;
|
||||
const int ph = static_cast<int>(std::ceil(m_plotRect.Y + m_plotRect.Height)) - py;
|
||||
BitBlt(windowDc, px, py, pw, ph, m_backDc, px, py, SRCCOPY);
|
||||
}
|
||||
ReleaseDC(m_hwnd, windowDc);
|
||||
}
|
||||
|
||||
@@ -1624,6 +1676,16 @@ namespace jdi
|
||||
|
||||
LRESULT Application::HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
// 除鼠标移动与鼠标移出(由下面的分支按“是否只影响绘图区”精确判断)之外,
|
||||
// 其余消息都可能改变标签栏 / 工具栏 / 面板的外观,统一标记装饰区域为脏。
|
||||
// WM_PAINT / WM_TIMER 的重绘范围由各自的处理分支判断,这里不做标记。
|
||||
if (message != WM_PAINT && message != WM_MOUSEMOVE && message != WM_MOUSELEAVE &&
|
||||
message != WM_SETCURSOR && message != WM_NCHITTEST && message != WM_TIMER &&
|
||||
message != WM_ERASEBKGND)
|
||||
{
|
||||
m_chromeDirty = true;
|
||||
}
|
||||
|
||||
// 菜单打开时优先消费鼠标与键盘事件,形成模态交互。
|
||||
if (m_menu.IsOpen())
|
||||
{
|
||||
@@ -1877,6 +1939,25 @@ namespace jdi
|
||||
return 1;
|
||||
case WM_PAINT:
|
||||
{
|
||||
// BeginPaint 会清空更新区域,因此必须先取出本次待重绘的范围:
|
||||
// 只有完全落在绘图区内的更新区域才允许走“局部回送”的快路径,
|
||||
// 其余情况(窗口首次显示、其它窗口遮挡后露出、DPI 变化等)必须整帧重绘。
|
||||
RECT update = {};
|
||||
const bool hasUpdate = GetUpdateRect(hwnd, &update, FALSE) != FALSE;
|
||||
if (hasUpdate)
|
||||
{
|
||||
const bool insidePlot =
|
||||
update.left >= static_cast<LONG>(std::floor(m_plotRect.X)) &&
|
||||
update.top >= static_cast<LONG>(std::floor(m_plotRect.Y)) &&
|
||||
update.right <= static_cast<LONG>(std::ceil(m_plotRect.X + m_plotRect.Width)) &&
|
||||
update.bottom <= static_cast<LONG>(std::ceil(m_plotRect.Y + m_plotRect.Height)) &&
|
||||
update.right > update.left && update.bottom > update.top;
|
||||
if (!insidePlot)
|
||||
{
|
||||
m_chromeDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
PAINTSTRUCT paintStruct = {};
|
||||
BeginPaint(hwnd, &paintStruct);
|
||||
Render();
|
||||
@@ -1898,9 +1979,28 @@ namespace jdi
|
||||
const int x = GET_X_LPARAM(lParam);
|
||||
const int y = GET_Y_LPARAM(lParam);
|
||||
const bool buttonDown = (wParam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) != 0;
|
||||
|
||||
// 仅当本次移动不会影响绘图区之外的区域时,才跳过装饰区域的重绘:
|
||||
// 正在拖拽绘图区(鼠标可能被捕获到绘图区外),或鼠标始终停留在绘图区内。
|
||||
const bool inPlot = UiPointInRect(m_plotRect, static_cast<float>(x), static_cast<float>(y));
|
||||
const bool plotOnlyMove = (m_activeTarget == Target::PLOT) || (inPlot && m_mouseOverPlot);
|
||||
if (!plotOnlyMove)
|
||||
{
|
||||
m_chromeDirty = true;
|
||||
}
|
||||
m_mouseOverPlot = inPlot;
|
||||
|
||||
if (HandleMouseMove(x, y, buttonDown))
|
||||
{
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
if (plotOnlyMove && !m_chromeDirty)
|
||||
{
|
||||
// 只失效绘图区,配合 WM_PAINT 中的判断即可走局部回送快路径。
|
||||
InvalidatePlotRect(hwnd, m_plotRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
}
|
||||
// 拖动(鼠标捕获)期间 Windows 不会派发 WM_SETCURSOR,
|
||||
// 因此需要主动刷新一次,否则光标形状会一直停在按下的状态。
|
||||
@@ -1913,11 +2013,25 @@ namespace jdi
|
||||
case WM_MOUSELEAVE:
|
||||
{
|
||||
m_trackingLeave = false;
|
||||
bool changed = m_tabBar.OnMouseLeave();
|
||||
changed = m_toolbar.OnMouseLeave() || changed;
|
||||
changed = m_panel.OnMouseLeave() || changed;
|
||||
changed = m_plot.OnMouseLeave() || changed;
|
||||
if (changed)
|
||||
// 只有标签栏 / 工具栏 / 面板的悬停态变化才需要重绘装饰区域;
|
||||
// 绘图区自身的悬停态变化(提示浮层、信号光晕)只需重绘绘图区,
|
||||
// 这样拖拽光标时鼠标短暂移出窗口也不会退化成整帧重绘。
|
||||
bool chromeChanged = m_tabBar.OnMouseLeave();
|
||||
chromeChanged = m_toolbar.OnMouseLeave() || chromeChanged;
|
||||
chromeChanged = m_panel.OnMouseLeave() || chromeChanged;
|
||||
const bool plotChanged = m_plot.OnMouseLeave();
|
||||
m_mouseOverPlot = false;
|
||||
|
||||
if (chromeChanged)
|
||||
{
|
||||
m_chromeDirty = true;
|
||||
}
|
||||
|
||||
if (plotChanged && !chromeChanged && !m_chromeDirty)
|
||||
{
|
||||
InvalidatePlotRect(hwnd, m_plotRect);
|
||||
}
|
||||
else if (chromeChanged || plotChanged)
|
||||
{
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
@@ -2262,6 +2376,10 @@ namespace jdi
|
||||
{
|
||||
cursor = m_plot.QueryCursor(point.x, point.y);
|
||||
}
|
||||
else if (UiPointInRect(m_panelRect, static_cast<float>(point.x), static_cast<float>(point.y)))
|
||||
{
|
||||
cursor = m_panel.QueryCursor(point.x, point.y);
|
||||
}
|
||||
if (cursor == nullptr)
|
||||
{
|
||||
cursor = LoadCursorW(nullptr, IDC_ARROW);
|
||||
|
||||
Reference in new issue
Block a user