Files
Just-DataInspector/Components/Toolbar/Src/Toolbar.cpp
T
DeepSeek V4.1 Flash 9c6c374cce feat: 支持多视图标签页、按文件分组的多文件信号管理与网格随勾选自动收敛
数据层与视图配置解耦:新增 DataSet(多文件只读数据层)与 TabConfig(每标签页独立配置),
以 SignalRef{fileIndex,signalIndex} 标识全局信号,替换原有单文件 SignalSet。

- 标签页:新增 TabBar 组件,支持新建/切换/关闭标签页;每页独立保存信号勾选、
  颜色/线宽/显示方式、网格划分、视图范围与时间光标,互不影响。
- 网格收敛:可见性变更统一经 TabConfig 的 Set*Visible 接口并同步重算网格,
  取消勾选后对应的空网格立即回收(修复此前留下空轨道的问题)。
- 多文件分组:左侧面板改为「文件分组 + 信号」树形列表,分组可折叠、可整组勾选,
  分组复选框支持全选/部分/未选三态;新加载文件的信号默认未勾选,由用户自行选择。
- 来源标识:信号行、样式编辑区标题、波形图例与光标读数在跨文件时标注来源文件名。
- 交互补充:面板滚动条支持点击跳转与拖拽,切换标签页自动滚动到已选信号。
- 工具栏精简为「打开 CSV/适应视图/放大/缩小/合并网格/每信号一格/全选信号/清空勾选」,
  按钮宽度改为按实际文本像素测量;打开对话框支持多选文件,拖放支持批量载入。
- 新增 TestData/[2].CSV 作为跨文件对比测试数据。
2026-09-29 15:48:04 +08:00

277 lines
6.7 KiB
C++

#include "Toolbar.hpp"
#include <cmath>
#include "Utf8Util.hpp"
namespace jdi
{
namespace
{
const float kButtonHeight = 30.0f; /**< 按钮高度(逻辑像素)。 */
const float kButtonGap = 6.0f; /**< 按钮间距。 */
const float kButtonPadding = 14.0f; /**< 按钮左右内边距。 */
const float kGroupGap = 18.0f; /**< 分组间距。 */
const float kButtonRadius = 5.0f; /**< 按钮圆角半径。 */
const float kSidePadding = 12.0f; /**< 工具栏左右留白。 */
const float kTopPadding = 8.0f; /**< 工具栏上下留白。 */
}
Toolbar::Toolbar() = default;
void Toolbar::Setup(const UiFonts* fonts, const Theme* theme)
{
m_fonts = fonts;
m_theme = theme;
}
void Toolbar::SetScale(float scale)
{
m_scale = (scale > 0.1f) ? scale : 1.0f;
}
void Toolbar::SetDocumentLoaded(bool hasDocument)
{
m_documentLoaded = hasDocument;
}
void Toolbar::SetStatusText(const std::wstring& text)
{
m_statusText = text;
}
float Toolbar::DesiredHeight() const
{
return (kButtonHeight + kTopPadding * 2.0f) * m_scale;
}
bool Toolbar::IsEnabled(const Button& button) const
{
return button.needsDocument ? m_documentLoaded : true;
}
void Toolbar::Layout(const gd::RectF& area)
{
m_area = area;
m_buttons.clear();
if (m_fonts == nullptr || m_fonts->ui == nullptr)
{
return;
}
struct Spec
{
ToolbarAction action;
const wchar_t* label;
bool primary;
bool needsDocument;
};
const Spec specs[] = {
{ToolbarAction::OPEN_FILE, L"打开 CSV", true, false},
{ToolbarAction::FIT_ALL, L"适应视图", false, true},
{ToolbarAction::ZOOM_IN, L"放大", false, true},
{ToolbarAction::ZOOM_OUT, L"缩小", false, true},
{ToolbarAction::MERGE_LANES, L"合并网格", false, true},
{ToolbarAction::SPLIT_LANES, L"每信号一格", false, true},
{ToolbarAction::SELECT_ALL, L"全选信号", false, true},
{ToolbarAction::CLEAR_ALL, L"清空勾选", false, true},
};
// 按钮宽度按实际文本像素宽 + 固定内边距计算,避免中文标签被裁切。
float cursorX = area.X + kSidePadding * m_scale;
const float top = area.Y + kTopPadding * m_scale;
const float height = kButtonHeight * m_scale;
const float horizontalPadding = kButtonPadding * 2.0f * m_scale;
for (const Spec& spec : specs)
{
Button button;
button.action = spec.action;
button.label = spec.label;
button.primary = spec.primary;
button.needsDocument = spec.needsDocument;
button.width = horizontalPadding + UiMeasureTextWidth(m_fonts->ui, button.label);
button.rect = gd::RectF(cursorX, top, button.width, height);
cursorX += button.width + kButtonGap * m_scale;
if (spec.action == ToolbarAction::OPEN_FILE || spec.action == ToolbarAction::ZOOM_OUT ||
spec.action == ToolbarAction::SPLIT_LANES)
{
cursorX += kGroupGap * m_scale;
}
m_buttons.push_back(std::move(button));
}
}
void Toolbar::Paint(gd::Graphics& g)
{
if (m_theme == nullptr || m_fonts == nullptr)
{
return;
}
UiFillRect(g, m_area, m_theme->toolbarBg);
for (const Button& button : m_buttons)
{
const bool enabled = IsEnabled(button);
std::uint32_t background = 0;
std::uint32_t border = 0;
std::uint32_t textColor = 0;
if (button.primary)
{
background = enabled ? m_theme->accent : MixColor(m_theme->accent, m_theme->toolbarBg, 0.6);
if (button.pressed)
{
background = MixColor(m_theme->accent, 0xFF000000u, 0.12);
}
else if (enabled && button.hover)
{
background = MixColor(m_theme->accent, 0xFFFFFFFFu, 0.12);
}
border = background;
textColor = m_theme->textOnAccent;
}
else
{
background = m_theme->toolbarBg;
if (button.pressed)
{
background = m_theme->pressed;
}
else if (enabled && button.hover)
{
background = m_theme->hover;
}
border = MixColor(m_theme->border, m_theme->toolbarBg, enabled ? 0.0 : 0.5);
textColor = enabled ? m_theme->text : m_theme->textMuted;
}
if (background != m_theme->toolbarBg)
{
UiFillRoundRect(g, button.rect, kButtonRadius * m_scale, background);
}
if (!button.primary)
{
UiDrawRoundRect(g, button.rect, kButtonRadius * m_scale, border, 1.0f);
}
UiText(g, button.label, m_fonts->ui, button.rect, textColor, TextAlign::CENTER);
}
// 右侧状态文本。
if (!m_statusText.empty())
{
const float rightPadding = kSidePadding * m_scale;
gd::RectF textRect(m_area.X, m_area.Y, m_area.Width - rightPadding, m_area.Height);
UiText(g, m_statusText, m_fonts->uiSmall, textRect, m_theme->textMuted, TextAlign::RIGHT);
}
// 底部分隔线。
UiDrawLine(g, m_area.X, m_area.Y + m_area.Height - 1.0f, m_area.X + m_area.Width,
m_area.Y + m_area.Height - 1.0f, m_theme->border, 1.0f);
}
bool Toolbar::HitTest(int x, int y) const
{
return UiPointInRect(m_area, static_cast<float>(x), static_cast<float>(y));
}
bool Toolbar::OnMouseMove(int x, int y)
{
int hoverIndex = -1;
for (std::size_t index = 0; index < m_buttons.size(); ++index)
{
if (UiPointInRect(m_buttons[index].rect, static_cast<float>(x), static_cast<float>(y)))
{
hoverIndex = static_cast<int>(index);
break;
}
}
if (hoverIndex == m_hoverIndex)
{
return false;
}
m_hoverIndex = hoverIndex;
return true;
}
bool Toolbar::OnLButtonDown(int x, int y, ToolbarAction& action)
{
action = ToolbarAction::NONE;
for (std::size_t index = 0; index < m_buttons.size(); ++index)
{
Button& button = m_buttons[index];
if (!UiPointInRect(button.rect, static_cast<float>(x), static_cast<float>(y)))
{
continue;
}
if (!IsEnabled(button))
{
return false;
}
m_pressedIndex = static_cast<int>(index);
button.pressed = true;
action = button.action;
return true;
}
return false;
}
bool Toolbar::OnLButtonUp(int x, int y, ToolbarAction& action)
{
action = ToolbarAction::NONE;
if (m_pressedIndex < 0)
{
return false;
}
const int index = m_pressedIndex;
m_pressedIndex = -1;
if (index >= static_cast<int>(m_buttons.size()))
{
return false;
}
Button& button = m_buttons[static_cast<std::size_t>(index)];
button.pressed = false;
const bool inside = UiPointInRect(button.rect, static_cast<float>(x), static_cast<float>(y));
m_hoverIndex = inside ? index : -1;
if (inside && IsEnabled(button))
{
action = button.action;
}
return true;
}
bool Toolbar::OnMouseLeave()
{
bool changed = false;
if (m_hoverIndex >= 0)
{
m_hoverIndex = -1;
changed = true;
}
if (m_pressedIndex >= 0)
{
if (m_pressedIndex < static_cast<int>(m_buttons.size()))
{
m_buttons[static_cast<std::size_t>(m_pressedIndex)].pressed = false;
}
m_pressedIndex = -1;
changed = true;
}
return changed;
}
} // namespace jdi