Skip to content

Instantly share code, notes, and snippets.

@moppius
Created April 8, 2023 16:18
Show Gist options
  • Save moppius/06a68eed0c4f96fd49b322495d35f5d1 to your computer and use it in GitHub Desktop.
Save moppius/06a68eed0c4f96fd49b322495d35f5d1 to your computer and use it in GitHub Desktop.
AssetStatus Angelscript Prototype
/** Settings for the Asset Status */
UCLASS(Config = Editor, DefaultConfig)
class UAssetStatusSettings : UDeveloperSettings
{
UPROPERTY(Config, EditDefaultsOnly, Category = "Asset Status", Meta = (TitleProperty = "Name"))
protected TArray<FAssetStatusColumnSchema> Schemas;
TArray<FAssetStatusColumnSchema> GetSchemas() const
{
return Schemas;
}
};
struct FAssetStatusColumnSchema
{
UPROPERTY(Config)
FString Name;
UPROPERTY(Config, Meta = (TitleProperty = "CollectionName"))
TArray<FAssetStatusColumn> Columns;
};
struct FAssetStatusColumn
{
UPROPERTY(Config)
FName CollectionName;
UPROPERTY(Config)
FText ColumnTitle;
UPROPERTY(Config)
FLinearColor Color = FLinearColor(0.017642f, 0.017642f, 0.017642f, 0.f);
};
/** Base class for the Asset Status Editor Utility Widget */
UCLASS(Abstract)
class UAssetStatusEditorUtilityWidget : UEditorUtilityWidget
{
UPROPERTY(BindWidget)
private UComboBoxString SchemaComboBox;
UPROPERTY(BindWidget)
private UButton SettingsButton;
UPROPERTY(BindWidget)
private UScrollBox ScrollBox;
UPROPERTY(BindWidget)
private UHorizontalBox ColumnContainer;
UPROPERTY(EditDefaultsOnly, Category = "Asset Status")
protected TSubclassOf<UAssetStatusColumnWidget> ColumnWidgetClass;
private TArray<UAssetStatusColumnWidget> ColumnWidgets;
private float LastWidth = 0.f;
UFUNCTION(BlueprintOverride)
void PreConstruct(bool bIsDesignTime)
{
const auto Settings = Cast<UAssetStatusSettings>(
UAssetStatusSettings::StaticClass().GetDefaultObject()
);
const auto Schemas = Settings.GetSchemas();
if (Schemas.Num() == 0)
{
Warning("NO SCHEMAS! Add some in the settings");
SchemaComboBox.SetVisibility(ESlateVisibility::Collapsed);
return;
}
SchemaComboBox.ClearOptions();
for (const auto& Schema : Schemas)
{
SchemaComboBox.AddOption(Schema.Name);
}
SchemaComboBox.SetSelectedIndex(0);
SetSelectedSchema(Schemas[0].Name, bIsDesignTime);
}
UFUNCTION(BlueprintOverride)
void Construct()
{
SchemaComboBox.OnSelectionChanged.AddUFunction(this, n"SchemaChanged");
SettingsButton.OnClicked.AddUFunction(this, n"OpenSettings");
}
UFUNCTION(BlueprintOverride)
void Tick(FGeometry MyGeometry, float InDeltaTime)
{
const int NumColumns = ColumnWidgets.Num();
if (NumColumns > 0)
{
const float CurrentWidth = MyGeometry.GetAbsoluteSize().X - 8.f;
if (!Math::IsNearlyEqual(CurrentWidth, LastWidth))
{
const float ColumnWidth = CurrentWidth / NumColumns;
for (auto ColumnWidget : ColumnWidgets)
{
ColumnWidget.SetWidth(ColumnWidth);
}
LastWidth = CurrentWidth;
}
}
}
UFUNCTION()
private void SchemaChanged(FString SelectedItem, ESelectInfo SelectionType)
{
SetSelectedSchema(SelectedItem);
}
private void SetSelectedSchema(FString SchemaName, bool bIsDesignTime = false)
{
LastWidth = 0.f;
ColumnWidgets.Empty();
ColumnContainer.ClearChildren();
const auto Settings = Cast<UAssetStatusSettings>(
UAssetStatusSettings::StaticClass().GetDefaultObject()
);
const auto Schemas = Settings.GetSchemas();
for (const auto& Schema : Schemas)
{
if (Schema.Name == SchemaName)
{
for (const auto& Column : Schema.Columns)
{
auto ColumnWidget = Cast<UAssetStatusColumnWidget>(
NewObject(this, ColumnWidgetClass.Get(), bTransient = true)
);
auto ColumnSlot = ColumnContainer.AddChildToHorizontalBox(ColumnWidget);
FSlateChildSize ColumnSize;
ColumnSize.SizeRule = ESlateSizeRule::Fill;
ColumnSize.Value = 1.f;
ColumnSlot.Size = ColumnSize;
ColumnWidget.Setup(Column, bIsDesignTime);
ColumnWidgets.Add(ColumnWidget);
if (!bIsDesignTime)
{
ColumnWidget.OnAssetDropped.AddUFunction(this, n"AssetChangedColumn");
}
}
return;
}
}
Error(f"Failed to find Schema named '{SchemaName}'");
}
UFUNCTION()
private void OpenSettings()
{
Editor::OpenSettings(n"Project", n"Editor", n"AssetStatusSettings");
}
UFUNCTION()
private void AssetChangedColumn(FName FromCollection, FName ToCollection, const FAssetData& AssetData)
{
auto TagsSubsystem = UAssetTagsSubsystem::Get();
const bool bToCollectionExists = TagsSubsystem.GetCollections().Contains(ToCollection);
if (!bToCollectionExists)
{
const auto CreateCollectionType = ECollectionScriptingShareType::Local;
TagsSubsystem.CreateCollection(ToCollection, CreateCollectionType);
}
const bool bChangedColumn = FromCollection != ToCollection;
for (auto ColumnWidget : ColumnWidgets)
{
const FName ColumnCollectionName = ColumnWidget.GetCollectionName();
if (bChangedColumn)
{
if (ColumnCollectionName == FromCollection)
{
TagsSubsystem.RemoveAssetDataFromCollection(FromCollection, AssetData);
ColumnWidget.UpdateAssetList();
}
else if (ColumnCollectionName == ToCollection)
{
TagsSubsystem.AddAssetDataToCollection(ToCollection, AssetData);
ColumnWidget.UpdateAssetList();
}
}
else
{
if (ColumnCollectionName == FromCollection)
{
ColumnWidget.UpdateAssetList();
break;
}
}
}
}
};
namespace AssetStatusEditor
{
UAssetStatusDragDropPayload GetPayloadFromOperation(UDragDropOperation Operation)
{
if (Operation.Payload.IsA(UAssetStatusDragDropPayload::StaticClass()))
{
return Cast<UAssetStatusDragDropPayload>(Operation.Payload);
}
return nullptr;
}
}
event void FAssetStatusOnAssetDroppedSignature(FName FromCollection, FName ToCollection, const FAssetData& AssetData);
UCLASS(Abstract)
class UAssetStatusColumnWidget : UUserWidget
{
UPROPERTY(EditDefaultsOnly, Category = "Asset Status")
protected FLinearColor ColumnColor = FLinearColor(0.05f, 0.05f, 0.05f, 0.5f);
UPROPERTY(Category = "Asset Status")
FAssetStatusOnAssetDroppedSignature OnAssetDropped;
UPROPERTY(BindWidget)
private USizeBox SizeBox;
UPROPERTY(BindWidget)
private UBorder ColumnBorder;
UPROPERTY(BindWidget)
private UTextBlock HeaderTextBlock;
UPROPERTY(BindWidget)
private UListView AssetList;
UPROPERTY(BindWidget)
private UAssetStatusRemoveDropAreaWidget RemoveDropArea;
private FName CollectionName = NAME_None;
UFUNCTION(BlueprintOverride)
void PreConstruct(bool bIsDesignTime)
{
ColumnBorder.SetBrushColor(ColumnColor);
}
UFUNCTION(BlueprintOverride)
void Construct()
{
RemoveDropArea.OnAssetRemoved.AddUFunction(this, n"AssetRemoved");
}
void Setup(const FAssetStatusColumn& InColumn, bool bIsDesignTime)
{
CollectionName = InColumn.CollectionName;
FText HeaderText = InColumn.ColumnTitle;
if (HeaderText.IsEmptyOrWhitespace())
{
HeaderText = FText::AsCultureInvariant(CollectionName.ToString().ToDisplayName());
}
HeaderTextBlock.SetText(HeaderText);
ColumnColor = InColumn.Color;
ColumnBorder.SetBrushColor(ColumnColor);
AssetList.BP_OnItemDoubleClicked.AddUFunction(this, n"ItemDoubleClicked");
if (!bIsDesignTime)
{
UpdateAssetList();
}
}
FName GetCollectionName() const
{
return CollectionName;
}
void SetWidth(float InWidth)
{
const float Width = Math::Max(SizeBox.GetMinDesiredWidth(), InWidth);
SizeBox.SetWidthOverride(Width);
}
UFUNCTION()
private void ItemDoubleClicked(UObject Item)
{
Cast<UAssetStatusListEntryObject>(Item).OpenInContentBrowser();
}
UFUNCTION()
private void AssetRemoved(FName FromCollection, const FAssetData& AssetData)
{
if (FromCollection == CollectionName)
{
UAssetTagsSubsystem::Get().RemoveAssetDataFromCollection(CollectionName, AssetData);
UpdateAssetList();
}
}
UFUNCTION(BlueprintOverride)
bool OnDrop(FGeometry MyGeometry, FPointerEvent PointerEvent, UDragDropOperation Operation)
{
const auto Payload = AssetStatusEditor::GetPayloadFromOperation(Operation);
if (Payload != nullptr)
{
OnAssetDropped.Broadcast(Payload.FromCollectionName, CollectionName, Payload.AssetData);
ColumnBorder.SetBrushColor(ColumnColor);
SetRemoveDropAreaVisibility(false);
return true;
}
return false;
}
UFUNCTION(BlueprintOverride)
void OnDragEnter(FGeometry MyGeometry, FPointerEvent PointerEvent, UDragDropOperation Operation)
{
const auto Payload = AssetStatusEditor::GetPayloadFromOperation(Operation);
if (Payload != nullptr)
{
if (Payload.FromCollectionName != CollectionName)
{
const float LerpAlpha = 0.25f;
const FLinearColor HoverColor(
Math::Lerp(ColumnColor.R, 1.f, LerpAlpha),
Math::Lerp(ColumnColor.G, 1.f, LerpAlpha),
Math::Lerp(ColumnColor.B, 1.f, LerpAlpha),
Math::Lerp(ColumnColor.A, 1.f, LerpAlpha),
);
ColumnBorder.SetBrushColor(HoverColor);
}
else
{
SetRemoveDropAreaVisibility(true);
}
}
}
UFUNCTION(BlueprintOverride)
void OnDragLeave(FPointerEvent PointerEvent, UDragDropOperation Operation)
{
const auto Payload = AssetStatusEditor::GetPayloadFromOperation(Operation);
if (Payload != nullptr && Payload.FromCollectionName != CollectionName)
{
ColumnBorder.SetBrushColor(ColumnColor);
}
}
UFUNCTION()
void UpdateAssetList()
{
if (AssetRegistry::IsLoadingAssets())
{
System::SetTimer(this, n"UpdateAssetList", 0.1f, false);
return;
}
auto TagsSubsystem = UAssetTagsSubsystem::Get();
const auto Assets = TagsSubsystem.GetAssetsInCollection(CollectionName);
AssetList.ClearListItems();
for (const auto& AssetData : Assets)
{
auto ListObject = UAssetStatusListEntryObject::New(this, CollectionName, AssetData);
AssetList.AddItem(ListObject);
}
SetRemoveDropAreaVisibility(false);
}
private void SetRemoveDropAreaVisibility(bool bShowRemoveDropArea)
{
const auto Vis = bShowRemoveDropArea ? ESlateVisibility::Visible : ESlateVisibility::Collapsed;
RemoveDropArea.SetVisibility(Vis);
}
};
event void FAssetStatusOnAssetRemovedSignature(FName FromCollection, const FAssetData& AssetData);
UCLASS(Abstract)
class UAssetStatusRemoveDropAreaWidget : UUserWidget
{
UPROPERTY(Category = "Asset Status")
FAssetStatusOnAssetRemovedSignature OnAssetRemoved;
UPROPERTY(BindWidget)
protected FLinearColor DefaultBackgroundColor = FLinearColor(0.1f, 0.0f, 0.0f);
UPROPERTY(BindWidget)
private UBorder BackgroundBorder;
UFUNCTION(BlueprintOverride)
void PreConstruct(bool bIsDesignTime)
{
BackgroundBorder.SetBrushColor(DefaultBackgroundColor);
}
UFUNCTION(BlueprintOverride)
void OnDragEnter(FGeometry MyGeometry, FPointerEvent PointerEvent, UDragDropOperation Operation)
{
const FLinearColor HoverColor = FLinearColor::LerpUsingHSV(DefaultBackgroundColor, FLinearColor::White, 0.1f);
BackgroundBorder.SetBrushColor(HoverColor);
}
UFUNCTION(BlueprintOverride)
void OnDragLeave(FPointerEvent PointerEvent, UDragDropOperation Operation)
{
BackgroundBorder.SetBrushColor(DefaultBackgroundColor);
}
UFUNCTION(BlueprintOverride)
bool OnDrop(FGeometry MyGeometry, FPointerEvent PointerEvent, UDragDropOperation Operation)
{
BackgroundBorder.SetBrushColor(DefaultBackgroundColor);
const auto Payload = AssetStatusEditor::GetPayloadFromOperation(Operation);
if (Payload != nullptr)
{
OnAssetRemoved.Broadcast(Payload.FromCollectionName, Payload.AssetData);
return true;
}
return false;
}
};
namespace UAssetStatusListEntryObject
{
UAssetStatusListEntryObject New(UObject Outer, FName CollectionName, const FAssetData& AssetData)
{
auto AssetObject = Cast<UAssetStatusListEntryObject>(
NewObject(Outer, UAssetStatusListEntryObject::StaticClass(), bTransient = true)
);
AssetObject.CollectionName = CollectionName;
AssetObject.AssetData = AssetData;
return AssetObject;
}
};
class UAssetStatusDragDropPayload : UObject
{
FName FromCollectionName;
FAssetData AssetData;
};
class UAssetStatusListEntryObject : UObject
{
FName CollectionName;
FAssetData AssetData;
void OpenInContentBrowser() const
{
TArray<FString> AssetPaths;
AssetPaths.Add(AssetData.PackageName.ToString());
EditorAsset::SyncBrowserToObjects(AssetPaths);
}
};
UCLASS(Abstract)
class UAssetStatusListEntryWidget : UUserWidget
{
UPROPERTY(BindWidget)
private UBorder BackgroundBorder;
UPROPERTY(BindWidget)
private UTextBlock AssetNameTextBlock;
UPROPERTY(EditDefaultsOnly, Category = "Asset Status")
protected const FLinearColor BackgroundDragColor = FLinearColor(0.f, 0.162029f, 0.745404f);
private FName CollectionName;
private FAssetData AssetData;
private bool bIsDragging = false;
UFUNCTION(BlueprintOverride)
void PreConstruct(bool bIsDesignTime)
{
UpdateBackgroundColor();
}
UFUNCTION(Category = "Asset Status")
protected void SetObject(UAssetStatusListEntryObject InObject)
{
CollectionName = InObject.CollectionName;
SetAssetData(InObject.AssetData);
}
void SetAssetData(const FAssetData& InAssetData)
{
AssetData = InAssetData;
UpdateText();
}
void SetIsDragging(bool bInIsDragging)
{
bIsDragging = bInIsDragging;
}
UFUNCTION(BlueprintOverride)
void Construct()
{
UpdateText();
UpdateBackgroundColor();
}
private void UpdateText()
{
if (AssetNameTextBlock != nullptr)
{
AssetNameTextBlock.SetText(FText::AsCultureInvariant(AssetData.AssetName.ToString()));
SetToolTipText(FText::AsCultureInvariant(AssetData.PackageName.ToString()));
}
RenderOpacity = 1.f;
}
UFUNCTION(BlueprintOverride)
FEventReply OnMouseButtonDown(FGeometry MyGeometry, FPointerEvent MouseEvent)
{
return Widget::DetectDragIfPressed(MouseEvent, this, EKeys::LeftMouseButton);
}
UFUNCTION(BlueprintOverride)
void OnDragDetected(FGeometry MyGeometry, FPointerEvent PointerEvent, UDragDropOperation& Operation)
{
Operation = Cast<UDragDropOperation>(
NewObject(this, UDragDropOperation::StaticClass(), bTransient = true)
);
auto DragWidget = Cast<UAssetStatusListEntryWidget>(NewObject(this, Class, bTransient = true));
DragWidget.SetAssetData(AssetData);
DragWidget.SetIsDragging(true);
Operation.DefaultDragVisual = DragWidget;
auto Payload = Cast<UAssetStatusDragDropPayload>(
NewObject(this, UAssetStatusDragDropPayload::StaticClass(), bTransient = true)
);
Payload.FromCollectionName = CollectionName;
Payload.AssetData = AssetData;
Operation.Payload = Payload;
RenderOpacity = 0.25f;
}
UFUNCTION(BlueprintOverride)
void OnDragCancelled(FPointerEvent PointerEvent, UDragDropOperation Operation)
{
RenderOpacity = 1.f;
}
UFUNCTION(BlueprintOverride)
void OnMouseEnter(FGeometry MyGeometry, FPointerEvent MouseEvent)
{
UpdateBackgroundColor();
}
UFUNCTION(BlueprintOverride)
void OnMouseLeave(FPointerEvent MouseEvent)
{
UpdateBackgroundColor();
}
private void UpdateBackgroundColor()
{
if (bIsDragging)
{
BackgroundBorder.SetBrushColor(BackgroundDragColor);
return;
}
const FLinearColor Color(1.f, 1.f, 1.f, IsHovered() ? 0.15f : 0.f);
BackgroundBorder.SetBrushColor(Color);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment