Merge branch 'master' of https://adnanedinayukistuff.top/Ado/ProjectEleri
This commit is contained in:
896
Source/ProjectEleriEditor/Private/ItemEditorWindow.cpp
Normal file
896
Source/ProjectEleriEditor/Private/ItemEditorWindow.cpp
Normal file
@@ -0,0 +1,896 @@
|
||||
// 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
|
||||
|
||||
@@ -1,23 +1,103 @@
|
||||
#include "ProjectEleriEditorModule.h"
|
||||
|
||||
#include "Framework/Docking/TabManager.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "PropertyEditorModule.h"
|
||||
#include "ToolMenus.h"
|
||||
#include "Widgets/Docking/SDockTab.h"
|
||||
|
||||
#include "ItemDataAssetCustomization.h"
|
||||
#include "ItemEditorWindow.h"
|
||||
|
||||
IMPLEMENT_GAME_MODULE(FProjectEleriEditorModule, ProjectEleriEditor);
|
||||
#define LOCTEXT_NAMESPACE "ProjectEleriEditorModule"
|
||||
|
||||
void FProjectEleriEditorModule::StartupModule()
|
||||
{
|
||||
FPropertyEditorModule& PropEd = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropEd.RegisterCustomClassLayout(
|
||||
TEXT("ItemDataAsset"), // UCLASS name
|
||||
FOnGetDetailCustomizationInstance::CreateStatic(&FItemDataAssetCustomization::MakeInstance)
|
||||
);
|
||||
}
|
||||
IMPLEMENT_GAME_MODULE(FProjectEleriEditorModule, ProjectEleriEditor);
|
||||
|
||||
void FProjectEleriEditorModule::ShutdownModule()
|
||||
{
|
||||
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) {
|
||||
FPropertyEditorModule& PropEd = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropEd.UnregisterCustomClassLayout(TEXT("ItemDataAsset"));
|
||||
}
|
||||
}
|
||||
const FName FProjectEleriEditorModule::ItemEditorTabName(TEXT("ProjectEleriItemEditor"));
|
||||
|
||||
void FProjectEleriEditorModule::StartupModule()
|
||||
{
|
||||
// Details customization for the item data asset (property map generation widget).
|
||||
FPropertyEditorModule& PropEd = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropEd.RegisterCustomClassLayout(
|
||||
TEXT("ItemDataAsset"),
|
||||
FOnGetDetailCustomizationInstance::CreateStatic(&FItemDataAssetCustomization::MakeInstance)
|
||||
);
|
||||
|
||||
// Register the Item Editor as a docking tab.
|
||||
if (FGlobalTabmanager::Get()->HasTabSpawner(ItemEditorTabName))
|
||||
{
|
||||
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ItemEditorTabName);
|
||||
}
|
||||
|
||||
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
|
||||
ItemEditorTabName,
|
||||
FOnSpawnTab::CreateLambda([](const FSpawnTabArgs& Args)
|
||||
{
|
||||
return SNew(SDockTab)
|
||||
.TabRole(ETabRole::NomadTab)
|
||||
.Label(LOCTEXT("ItemEditorTabLabel", "Item Editor"))
|
||||
.ToolTipText(LOCTEXT("ItemEditorTabTooltip", "Search items by name and edit their data assets"))
|
||||
[
|
||||
SNew(SItemEditorWindow)
|
||||
];
|
||||
}))
|
||||
.SetDisplayName(LOCTEXT("ItemEditorDisplayName", "Item Editor"))
|
||||
.SetMenuType(ETabSpawnerMenuType::Hidden);
|
||||
|
||||
// Add a menu entry to open the window. Menus might not be ready yet during startup.
|
||||
if (UToolMenus::IsToolMenuUIEnabled())
|
||||
{
|
||||
RegisterMenus();
|
||||
}
|
||||
else
|
||||
{
|
||||
UToolMenus::RegisterStartupCallback(
|
||||
FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FProjectEleriEditorModule::RegisterMenus));
|
||||
}
|
||||
}
|
||||
|
||||
void FProjectEleriEditorModule::ShutdownModule()
|
||||
{
|
||||
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
|
||||
{
|
||||
FPropertyEditorModule& PropEd = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropEd.UnregisterCustomClassLayout(TEXT("ItemDataAsset"));
|
||||
}
|
||||
|
||||
if (FGlobalTabmanager::Get()->HasTabSpawner(ItemEditorTabName))
|
||||
{
|
||||
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ItemEditorTabName);
|
||||
}
|
||||
|
||||
UToolMenus::UnregisterOwner(this);
|
||||
}
|
||||
|
||||
void FProjectEleriEditorModule::OpenItemEditorTab()
|
||||
{
|
||||
FGlobalTabmanager::Get()->TryInvokeTab(ItemEditorTabName);
|
||||
}
|
||||
|
||||
void FProjectEleriEditorModule::RegisterMenus()
|
||||
{
|
||||
FToolMenuOwnerScoped OwnerScoped(this);
|
||||
|
||||
if (UToolMenu* WindowMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window"))
|
||||
{
|
||||
FToolMenuSection& Section = WindowMenu->AddSection(
|
||||
TEXT("ItemEditor"),
|
||||
LOCTEXT("ItemEditorSectionLabel", "Eleri Tools"),
|
||||
FToolMenuInsert(NAME_None, EToolMenuInsertType::First)
|
||||
);
|
||||
|
||||
Section.AddMenuEntry(
|
||||
TEXT("OpenItemEditor"),
|
||||
LOCTEXT("ItemEditorMenuLabel", "Item Editor"),
|
||||
LOCTEXT("ItemEditorMenuTooltip", "Open a window to search items by name and edit their data assets."),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateRaw(this, &FProjectEleriEditorModule::OpenItemEditorTab))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
@@ -19,6 +19,9 @@ public class ProjectEleriEditor : ModuleRules
|
||||
"InputCore",
|
||||
"EditorSubsystem",
|
||||
"Projects",
|
||||
"ToolMenus",
|
||||
"AssetRegistry",
|
||||
"ContentBrowser",
|
||||
"ProjectEleri"});
|
||||
}
|
||||
}
|
||||
116
Source/ProjectEleriEditor/Public/ItemEditorWindow.h
Normal file
116
Source/ProjectEleriEditor/Public/ItemEditorWindow.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "Types/SlateEnums.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
#include "Widgets/Views/SListView.h"
|
||||
|
||||
class IDetailsView;
|
||||
class ITableRow;
|
||||
class SSearchBox;
|
||||
class STableViewBase;
|
||||
class STextBlock;
|
||||
class UItemDataAsset;
|
||||
struct FPropertyChangedEvent;
|
||||
|
||||
/** One row shown in the item list of the editor window. */
|
||||
struct FItemListEntry
|
||||
{
|
||||
/** Registry data of the data asset (never loads the asset on its own). */
|
||||
FAssetData AssetData;
|
||||
|
||||
/** Human readable item name (FText source), used as the row label and for sorting. */
|
||||
FString ItemName;
|
||||
|
||||
/** Underlying value of the ItemCategory enum (e.g. Category::Plant), INDEX_NONE if unknown. */
|
||||
int32 CategoryValue = INDEX_NONE;
|
||||
|
||||
/** Lower-cased text used for filtering (name + category + asset + package path). */
|
||||
FString SearchText;
|
||||
};
|
||||
|
||||
/**
|
||||
* Editor tool window that lets the user type an item name, pick the matching
|
||||
* ItemDataAsset and edit its properties directly on the asset.
|
||||
*/
|
||||
class SItemEditorWindow : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SItemEditorWindow) {}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
virtual ~SItemEditorWindow();
|
||||
|
||||
/** Re-queries the Asset Registry for every UItemDataAsset in the project. */
|
||||
void RefreshAssetList();
|
||||
|
||||
private:
|
||||
using FItemEntry = TSharedPtr<FItemListEntry>;
|
||||
|
||||
// Slate callbacks
|
||||
TSharedRef<ITableRow> OnGenerateRow(FItemEntry InItem, const TSharedRef<STableViewBase>& InOwnerTable);
|
||||
void OnSearchTextChanged(const FText& InText);
|
||||
void OnSearchTextCommitted(const FText& InText, ETextCommit::Type InCommitType);
|
||||
void OnItemSelectionChanged(FItemEntry InItem, ESelectInfo::Type InSelectInfo);
|
||||
void OnItemDoubleClicked(FItemEntry InItem);
|
||||
void OnAssetPropertyChanged(const FPropertyChangedEvent& InEvent);
|
||||
|
||||
// Toolbar / button handlers
|
||||
FReply OnRefreshClicked();
|
||||
FReply OnSaveClicked();
|
||||
FReply OnOpenAssetEditorClicked();
|
||||
FReply OnFindInContentBrowserClicked();
|
||||
void OnAutoSaveCheckChanged(ECheckBoxState InState);
|
||||
|
||||
// Category filter
|
||||
void InitCategoryFilter();
|
||||
TSharedRef<SWidget> MakeCategoryFilterWidget();
|
||||
TSharedRef<SWidget> MakeCategoryCheckBox(int32 InCategoryValue, const FText& InLabel);
|
||||
ECheckBoxState IsCategoryChecked(int32 InCategoryValue) const;
|
||||
void OnCategoryToggled(int32 InCategoryValue, ECheckBoxState InState);
|
||||
|
||||
// Helpers
|
||||
void ApplyFilter();
|
||||
void SelectFirstVisibleItem();
|
||||
FItemEntry FindEntryForAsset(const UItemDataAsset* InAsset);
|
||||
UItemDataAsset* LoadAssetForEntry(FItemEntry InItem);
|
||||
UItemDataAsset* GetCurrentAsset() const;
|
||||
void UpdateEntryFromLoadedAsset(FItemEntry InEntry, const UItemDataAsset* InAsset);
|
||||
void SaveCurrentAsset();
|
||||
void SetStatusMessage(const FString& InMessage, bool bIsError = false);
|
||||
void OpenInAssetEditor(UItemDataAsset* InAsset);
|
||||
void ShowInContentBrowser(UItemDataAsset* InAsset);
|
||||
|
||||
FText GetSelectionStatusText() const;
|
||||
|
||||
static FString GetEntryDisplayName(const FItemListEntry& InEntry);
|
||||
static FString BuildEntrySearchText(const FItemListEntry& InEntry);
|
||||
static FString GetTaggedItemName(const FAssetData& InAsset);
|
||||
|
||||
/** All data assets of type UItemDataAsset found in the project (registry only). */
|
||||
TArray<TSharedPtr<FItemListEntry>> ItemEntries;
|
||||
|
||||
/** Subset of ItemEntries currently visible after applying the search filter. */
|
||||
TArray<FItemEntry> FilteredItems;
|
||||
|
||||
/** Ordered category values offered as filter chips (underlying Category enum values). */
|
||||
TArray<int32> CategoryFilterValues;
|
||||
|
||||
/** ItemCategory values that should be shown in the list; empty means show every category. */
|
||||
TSet<int32> SelectedCategoryValues;
|
||||
|
||||
/** The data asset currently being edited. */
|
||||
TWeakObjectPtr<UItemDataAsset> CurrentAsset;
|
||||
|
||||
TSharedPtr<SSearchBox> SearchBox;
|
||||
TSharedPtr<SListView<FItemEntry>> ListView;
|
||||
TSharedPtr<IDetailsView> DetailsView;
|
||||
TSharedPtr<STextBlock> StatusText;
|
||||
|
||||
FText SearchFilterText;
|
||||
bool bAutoSaveOnChange = false;
|
||||
};
|
||||
@@ -1,10 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FProjectEleriEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
/** Opens (or focuses) the Item Editor window. */
|
||||
void OpenItemEditorTab();
|
||||
|
||||
private:
|
||||
void RegisterMenus();
|
||||
|
||||
/** Tab id of the Item Editor window. */
|
||||
static const FName ItemEditorTabName;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user