diff --git a/CLAUDE.md b/CLAUDE.md index 30f7870..e67aa97 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -680,8 +680,9 @@ QQuickDetailsViewModel::setObject(QObject*) ← QAbstractItemModel │ ├── 有 IPropertyTypeCustomization? │ │ YES → customizeChildren(handle, layoutBuilder) │ │ NO → PropertyTypeCustomization_ObjectDefault::customizeChildren() - │ │ └─ 遍历 metaObject->propertyCount() - │ │ └─ 每个 QMetaProperty 创建一个子 QPropertyHandle + │ │ ├─ 检查 objectHandle 是否实现 IPropertyGroupProvider + │ │ │ YES → 按 group 名称收集属性 → addGroup() + 子 LayoutBuilder + │ │ │ NO → 遍历 metaObject->propertyCount() 平铺属性 │ │ └─ layoutBuilder->addProperty(childHandle) │ │ │ ▼ @@ -739,23 +740,27 @@ class QQuickDetailsViewModel : public QAbstractItemModel { }; ``` -**模型的树结构**: +**模型的树结构**(带分组示例): ``` mRoot (QDetailsViewRow_Property, 不可见) - ├── child[0]: QDetailsViewRow_Property("Int") ← QMetaProperty - ├── child[1]: QDetailsViewRow_Property("Float") - ├── child[2]: QDetailsViewRow_Property("LimitedDouble") - ├── child[3]: QDetailsViewRow_Property("String") - ├── ... - ├── child[N]: QDetailsViewRow_Property("CustomType") ← IPropertyTypeCustomization - │ ├── child[0]: QDetailsViewRow_Property("ArraySize") - │ └── child[1]: QDetailsViewRow_Property("Array") - │ ├── child[0]: QDetailsViewRow_Property("[0]") - │ ├── child[1]: QDetailsViewRow_Property("[1]") - │ └── ... - └── child[N+1]: QDetailsViewRow_Property("CustomGadget") - └── child[0]: QDetailsViewRow_Property("LimitedDouble") - └── child[1]: QDetailsViewRow_Property("Desc") + ├── QDetailsViewRow_Group("数值") ← 分组标题(可折叠) + │ ├── QDetailsViewRow_Property("Int") ← QMetaProperty + │ ├── QDetailsViewRow_Property("Float") + │ └── QDetailsViewRow_Property("LimitedDouble") + ├── QDetailsViewRow_Group("文本") ← 分组标题(可折叠) + │ ├── QDetailsViewRow_Property("String") + │ └── QDetailsViewRow_Property("Directory") + ├── QDetailsViewRow_Group("自定义类型") ← 分组标题(可折叠) + │ ├── QDetailsViewRow_Property("CustomEnum") + │ ├── QDetailsViewRow_Property("CustomType") ← IPropertyTypeCustomization + │ │ ├── QDetailsViewRow_Property("ArraySize") + │ │ └── QDetailsViewRow_Property("Array") + │ │ ├── QDetailsViewRow_Property("[0]") + │ │ └── QDetailsViewRow_Property("[1]") + │ └── QDetailsViewRow_Property("CustomGadget") + │ ├── QDetailsViewRow_Property("LimitedDouble") + │ └── QDetailsViewRow_Property("Desc") + └── QDetailsViewRow_Property("m_lastBoudingRectF") ← 未分组属性(直接挂根) ``` #### 3. QPropertyHandle(属性操作的统一入口) @@ -813,10 +818,21 @@ IDetailsViewRow (抽象基类) │ 否则 → makePropertyRow(handle)(默认的名称+值编辑器布局) │ attachChildren(): 如果有 IPropertyTypeCustomization → customizeChildren() │ 否则 → PropertyTypeCustomization_ObjectDefault(遍历 metaObject->propertyCount()) - └── QDetailsViewRow_Custom ← 支持通过 lambda 创建自定义行 - setupItem(): 调用 mRowCreator(&builder) 直接构造 QML + ├── QDetailsViewRow_Custom ← 支持通过 lambda 创建自定义行 + │ setupItem(): 调用 mRowCreator(&builder) 直接构造 QML + └── QDetailsViewRow_Group ← 可折叠的属性分组标题行 + isGroup() 返回 true,setupItem() 渲染粗体分组标题 + 折叠时显示子项计数 "(N)" ``` +**QDetailsViewRow_Group**: +- `isGroup()` 返回 `true`,通过 model 的 `isGroup` role 暴露给 QML delegate +- `setupItem()` 使用 `makeNameValueSlot()` 构建行布局: + - **名称区**:粗体 (`font.bold: true`) 文本显示分组名(如"数值"、"颜色") + - **值区**:折叠时显示 `"(N)"` 表示子属性数量(通过 `groupChildCount` 属性绑定) + - 高度固定 25px,颜色使用 `ColorPalette.theme.labelPrimary` +- 展开/折叠由 delegate 模板中的 `TapHandler` 触发 `detailsView.toggleExpanded(row)` +- `setObject()` 时 C++ 侧自动展开所有 group 行(一次性,避免 delegate 重建时的重复展开循环) + #### 6. IPropertyTypeCustomization(类型自定义扩展接口) ```cpp class IPropertyTypeCustomization { @@ -858,6 +874,7 @@ QHash mTypeEditorCreatorMap; - **LayoutBuilder**:管理子行结构 - `addProperty(handle, overrideName)` — 添加属性子行 - `addCustomRow(lambda, overrideName)` — 添加自定义行 + - `addGroup(groupName)` — 创建 `QDetailsViewRow_Group` 分组标题行,返回其指针供后续向组内添加子行 - `addObject(QObject*)` — 添加对象作为属性行 ### Q_CLASSINFO 元数据系统 @@ -911,8 +928,9 @@ void CMainWindow::onSignal_selectionChanged() { ``` **当前图元的 Q_PROPERTY 状态**: -- `GraphicsBusSectionItem`:唯一完整声明了 Q_PROPERTY 的图元(Int, Float, LimitedDouble, String, Directory, Vec2-4, Mat4, Color, ColorList, ColorMap, CustomEnum, CustomType, CustomGadget, CustomGadgetPtr, CustomGadgetSharedPtr 等) — 这些是 PropertyEditor 示例代码中的测试属性 -- `GraphicsBaseItem`、`GraphicsRectItem`、`GraphicsPolygonItem`、`GraphicsItemGroup`:**无 Q_PROPERTY 声明** — 因此属性编辑器不会显示任何属性行 +- `GraphicsBusSectionItem`:唯一完整声明了 Q_PROPERTY 的图元(Int, Float, LimitedDouble, String, Directory, Vec2-4, Mat4, Color, ColorList, ColorMap, CustomEnum, CustomType, CustomGadget, CustomGadgetPtr, CustomGadgetSharedPtr 等) — 同时重写 `getPropertyGroup()` 将属性分入 5 个可折叠分组(数值、文本、向量/矩阵、颜色、自定义类型) +- `GraphicsBaseItem` 实现了 `IPropertyGroupProvider` 接口,默认返回空字符串(无分组),子类可重写 +- `GraphicsRectItem`、`GraphicsPolygonItem`、`GraphicsItemGroup`:**无 Q_PROPERTY 声明** — 因此属性编辑器不会显示任何属性行 **BayTemplate 自定义类型**: - `QCustomType` (结构体):`{ unsigned int ArraySize; QVector Array; }` — 通过 `PropertyTypeCustomization_CustomType` 定制,header 显示 Sort 按钮,children 展开为 ArraySize + Array @@ -934,6 +952,113 @@ QPropertyHandle::setVar(var) └── if (currVar != var) emit asRequestRollback(currVar) ← 值被拦截时通知编辑器回滚 ``` +### 属性分组系统 (Property Grouping) + +属性分组允许 QObject 子类将 Q_PROPERTY 属性归类到可折叠的分组标题下,使属性编辑器在有大量属性时更易浏览。 + +#### IPropertyGroupProvider 接口 + +```cpp +// PropertyEditor/source/include/IPropertyGroupProvider.h +class IPropertyGroupProvider { +public: + virtual ~IPropertyGroupProvider() = default; + virtual QString getPropertyGroup(const QString& propertyName) const = 0; +}; +``` + +- 返回空字符串表示属性不分组(默认行为) +- 返回非空字符串表示属性属于该名称的分组 +- 同名分组的属性会被收集到同一个 group 下,按分组调用顺序排列 +- **不属于 PropertyEditor 库本身**,而是由使用方(如 BayTemplate 的 GraphicsBaseItem)实现 + +#### 分组构建流程 + +``` +PropertyTypeCustomization_ObjectDefault::customizeChildren(handle, builder) + │ + ├── 检查 objectHandle->getObject() 是否实现 IPropertyGroupProvider? + │ NO → 原有逻辑:遍历 metaObject->propertyCount() 平铺所有属性 + │ + │ YES → 新分组逻辑: + │ 1. 遍历所有 QMetaProperty + │ 2. 调用 groupProvider->getPropertyGroup(prop.name()) 获取分组名 + │ 3. 空字符串 → 加入 ungroupedProps 列表 + │ 4. 非空 → 加入对应 groupedProps[groupName] 列表 + │ 5. 遍历 groupedProps: + │ ├── builder->addGroup(groupName) → 创建 QDetailsViewRow_Group + │ └── 在 group 内用子 LayoutBuilder 添加该组的属性 + │ 6. 遍历 ungroupedProps: + │ └── builder->addProperty() 直接添加到根(不分组) +``` + +#### QDetailsViewRow_Group 渲染 + +在 QML delegate 中通过 `isGroup` role 区分 group 行和普通属性行: + +- **已展开**:显示粗体分组标题(如"颜色"),右侧无内容 +- **已折叠**:显示粗体分组标题 + 右侧 `"(N)"` 显示子属性数量 +- 展开/折叠箭头由 `makeNameValueSlot()` 中的图标控制(根据 `detailsDelegate.expanded` 选择 expand.png / unexpand.png) +- 点击行触发 `TapHandler { onTapped: detailsView.toggleExpanded(row) }` + +**折叠计数实现**(`QQuickDetailsViewRow.cpp`): +```qml +// 在 value slot 区域创建 countLabel +Item { + property int groupChildCount: 0 + visible: !detailsDelegate.expanded // 仅折叠时可见 + Text { + anchors.right: parent.right + text: "(" + groupChildCount + ")" + color: ColorPalette.theme.textPrimary + } +} +``` +`groupChildCount` 在 C++ 侧通过 `countLabel->setProperty("groupChildCount", childCount)` 设置。 + +#### Model 角色扩展 + +ViewModel 新增 `isGroup` role: + +```cpp +// QQuickDetailsViewModel +enum Roles { name = 0, isGroup }; +// roleNames() 返回 { { Roles::isGroup, "isGroup" } } + +// data() 分发 +case Roles::isGroup: return node->isGroup(); +``` + +`QQuickDetailsViewPrivate::updateRequiredProperties()` 在每个 delegate 初始化时通过 `setRequiredProperty("isGroup", ...)` 将值推送到 QML delegate。 + +#### setObject() 自动展开 + +`QQuickDetailsView::setObject()` 在切换检查对象后,遍历顶层 model 行,对所有 `isGroup()` 为 true 的行调用 `expand(row)`。这是一次性的 C++ 侧操作,在 delegate 创建之前完成,避免 delegate 重建时的重复展开循环。 + +#### BayTemplate 集成 + +**GraphicsBaseItem** 实现 `IPropertyGroupProvider`,默认返回空字符串(无分组): + +```cpp +class GraphicsBaseItem : public QObject, public AbstractShape, public IPropertyGroupProvider { + QString getPropertyGroup(const QString&) const override { return {}; } +}; +``` + +**GraphicsBusSectionItem** 重写 `getPropertyGroup()` 作为使用示例(`source/graphicsItem/graphicsBusSectionItem.cpp`): + +| 分组名 | 包含属性 | +|--------|---------| +| 数值 | Int, Float, LimitedDouble | +| 文本 | String, Directory | +| 向量/矩阵 | Vec2, Vec3, Vec4, Mat4 | +| 颜色 | Color, ColorList, ColorMap | +| 自定义类型 | CustomEnum, CustomType, CustomGadget, CustomGadgetPtr, CustomGadgetSharedPtr | + +不分组(返回空)的属性:`m_lastBoudingRectF`, `m_lastBoudingPointF`, `m_dRatioX`, `m_dRatioY`(内部状态属性)。 + +**扩展方式**:任意 QObject 子类只需实现 `IPropertyGroupProvider` 接口并重写 `getPropertyGroup()`,PropertyEditor 的 `PropertyTypeCustomization_ObjectDefault` 会自动检测并使用分组布局。 + ### 关键文件索引 | 文件 | 角色 | @@ -952,7 +1077,8 @@ QPropertyHandle::setVar(var) | `PropertyEditor/source/src/QQuickDetailsViewRow.cpp` | 模型行节点(setupItem + attachChildren)| | `PropertyEditor/source/src/QQuickDetailsViewLayoutBuilder.cpp` | QML 布局构建器 | | `PropertyEditor/source/include/IPropertyTypeCustomization.h` | 类型定制接口 | -| `PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.cpp` | 默认 QObject 属性展开(遍历 QMetaProperty)| +| `PropertyEditor/source/include/IPropertyGroupProvider.h` | 属性分组接口(QObject 声明属性所属分组)| +| `PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.cpp` | 默认 QObject 属性展开(含分组检测逻辑)| | `PropertyEditor/source/include/QQuickDetailsViewMananger.h` | 全局注册中心单例 | | `PropertyEditor/source/src/QQuickDetailsViewMananger.cpp` | 类型编辑器注册、自定义类型查找 | | `PropertyEditor/source/src/QQuickDetailsViewBasicTypeEditor.cpp` | 基本类型编辑器注册(int, float, QString, QColor 等)|