#include "Toolbar.hpp" #include #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::FIT_TIME, 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}, }; // 按钮宽度先按固定预算计算,避免依赖绘图上下文。 float cursorX = area.X + kSidePadding * m_scale; const float top = area.Y + kTopPadding * m_scale; const float height = kButtonHeight * 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 = (kButtonPadding * 2.0f + static_cast(button.label.size()) * 13.0f) * m_scale; 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(x), static_cast(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(x), static_cast(y))) { hoverIndex = static_cast(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(x), static_cast(y))) { continue; } if (!IsEnabled(button)) { return false; } m_pressedIndex = static_cast(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(m_buttons.size())) { return false; } Button& button = m_buttons[static_cast(index)]; button.pressed = false; const bool inside = UiPointInRect(button.rect, static_cast(x), static_cast(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(m_buttons.size())) { m_buttons[static_cast(m_pressedIndex)].pressed = false; } m_pressedIndex = -1; changed = true; } return changed; } } // namespace jdi