Files

897 lines
23 KiB
C++
Raw Permalink Normal View History

2026-09-05 21:20:30 +02:00
// Fill out your copyright notice in the Description page of Project Settings.
#include "ItemEditorWindow.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "DetailsViewArgs.h"
#include "Editor.h"
#include "FileHelpers.h"
#include "IDetailsView.h"
#include "Modules/ModuleManager.h"
#include "PropertyEditorModule.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateColor.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "UObject/Class.h"
#include "UObject/UnrealType.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/Layout/SWrapBox.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Views/STableRow.h"
#include "ProjectEleri/Items/DataAssets/ItemDataAsset.h"
#define LOCTEXT_NAMESPACE "ItemEditorWindow"
/*-----------------------------------------------------------------------------
Static helpers
-----------------------------------------------------------------------------*/
namespace
{
/** Best-effort conversion of the ItemCategory registry tag into an underlying Category enum value. */
int32 ParseCategoryFromRegistry(const FAssetData& InAsset)
{
const UEnum* Enum = StaticEnum<Category>();
if (!Enum)
{
return INDEX_NONE;
}
FString TagValue;
if (!InAsset.GetTagValue<FString>(TEXT("ItemCategory"), TagValue))
{
return INDEX_NONE;
}
TagValue.TrimStartAndEndInline();
if (TagValue.IsEmpty())
{
return INDEX_NONE;
}
// Some exports store the underlying integer ("2"), most store the value name ("Plant").
if (TagValue.IsNumeric())
{
const int64 NumericValue = FCString::Atoi64(*TagValue);
return Enum->GetIndexByValue(NumericValue) != INDEX_NONE ? (int32)NumericValue : INDEX_NONE;
}
int64 ParsedValue = Enum->GetValueByNameString(TagValue);
if (ParsedValue != INDEX_NONE)
{
return (int32)ParsedValue;
}
// Defensive: strip a possible "Category::Plant" style prefix.
const int32 ScopeIndex = TagValue.Find(TEXT("::"));
if (ScopeIndex != INDEX_NONE)
{
ParsedValue = Enum->GetValueByNameString(TagValue.RightChop(ScopeIndex + 2));
if (ParsedValue != INDEX_NONE)
{
return (int32)ParsedValue;
}
}
return INDEX_NONE;
}
/** Short display name of a Category value ("Plant"), or empty when not resolvable. */
FString GetCategoryShortName(int32 InCategoryValue)
{
const UEnum* Enum = StaticEnum<Category>();
if (!Enum || InCategoryValue < 0)
{
return FString();
}
return Enum->GetNameStringByValue((int64)InCategoryValue);
}
} // namespace
FString SItemEditorWindow::GetTaggedItemName(const FAssetData& InAsset)
{
FString ItemName;
if (InAsset.GetTagValue<FString>(TEXT("ItemName"), ItemName))
{
return ItemName;
}
return FString();
}
FString SItemEditorWindow::GetEntryDisplayName(const FItemListEntry& InEntry)
{
if (!InEntry.ItemName.IsEmpty())
{
return InEntry.ItemName;
}
// No ItemName tag on disk yet: fall back to the asset file name.
return InEntry.AssetData.AssetName.ToString();
}
FString SItemEditorWindow::BuildEntrySearchText(const FItemListEntry& InEntry)
{
FString Combined = FString::Printf(TEXT("%s %s %s %s"),
*GetEntryDisplayName(InEntry),
*GetCategoryShortName(InEntry.CategoryValue),
*InEntry.AssetData.AssetName.ToString(),
*InEntry.AssetData.PackageName.ToString());
return Combined.ToLower();
}
/*-----------------------------------------------------------------------------
Widget construction
-----------------------------------------------------------------------------*/
SItemEditorWindow::~SItemEditorWindow()
{
if (DetailsView.IsValid())
{
DetailsView->OnFinishedChangingProperties().RemoveAll(this);
}
}
void SItemEditorWindow::Construct(const FArguments& InArgs)
{
// --- Details view that edits the selected data asset object directly ---
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
FDetailsViewArgs DetailsArgs;
DetailsArgs.bHideSelectionTip = true;
DetailsArgs.bLockable = false;
DetailsArgs.bAllowSearch = true;
DetailsArgs.bShowOptions = true;
DetailsArgs.bShowObjectLabel = false;
DetailsArgs.bShowModifiedPropertiesOption = true;
DetailsArgs.bAllowFavoriteSystem = true;
DetailsArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsView = PropertyEditorModule.CreateDetailView(DetailsArgs);
DetailsView->SetObject(nullptr);
DetailsView->OnFinishedChangingProperties().AddRaw(this, &SItemEditorWindow::OnAssetPropertyChanged);
InitCategoryFilter();
ChildSlot
[
SNew(SVerticalBox)
// ---- Search box + refresh button ------------------------------------------------
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(4.f, 4.f, 4.f, 6.f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.f)
.VAlign(VAlign_Center)
[
SAssignNew(SearchBox, SSearchBox)
.HintText(LOCTEXT("SearchHint", "Type an item name to find it..."))
.OnTextChanged(this, &SItemEditorWindow::OnSearchTextChanged)
.OnTextCommitted(this, &SItemEditorWindow::OnSearchTextCommitted)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(6.f, 0.f, 0.f, 0.f)
[
SNew(SButton)
.Text(LOCTEXT("RefreshList", "Refresh"))
.ToolTipText(LOCTEXT("RefreshTip", "Re-scan the Asset Registry for every item data asset."))
.OnClicked(this, &SItemEditorWindow::OnRefreshClicked)
]
]
// ---- Category filter chips (multi select, empty selection = all) ----------------
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(4.f, 0.f, 4.f, 6.f)
[
MakeCategoryFilterWidget()
]
// ---- List of items (left) + details view (right) -------------------------------
+ SVerticalBox::Slot()
.FillHeight(1.f)
.Padding(4.f, 0.f, 4.f, 0.f)
[
SNew(SSplitter)
.Orientation(Orient_Horizontal)
+ SSplitter::Slot()
.Value(0.32f)
.MinSize(240.f)
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush(TEXT("Border")))
.Padding(4.f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.f, 2.f, 0.f, 4.f)
[
SNew(STextBlock)
.Text(LOCTEXT("ItemsLabel", "Items"))
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("Bold"), 11))
]
+ SVerticalBox::Slot()
.FillHeight(1.f)
[
SAssignNew(ListView, SListView<FItemEntry>)
.ListItemsSource(&FilteredItems)
.SelectionMode(ESelectionMode::Single)
.OnGenerateRow(this, &SItemEditorWindow::OnGenerateRow)
.OnSelectionChanged(this, &SItemEditorWindow::OnItemSelectionChanged)
.OnMouseButtonDoubleClick(this, &SItemEditorWindow::OnItemDoubleClicked)
]
]
]
+ SSplitter::Slot()
.Value(0.68f)
.MinSize(360.f)
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush(TEXT("Border")))
.Padding(4.f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.f, 2.f, 0.f, 6.f)
[
SNew(STextBlock)
.Text(this, &SItemEditorWindow::GetSelectionStatusText)
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("Bold"), 11))
]
+ SVerticalBox::Slot()
.FillHeight(1.f)
[
DetailsView->AsShared()
]
]
]
]
// ---- Status bar with auto-save + save actions ----------------------------------
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(4.f, 4.f, 4.f, 4.f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0.f, 0.f, 12.f, 0.f)
[
SNew(SCheckBox)
.ToolTipText(LOCTEXT("AutoSaveTip", "Save the data asset to disk automatically after every change."))
.IsChecked_Lambda([this]() -> ECheckBoxState
{
return bAutoSaveOnChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged(this, &SItemEditorWindow::OnAutoSaveCheckChanged)
[
SNew(STextBlock)
.Text(LOCTEXT("AutoSaveLabel", "Auto-save on edit"))
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.f)
.VAlign(VAlign_Center)
.Padding(0.f, 0.f, 12.f, 0.f)
[
SAssignNew(StatusText, STextBlock)
.Text(FText::GetEmpty())
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("Regular"), 9))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.f, 0.f, 0.f, 0.f)
[
SNew(SButton)
.Text(LOCTEXT("SaveAsset", "Save"))
.ToolTipText(LOCTEXT("SaveTip", "Write the selected data asset to disk."))
.IsEnabled_Lambda([this]() { return CurrentAsset.IsValid(); })
.OnClicked(this, &SItemEditorWindow::OnSaveClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.f, 0.f, 0.f, 0.f)
[
SNew(SButton)
.Text(LOCTEXT("OpenEditor", "Open Asset Editor"))
.ToolTipText(LOCTEXT("OpenEditorTip", "Open the selected asset in the regular data asset editor."))
.IsEnabled_Lambda([this]() { return CurrentAsset.IsValid(); })
.OnClicked(this, &SItemEditorWindow::OnOpenAssetEditorClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.f, 0.f, 0.f, 0.f)
[
SNew(SButton)
.Text(LOCTEXT("FindInCB", "Find in Content Browser"))
.ToolTipText(LOCTEXT("FindInCBTip", "Select the asset in the Content Browser."))
.IsEnabled_Lambda([this]() { return CurrentAsset.IsValid(); })
.OnClicked(this, &SItemEditorWindow::OnFindInContentBrowserClicked)
]
]
];
RefreshAssetList();
}
/*-----------------------------------------------------------------------------
Category filter
-----------------------------------------------------------------------------*/
void SItemEditorWindow::InitCategoryFilter()
{
CategoryFilterValues.Reset();
SelectedCategoryValues.Reset();
const UEnum* Enum = StaticEnum<Category>();
if (!Enum)
{
return;
}
for (int32 Index = 0; Index < Enum->NumEnums(); ++Index)
{
if (Enum->HasMetaData(TEXT("Hidden"), Index))
{
continue; // e.g. Category::MAX
}
const int64 Value = Enum->GetValueByIndex(Index);
if (Value >= 0)
{
CategoryFilterValues.Add((int32)Value);
}
}
}
TSharedRef<SWidget> SItemEditorWindow::MakeCategoryFilterWidget()
{
const UEnum* Enum = StaticEnum<Category>();
TSharedRef<SWrapBox> WrapBox = SNew(SWrapBox)
.UseAllottedSize(true)
.InnerSlotPadding(FVector2D(4.f, 3.f));
// "All" chip - clears every category selection.
WrapBox->AddSlot()
[
MakeCategoryCheckBox(INDEX_NONE, LOCTEXT("AllCategories", "All"))
];
for (const int32 CategoryValue : CategoryFilterValues)
{
FText Label;
if (Enum)
{
Label = Enum->GetDisplayNameTextByValue((int64)CategoryValue);
}
if (Label.IsEmpty())
{
Label = FText::FromString(FString::FromInt(CategoryValue));
}
WrapBox->AddSlot()
[
MakeCategoryCheckBox(CategoryValue, Label)
];
}
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 10.f, 0.f))
[
SNew(STextBlock)
.Text(LOCTEXT("CategoryFilterLabel", "Category:"))
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("Bold"), 10))
]
+ SHorizontalBox::Slot()
.FillWidth(1.f)
.VAlign(VAlign_Center)
[
WrapBox
];
}
TSharedRef<SWidget> SItemEditorWindow::MakeCategoryCheckBox(int32 InCategoryValue, const FText& InLabel)
{
return SNew(SCheckBox)
.Padding(FMargin(2.f, 1.f, 4.f, 1.f))
.IsChecked_Lambda([this, InCategoryValue]()
{
return IsCategoryChecked(InCategoryValue);
})
.OnCheckStateChanged_Lambda([this, InCategoryValue](ECheckBoxState InState)
{
OnCategoryToggled(InCategoryValue, InState);
})
[
SNew(STextBlock)
.Text(InLabel)
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("Regular"), 10))
];
}
ECheckBoxState SItemEditorWindow::IsCategoryChecked(int32 InCategoryValue) const
{
if (InCategoryValue == INDEX_NONE)
{
// "All" is active when no specific category is selected.
return SelectedCategoryValues.Num() == 0 ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
return SelectedCategoryValues.Contains(InCategoryValue) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void SItemEditorWindow::OnCategoryToggled(int32 InCategoryValue, ECheckBoxState InState)
{
if (InCategoryValue == INDEX_NONE)
{
SelectedCategoryValues.Reset();
}
else if (InState == ECheckBoxState::Checked)
{
SelectedCategoryValues.Add(InCategoryValue);
}
else
{
SelectedCategoryValues.Remove(InCategoryValue);
}
ApplyFilter();
SetStatusMessage(FString::Printf(TEXT("%d of %d items shown"), FilteredItems.Num(), ItemEntries.Num()));
}
/*-----------------------------------------------------------------------------
Item list / filtering
-----------------------------------------------------------------------------*/
void SItemEditorWindow::RefreshAssetList()
{
const UItemDataAsset* PreviouslySelected = GetCurrentAsset();
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
TArray<FAssetData> Assets;
AssetRegistry.GetAssetsByClass(UItemDataAsset::StaticClass()->GetClassPathName(), Assets, /*bSearchSubClasses=*/true);
ItemEntries.Reset();
ItemEntries.Reserve(Assets.Num());
for (FAssetData& Asset : Assets)
{
TSharedPtr<FItemListEntry> Entry = MakeShared<FItemListEntry>();
Entry->AssetData = Asset;
// Prefer the live object so name/category are always exact; registry tags are the fallback.
UItemDataAsset* DataAsset = Cast<UItemDataAsset>(Asset.GetAsset());
if (DataAsset)
{
Entry->ItemName = DataAsset->ItemName.ToString();
Entry->CategoryValue = (int32)DataAsset->ItemCategory;
}
else
{
Entry->ItemName = GetTaggedItemName(Asset);
Entry->CategoryValue = ParseCategoryFromRegistry(Asset);
}
Entry->SearchText = BuildEntrySearchText(*Entry);
ItemEntries.Add(Entry);
}
ApplyFilter();
// Restore the list highlight for the asset we were editing (details view stays untouched).
if (PreviouslySelected && ListView)
{
if (FItemEntry Entry = FindEntryForAsset(PreviouslySelected))
{
ListView->SetSelection(Entry, ESelectInfo::Direct);
}
}
SetStatusMessage(FString::Printf(TEXT("%d item data assets found (%d shown)"), ItemEntries.Num(), FilteredItems.Num()));
}
void SItemEditorWindow::ApplyFilter()
{
FString SearchTerm = SearchFilterText.ToString();
SearchTerm.TrimStartAndEndInline();
const FString SearchLower = SearchTerm.ToLower();
FilteredItems.Reset();
const bool bFilteringByCategory = SelectedCategoryValues.Num() > 0;
for (const FItemEntry& Entry : ItemEntries)
{
// Name / asset search term.
if (!SearchLower.IsEmpty() && !Entry->SearchText.Contains(SearchLower))
{
continue;
}
// Category inclusion filter (empty selection shows every category).
if (bFilteringByCategory &&
(Entry->CategoryValue == INDEX_NONE || !SelectedCategoryValues.Contains(Entry->CategoryValue)))
{
continue;
}
FilteredItems.Add(Entry);
}
// Case insensitive alphabetical sort by item name.
FilteredItems.Sort([](const FItemEntry& A, const FItemEntry& B)
{
const FString NameA = A->ItemName.ToLower();
const FString NameB = B->ItemName.ToLower();
if (NameA == NameB)
{
return A->AssetData.AssetName.LexicalLess(B->AssetData.AssetName);
}
return NameA < NameB;
});
if (ListView)
{
ListView->RequestListRefresh();
}
}
void SItemEditorWindow::SelectFirstVisibleItem()
{
if (FilteredItems.Num() == 0)
{
return;
}
FItemEntry FirstItem = FilteredItems[0];
ListView->SetSelection(FirstItem, ESelectInfo::OnNavigation);
ListView->RequestScrollIntoView(FirstItem);
}
SItemEditorWindow::FItemEntry SItemEditorWindow::FindEntryForAsset(const UItemDataAsset* InAsset)
{
if (!InAsset)
{
return nullptr;
}
const FString AssetPath = InAsset->GetPathName();
for (const FItemEntry& Entry : ItemEntries)
{
if (Entry->AssetData.GetObjectPathString() == AssetPath)
{
return Entry;
}
}
return nullptr;
}
void SItemEditorWindow::UpdateEntryFromLoadedAsset(FItemEntry InEntry, const UItemDataAsset* InAsset)
{
if (!InEntry || !InAsset)
{
return;
}
InEntry->ItemName = InAsset->ItemName.ToString();
InEntry->CategoryValue = (int32)InAsset->ItemCategory;
InEntry->SearchText = BuildEntrySearchText(*InEntry);
}
/*-----------------------------------------------------------------------------
Item rows
-----------------------------------------------------------------------------*/
TSharedRef<ITableRow> SItemEditorWindow::OnGenerateRow(FItemEntry InItem, const TSharedRef<STableViewBase>& InOwnerTable)
{
check(InItem.IsValid());
const FString CategoryLabel = GetCategoryShortName(InItem->CategoryValue);
const FString AssetPath = CategoryLabel.IsEmpty()
? FString::Printf(TEXT("%s / %s"),
*InItem->AssetData.PackageName.ToString(),
*InItem->AssetData.AssetName.ToString())
: FString::Printf(TEXT("%s / %s (%s)"),
*InItem->AssetData.PackageName.ToString(),
*InItem->AssetData.AssetName.ToString(),
*CategoryLabel);
return SNew(STableRow<FItemEntry>, InOwnerTable)
.Padding(FMargin(4.f, 2.f, 4.f, 2.f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(FText::FromString(GetEntryDisplayName(*InItem)))
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("Bold"), 10))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(FText::FromString(AssetPath))
.Font(FCoreStyle::GetDefaultFontStyle(TEXT("Regular"), 8))
.ColorAndOpacity(FSlateColor(FLinearColor(0.55f, 0.55f, 0.55f)))
]
];
}
/*-----------------------------------------------------------------------------
Search & selection callbacks
-----------------------------------------------------------------------------*/
void SItemEditorWindow::OnSearchTextChanged(const FText& InText)
{
SearchFilterText = InText;
ApplyFilter();
if (FilteredItems.Num() == 0)
{
SetStatusMessage(TEXT("No items match that name."));
}
else
{
SetStatusMessage(FString::Printf(TEXT("%d of %d items shown"), FilteredItems.Num(), ItemEntries.Num()));
}
}
void SItemEditorWindow::OnSearchTextCommitted(const FText& InText, ETextCommit::Type InCommitType)
{
if (InCommitType == ETextCommit::OnEnter)
{
SelectFirstVisibleItem();
}
}
void SItemEditorWindow::OnItemSelectionChanged(FItemEntry InItem, ESelectInfo::Type InSelectInfo)
{
// Programmatic selections (e.g. restoring the highlight after a refresh) are already handled.
if (InSelectInfo == ESelectInfo::Direct)
{
return;
}
if (!InItem)
{
return;
}
UItemDataAsset* Asset = LoadAssetForEntry(InItem);
if (!Asset)
{
return;
}
CurrentAsset = Asset;
DetailsView->SetObject(Asset, /*bForceRefresh=*/true);
SetStatusMessage(FString::Printf(TEXT("Editing %s"), *Asset->GetPathName()));
}
void SItemEditorWindow::OnItemDoubleClicked(FItemEntry InItem)
{
if (!InItem)
{
return;
}
UItemDataAsset* Asset = LoadAssetForEntry(InItem);
if (!Asset)
{
return;
}
CurrentAsset = Asset;
DetailsView->SetObject(Asset, /*bForceRefresh=*/true);
OpenInAssetEditor(Asset);
}
void SItemEditorWindow::OnAssetPropertyChanged(const FPropertyChangedEvent& InEvent)
{
if (UItemDataAsset* Asset = GetCurrentAsset())
{
// Keep the list label in sync (e.g. when ItemName is edited).
if (FItemEntry Entry = FindEntryForAsset(Asset))
{
UpdateEntryFromLoadedAsset(Entry, Asset);
if (ListView)
{
ListView->RequestListRefresh();
}
}
if (bAutoSaveOnChange)
{
SaveCurrentAsset();
}
}
}
/*-----------------------------------------------------------------------------
Asset loading / accessors
-----------------------------------------------------------------------------*/
UItemDataAsset* SItemEditorWindow::GetCurrentAsset() const
{
return CurrentAsset.Get();
}
UItemDataAsset* SItemEditorWindow::LoadAssetForEntry(FItemEntry InItem)
{
if (!InItem)
{
return nullptr;
}
UObject* LoadedObject = InItem->AssetData.GetAsset();
if (!LoadedObject)
{
SetStatusMessage(FString::Printf(TEXT("Failed to load %s."), *InItem->AssetData.GetObjectPathString()), /*bIsError=*/true);
return nullptr;
}
UItemDataAsset* Asset = Cast<UItemDataAsset>(LoadedObject);
if (!Asset)
{
SetStatusMessage(FString::Printf(TEXT("%s is not an ItemDataAsset."), *InItem->AssetData.GetObjectPathString()), /*bIsError=*/true);
return nullptr;
}
return Asset;
}
/*-----------------------------------------------------------------------------
Actions
-----------------------------------------------------------------------------*/
FReply SItemEditorWindow::OnRefreshClicked()
{
RefreshAssetList();
return FReply::Handled();
}
FReply SItemEditorWindow::OnSaveClicked()
{
SaveCurrentAsset();
return FReply::Handled();
}
FReply SItemEditorWindow::OnOpenAssetEditorClicked()
{
if (UItemDataAsset* Asset = GetCurrentAsset())
{
OpenInAssetEditor(Asset);
}
return FReply::Handled();
}
FReply SItemEditorWindow::OnFindInContentBrowserClicked()
{
if (UItemDataAsset* Asset = GetCurrentAsset())
{
ShowInContentBrowser(Asset);
}
return FReply::Handled();
}
void SItemEditorWindow::OnAutoSaveCheckChanged(ECheckBoxState InState)
{
bAutoSaveOnChange = (InState == ECheckBoxState::Checked);
SetStatusMessage(bAutoSaveOnChange ? TEXT("Auto-save enabled") : TEXT("Auto-save disabled"));
}
void SItemEditorWindow::SaveCurrentAsset()
{
UItemDataAsset* Asset = GetCurrentAsset();
if (!Asset)
{
SetStatusMessage(TEXT("No item selected."), /*bIsError=*/true);
return;
}
UPackage* Package = Asset->GetPackage();
if (!Package)
{
SetStatusMessage(TEXT("Selected item has no package."), /*bIsError=*/true);
return;
}
Package->SetDirtyFlag(true);
if (UEditorLoadingAndSavingUtils::SavePackages(TArray<UPackage*>{Package}, /*bOnlyDirty=*/true))
{
if (FItemEntry Entry = FindEntryForAsset(Asset))
{
UpdateEntryFromLoadedAsset(Entry, Asset);
if (ListView)
{
ListView->RequestListRefresh();
}
}
SetStatusMessage(FString::Printf(TEXT("Saved %s"), *Asset->GetName()));
}
else
{
SetStatusMessage(FString::Printf(TEXT("Failed to save %s."), *Asset->GetName()), /*bIsError=*/true);
}
}
void SItemEditorWindow::OpenInAssetEditor(UItemDataAsset* InAsset)
{
if (!InAsset)
{
return;
}
if (UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
{
AssetEditorSubsystem->OpenEditorForAsset(InAsset);
}
}
void SItemEditorWindow::ShowInContentBrowser(UItemDataAsset* InAsset)
{
if (!InAsset)
{
return;
}
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
ContentBrowserModule.Get().SyncBrowserToAssets(TArray<UObject*>{InAsset});
}
void SItemEditorWindow::SetStatusMessage(const FString& InMessage, bool bIsError)
{
if (!StatusText)
{
return;
}
const FLinearColor TextColor = bIsError ? FLinearColor(1.f, 0.35f, 0.35f) : FLinearColor(0.7f, 0.7f, 0.7f);
StatusText->SetColorAndOpacity(FSlateColor(TextColor));
StatusText->SetText(FText::FromString(InMessage));
}
FText SItemEditorWindow::GetSelectionStatusText() const
{
if (UItemDataAsset* Asset = GetCurrentAsset())
{
return FText::FromString(Asset->GetPathName());
}
return LOCTEXT("NoSelection", "Select an item to edit it");
}
#undef LOCTEXT_NAMESPACE