Files
Just-DataInspector/Components/Shared/Inc/UiButtonWidget.hpp
T
DeepSeek V4.1 Flash ab19a6e72e feat(UI): 显示布局边界(Android 风格)+ 工具栏设置菜单(齿轮)+ 三处可见收益优化
## 1. 新功能:齿轮按钮 -> 应用设置菜单 -> 显示布局边界

- ToolbarAction::SETTINGS 位于「导入文件」左侧;工具栏布局改为
  「左侧固定簇(kLeftClusterCount = 2:齿轮 + 导入文件)+ 右侧按钮组」。
- 齿轮是矢量化绘制(8 齿 + 外环 + 轴孔,全部按 half 的比例推导),
  不用字体字符「⚙」——该字符在不同回退字体里大小与基线不一致,会忽大忽小、垂直不居中。
  UiButtonWidget 新增 UiButtonIcon(NONE / GEAR)与 SetIcon。
- MenuScope::APP_SETTINGS(新增)+ 设置菜单:HEADER「调试」+ 勾选切换项「显示布局边界」
  (keepOpen:点击后菜单保持打开,勾选态由 UpdateItemLabel 回写)。
  锚点取 Toolbar::ButtonRect(SETTINGS) 的控件实测矩形,宿主不再自己推算按钮几何。

## 2. 布局边界:基类给协议,组件自己上报

- UiComponent 新增 virtual PaintDebugBounds(g, scale),默认只画 Area()。
- Toolbar / TabBar 覆盖:跳过与区域重合的根控件,遍历 m_root->Children()(层级 1);
  TabBar 另上报每个 ✕(层级 2)。
- SignalPanel 覆盖:表头 / 筛选框 / 列表 / 样式区(1)+ 滚动条、样式行、当前可见列表行(2)。
- PlotView 覆盖:每个 LaneVisual::rect(1);不上报随数据变化的瞬态几何(曲线/胶囊/提示)。
- UiWidget 新增 PaintDebugBounds(g, scale, depth) 递归整棵控件树 —— 已上树的控件零成本获得边界。
- 宿主 PaintLayoutBounds 补客户区外框、分隔条、打开的菜单/拾色器。

视觉与实现约束(Android「Show layout bounds」风):
- 只描边、不填充(几十个嵌套矩形叠加填充会把内容糊住);
- 层级配色 青蓝(2px) / 品红 / 琥珀 / 绿(1px);
- 线宽按设备像素固定(不乘 scale),临时 PixelOffsetModeNone + 关抗锯齿 + 整像素对齐,
  保证 1px 就是 1px;
- ⚠️ 只在 Render 最后一步叠加,绝不进入任何位图缓存(PlotView 4 层缓存 / SignalPanel 行位图缓存),
  否则关掉开关后线条会残留;
- ⚠️ fullFrame 并入 m_showLayoutBounds:开启时必须整帧回送,否则「只回送绘图区」的快路径
  会让装饰区边界停在旧帧上。

## 3. 可见收益优化(行为不变)

- Toolbar::Layout:控件树改为按需重建(m_treeDirty),只在规格/字体/主题/缩放/动态文本
  变化时重建。拖动面板分隔条时 Relayout 每帧调用一次 -> 不再每帧 10 次堆分配 + 8 次测量缓存失效。
- Toolbar::SetActionLabel:只写 m_labelOverrides(唯一权威)+ 置树脏,消除「覆盖表与控件文本
  双写」可能不同步的问题。
- UiWidget::DispatchRedraw(event):新增;收敛 Toolbar / TabBar 里重复的
  Dispatch(); return UiRectHasArea(TakeDirty());(TakeDirty 会清零,漏一次就把下一次真实变化
  误判成「无变化」)。

## 4. 验证

- make release / make debug:零 error 零 warning;TargetCheck.exe 897,024 B(< 900 KB)。
- 新增离屏探针 .preflight/lbcheck:21 / 21 PASS
  (齿轮在导入文件左侧/同高/更窄;开启后青蓝 24,930 px、品红 19,734 px;关闭后逐像素还原;
   菜单 scope、锚定贴合按钮、宽度未被撑大、条目初始未勾选、点击翻转开关、菜单保持打开)。
- Tools/rendercheck:8 个场景的「工具栏带以下区域」哈希与改造前**逐一相同**
  (证明改动像素被完全限制在工具栏带内);整帧哈希因新增按钮整体变化,属预期。
  FUNC 断言 7 / 7 PASS。
- Tools/uicheck:69 passed / 0 failed。

## 5. 文档

Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第六阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第八轮」硬约束与验证坑。
2026-10-28 15:46:45 +08:00

103 lines
4.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef __UI_BUTTON_WIDGET_HPP__
#define __UI_BUTTON_WIDGET_HPP__
#include <functional>
#include <string>
#include "UiWidget.hpp"
namespace jdi
{
/**
* @brief 按钮图标(非 `NONE` 时按钮**只画图标**,忽略文本)。
*
* 图标用矢量绘制(不用字体里的符号):`⚙` 这类字符在中文/西文回退字体里
* 宽高与基线各不相同,会出现"图标忽大忽小、垂直不居中"的问题。
*/
enum class UiButtonIcon : int
{
NONE = 0, /**< 无图标(只画文本)。 */
GEAR = 1 /**< 齿轮:设置菜单入口。 */
};
/**
* @brief 一个"文字按钮"控件(工具栏按钮的原语)。
*
* 视觉规则与旧 `Toolbar` 的按钮**逐像素一致**:
* * 主按钮(`primary`)= 强调色实底 + `textOnAccent` 文字,按下 / 悬停时整体提亮或压暗;
* * 普通按钮 = 透明底 + 1px 描边,可切换按钮选中时用 `accentSoft` 底 + `accent` 描边;
* * 禁用时底色回退为工具栏底色、文字用 `textMuted`、描边与底色混 50%。
*
* 交互:按下即 `CaptureMouse()` 并进入按下态;**在自身范围内**松开才触发 `onClick`
*(与"按住移开再松开 = 放弃"的既有语义一致)。
*/
class UiButtonWidget : public UiWidget
{
public:
UiButtonWidget() = default;
/** @brief 一次性注入绘制资源与外观参数(尺寸由 `OnMeasure` 按文本实测得出)。 */
void Configure(const UiFonts* fonts, const Theme* theme, float scale, std::wstring label,
bool primary, bool toggle, float textPadding, float height);
/** @brief 设置图标(`NONE` 表示回到纯文本按钮)。 */
void SetIcon(UiButtonIcon icon);
/** @brief 当前图标。 */
UiButtonIcon Icon() const { return m_icon; }
/** @brief 修改文本(宽度会在下一次 `InvalidateMeasure` 后重新实测)。 */
void SetLabel(std::wstring label);
/** @brief 文本(只读)。 */
const std::wstring& Label() const { return m_label; }
/** @brief 设置可切换按钮的选中态。 */
void SetChecked(bool checked);
/** @brief 选中态。 */
bool Checked() const { return m_checked; }
/** @brief 是否可切换按钮。 */
bool IsToggle() const { return m_toggle; }
/** @brief 设置可用性(禁用时不可点、不可命中)。 */
void SetEnabled(bool enabled);
/** @brief 是否可用。 */
bool Enabled() const { return m_enabled; }
/** @brief 是否处于按下态(按下未松开)。 */
bool Pressed() const { return m_pressed; }
/** @brief 放弃当前按下(指针离开窗口等不会再收到抬起的情形)。 */
void CancelPress();
/** @brief 点击回调(在自身范围内松开时触发)。 */
std::function<void()> onClick;
/** @brief 工具栏底色(普通按钮的底色与禁用配色都以它为基准)。 */
void SetCanvasColor(std::uint32_t color) { m_canvasColor = color; }
protected:
gd::SizeF OnMeasure(const UiMeasureContext& ctx) override;
void OnPaint(UiPaintContext& ctx) override;
UiEventResult OnEvent(UiEvent& event) override;
HCURSOR OnQueryCursor(int x, int y) override;
std::uint64_t ContentKey() const override;
private:
const UiFonts* m_fonts = nullptr; /**< 字体。 */
const Theme* m_theme = nullptr; /**< 主题。 */
float m_scale = 1.0f; /**< 缩放系数。 */
std::wstring m_label; /**< 按钮文本。 */
UiButtonIcon m_icon = UiButtonIcon::NONE; /**< 按钮图标(非 `NONE` 时忽略文本)。 */
bool m_primary = false; /**< 主按钮(强调色实底)。 */
bool m_toggle = false; /**< 是否可切换。 */
bool m_checked = false; /**< 可切换按钮的选中态。 */
bool m_enabled = true; /**< 是否可用。 */
bool m_pressed = false; /**< 按下态。 */
float m_textPadding = 14.0f; /**< 左右内边距(逻辑像素)。 */
float m_height = 30.0f; /**< 按钮高度(逻辑像素)。 */
std::uint32_t m_canvasColor = 0; /**< 工具栏底色(配色基准)。 */
};
} // namespace jdi
#endif // __UI_BUTTON_WIDGET_HPP__