컨텐츠 브라우저 편집

 

컨텐츠 브라우저 편집

image.png

컨텐츠 브라우저를 편집 해서 현재 보이는 파일, 폴더를 수정 합니다.

컨텐츠 브라우저를 활용해서 SOverlay를 작성하여 표기한다.

⚠️ 컨텐츠 브라우저는 매우 자주 호출되므로 최대한 부하가 없도록 설계해야 합니다.

구현 설계

네이밍으로 유추 해보니 아래와 같은 아이템으로 예상

  1. SAssetTileItem Engine\Source\Editor\ContentBrowser\Private\AssetViewWidgets.cpp
  2. SAssetView Engine\Source\Editor\ContentBrowser\Private\SAssetView.cpp

뭔가 기능이 비슷해 보이는데 코드를 보니 아래와 같다.

구분 SAssetTileItem SAssetView
역할 개별 에셋 아이템 UI (타일 한 칸) 에셋 아이템들의 전체 리스트/타일 뷰 관리
상위 개념 하위 아이템 위젯 전체 뷰 위젯
포함 관계 SAssetView에 포함되어 여러 개 존재 여러 SAssetTileItem(또는 리스트 아이템)을 포함
담당 기능 아이콘, 이름, 상태 표시, 클릭 처리 정렬, 필터링, 선택, 스크롤 등 뷰 관리
Slate 기반 SCompoundWidget SListView 또는 STileView 기반

생성 순서

  1. SAssetView 생성
  2. STileView(또는 SListView) 생성 & 데이터 바인딩
  3. Slate 뷰에서 화면에 보여질 아이템 개수만큼 OnGenerateWidget 호출 (SAssetView에서 바인딩된 함수)
  4. SAssetTileItem 생성 & 반환
  5. SAssetTileItem 초기화 및 UI 렌더링

그렇다면, 구현은 아래와 같은 방법으로 생각 가능하다

  1. SAssetView::MakeTileViewWidget에서 SAssetTileItem을 생성한 뒤에 SOverlay로 감싸기
  2. SAssetTileItem::Construct에서 SOverlay로 감싼 뒤에 반환 하기.

1번을 한다고 가정하면

SAssetView::MakeTileViewWidget에서 SOverlay으로 감싼다.

구현 플로우

  1. SAssetTileItem을 생성한다 (기본 로직)
  2. IsFolder()조건으로 각각 나누던 것을 하나의 변수로 통합
  3. 로직 분기가 끝나는 시점에 Soverlay를 생성하고 SAssetTileItem을 감싼다.
  4. TableRowWidget에 적용한뒤 return해준다

구현 1. 변수 통합

코드에서는 if (AssetItem->IsFolder()) 으로 분기를 쓰고 있어서 같은 코드를 두번 생성해야 하기 떄문에 로직을 통합 합니다.

아래 두 변수가 각각 선언되어 있으므로 하나로 통합

  • TSharedPtr< STableRow<TSharedPtr<FAssetViewItem>> > TableRowWidget;
  • TSharedRef<SAssetTileItem> Item
    • ref는 nullptr이 될 수 없으므로 TSharedPtr으로 변경

구현 1

구현 2. overray 생성

Item을 SOverlay로 감싸는 코드 작성

TSharedRef<SWidget> Overlay = SNew(SOverlay)
	+ SOverlay::Slot()
	[
		// Item을 감싼다.
		Item.ToSharedRef()
	]
	+ SOverlay::Slot()
	.HAlign(HAlign_Right)
	.VAlign(VAlign_Top)
	.Padding(FMargin(2.0f)) // 위치 조정
	[
		// 조건에 따라 보일 이미지
		SNew(SImage)
			.Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘
	];

이후 TableRowWidgetItem을 Set하는 코드를 Overlay를 Set하도록 변경

TableRowWidget->SetContent(Overlay);

return TableRowWidget.ToSharedRef();

엔진 콘텐츠를 보면 모든 아이콘에 x자가 표기 되는것을 확인할 수 있음.

image.png

구현 2

구현 3. 조건에 맞게 보이게 설정

여러가지 방법이 있음.

당장 생각나는건

  1. SOverlay로 감싸고, 조건을 검사한 뒤에 SOverlay의 Visibiliry제어
    1. 코드가 한곳에 위치하여 가독성이 좋아 보임.
  2. 조건을 검사한 뒤에 SOverlay를 감싸서 반환 하거나, 감싸지 않는 방법
    1. Slate갯수가 최적화 되는 장점이 있음

콘텐츠 브라우저에서 동작하기 때문에 성능에 직결되어 2로 구현

  1. NeverCook 리스트를 얻어와 Asset이 들어가는지 파악 NeverCook은 DefaultGame.ini의 [/Script/UnrealEd.ProjectPackagingSettings] 섹션에 존재. ```cpp bool ContainNeverCook = false; TArray CachedNeverCookDirs; const FString SectionName = TEXT("/Script/UnrealEd.ProjectPackagingSettings");

    if (AssetItem.IsValid())
    {
    	const FString ItemPath = AssetItem->GetItem().GetVirtualPath().ToString(); // 예: "/Game/Assets/MyAsset"
    
    	const UProjectPackagingSettings* PackagingSettings = GetDefault<UProjectPackagingSettings>();
    	const TArray<FDirectoryPath>& NeverCookDirs = PackagingSettings->DirectoriesToNeverCook;
    	for (const FDirectoryPath& DirectoryPath : PackagingSettings->DirectoriesToNeverCook)
    	{
    		if (ItemPath.StartsWith(DirectoryPath.Path))
    		{
    			ContainNevercook = true;
    			break;
    		}
    	}
    }
    ```
    
  2. 해당 조건에 맞게 SOverlay 생성 제어

    
    	if (ContainNevercook)
    	{
    		TSharedRef<SWidget> Overlay = SNew(SOverlay)
    			+ SOverlay::Slot()
    			[
    				Item.ToSharedRef()
    			]
    			+ SOverlay::Slot()
    			.HAlign(HAlign_Right)
    			.VAlign(VAlign_Top)
    			.Padding(FMargin(2.0f)) // 위치 조정
    			[
    				// 조건에 따라 보일 이미지
    				SNew(SImage)
    					.Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘
    			];
    
    		TableRowWidget->SetContent(Overlay);
    	}
    	else
    	{
    		TableRowWidget->SetContent(Item.ToSharedRef());
    	}
    
    	return TableRowWidget.ToSharedRef();
    

이렇게 하면 아래와 같이 보인다.

image.png

구현 3

최적화

Nevercook을 캐싱하고 사용한다면 성능 최적화를 기대할 수 있다.

  • static변수를 통한 캐싱 사용
bool ContainNevercook = false;
// TArray를 static변수로 추가, bIsInitialized도 추가
static TArray<FString> CachedNeverCookDirs;
static bool bIsInitialized = false;

const FString SectionName = TEXT("/Script/UnrealEd.ProjectPackagingSettings");

// 초기화를 한번 하면 UProjectPackagingSettings을 참조하지 않아 성능상 이점이 생길 것.
if (bIsInitialized == false)
{
	const UProjectPackagingSettings* PackagingSettings = GetDefault<UProjectPackagingSettings>();
	const TArray<FDirectoryPath>& NeverCookDirs = PackagingSettings->DirectoriesToNeverCook;
	for (const FDirectoryPath& DirectoryPath : PackagingSettings->DirectoriesToNeverCook)
	{
		CachedNeverCookDirs.Add(DirectoryPath.Path);
	}

	bIsInitialized = true;
}

if (AssetItem.IsValid())
{
	const FString ItemPath = AssetItem->GetItem().GetVirtualPath().ToString();

	for (const FString& NeverCookDir : CachedNeverCookDirs)
	{
		if (ItemPath.StartsWith(NeverCookDir))
		{
			ContainNevercook = true;
			break;
		}
	}
}
  • 단. 런타임 도중 추가 하는건 어려울 것 같다.
    • 런타임 도중에 추가하고 싶다는 니즈가 생긴다면 고민 해봐야 하겠지만 PackagingSettings을 GetMutableDefault으로 받아와서 수정하지 않을까 싶다.

최적화

번외

Visibility제어

TSharedRef<SWidget> Overlay = SNew(SOverlay)
	+ SOverlay::Slot()
	[
		Item.ToSharedRef()
	]
	+ SOverlay::Slot()
	.HAlign(HAlign_Right)
	.VAlign(VAlign_Top)
	.Padding(FMargin(2.0f)) // 위치 조정
	[
		// 조건에 따라 보일 이미지
		SNew(SImage)
			.Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘
			.Visibility(ContainNevercook ? EVisibility::Visible : EVisibility::Collapsed)
	];

TableRowWidget->SetContent(Overlay);
  • SOverlay를 만들고 Visibility를 조절하면 된다.

Visibility_Lambda

람다식을 사용하여 Visibility를 제어 하는 방법

  • 코드가 SOverlay생성 하는 곳에 있어서 (개인적으로)가독성이 높아 보인다.
TSharedRef<SWidget> Overlay = SNew(SOverlay)
	+ SOverlay::Slot()
	[
		Item.ToSharedRef()
	]
	+ SOverlay::Slot()
	.HAlign(HAlign_Right)
	.VAlign(VAlign_Top)
	.Padding(FMargin(2.0f)) // 위치 조정
	[
		// 조건에 따라 보일 이미지
		SNew(SImage)
			.Image(FEditorStyle::GetBrush("Icons.Cross")) // 커스텀 아이콘
			.Visibility_Lambda([AssetItem]()
				{
					static TArray<FString> CachedNeverCookDirs;
					static bool bIsInitialized = false;

					if (!bIsInitialized)
					{
						const FString SectionName = TEXT("/Script/UnrealEd.ProjectPackagingSettings");

						const UProjectPackagingSettings* PackagingSettings = GetDefault<UProjectPackagingSettings>();
						const TArray<FDirectoryPath>& NeverCookDirs = PackagingSettings->DirectoriesToNeverCook;
						for (const FDirectoryPath& DirectoryPath : PackagingSettings->DirectoriesToNeverCook)
						{
							CachedNeverCookDirs.Add(DirectoryPath.Path);
						}

						bIsInitialized = true;
					}

					if (AssetItem.IsValid())
					{
						const FString ItemPath = AssetItem->GetItem().GetVirtualPath().ToString(); // 예: "/Game/Assets/MyAsset"

						for (const FString& NeverCookDir : CachedNeverCookDirs)
						{
							if (ItemPath.StartsWith(NeverCookDir))
							{
								return EVisibility::Visible;
							}
						}
					}

					return EVisibility::Collapsed;
				})
	];

TableRowWidget->SetContent(Overlay);

번외 구현