Published on

使用自定义Slate笔刷

Authors
  • avatar
    Name
    东哥
    Twitter

前言

使用slate制作界面的时候往往需要使用到笔刷, 本文简单介绍如何使用自定义的笔刷以及使用系统的笔刷

自定义笔刷

纯C++

参考引擎其他地方用到的获取笔刷的方式

主要思路是创建一个指针TSharedPtr< class FSlateStyleSet > StyleSet然后通过方法FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get())注册笔刷


国际惯例(照着引擎代码), 定义几个宏

#define IMAGE_BRUSH(RelativePath, ... ) FSlateImageBrush( FVSM_Style::InResources( RelativePath, ".png" ), __VA_ARGS__ )
#define BOX_BRUSH(RelativePath, ... ) FSlateBoxBrush( FVSM_Style::InResources( RelativePath, ".png" ), __VA_ARGS__ )

先初始化

StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName()));
	StyleSet->SetContentRoot(IPluginManager::Get().FindPlugin(TEXT("VSM"))->GetContentDir());

然后从具体目录获取图片

StyleSet->Set("VSM.StateMachineSmall", new IMAGE_BRUSH("Icons/StateMachine-ToolbarStateMachine-20", Icon20x20));	
StyleSet->Set("VSM.NodeBody", new BOX_BRUSH(TEXT("Graph/NodeBody"), FMargin(4.f / 64.f, 4.f / 64.f, 4.f / 64.f, 4.f / 64.f)));

第一个字符串就是获取笔刷的名称

获取方式

SNew(SBorder)			.BorderImage(FVSM_Style::GetBrush(TEXT("FrameworkGraph.NodeBody")))

自定义资源缩略图

缩略图的自定义困扰了我很久, 第一时间想到的就是Factory类里面的虚函数GetNewAssetThumbnailOverride(), 重写以后返回一个之前注册过的笔刷名称,比如上面的 "VSM.StateMachineSmall"

那么在右键菜单准备创建的时候确实是正确的图标, 但是创建资源以后就是默认UObject的原型图案了, 这个问题也查不到资料, 我甚至把蓝图资源创建的流程代码整个翻了个遍, 还是得不到有效信息

最后意外的发现, 原来问题出在了注册笔刷的地方

比如我们这个资源的蓝图类叫做VSM_Blueprint(去掉了前缀U), 那么于此对应的缩略图和Icon就这样注册

	StyleSet->Set("ClassIcon.VSM_Blueprint", new IMAGE_BRUSH(TEXT("Icons/StateMachine"), Icon16x16));
	StyleSet->Set("ClassThumbnail.VSM_Blueprint", new IMAGE_BRUSH(TEXT("Icons/StateMachine"), Icon128x128));

同时, 我们也不需要重写Factory类里面的相关缩略图方法了;

还要注意一个问题, 蓝图类与它的ParentClass类都需要注册笔刷

UE4Editor_3gT4XKdY99

PYM5GUua6f

WidgetSlateStyle

此方式方便在蓝图里配置笔刷,不用编译即可修改slate样式

我们创建此类的c++类

struct VSMEDITOR_API FVSM_SlateStyle : public FSlateWidgetStyle
{
    //.................
  	//输入框的样式
    UPROPERTY(EditAnywhere, Category = Appearance)
		FInlineEditableTextBlockStyle EditTextBlockStyle;
}

其余均保持默认即可

为了区别c++的style, 我们在FVSM_Style中的变量命名成 static TSharedPtr BlueprintStyleSet;

void FVSM_Style::Initialize()
{
    if (!BlueprintStyleSet)
	{

		FString styleDir = TEXT("/VSM/Style/");
		BlueprintStyleSet = FSlateGameResources::New(FVSM_Style::GetBPStyleSetName(),*styleDir, *styleDir);
		if (BlueprintStyleSet)
		{
			FSlateStyleRegistry::RegisterSlateStyle(*BlueprintStyleSet);
		}
	}
}
    

这里需要注意的是, 这里指定的目录是相对目录, 一开始我指定了绝对目录以后一路排查发现在EngineUtils::FindOrLoadAssetsByPath()函数里找不到对应的文件

然后在的slate里面使用

//.h
const  FVSM_SlateStyle* Style;

//.cpp
//VSMStyle对应的是蓝图的名称,如果错误可能会导致崩溃问题
Style = &FVSM_Style::GetBPStyle().`GetWidgetStyle<FVSM_SlateStyle>`("VSMStyle");


//.................
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
								.Style(&Style->EditTextBlockStyle)

然后创建蓝图style

image-20210412183656909

效果

UE4Editor_txLE2n7qwp

每次修改以后需要重新打开对应的slate资源才能刷新

编译以后如果蓝图的style还没有创建, 那么创建以后如果提示找不到笔刷, 则需要再重新启动一次编辑器

系统笔刷

系统的笔刷文件多数在 *\Engine\Content\Editor\Slate

通过文件SlateEditorStyle.cpp中定义和获取

FEditorStyle::GetBrush(TEXT("Graph.StateNode.Pin.Background"));

当然引擎还有很多其他类也定义了自己的笔刷, 如VREditorStyle.cpp, GameMenuBuilderStyle.cpp,具体笔刷名称是啥就直接去看cpp

Style类完整代码

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "VSM_SlateWidgetStyle.h"

/**
 * 
 */
class VSMEDITOR_API FVSM_Style
{


    /************************************************************************/
    /* FUNCTION                                                           */
    /************************************************************************/
public:
    static FLinearColor TransitionNodeHorverColor;
    static FLinearColor TransitionNodeBaseColor;
    static FLinearColor NodeBodyBaseColor;


	static void Initialize();

	static void Shutdown();

	static `TSharedPtr< class ISlateStyle >` Get();

	static FName GetStyleSetName();
    static FName GetBPStyleSetName();


	static const FSlateBrush* GetBrush(FName PropertyName, const ANSICHAR* Specifier = NULL);

    static ISlateStyle& GetBPStyle();

    
private:


    static FString InResources(const FString& RelativePath, const ANSICHAR* Extension);



    /************************************************************************/
    /* PROPERTIES                                                           */
    /************************************************************************/
public:



private:
    static `TSharedPtr< class FSlateStyleSet >` StyleSet;

    static `TSharedPtr<FSlateStyleSet>` BlueprintStyleSet;
};

// Fill out your copyright notice in the Description page of Project Settings.


#include "Style/VSM_Style.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateStyle.h"
#include "Interfaces/IPluginManager.h"
#include "Styling/SlateStyleRegistry.h"
#include "SlateOptMacros.h"
#include "Slate/SlateGameResources.h"


#define IMAGE_BRUSH(RelativePath, ... ) FSlateImageBrush( FVSM_Style::InResources( RelativePath, ".png" ), __VA_ARGS__ )
#define BOX_BRUSH(RelativePath, ... ) FSlateBoxBrush( FVSM_Style::InResources( RelativePath, ".png" ), __VA_ARGS__ )

`TSharedPtr< FSlateStyleSet >` FVSM_Style::StyleSet = nullptr;
`TSharedPtr< FSlateStyleSet >` FVSM_Style::BlueprintStyleSet = nullptr;

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void FVSM_Style::Initialize()
{
	// Const icon sizes
	const FVector2D Icon8x8(8.0f, 8.0f);
	const FVector2D Icon16x16(16.0f, 16.0f);
	const FVector2D Icon20x20(20.0f, 20.0f);
	const FVector2D Icon40x40(40.0f, 40.0f);
	const FVector2D Generic10x10(10.0f, 10.0f);
	const FVector2D NodeBody64x64(64.0f, 64.0f);
	const FVector2D Icon128x128(64.0f, 64.0f);

	if (!BlueprintStyleSet)
	{
		FString styleDir = TEXT("/VSM/Style/");

		BlueprintStyleSet = FSlateGameResources::New(FVSM_Style::GetBPStyleSetName(),*styleDir, *styleDir);
		if (BlueprintStyleSet)
		{
			FSlateStyleRegistry::RegisterSlateStyle(*BlueprintStyleSet);
		}
	}

	// Only register once
	if (StyleSet.IsValid())
		return;

	StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName()));
	//StyleSet->SetContentRoot(IPluginManager::Get().FindPlugin(TEXT("VSM"))->GetBaseDir() / TEXT("Resources"));
	StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate"));
	StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));

	// Icons 
	StyleSet->Set("ClassIcon.VSM_Blueprint", new IMAGE_BRUSH(TEXT("Icons/StateMachine_16"), Icon16x16));
	StyleSet->Set("ClassThumbnail.VSM_Blueprint", new IMAGE_BRUSH(TEXT("Icons/StateMachine_128"), Icon128x128));

	StyleSet->Set("ClassIcon.VSM_StateMachine", new IMAGE_BRUSH(TEXT("Icons/StateMachine_16"), Icon16x16));
	StyleSet->Set("ClassThumbnail.VSM_StateMachine", new IMAGE_BRUSH(TEXT("Icons/StateMachine_128"), Icon128x128));

	StyleSet->Set("ClassIcon.VSM_Blueprint_Task", new IMAGE_BRUSH(TEXT("Icons/Task_16"), Icon16x16));
	StyleSet->Set("ClassThumbnail.VSM_Blueprint_Task", new IMAGE_BRUSH(TEXT("Icons/Task_128"), Icon128x128));

	StyleSet->Set("ClassIcon.VSM_Task_Base", new IMAGE_BRUSH(TEXT("Icons/Task_16"), Icon16x16));
	StyleSet->Set("ClassThumbnail.VSM_Task_Base", new IMAGE_BRUSH(TEXT("Icons/Task_128"), Icon128x128));




	// Toolbar

	StyleSet->Set("VSM.StateMachine", new IMAGE_BRUSH("Icons/StateMachine", Icon40x40));
	StyleSet->Set("VSM.StateMachineSmall", new IMAGE_BRUSH("Icons/StateMachine", Icon20x20));
	StyleSet->Set("VSM.StateMachineGraphSmall", new IMAGE_BRUSH("Icons/GraphIconSmall", Icon20x20));
	StyleSet->Set("VSM.Task", new IMAGE_BRUSH("Icons/Task", Icon40x40));


	// Graph
	StyleSet->Set("VSM.NodeBody", new BOX_BRUSH(TEXT("Graph/NodeBody"), FMargin(4.f / 64.f, 4.f / 64.f, 4.f / 64.f, 4.f / 64.f)));
	//StyleSet->Set("FrameworkGraph.NodeBodySelected", new BOX_BRUSH(TEXT("Graph/NodeBodySelected"), FMargin(18.0f / 64.0f)));
	//StyleSet->Set("FrameworkGraph.NodePin", new IMAGE_BRUSH(TEXT("Graph/NodePin"), Generic10x10));
	StyleSet->Set("VSM.NodePinHoverCue", new IMAGE_BRUSH(TEXT("Graph/NodePinHoverCue"), Generic10x10));
	//默认阴影
	StyleSet->Set("VSM.RegularNodeShadow", new BOX_BRUSH(TEXT("Graph/RegularNodeShadow"), FMargin(18.0f / 64.0f)));
	/*StyleSet->Set("FrameworkGraph.RegularNodeTitleNormal", new BOX_BRUSH(TEXT("Graph/RegularNodeTitleNormal"), FMargin(12.0f / 64.0f)));
	StyleSet->Set("FrameworkGraph.RegularNodeTitleHighlight", new BOX_BRUSH(TEXT("Graph/RegularNodeTitleHighlight"), FMargin(16.0f / 64.0f, 1.0f, 16.0f / 64.0f, 0.0f)));*/

	// State Machine 
	//默认边框部分
	StyleSet->Set("VSM.StateMachine.NodeBody", new BOX_BRUSH(TEXT("Graph/StateMachine/NodeBody"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));
	//节点选中阴影
	StyleSet->Set("VSM.StateMachine.NodeBodySelected", new BOX_BRUSH(TEXT("Graph/StateMachine/NodeBodySelected"), FMargin(18.0f / 64.0f)));
	//节点主体内部
	StyleSet->Set("VSM.StateMachine.NodeBodyColorSpill", new BOX_BRUSH(TEXT("Graph/StateMachine/NodeBodyColorSpill"), FMargin(4.0f / 64.0f, 4.0f / 32.0f)));
	//鼠标移动到Node上的背景颜色
	StyleSet->Set("VSM.StateMachine.NodePinHoverCue", new BOX_BRUSH(TEXT("Graph/StateMachine/NodePinHoverCue"), FMargin(12.0f / 64.0f, 12.0f / 64.0f, 12.0f / 64.0f, 12.0f / 64.0f)));
	StyleSet->Set("VSM.StateMachine.Icon", new IMAGE_BRUSH("Graph/StateMachine/NodeIcon", Icon16x16));
	//过渡节点主体,已废弃
	StyleSet->Set("vsm.StateMachine.TransitionNodeBody", new BOX_BRUSH("Graph/StateMachine/TransNodeBody", FMargin(16.f / 64.f, 12.f / 28.f)));
	//过渡节点的底色
	StyleSet->Set("VSM.StateMachine.TransitionNodeColorSpill", new BOX_BRUSH("Graph/StateMachine/TransNodeColorSpill", FMargin(0.25f, 0.25f, 0.25f, 0.25f))); //16.f / 64.f, 16.f / 28.f, 16.f / 64.f, 4.f / 28.f)
	//过渡节点的图标
	StyleSet->Set("VSM.StateMachine.TransitionNodeIcon", new IMAGE_BRUSH("Graph/StateMachine/TransNodeIcon", FVector2D(25, 25)));

	FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());



}



const FSlateBrush* FVSM_Style::GetBrush(FName PropertyName, const ANSICHAR* Specifier /*= NULL*/)
{
	return FVSM_Style::Get()->GetBrush(PropertyName, Specifier);
}



ISlateStyle& FVSM_Style::GetBPStyle()
{
	return *BlueprintStyleSet;
}

END_SLATE_FUNCTION_BUILD_OPTIMIZATION


FLinearColor FVSM_Style::TransitionNodeHorverColor(0.22f, 1.0f, 1.0f, 1.0f);
FLinearColor FVSM_Style::TransitionNodeBaseColor(0.8f, 0.8f, 0.8f, 0.8f);
FLinearColor FVSM_Style::NodeBodyBaseColor(0.8f, 0.8f, 0.8f, 0.8f);


#undef IMAGE_BRUSH
#undef BOX_BRUSH


void FVSM_Style::Shutdown()
{
	if (StyleSet.IsValid())
	{
		FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
		ensure(StyleSet.IsUnique());
		StyleSet.Reset();
	}
	if (BlueprintStyleSet.IsValid())
	{
		FSlateStyleRegistry::UnRegisterSlateStyle(*BlueprintStyleSet.Get());
		ensure(BlueprintStyleSet.IsUnique());
		BlueprintStyleSet.Reset();
	}
}

`TSharedPtr< class ISlateStyle >` FVSM_Style::Get()
{
	return StyleSet;
}

FName FVSM_Style::GetStyleSetName()
{
	static FName StyleName(TEXT("FVSM_Style"));
	return StyleName;
}



FName FVSM_Style::GetBPStyleSetName()
{

	FName BPStyleName(TEXT("FVSM_BlueprintStyle"));
	return BPStyleName;
}



FString FVSM_Style::InResources(const FString& RelativePath, const ANSICHAR* Extension)
{
	static FString ContentDir = IPluginManager::Get().FindPlugin(TEXT("VSM"))->GetBaseDir() / TEXT("Resources");
	return (ContentDir / RelativePath) + Extension;
}