#ifndef __UI_DEBUG_BOUNDS_HPP__ #define __UI_DEBUG_BOUNDS_HPP__ #include #include #include #include #include #include #include "PaintUtil.hpp" #include "Theme.hpp" namespace jdi { /** * @brief 布局边界描边颜色(按层级取色)。 * * 层级只用于**区分归属**,不代表重要性:层级越深说明越靠近叶子控件。 * * @param [IN] depth 层级(0 = 顶层组件区域,≥1 = 内部控件)。 * @return ARGB 颜色(不透明)。 */ std::uint32_t UiDebugBoundsColor(int depth); /** * @brief 一个待显示的布局边界 box(**纯值类型**)。 * * 刻意不继承控件基类:`PlotView` 的 4 层位图缓存、`SignalPanel` 的行位图缓存都要求 * "绘制由组件自己负责",把几何塞进继承体系会和缓存体系打架。box 只描述 * "哪里有一个矩形、属于第几层、在第几绘制层",因此**几何可以被绘制、命中与调试共用**, * 也不需要任何虚函数或生命周期管理。 */ struct UiDebugBox { gd::RectF rect; /**< 矩形(客户区坐标)。 */ gd::RectF clip; /**< 裁剪矩形(= 所属组件的区域;空 = 不裁剪)。 */ int depth = 0; /**< 层级(决定配色与包角粗细)。 */ int layer = 0; /**< 绘制层(越大越靠上):由 `UiDebugBoxList::PushLayer` 分配。 */ }; /** * @brief box 收集器(值语义容器)。 * * 由宿主每帧构造一次、交给各组件填充、再一次性画完 —— 全程只在「显示布局边界」打开时执行。 * * ⚠️ **两个排序键缺一不可**: * * `layer`(绘制层,来自 `PushLayer()`):保证**后收集的内容压住先收集的**。 * 没有它,"信号面板的行边界"会盖住"右键弹出的二级菜单"(菜单是后收集的浮层)。 * * `depth`(层级):同一层内先画父(浅层)再画子(深层),子元素的彩色包角才不被父覆盖。 */ class UiDebugBoxList : public UiTextRectSink { public: /** @brief 清空(复用同一实例时调用),同时把绘制层复位为 0。 */ void Clear(); /** * @brief 进入下一个绘制层。 * * 宿主在每个组件收集完之后调用一次;组件内部若有"浮层压住本体"的情形 * (例如菜单的二级子菜单)也应在收集子层之前调用。 */ void PushLayer() { ++m_layer; } /** * @brief 显式设置当前绘制层(绘制期间收集文本几何时用)。 * @param [IN] layer 层号(越大越靠上)。 */ void SetLayer(int layer) { m_layer = layer; } /** @brief 回报一段文本的墨迹矩形(由 `PaintUtil` 在绘制文本时调用)。 */ void OnTextRect(const gd::RectF& rect) override; /** * @brief 设置后续 box 的裁剪矩形(= 当前组件的区域)。 * * ⚠️ **必须像裁剪绘制一样裁剪调试 box**:组件内部元素可能落在地盘之外 * (典型:滚出可视区的网格 `lane.rect` 的 Y 会是负数),不裁剪就会画到相邻组件 * (工具栏 / 标签栏)上面 —— 这正是用户报告“工具栏上出现大量残留”的直接原因。 * * @param [IN] clip 裁剪矩形;传空矩形表示不裁剪。 */ void SetClip(const gd::RectF& clip) { m_clip = clip; } /** @brief 追加一个 box(空矩形 / 与裁剪矩形无交集的 box 自动忽略)。 */ void Add(const gd::RectF& rect, int depth); /** @brief 追加一组同层级的 box(空矩形逐个忽略)。 */ void AddAll(const std::vector& rects, int depth); /** * @brief 登记一个**遮挡矩形**(浮层本体,如弹出菜单 / 拾色器)。 * * ⚠️ 必须做这件事:调试 box 是画在所有内容**之后**的,仅靠 `layer` 排序只能决定 * “两个 box 重叠时谁压谁”,并不能阻止“下层组件的边框线画在不透明浮层上面”。 * 登记为 occluder 后,**绘制层比它低**的 box 在该区域内会被裁掉, * 于是观察右键菜单的空间布局时不会被底下的行/网格边界干扰。 * * @param [IN] rect 遮挡矩形(客户区坐标;空矩形忽略)。 */ void AddOccluder(const gd::RectF& rect); /** @brief 是否为空。 */ bool Empty() const { return m_boxes.empty(); } /** @brief box 数量。 */ std::size_t Count() const { return m_boxes.size(); } /** @brief 只读访问(**未排序**,绘制时内部会按 layer/depth 稳定排序)。 */ const std::vector& Items() const { return m_boxes; } /** @brief 一个遮挡矩形(含它所在的绘制层)。 */ struct Occluder { gd::RectF rect; /**< 遮挡矩形。 */ int layer = 0; /**< 所在绘制层(只遮挡更低的层)。 */ }; /** @brief 遮挡矩形集合(只读)。 */ const std::vector& Occluders() const { return m_occluders; } private: std::vector m_boxes; std::vector m_occluders; int m_layer = 0; /**< 当前绘制层。 */ gd::RectF m_clip; /**< 当前裁剪矩形(空 = 不裁剪)。 */ }; /** * @brief 绘制一个矩形的布局边界(**四角粗、四边细**,且粗细随矩形尺寸自适应)。 * * 视觉规则: * * 四条边固定 1 设备像素; * * 四个角额外加粗成 `⌜ ⌝ ⌞ ⌟` —— 这是"一眼定位元素边界"的关键, * 即使多个矩形共边也能靠角的形状分辨层次; * * **包角粗细与长度都随矩形短边自适应**:元素越小越细(小方框不会被描成"粗边框"), * 短边 ≤ 12px 时只画四个角块、不画边,短边 ≤ 5px 时完全不画(否则就盖住元素本身了); * * **不做任何内缩**:框必须与"可见形状的外接矩形"完全重合。 * 曾经为"父子共边不叠粗"内缩 1px,结果把胶囊/圆角元素(GX 徽标、色块、✕) * 的圆弧留在框外 —— 那是错误的取舍。重叠时让"后画的那个"覆盖即可; * * **只描边、不填充、不写字** —— 填充会糊住内容。 * * ⚠️ 线宽按**设备像素**固定(不乘 `scale`)并临时 `PixelOffsetModeNone` + 关抗锯齿, * 否则 1px 线会被抗锯齿摊成 2px 灰边,无法判断真实边界。 * * @param [IN,OUT] g 绘图上下文。 * @param [IN] rect 目标矩形(客户区坐标)。 * @param [IN] depth 层级(决定配色与包角基础粗细)。 */ void UiDebugPaintBounds(gd::Graphics& g, const gd::RectF& rect, int depth); /** * @brief `UiDebugPaintBounds` 的内部实现(带裁剪矩形)。 * * 供 `UiDebugPaintBoxList` 使用:非空 `clip` 表示"只在所属组件的地盘内绘制"。 * * @param [IN,OUT] g 绘图上下文。 * @param [IN] rect 目标矩形。 * @param [IN] clip 裁剪矩形(空 = 不裁剪)。 * @param [IN] depth 层级。 */ void UiDebugPaintBoundsClipped(gd::Graphics& g, const gd::RectF& rect, const gd::RectF& clip, int depth); /** * @brief 一次性画完整个 box 列表(在宿主绘制的最后一步调用)。 * * 按 `(layer, depth)` 稳定排序后绘制:后收集的层压住先收集的层,同层内父先子后。 * * @param [IN,OUT] g 绘图上下文。 * @param [IN] boxes box 列表。 */ void UiDebugPaintBoxList(gd::Graphics& g, const UiDebugBoxList& boxes); } // namespace jdi #endif // __UI_DEBUG_BOUNDS_HPP__