Files
UnrealEngine/Engine/Source/Editor/SceneOutliner/Private/SceneOutlinerUnsavedColumn.cpp
2025-05-18 13:04:45 +08:00

211 lines
6.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SceneOutlinerUnsavedColumn.h"
#include "ActorTreeItem.h"
#include "SourceControlHelpers.h"
#include "UnsavedAssetsTrackerModule.h"
#include "SortHelper.h"
#include "ISceneOutliner.h"
#include "ISceneOutlinerMode.h"
#include "SceneOutlinerHelpers.h"
#include "GameFramework/Actor.h"
#define LOCTEXT_NAMESPACE "SceneOutlinerActorUnsavedColumn"
class SUnsavedActorWidget : public SImage
{
public:
SLATE_BEGIN_ARGS(SUnsavedActorWidget) {}
SLATE_END_ARGS()
~SUnsavedActorWidget();
/** Construct this widget */
void Construct(const FArguments& InArgs, TWeakPtr<ISceneOutlinerTreeItem> InWeakTreeItem);
bool IsUnsaved() const;
void OnUnsavedAssetAdded(const FString& FileAbsPathname);
void OnUnsavedAssetRemoved(const FString& FileAbsPathname);
private:
void UpdateImage();
void UpdateExternalPackageFilename();
private:
FString ExternalPackageFilename;
TWeakPtr<ISceneOutlinerTreeItem> WeakTreeItem;
bool bIsUnsaved;
FDelegateHandle OnPackagingModeChangedHandle;
FDelegateHandle OnUnsavedAssetAddedHandle;
FDelegateHandle OnUnsavedAssetRemovedHandle;
};
void SUnsavedActorWidget::Construct(const FArguments& InArgs, TWeakPtr<ISceneOutlinerTreeItem> InWeakTreeItem)
{
WeakTreeItem = InWeakTreeItem;
SImage::Construct(
SImage::FArguments()
.ColorAndOpacity(FSlateColor::UseForeground())
.Image(FStyleDefaults::GetNoBrush()));
UpdateExternalPackageFilename();
// Handle Actor package change
if (FActorTreeItem* ActorItem = WeakTreeItem.Pin()->CastTo<FActorTreeItem>())
{
if (AActor* Actor = ActorItem->Actor.Get())
{
OnPackagingModeChangedHandle = Actor->OnPackagingModeChanged.AddLambda([WeakThis = AsWeak()](AActor* InActor, bool bExternal)
{
if (TSharedPtr<SUnsavedActorWidget> This = StaticCastSharedPtr<SUnsavedActorWidget>(WeakThis.Pin()))
{
This->UpdateExternalPackageFilename();
}
});
}
}
}
SUnsavedActorWidget::~SUnsavedActorWidget()
{
if (FActorTreeItem* ActorItem = WeakTreeItem.IsValid() ? WeakTreeItem.Pin()->CastTo<FActorTreeItem>() : nullptr)
{
if (AActor* Actor = ActorItem->Actor.Get())
{
Actor->OnPackagingModeChanged.Remove(OnPackagingModeChangedHandle);
}
}
if (FUnsavedAssetsTrackerModule* UnsavedAssetsTrackerModule = FModuleManager::GetModulePtr<FUnsavedAssetsTrackerModule>("UnsavedAssetsTracker"))
{
UnsavedAssetsTrackerModule->OnUnsavedAssetAdded.Remove(OnUnsavedAssetAddedHandle);
UnsavedAssetsTrackerModule->OnUnsavedAssetRemoved.Remove(OnUnsavedAssetRemovedHandle);
}
}
void SUnsavedActorWidget::UpdateExternalPackageFilename()
{
const bool bWasExternal = !ExternalPackageFilename.IsEmpty();
const FString ExternalPackageName = WeakTreeItem.IsValid() ? WeakTreeItem.Pin()->GetPackageName() : FString();
ExternalPackageFilename = !ExternalPackageName.IsEmpty() ? USourceControlHelpers::PackageFilename(ExternalPackageName) : FString();
FUnsavedAssetsTrackerModule& UnsavedAssetsTrackerModule = FUnsavedAssetsTrackerModule::Get();
// Register/Unregister if needed
if (bWasExternal && ExternalPackageFilename.IsEmpty())
{
UnsavedAssetsTrackerModule.OnUnsavedAssetAdded.Remove(OnUnsavedAssetAddedHandle);
OnUnsavedAssetAddedHandle.Reset();
UnsavedAssetsTrackerModule.OnUnsavedAssetRemoved.Remove(OnUnsavedAssetRemovedHandle);
OnUnsavedAssetRemovedHandle.Reset();
}
else if (!bWasExternal && !ExternalPackageFilename.IsEmpty())
{
OnUnsavedAssetAddedHandle = UnsavedAssetsTrackerModule.OnUnsavedAssetAdded.AddSP(this, &SUnsavedActorWidget::OnUnsavedAssetAdded);
OnUnsavedAssetRemovedHandle = UnsavedAssetsTrackerModule.OnUnsavedAssetRemoved.AddSP(this, &SUnsavedActorWidget::OnUnsavedAssetRemoved);
}
bIsUnsaved = UnsavedAssetsTrackerModule.IsAssetUnsaved(ExternalPackageFilename);
UpdateImage();
}
bool SUnsavedActorWidget::IsUnsaved() const
{
return bIsUnsaved;
}
void SUnsavedActorWidget::OnUnsavedAssetAdded(const FString& FileAbsPathname)
{
if (FileAbsPathname == ExternalPackageFilename)
{
// We should never be desynced, i.e if this item was added as an unsaved asset bIsUnsavedAsset MUST be false before
check(!bIsUnsaved)
bIsUnsaved = true;
UpdateImage();
}
}
void SUnsavedActorWidget::OnUnsavedAssetRemoved(const FString& FileAbsPathname)
{
if (FileAbsPathname == ExternalPackageFilename)
{
// We should never be desynced, i.e if this item was removed from the unsaved asset list bIsUnsavedAsset MUST be true before
check(bIsUnsaved)
bIsUnsaved = false;
UpdateImage();
}
}
void SUnsavedActorWidget::UpdateImage()
{
if (bIsUnsaved)
{
SetImage(FAppStyle::GetBrush("Icons.DirtyBadge"));
}
else
{
SetImage(nullptr);
}
}
FName FSceneOutlinerActorUnsavedColumn::GetColumnID()
{
return GetID();
}
SHeaderRow::FColumn::FArguments FSceneOutlinerActorUnsavedColumn::ConstructHeaderRowColumn()
{
return SHeaderRow::Column(GetColumnID())
.FixedWidth(24.f)
.HAlignHeader(HAlign_Center)
.VAlignHeader(VAlign_Center)
.HAlignCell(HAlign_Center)
.VAlignCell(VAlign_Center)
.DefaultTooltip(FText::FromName(GetColumnID()))
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.DirtyBadge"))
.ColorAndOpacity(FSlateColor::UseForeground())
];
}
const TSharedRef<SWidget> FSceneOutlinerActorUnsavedColumn::ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow<FSceneOutlinerTreeItemPtr>& Row)
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SUnsavedActorWidget, TreeItem.ToWeakPtr())
];
}
void FSceneOutlinerActorUnsavedColumn::SortItems(TArray<FSceneOutlinerTreeItemPtr>& RootItems, const EColumnSortMode::Type SortMode) const
{
FUnsavedAssetsTrackerModule& UnsavedAssetsTrackerModule = FUnsavedAssetsTrackerModule::Get();
FSceneOutlinerSortHelper<bool, SceneOutliner::FNumericStringWrapper>()
/** Sort by unsaved first */
.Primary([UnsavedAssetsTrackerModule](const ISceneOutlinerTreeItem& Item)
{
if (const FString PackageName = Item.GetPackageName(); !PackageName.IsEmpty())
{
return !UnsavedAssetsTrackerModule.IsAssetUnsaved(USourceControlHelpers::PackageFilename(PackageName));
}
return true;
}, SortMode)
/** Then by type */
.Secondary([this](const ISceneOutlinerTreeItem& Item){ return SceneOutliner::FNumericStringWrapper(Item.GetDisplayString()); }, SortMode)
.Sort(RootItems);
}
#undef LOCTEXT_NAMESPACE