UE5源码分析之Editor——AssetTagsEditor模块全面分析

AssetTagsEditor 模块全面系统分析


源文档:https://gitee.com/chouchouxia/share-ue5

目录

  1. 模块概述
  2. 模块整体架构解析
  3. 类级代码注释详解
  4. 功能使用示例编写
  5. 总结与最佳实践

1 模块概述

1.1 基本信息

属性
模块名称 AssetTagsEditor
类型 Editor(编辑器模块)
位置 Engine/Source/Editor/AssetTagsEditor
描述 提供内容浏览器中资产标签(Asset Tags)的 Slate UI 控件,包括标签项的显示、勾选、重命名、计数和工具提示功能

整个模块只有 5 个文件,代码量非常小,但它在 UE5 编辑器的内容浏览器中扮演着不可忽视的角色------我们每天在内容浏览器左侧看到的收藏夹(Collections)树、标签筛选面板,其每个条目都是由这个模块提供的控件渲染出来的。

模块的核心由一个 Slate 复合控件 SAssetTagItem 构成,辅以 SAssetTagItemTableRow 表格行容器、SAssetTagItemToolTip 自定义工具提示和 AssetTagItemTypes.h 中的类型定义。依赖关系非常简洁,只依赖了 CoreCoreUObjectSlateSlateCore 四个引擎基础模块。


2 模块整体架构解析

2.1 架构图

这个模块的架构相当扁平,没有复杂的继承层次,也没有子系统之间的耦合。它本质上就是一个"UI 控件 + 类型定义"的工具包:

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                      内容浏览器 (Content Browser)                      │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │          SAssetTagItemTableRow<ItemType> (模板表格行)           │ │
│  │  ┌──────────────────────────────────────────────────────────┐  │ │
│  │  │                   SAssetTagItem (核心控件)                 │  │ │
│  │  │  ┌──────────┐  ┌──────────────┐  ┌────────┐  ┌────────┐ │  │ │
│  │  │  │ SCheckBox │  │ SInlineEdit- │  │ SImage │  │ SBorder│ │  │ │
│  │  │  │ (勾选框)  │  │ ableTextBlock│  │(警告)  │  │(计数)  │ │  │ │
│  │  │  └──────────┘  │ (名称/重命名)│  └────────┘  └────────┘ │  │ │
│  │  │                └──────────────┘                          │  │ │
│  │  └──────────────────────────────────────────────────────────┘  │ │
│  │                            │                                     │ │
│  │                            ▼                                     │ │
│  │              SAssetTagItemToolTip (自定义工具提示)                │ │
│  └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│                  AssetTagItemTypes.h (类型定义层)                      │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │  EAssetTagItemViewMode : Standard / Compact                    │  │
│  │  FOnBuildAssetTagItemToolTipInfoEntry : TFunctionRef           │  │
│  │  FOnBuildAssetTagItemToolTipInfo : DECLARE_DELEGATE_OneParam   │  │
│  └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

2.2 模块间依赖关系

复制代码
AssetTagsEditor 模块
    ↓ 依赖
┌──────────────┬──────────────┬──────────────┬──────────────┐
│    Core      │  CoreUObject │    Slate     │  SlateCore   │
│ (基础类型)   │ (UObject系统) │ (UI框架)     │ (UI核心)     │
└──────────────┴──────────────┴──────────────┴──────────────┘

仅依赖四个引擎基础模块,没有任何外部插件依赖。这意味着 AssetTagsEditor 是一个自包含的 UI 工具包------任何需要展示资产标签列表的编辑器功能都可以直接引用它。

2.3 模块划分

1. 类型定义层 --- AssetTagItemTypes.h

职责:

  • 定义 EAssetTagItemViewMode 枚举,区分 StandardCompact 两种展示模式
  • 定义 FOnBuildAssetTagItemToolTipInfo 委托类型,用于外部向工具提示中注入自定义信息行

特点:

  • 极简设计,只定义必要的类型别名
  • 使用 TFunctionRef 作为回调,避免不必要的拷贝开销

2. UI 控件层 --- SAssetTagItem

职责:

  • 渲染单个资产标签条目的完整 UI(勾选框 + 名称 + 警告图标 + 计数徽章)
  • 支持内联重命名(通过 SInlineEditableTextBlock
  • 支持选中高亮和拖放目标状态
  • 根据 ViewMode 自动切换 Standard / Compact 样式

特点:

  • 使用 Slate 声明式语法构建 UI
  • 所有外观属性通过 TAttribute 绑定,支持动态更新
  • 可选功能(勾选框、重命名、计数、警告)通过参数是否绑定的方式自动启用/禁用

3. 表格行容器 --- SAssetTagItemTableRow

职责:

  • SAssetTagItem 包装为 STableRow,使其可在 SListView / STreeView 中使用
  • 提供统一的选中边框和拖放目标边框样式

特点:

  • 模板类,可适配任意 ItemType
  • 自动处理选中边框的外边距,确保视觉一致性

4. 工具提示 --- SAssetTagItemToolTip

职责:

  • 在鼠标悬停时显示标签名称和详细信息
  • 通过 FOnBuildAssetTagItemToolTipInfo 委托让外部注入自定义信息行

特点:

  • 继承自 SToolTip,重写 OnOpening 延迟构建内容
  • 布局与内容浏览器瓦片视图的工具提示风格一致

2.4 数据流走向

标准渲染流程:

复制代码
1. 外部代码(如内容浏览器)创建 SAssetTagItemTableRow
   ↓
2. SAssetTagItemTableRow 内部创建 SAssetTagItem 作为内容
   ↓
3. SAssetTagItem::Construct() 被调用
   ├─ 创建 SAssetTagItemToolTip 作为工具提示
   ├─ 根据 ViewMode 选择样式后缀 (Standard / Compact)
   ├─ 根据 OnNameCommitted 是否绑定,决定使用 SInlineEditableTextBlock 还是 STextBlock
   ├─ 根据 IsChecked / OnCheckStateChanged 是否绑定,决定是否启用 SCheckBox
   ├─ 根据 WarningText 是否设置,决定是否显示警告图标
   └─ 根据 CountText 是否设置,决定是否显示计数徽章
   ↓
4. 渲染完成,显示在内容浏览器中

重命名流程:

复制代码
1. 用户右键点击标签 → 外部代码调用 SAssetTagItem::RequestRename()
   ↓
2. InlineRenameWidget->EnterEditingMode() 被调用
   ↓
3. SInlineEditableTextBlock 进入编辑模式
   ↓
4. 用户输入新名称 → OnVerifyName 验证 → OnNameCommitted 提交
   ↓
5. 外部代码处理重命名逻辑(如更新 Collection 名称)

工具提示显示流程:

复制代码
1. 用户鼠标悬停在 SAssetTagItem 上
   ↓
2. SToolTip::OnOpening() 被触发
   ↓
3. SAssetTagItemToolTip::CreateToolTipWidget() 构建内容
   ├─ 顶部区域:显示标签名称
   └─ 底部区域:调用 OnBuildToolTipInfo 委托获取详细信息
   ↓
4. 工具提示显示

2.5 核心技术栈

技术/类 用途
SCompoundWidget SAssetTagItem 的基类,Slate 复合控件
STableRow SAssetTagItemTableRow 的基类,表格行
SToolTip SAssetTagItemToolTip 的基类,工具提示
SCheckBox 勾选框,用于选中/取消选中标签
SInlineEditableTextBlock 内联可编辑文本,用于标签重命名
STextBlock 静态文本,用于显示标签名称和计数
SImage 警告图标
SBorder 计数徽章容器
SBox 计数徽章尺寸控制
TAttribute 属性绑定,实现响应式 UI 更新
TFunctionRef 轻量级函数引用,用于工具提示构建回调
FAppStyle 编辑器样式系统,获取统一的样式参数

3 类级代码注释详解

3.1 AssetTagItemTypes.h --- 类型定义

3.1.1 概述

AssetTagItemTypes.h 是整个模块的类型定义文件,虽然代码量极少,但它是模块的"约定层"------定义了所有控件之间通信的枚举和委托类型。

核心设计理念:

  • 极简:只定义必要的类型,不引入任何不必要的抽象
  • 约定优于配置:通过统一的委托类型,使外部代码遵循一致的接口约定

3.1.2 EAssetTagItemViewMode 枚举

cpp 复制代码
enum class EAssetTagItemViewMode : uint8
{
    Standard,
    Compact,
};

功能分析:

两个枚举值控制 SAssetTagItem 的视觉样式:

  • Standard(标准模式):适用于内容浏览器的默认视图,标签条目有正常的间距和字体大小
  • Compact(紧凑模式):适用于空间受限的场景(如侧边栏),标签条目使用更紧凑的间距和更小的字体

实现原理:

SAssetTagItem::Construct() 中,该枚举值会被转换为一个样式后缀字符串指针:

cpp 复制代码
const ANSICHAR* StyleSpecifier = nullptr;
if (InArgs._ViewMode == EAssetTagItemViewMode::Compact)
{
    StyleSpecifier = ".Compact";
}

然后通过 FAppStyle::GetMargin("ContentBrowser.AssetTagNamePadding", StyleSpecifier) 来获取对应的样式参数。当 StyleSpecifiernullptr 时,FAppStyle 会使用默认键名;当为 ".Compact" 时,会在键名后追加 .Compact 后缀去查找对应的紧凑样式。


3.1.3 委托类型定义

cpp 复制代码
typedef TFunctionRef<void(const FText&, const FText&)> FOnBuildAssetTagItemToolTipInfoEntry;
DECLARE_DELEGATE_OneParam(FOnBuildAssetTagItemToolTipInfo, const FOnBuildAssetTagItemToolTipInfoEntry&);

功能分析:

这两个类型构成了工具提示内容构建的回调链路:

  • FOnBuildAssetTagItemToolTipInfoEntry:一个 TFunctionRef,由工具提示内部调用,用来往信息框中添加一行(Key-Value 对)。使用 TFunctionRef 而非 TFunction 意味着它不持有函数对象的所有权,避免了不必要的堆分配
  • FOnBuildAssetTagItemToolTipInfo:由外部代码绑定的委托。当工具提示需要构建信息内容时,会将一个 FOnBuildAssetTagItemToolTipInfoEntry 实例传递给这个委托,外部代码调用这个实例来逐行添加信息

使用模式:

cpp 复制代码
// 外部代码绑定委托
OnBuildToolTipInfo.BindLambda([](const FOnBuildAssetTagItemToolTipInfoEntry& AddEntry)
{
    AddEntry(LOCTEXT("Key1", "类型"), LOCTEXT("Value1", "收藏夹"));
    AddEntry(LOCTEXT("Key2", "包含资产数"), FText::AsNumber(42));
});

这种设计巧妙地将"如何构建 UI"(由 SAssetTagItemToolTip 负责)和"要显示什么数据"(由外部代码负责)解耦开来了。


3.2 SAssetTagItem --- 核心标签条目控件

3.2.1 概述

SAssetTagItem 是整个模块的核心,它继承自 SCompoundWidget,是一条资产标签在内容浏览器中的完整 UI 呈现。它不是一个抽象基类,而是一个可以直接使用的具体控件------外部代码通过 SLATE_BEGIN_ARGS/SLATE_END_ARGS 宏定义的参数系统来定制它的外观和行为。

核心设计理念:

  • 可选功能自动启用:勾选框、重命名、警告图标、计数徽章都是可选的,通过是否绑定对应参数来决定是否启用
  • 属性绑定驱动 :所有外观属性(颜色、文本、可见性)都通过 TAttribute 绑定,支持外部动态更新
  • 声明式 UI 构建:使用 Slate 的声明式语法,UI 结构一目了然

3.2.2 SLATE_BEGIN_ARGS 参数系统

SAssetTagItem 的参数系统设计得非常完善,我们逐个来看:

参数 类型 必须 作用
ViewMode EAssetTagItemViewMode Standard 或 Compact 样式
BaseColor TAttribute<FLinearColor> 标签的基础颜色,派生明亮/暗淡/禁用三种状态
DisplayName TAttribute<FText> 标签的显示名称
CountText TAttribute<FText> 计数文本,不设置则隐藏计数徽章
WarningText TAttribute<FText> 警告文本,不设置则隐藏警告图标
HighlightText TAttribute<FText> 搜索高亮文本
IsNameReadOnly TAttribute<bool> 是否禁止重命名
IsCheckBoxEnabled TAttribute<bool> 是否启用勾选框
IsChecked TAttribute<ECheckBoxState> 勾选框状态,不设置则禁用勾选框
OnCheckStateChanged FOnCheckStateChanged 勾选状态变化回调,不绑定则禁用勾选框
OnBeginNameEdit FOnBeginTextEdit 开始编辑名称回调
OnNameCommitted FOnTextCommitted 名称提交回调,不绑定则使用只读文本
OnVerifyName FOnVerifyTextChanged 名称验证回调
IsSelected FIsSelected 选中状态回调
OnBuildToolTipInfo FOnBuildAssetTagItemToolTipInfo 构建工具提示信息回调

3.2.3 Construct() --- 构建函数

这是整个模块最核心的函数,我们来逐段拆解它的构建逻辑。

第一步:基础属性存储

cpp 复制代码
BaseColor = InArgs._BaseColor;
WarningText = InArgs._WarningText;
IsSelectedCallback = InArgs._IsSelected;

checkf(InArgs._DisplayName.IsSet(), TEXT("SAssetTagItem DisplayName must be set!"));

DisplayName 是唯一必须设置的参数,因为一个标签条目不能没有名字。checkf 确保在开发阶段就能发现这个错误。

第二步:创建工具提示

cpp 复制代码
TSharedRef<SAssetTagItemToolTip> AssetTagToolTip = SNew(SAssetTagItemToolTip)
    .DisplayName(InArgs._DisplayName)
    .OnBuildToolTipInfo(InArgs._OnBuildToolTipInfo);

工具提示在 Construct 阶段就创建好了,但内容(特别是详细信息部分)是延迟构建的------在 OnOpening 时才调用 CreateToolTipWidget()。这避免了每次属性变化都重建工具提示的开销。

第三步:确定样式后缀

cpp 复制代码
const ANSICHAR* StyleSpecifier = nullptr;
if (InArgs._ViewMode == EAssetTagItemViewMode::Compact)
{
    StyleSpecifier = ".Compact";
}

这个设计很巧妙------通过一个简单的字符串后缀来切换样式。FAppStyle::GetMargin() 和其他样式获取方法都支持这种后缀拼接,意味着不需要为每种视图模式定义一套全新的样式键。

第四步:智能选择名称控件

cpp 复制代码
TSharedPtr<SWidget> NameWidget;
if (InArgs._OnNameCommitted.IsBound())
{
    NameWidget = SAssignNew(InlineRenameWidget, SInlineEditableTextBlock)
        .Text(InArgs._DisplayName)
        .HighlightText(InArgs._HighlightText)
        .OnBeginTextEdit(InArgs._OnBeginNameEdit)
        .OnTextCommitted(InArgs._OnNameCommitted)
        .OnVerifyTextChanged(InArgs._OnVerifyName)
        .IsSelected(IsSelectedCallback)
        .IsReadOnly(InArgs._IsNameReadOnly);
}
else
{
    NameWidget = SNew(STextBlock)
        .Text(InArgs._DisplayName)
        .HighlightText(InArgs._HighlightText);
}

这是 SAssetTagItem 最精妙的设计之一:根据外部是否提供了重命名回调,自动选择使用可编辑还是只读文本控件 。如果 OnNameCommitted 没有绑定,标签就是只读的(比如大多数内置标签);如果绑定了,用户就可以双击或按 F2 来重命名(比如自定义收藏夹)。

第五步:构建主布局

cpp 复制代码
ChildSlot
[
    SAssignNew(HBox, SHorizontalBox)
    .ToolTip(AssetTagToolTip)
    +SHorizontalBox::Slot()
    .AutoWidth()
    [
        SAssignNew(CheckBox, SCheckBox)
        .IsEnabled(IsCheckBoxEnabled)
        .IsChecked(InArgs._IsChecked)
        .OnCheckStateChanged(InArgs._OnCheckStateChanged)
        .ToolTipText(this, &SAssetTagItem::GetCheckBoxTooltipText)
    ]

    +SHorizontalBox::Slot()
    .Padding(FAppStyle::GetMargin("ContentBrowser.AssetTagNamePadding", StyleSpecifier))
    .VAlign(VAlign_Center)
    [
        NameWidget.ToSharedRef()
    ]
];

主布局是一个水平盒子,包含勾选框和名称控件。注意勾选框的 IsEnabled 属性在代码前面已经做了处理:如果 IsCheckedOnCheckStateChanged 没有设置,IsCheckBoxEnabled 会被强制设为 false

第六步:条件性添加警告图标

cpp 复制代码
if (WarningText.IsSet())
{
    HBox->AddSlot()
        .AutoWidth()
        .VAlign(VAlign_Center)
        [
            SNew(SImage)
            .Visibility(this, &SAssetTagItem::GetWarningIconVisibility)
            .Image(FAppStyle::Get().GetBrush("Icons.Warning"))
            .ColorAndOpacity(FAppStyle::Get().GetSlateColor("Colors.AccentYellow"))
            .ToolTipText(WarningText)
        ];
}

警告图标只有在 WarningText 属性被设置了才会添加。而且即使设置了,它的可见性还取决于 GetWarningIconVisibility() 的返回值------如果警告文本为空字符串,图标会折叠隐藏。这提供了双重保护。

第七步:条件性添加计数徽章

cpp 复制代码
if (InArgs._CountText.IsSet())
{
    HBox->AddSlot()
        .AutoWidth()
        .Padding(FAppStyle::GetMargin("ContentBrowser.AssetTagCountPadding", StyleSpecifier))
        [
            SNew(SBorder)
            .Padding(0.0f)
            .VAlign(VAlign_Center)
            .BorderBackgroundColor(this, &SAssetTagItem::GetCountBackgroundColor)
            .BorderImage(FAppStyle::GetBrush("ContentBrowser.AssetTagBackground"))
            [
                SNew(SBox)
                .VAlign(VAlign_Center)
                .MinDesiredWidth(30.0f)
                [
                    SNew(STextBlock)
                    .Text(InArgs._CountText)
                    .Justification(ETextJustify::Center)
                ]
            ]
        ];
}

计数徽章是一个带圆角背景的文本块,最小宽度 30px,文本居中。背景色通过 GetCountBackgroundColor() 动态获取,与标签的基础颜色联动。


3.2.4 颜色派生方法

SAssetTagItem 提供了三个颜色派生方法,从 BaseColor 派生出不同透明度:

cpp 复制代码
FLinearColor GetAssetTagBrightColor() const
{
    return BaseColor.Get().CopyWithNewOpacity(0.3f);
}

FLinearColor GetAssetTagDullColor() const
{
    return BaseColor.Get().CopyWithNewOpacity(0.1f);
}

FLinearColor GetAssetTagDisabledColor() const
{
    return BaseColor.Get().CopyWithNewOpacity(0.04f);
}

设计意图:

这三个方法的设计体现了 UE5 编辑器 UI 中一个常见的模式------通过透明度来传达状态。不是重新定义一堆颜色常量,而是从一个基础颜色出发,用不同透明度来表示不同状态:

  • Bright(0.3):正常/激活状态,如计数徽章背景
  • Dull(0.1):次要/非激活状态
  • Disabled(0.04):禁用状态

这种方式的优势在于:如果外部改变了标签的颜色(比如给不同类型的标签设置不同的颜色标记),所有派生状态自动适配,不需要手动调整多套颜色。


3.2.5 勾选框工具提示

cpp 复制代码
FText GetCheckBoxTooltipText() const
{
    return CheckBox && CheckBox->IsEnabled()
        ? CheckBox->IsChecked() 
            ? NSLOCTEXT("AssetTagsEditor", "RemoveAssetSelectionFromAssetTagItem", "Untag the current asset selection")
            : NSLOCTEXT("AssetTagsEditor", "AddAssetSelectionToAssetTagItem", "Tag the current asset selection")
        : FText::GetEmpty();
}

勾选框的工具提示会根据当前状态动态切换文案:

  • 已勾选时:提示"取消当前选中资产的标签"
  • 未勾选时:提示"将当前选中资产打上标签"

这种动态文案让用户一眼就知道点击会发生什么,是很贴心的 UX 细节。


3.2.6 RequestRename() --- 请求重命名

cpp 复制代码
void RequestRename()
{
    if (InlineRenameWidget)
    {
        InlineRenameWidget->EnterEditingMode();
    }
}

这是一个简洁的公开方法,让外部代码可以程序化地触发重命名(比如右键菜单中的"重命名"选项)。如果标签是不可重命名的(InlineRenameWidget 为空),调用也没副作用。


3.3 SAssetTagItemTableRow --- 表格行容器

3.3.1 概述

SAssetTagItemTableRow 是一个模板类,继承自 STableRow<ItemType>,用于将 SAssetTagItem 包装为可在列表/树视图中使用的行。

模板参数:

  • ItemType:列表项的数据类型,透传给 STableRow

3.3.2 Construct() --- 构建函数

cpp 复制代码
void Construct(const typename STableRow<ItemType>::FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
    this->ConstructInternal(InArgs, InOwnerTableView);

    this->ConstructChildren(
        InOwnerTableView->TableViewMode,
        ApplySelectionBorderOuterPadding(InArgs._Padding),
        WrapContentInSelectionBorder(InArgs._Content.Widget)
        );
}

构建过程分三步:

  1. ConstructInternal:初始化 STableRow 内部状态
  2. ApplySelectionBorderOuterPadding:在原始 Padding 基础上增加底部 1px 的额外间距(避免相邻行的选中边框重叠)
  3. WrapContentInSelectionBorder:用 SBorder 包裹内容,边框笔刷绑定到 GetBorder() 方法

3.3.3 SetIsDropTarget() --- 设置拖放目标状态

cpp 复制代码
void SetIsDropTarget(const TAttribute<bool>& InIsDropTarget)
{
    IsDropTarget = InIsDropTarget;
}

当该行成为拖放目标时,GetBorder() 会返回 InactiveHoveredBrush(高亮边框),给用户视觉反馈。


3.3.4 GetBorder() --- 边框笔刷选择

cpp 复制代码
virtual const FSlateBrush* GetBorder() const override
{
    if (IsDropTarget.Get(false))
    {
        return &this->Style->InactiveHoveredBrush;
    }

    return STableRow<ItemType>::GetBorder();
}

拖放目标状态优先于默认选中状态------如果当前行既是拖放目标又被选中,显示拖放目标的高亮边框。


3.4 SAssetTagItemToolTip --- 自定义工具提示

3.4.1 概述

SAssetTagItemToolTip 继承自 SToolTip,为资产标签条目提供自定义工具提示。它重写了 OnOpening() 来延迟构建内容,这意味着工具提示内容只在真正需要显示时才构建,而不是在控件创建时就构建。


3.4.2 Construct() --- 构建函数

cpp 复制代码
void Construct(const FArguments& InArgs)
{
    DisplayName = InArgs._DisplayName;
    OnBuildToolTipInfo = InArgs._OnBuildToolTipInfo;

    SToolTip::Construct(
        SToolTip::FArguments()
        .TextMargin(1.0f)
        .BorderImage(FAppStyle::GetBrush("ContentBrowser.TileViewTooltip.ToolTipBorder"))
        );
}

注意这里只设置了工具提示的外框样式,没有设置内容 。内容在 OnOpening() 中延迟构建。


3.4.3 IsEmpty() --- 空状态检查

cpp 复制代码
virtual bool IsEmpty() const override
{
    return false;
}

始终返回 false,确保即使外部没有提供详细信息,工具提示也会显示(至少显示标签名称)。


3.4.4 OnOpening() --- 延迟构建

cpp 复制代码
virtual void OnOpening() override
{
    SetContentWidget(CreateToolTipWidget());
}

在工具提示即将显示时调用 CreateToolTipWidget() 构建内容。这种延迟构建的优势在于:

  • 如果工具提示从未显示,就不会浪费资源构建内容
  • 每次显示时都重新构建,确保内容是最新的

3.4.5 CreateToolTipWidget() --- 内容构建

工具提示的内容分为上下两个区域:

顶部区域:标签名称

cpp 复制代码
SNew(SBorder)
.Padding(6)
.BorderImage(FAppStyle::GetBrush("ContentBrowser.TileViewTooltip.ContentBorder"))
[
    SNew(STextBlock)
    .Text(DisplayName)
    .Font(FAppStyle::GetFontStyle("ContentBrowser.TileViewTooltip.NameFont"))
]

底部区域:详细信息(如果有)

cpp 复制代码
if (InfoBox->NumSlots() > 0)
{
    OverallTooltipVBox->AddSlot()
        .AutoHeight()
        .Padding(0, 4, 0, 0)
        [
            SNew(SBorder)
            .Padding(6)
            .BorderImage(FAppStyle::GetBrush("ContentBrowser.TileViewTooltip.ContentBorder"))
            [
                InfoBox
            ]
        ];
}

底部区域只有在有实际内容时才显示,避免出现空的灰色区块。


3.4.6 AddToToolTipInfoBox() --- 信息行添加

cpp 复制代码
static void AddToToolTipInfoBox(const TSharedRef<SVerticalBox>& InfoBox, const FText& Key, const FText& Value)
{
    InfoBox->AddSlot()
    .AutoHeight()
    .Padding(0, 1)
    [
        SNew(SHorizontalBox)
        +SHorizontalBox::Slot()
        .AutoWidth()
        .Padding(0, 0, 4, 0)
        [
            SNew(STextBlock)
            .Text(FText::Format(LOCTEXT("AssetTagTooltipKeyFormat", "{0}:"), Key))
            .ColorAndOpacity(FSlateColor::UseSubduedForeground())
        ]
        +SHorizontalBox::Slot()
        .AutoWidth()
        [
            SNew(STextBlock)
            .Text(Value)
            .ColorAndOpacity(FSlateColor::UseForeground())
            .WrapTextAt(700.0f)
        ]
    ];
}

每一行信息是一个水平盒子,包含:

  • 左侧:Key 文本(如"类型:"),使用 SubduedForeground 颜色(灰色)
  • 右侧:Value 文本,使用 Foreground 颜色(白色),支持 700px 处换行

4 功能使用示例编写

示例1:创建基础的资产标签条目

这是最基础的用法------创建一个只读的标签条目,显示名称和包含的资产数量。

cpp 复制代码
// 在某个 Slate 面板中
TSharedRef<SAssetTagItem> CreateBasicTagItem()
{
    return SNew(SAssetTagItem)
        .DisplayName(FText::FromString(TEXT("武器模型")))
        .CountText(FText::AsNumber(42))
        .BaseColor(FLinearColor(0.2f, 0.6f, 1.0f));  // 蓝色基调
}

渲染结果是一个带有蓝色计数徽章的"武器模型 42"标签条目。


示例2:创建支持勾选的标签条目

在内容浏览器中,选中一些资产后,我们可能需要给它们批量打标签。勾选框就是用来控制这个的。

cpp 复制代码
class FMyTagBrowser
{
public:
    TSharedRef<SAssetTagItem> CreateCheckableTagItem(const FString& TagName, int32 AssetCount)
    {
        return SNew(SAssetTagItem)
            .DisplayName(FText::FromString(TagName))
            .CountText(FText::AsNumber(AssetCount))
            .IsChecked(this, &FMyTagBrowser::IsTagChecked, TagName)
            .OnCheckStateChanged(this, &FMyTagBrowser::OnTagCheckStateChanged, TagName)
            .BaseColor(FLinearColor::Green);
    }

private:
    ECheckBoxState IsTagChecked(FString TagName) const
    {
        // 检查当前选中资产是否都已经打上了这个标签
        return CheckedTags.Contains(TagName)
            ? ECheckBoxState::Checked
            : ECheckBoxState::Unchecked;
    }

    void OnTagCheckStateChanged(ECheckBoxState NewState, FString TagName)
    {
        if (NewState == ECheckBoxState::Checked)
        {
            // 给当前选中的资产打上这个标签
            AddTagToSelectedAssets(TagName);
        }
        else
        {
            // 从当前选中的资产上移除这个标签
            RemoveTagFromSelectedAssets(TagName);
        }
    }

    TSet<FString> CheckedTags;
};

示例3:创建支持重命名的标签条目

对于用户自定义的收藏夹,需要支持重命名功能。

cpp 复制代码
TSharedRef<SAssetTagItem> CreateRenamableTagItem(const FString& InitialName)
{
    return SNew(SAssetTagItem)
        .DisplayName(FText::FromString(InitialName))
        .OnBeginNameEdit(this, &FMyPanel::OnTagBeginRename)
        .OnNameCommitted(this, &FMyPanel::OnTagNameCommitted)
        .OnVerifyName(this, &FMyPanel::OnTagVerifyName)
        .IsSelected(this, &FMyPanel::IsTagSelected);
}

bool FMyPanel::OnTagVerifyName(const FText& NewName, FText& OutError) const
{
    FString NewNameStr = NewName.ToString().TrimStartAndEnd();

    if (NewNameStr.IsEmpty())
    {
        OutError = LOCTEXT("EmptyNameError", "名称不能为空");
        return false;
    }

    if (ExistingTagNames.Contains(NewNameStr))
    {
        OutError = LOCTEXT("DuplicateNameError", "该名称已被使用");
        return false;
    }

    return true;
}

void FMyPanel::OnTagNameCommitted(const FText& NewName, ETextCommit::Type CommitType)
{
    if (CommitType == ETextCommit::OnEnter || CommitType == ETextCommit::OnUserMovedFocus)
    {
        FString NewNameStr = NewName.ToString().TrimStartAndEnd();
        if (!NewNameStr.IsEmpty())
        {
            RenameTag(CurrentRenamingTag, NewNameStr);
        }
    }
}

示例4:在列表视图中使用 SAssetTagItemTableRow

SAssetTagItem 嵌入到 SListView 中:

cpp 复制代码
// 数据模型
struct FTagItemData
{
    FString Name;
    int32 Count;
    FLinearColor Color;
};

// 行生成回调
TSharedRef<ITableRow> FMyTagBrowser::OnGenerateTagRow(
    TSharedPtr<FTagItemData> Item, 
    const TSharedRef<STableViewBase>& OwnerTable)
{
    return SNew(SAssetTagItemTableRow<TSharedPtr<FTagItemData>>, OwnerTable)
        .Padding(FMargin(0, 1))
        [
            SNew(SAssetTagItem)
            .DisplayName(FText::FromString(Item->Name))
            .CountText(FText::AsNumber(Item->Count))
            .BaseColor(Item->Color)
            .IsSelected_Lambda([this, Item]() 
            { 
                return SelectedItem == Item; 
            })
        ];
}

// 创建列表视图
TagListView = SNew(SListView<TSharedPtr<FTagItemData>>)
    .ListItemsSource(&TagItems)
    .OnGenerateRow(this, &FMyTagBrowser::OnGenerateTagRow);

示例5:使用工具提示信息委托

给标签条目的工具提示添加自定义信息:

cpp 复制代码
TSharedRef<SAssetTagItem> CreateTagWithRichTooltip(const FTagItemData& Data)
{
    return SNew(SAssetTagItem)
        .DisplayName(FText::FromString(Data.Name))
        .CountText(FText::AsNumber(Data.Count))
        .BaseColor(Data.Color)
        .OnBuildToolTipInfo_Lambda([&Data](const FOnBuildAssetTagItemToolTipInfoEntry& AddEntry)
        {
            AddEntry(LOCTEXT("Type", "类型"), 
                Data.bIsCollection 
                    ? LOCTEXT("Collection", "收藏夹") 
                    : LOCTEXT("Tag", "标签"));
            AddEntry(LOCTEXT("AssetCount", "包含资产"), FText::AsNumber(Data.Count));
            AddEntry(LOCTEXT("LastModified", "最后修改"), FText::AsDate(Data.LastModified));
        });
}

示例6:带警告状态的标签条目

当标签引用的资产路径有损坏时,显示警告图标:

cpp 复制代码
TSharedRef<SAssetTagItem> CreateTagWithWarning(const FTagItemData& Data)
{
    FText WarningMsg = Data.HasBrokenReferences
        ? LOCTEXT("BrokenRefWarning", "此标签引用了部分已删除的资产")
        : FText::GetEmpty();

    TAttribute<FText> WarningText;
    if (Data.HasBrokenReferences)
    {
        WarningText = TAttribute<FText>(WarningMsg);
    }

    return SNew(SAssetTagItem)
        .DisplayName(FText::FromString(Data.Name))
        .CountText(FText::AsNumber(Data.Count))
        .BaseColor(FLinearColor::Yellow)
        .WarningText(WarningText);
}

5 总结与最佳实践

5.1 核心要点

  1. AssetTagsEditor 是一个 UI 控件工具包,不是业务逻辑模块

    • 它只负责"如何渲染",不负责"渲染什么数据"
    • 所有数据通过 TAttribute 和委托从外部注入
    • 这种设计使其可以被内容浏览器、资产选择器、标签管理器等多个场景复用
  2. 可选功能自动启用的设计模式

    • SAssetTagItem 根据参数是否绑定,自动决定启用哪些功能
    • 勾选框:IsCheckedOnCheckStateChanged 同时设置才启用
    • 重命名:OnNameCommitted 绑定才启用
    • 警告图标:WarningText 设置才显示
    • 计数徽章:CountText 设置才显示
  3. 延迟构建工具提示

    • SAssetTagItemToolTipOnOpening() 时才构建内容
    • 避免为从未显示的标签预建工具提示
  4. 颜色派生模式

    • 从单一的 BaseColor 派生出不同透明度来区分状态
    • 外部改动颜色后,所有派生状态自动适配
  5. 样式后缀切换

    • 通过 StyleSpecifier 字符串后缀(nullptr / ".Compact")来切换样式
    • 利用 FAppStyle 的键名拼接机制,无需为每种视图模式定义独立的样式键

5.2 值得学习的设计技巧

  1. TAttribute 驱动的响应式 UI

    • 所有外观属性都用 TAttribute 绑定,而非在构造时写入固定值
    • 外部数据变化时,UI 自动更新,无需手动调用刷新
  2. TFunctionRef 用于轻量回调

    • FOnBuildAssetTagItemToolTipInfoEntry 使用 TFunctionRef 而非 TFunction
    • 在不需要持有回调所有权的场景下,避免了堆分配开销
  3. checkf 保障必须参数

    • DisplayName 是唯一必须的参数,Construct 开头就用 checkf 验证
    • 在开发阶段尽早暴露配置错误
  4. 模板 + 组合的控件复用

    • SAssetTagItemTableRow 是模板类,可适配任意 ItemType
    • 内部组合 SAssetTagItem,而非继承它,保持了职责分离

5.3 潜在改进点

  1. 自定义颜色状态数量有限

    • 目前只有三种透明度派生(Bright / Dull / Disabled),如果将来需要更多状态(如 Hovered、Pressed),需要修改源码
    • 可以考虑将颜色派生逻辑开放为可配置的策略
  2. 工具提示样式硬编码

    • 工具提示的样式键(如 "ContentBrowser.TileViewTooltip.ToolTipBorder")是硬编码的
    • 如果要在非内容浏览器场景下使用,样式可能不完全匹配,建议将样式键也开放为参数
  3. 缺少蓝图支持

    • SAssetTagItem 是纯 C++ Slate 控件,没有对应的 UWidget 包装
    • 如果需要在 UMG 中使用,需要额外封装一层
  4. 紧凑模式与标准模式的切换粒度

    • 目前只有两种视图模式,且切换的是整个样式后缀
    • 如果只想调整某个特定属性(如只改间距但不改字体),目前无法实现

附录:文件结构参考

复制代码
AssetTagsEditor/
├── AssetTagsEditor.Build.cs
├── Private/
│   ├── AssetTagsEditorModule.cpp
│   └── SAssetTagItem.cpp
└── Public/
    ├── AssetTagItemTypes.h
    └── SAssetTagItem.h
相关推荐
日月云棠6 小时前
UE5源码分析之Editor——Blutility模块全面分析
ue5
日月云棠7 小时前
UE5源码分析之Editor——BehaviorTreeEditor模块全面分析
ue5
远离UE41 天前
UE5 显存 虚拟内存 深入学习笔记
笔记·学习·ue5
日月云棠1 天前
UE5源码分析之Editor——AnimationSettings模块全面分析
ue5
远离UE42 天前
UE5 SF_VertexShader 如何使用
ue5
电子云与长程纠缠2 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 上
开发语言·学习·3d·ue5·游戏引擎
电子云与长程纠缠2 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 下
开发语言·学习·游戏·ui·ue5
远离UE43 天前
UE5 GPU内部异步
ue5
1204157137 肖哥3 天前
UE5 PCG PCGBlueprintElement简介
ue5