// Copyright Epic Games, Inc. All Rights Reserved. #include "SChooserTableRow.h" #include "Chooser.h" #include "ChooserEditorStyle.h" #include "ChooserTableEditor.h" #include "IContentBrowserSingleton.h" #include "IObjectChooser.h" #include "ObjectChooserWidgetFactories.h" #include "ObjectChooser_Asset.h" #include "SChooserRowHandle.h" #include "ScopedTransaction.h" #include "DragAndDrop/AssetDragDropOp.h" #include "Widgets/Colors/SColorBlock.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Views/STableRow.h" #include "Widgets/Layout/SSeparator.h" #define LOCTEXT_NAMESPACE "ChooserTableRow" namespace UE::ChooserEditor { static const FLinearColor DisabledColor(0.0105,0.0105,0.0105,0.5); static const FLinearColor TestPassedColor(0.0,1.0, 0.0,0.3); static const FLinearColor TestFailedColor(1.0,0.0, 0.0,0.2); void SChooserTableRow::Construct(const FArguments& Args, const TSharedRef& OwnerTableView) { RowIndex = Args._Entry; Chooser = Args._Chooser; Editor = Args._Editor; SMultiColumnTableRow>::Construct( FSuperRowType::FArguments(), OwnerTableView ); if (RowIndex->RowIndex >=0) { SetContent(SNew(SOverlay) + SOverlay::Slot() [ Content.Pin().ToSharedRef() ] + SOverlay::Slot().VAlign(VAlign_Bottom) [ SNew(SSeparator) .SeparatorImage(FAppStyle::GetBrush("PropertyEditor.HorizontalDottedLine")) .ColorAndOpacity_Lambda([this]() { return FSlateColor(bDropSupported ? EStyleColor::Select : EStyleColor::Error); }) .Visibility_Lambda([this]() { return bDragActive && !bDropAbove ? EVisibility::Visible : EVisibility::Hidden; }) ] + SOverlay::Slot().VAlign(VAlign_Top) [ SNew(SSeparator) .SeparatorImage(FAppStyle::GetBrush("PropertyEditor.HorizontalDottedLine")) .ColorAndOpacity_Lambda([this]() { return FSlateColor(bDropSupported ? EStyleColor::Select : EStyleColor::Error); }) .Visibility_Lambda([this]() { return bDragActive && bDropAbove ? EVisibility::Visible : EVisibility::Hidden; }) ] ); } else if (RowIndex->RowIndex == SpecialIndex_Fallback || RowIndex->RowIndex == SpecialIndex_AddRow ) { SetContent( SNew(SOverlay) + SOverlay::Slot() [ Content.Pin().ToSharedRef() ] + SOverlay::Slot().VAlign(VAlign_Top) [ SNew(SSeparator) .SeparatorImage(FAppStyle::GetBrush("PropertyEditor.HorizontalDottedLine")) .ColorAndOpacity_Lambda([this]() { return FSlateColor(bDropSupported ? EStyleColor::Select : EStyleColor::Error); }) .Visibility_Lambda([this]() { return bDragActive ? EVisibility::Visible : EVisibility::Hidden; }) ] ); } } /** Overridden from SMultiColumnTableRow. Generates a widget for this column of the list view. */ TSharedRef SChooserTableRow::GenerateWidgetForColumn(const FName& ColumnName) { static FName Result = "Result"; static FName Handles = "Handles"; static FName AddColumn = "Add"; if (Chooser->ResultsStructs.IsValidIndex(RowIndex->RowIndex)) { if (ColumnName == AddColumn || ColumnName == Handles) { bool bShowHandleImage = ColumnName == Handles; // row drag handle return SNew(SOverlay) + SOverlay::Slot() [ SNew(SChooserRowHandle, bShowHandleImage).ChooserEditor(Editor).RowIndex(RowIndex->RowIndex) ] + SOverlay::Slot() [ SNew(SColorBlock).Color(DisabledColor) .Visibility_Lambda( [this]() { if (Chooser->IsRowDisabled(RowIndex->RowIndex)) { return EVisibility::HitTestInvisible; } return EVisibility::Hidden; }) ]; } else if (ColumnName == Result) { FChooserWidgetValueChanged ChooserWidgetValueChanged = FChooserWidgetValueChanged::CreateLambda([this]() { check(Editor); Editor->AutoPopulateRow(RowIndex->RowIndex); }); UChooserTable* ContextOwner = Chooser->GetRootChooser(); TSharedPtr ResultWidget = FObjectChooserWidgetFactories::CreateWidget( false, Chooser, FObjectChooserBase::StaticStruct(), Chooser->ResultsStructs[RowIndex->RowIndex].GetMutableMemory(), Chooser->ResultsStructs[RowIndex->RowIndex].GetScriptStruct(), ContextOwner->OutputObjectType, FOnStructPicked::CreateLambda([this, InRowIndex = RowIndex->RowIndex, ChooserWidgetValueChanged](const UScriptStruct* ChosenStruct) { UChooserTable* ContextOwner = Chooser->GetRootChooser(); const FScopedTransaction Transaction(LOCTEXT("Change Row Result Type", "Change Row Result Type")); Chooser->Modify(true); Chooser->ResultsStructs[InRowIndex].InitializeAs(ChosenStruct); FObjectChooserWidgetFactories::CreateWidget( false, Chooser, FObjectChooserBase::StaticStruct(), Chooser->ResultsStructs[InRowIndex].GetMutableMemory(), ChosenStruct, ContextOwner->OutputObjectType, FOnStructPicked(), &CacheBorder, ChooserWidgetValueChanged); }), &CacheBorder, ChooserWidgetValueChanged ); return SNew(SOverlay) + SOverlay::Slot() [ ResultWidget.ToSharedRef() ] + SOverlay::Slot() [ SNew(SColorBlock).Color(DisabledColor) .Visibility_Lambda( [this]() { if (Chooser->IsRowDisabled(RowIndex->RowIndex)) { return EVisibility::HitTestInvisible; } return EVisibility::Hidden; }) ]; } else { const int ColumnIndex = ColumnName.GetNumber() - 1; if (ColumnIndex < Chooser->ColumnsStructs.Num() && ColumnIndex >=0) { FChooserColumnBase* Column = &Chooser->ColumnsStructs[ColumnIndex].GetMutable(); const UStruct * ColumnStruct = Chooser->ColumnsStructs[ColumnIndex].GetScriptStruct(); TSharedPtr ColumnWidget = FObjectChooserWidgetFactories::CreateColumnWidget(Column, ColumnStruct, Chooser, RowIndex->RowIndex); if (ColumnWidget.IsValid()) { return SNew(SOverlay) + SOverlay::Slot() [ SNew(SBorder) .BorderBackgroundColor(FLinearColor(0,0,0,0)) .Padding(FMargin(4,0)) .Content() [ SNew(SOverlay) + SOverlay::Slot() [ SNew(SColorBlock).Color_Lambda([this, ColumnIndex](){ return Editor->TableHasFocus() ? FStyleColors::Select.GetSpecifiedColor() : FStyleColors::SelectInactive.GetSpecifiedColor(); } ) .Visibility_Lambda( [this, ColumnIndex]() { if (Editor->IsColumnSelected(ColumnIndex)) { return EVisibility::Visible; } return EVisibility::Hidden; }) ] + SOverlay::Slot() [ ColumnWidget.ToSharedRef() ] ] ] + SOverlay::Slot() [ SNew(SColorBlock).Visibility(EVisibility::HitTestInvisible).Color_Lambda( [this,Column]() { if (Chooser->GetDebugTestValuesValid()) { if (Column->HasCosts()) { if (!Column->HasFilters() || Column->EditorTestFilter(RowIndex->RowIndex)) { const double Cost = Column->EditorTestCost(RowIndex->RowIndex); const FLinearColor Low(0.0, 1.0, 0.0, 0.30); const FLinearColor Mid(0.8, 0.8, 0.0, 0.30); const FLinearColor High(1.0, 0.0, 0.0, 0.20); if (Cost < 0.5) { return FLinearColor::LerpUsingHSV(Low, Mid, FMath::Clamp(Cost * 2.0, 0.0f, 1.0f)); } else { return FLinearColor::LerpUsingHSV(Mid, High, FMath::Clamp((Cost - 0.5) * 2.0, 0.0f, 1.0f)); } } } else if (Column->HasFilters()) { if (Column->EditorTestFilter(RowIndex->RowIndex)) { return TestPassedColor; } else { return TestFailedColor; } } } return FLinearColor::Transparent; }) ] + SOverlay::Slot() [ SNew(SColorBlock).Color(DisabledColor) .Visibility_Lambda( [this,Column]() { if (Chooser->IsRowDisabled(RowIndex->RowIndex) || Column->bDisabled) { return EVisibility::HitTestInvisible; } return EVisibility::Hidden; }) ] ; } } } } else if (RowIndex->RowIndex == SpecialIndex_Fallback) { if (ColumnName == Handles) { return SNew(SOverlay) + SOverlay::Slot() [ SNew(SBox).Padding(0.0f) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .WidthOverride(16.0f) [ SNew(SImage) .Image(FChooserEditorStyle::Get().GetBrush("ChooserEditor.FallbackIcon")) .ToolTipText(LOCTEXT("FallbackTooltip","Fallback result: Returned if all rows failed.")) ] ] + SOverlay::Slot() [ SNew(SBox).Padding(0.0f) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .WidthOverride(16.0f) [ SNew(SImage) .Visibility_Lambda([this]() { return Chooser->GetDebugTestValuesValid() && RowIndex->RowIndex == Chooser->GetDebugSelectedRow() ? EVisibility::HitTestInvisible : EVisibility::Hidden; }) .Image(FAppStyle::Get().GetBrush("Icons.ArrowRight")) ] ]; } else if (ColumnName == Result) { FChooserWidgetValueChanged ChooserWidgetValueChanged = FChooserWidgetValueChanged::CreateLambda([this]() { check(Editor); Editor->AutoPopulateRow(RowIndex->RowIndex); }); UChooserTable* ContextOwner = Chooser->GetRootChooser(); TSharedPtr ResultWidget = FObjectChooserWidgetFactories::CreateWidget( false, Chooser, FObjectChooserBase::StaticStruct(), Chooser->FallbackResult.GetMutableMemory(), Chooser->FallbackResult.GetScriptStruct(), ContextOwner->OutputObjectType, FOnStructPicked::CreateLambda([this, InRowIndex = RowIndex->RowIndex, ChooserWidgetValueChanged](const UScriptStruct* ChosenStruct) { UChooserTable* ContextOwner = Chooser->GetRootChooser(); const FScopedTransaction Transaction(LOCTEXT("Change Row Result Type", "Change Row Result Type")); Chooser->Modify(true); Chooser->FallbackResult.InitializeAs(ChosenStruct); FObjectChooserWidgetFactories::CreateWidget( false, Chooser, FObjectChooserBase::StaticStruct(), Chooser->FallbackResult.GetMutableMemory(), ChosenStruct, ContextOwner->OutputObjectType, FOnStructPicked(), &CacheBorder, ChooserWidgetValueChanged, LOCTEXT("Fallback Result", "Fallback Result: (None)") ); }), &CacheBorder, ChooserWidgetValueChanged, LOCTEXT("Fallback Result", "Fallback Result: (None)") ); return ResultWidget.ToSharedRef(); } else { const int ColumnIndex = ColumnName.GetNumber() - 1; if (ColumnIndex < Chooser->ColumnsStructs.Num() && ColumnIndex >=0) { FChooserColumnBase* Column = &Chooser->ColumnsStructs[ColumnIndex].GetMutable(); const UStruct * ColumnStruct = Chooser->ColumnsStructs[ColumnIndex].GetScriptStruct(); TSharedPtr ColumnWidget = FObjectChooserWidgetFactories::CreateColumnWidget(Column, ColumnStruct, Chooser->GetRootChooser(), -2); if (ColumnWidget.IsValid()) { return SNew(SOverlay) + SOverlay::Slot() [ SNew(SBorder) .BorderBackgroundColor(FLinearColor(0,0,0,0)) .Padding(FMargin(4,0)) .Content() [ SNew(SOverlay) +SOverlay::Slot() [ SNew(SColorBlock).Color_Lambda([this, ColumnIndex](){ return Editor->TableHasFocus() ? FStyleColors::Select.GetSpecifiedColor() : FStyleColors::SelectInactive.GetSpecifiedColor(); } ) .Visibility_Lambda( [this, ColumnIndex]() { if (Editor->IsColumnSelected(ColumnIndex)) { return EVisibility::Visible; } return EVisibility::Hidden; }) ] + SOverlay::Slot() [ ColumnWidget.ToSharedRef() ] ] ]; } } } } else if (RowIndex->RowIndex == SpecialIndex_AddRow) { // on the row past the end, show an Add button in the first column available bool bIsLeftmostColumn; if (Chooser->ResultType != EObjectChooserResultType::NoPrimaryResult) { bIsLeftmostColumn = ColumnName == Result; } else if (Chooser->ColumnsStructs.IsEmpty()) { bIsLeftmostColumn = ColumnName == AddColumn; } else { bIsLeftmostColumn = ColumnName.GetNumber() == 1; } if (bIsLeftmostColumn) { return SNew(SHorizontalBox) +SHorizontalBox::Slot().AutoWidth() [ Editor->GetCreateRowComboButton().ToSharedRef() ]; } } return SNullWidget::NullWidget; } void SChooserTableRow::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { bDropSupported = false; if (TSharedPtr Operation = DragDropEvent.GetOperationAs()) { bDropSupported = true; } else if (TSharedPtr ContentDragDropOp = DragDropEvent.GetOperationAs()) { if (Chooser->ResultType == EObjectChooserResultType::ObjectResult) { bDropSupported = true; UChooserTable* ContextOwner = Chooser->GetRootChooser(); if (ContextOwner->OutputObjectType) // if OutputObjectType is null, then any kind of object is supported, don't need to check all of them { for (const FAssetData& Asset : ContentDragDropOp->GetAssets()) { const UClass* AssetClass = Asset.GetClass(); if(AssetClass->IsChildOf(UChooserTable::StaticClass())) { const UChooserTable* DraggedChooserTable = Cast(Asset.GetAsset()); // verify dragged chooser result type matches this chooser result type if (DraggedChooserTable->ResultType == EObjectChooserResultType::ClassResult || DraggedChooserTable->OutputObjectType == nullptr || !DraggedChooserTable->OutputObjectType->IsChildOf(ContextOwner->OutputObjectType)) { bDropSupported = false; break; } } else if(!AssetClass->IsChildOf(ContextOwner->OutputObjectType)) { bDropSupported = false; break; } } } } } float Center = MyGeometry.Position.Y + MyGeometry.Size.Y; bDropAbove = DragDropEvent.GetScreenSpacePosition().Y < Center; bDragActive = true; } void SChooserTableRow::OnDragLeave(const FDragDropEvent& DragDropEvent) { bDragActive = false; } FReply SChooserTableRow::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { DragActiveCounter = 2; bDragActive = true; float Center = MyGeometry.AbsolutePosition.Y + MyGeometry.Size.Y/2; bDropAbove = DragDropEvent.GetScreenSpacePosition().Y < Center; return FReply::Handled(); } FReply SChooserTableRow::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { bDragActive = false; if (!bDropSupported) { return FReply::Unhandled(); } if (TSharedPtr Operation = DragDropEvent.GetOperationAs()) { int NewRowIndex; if (!Chooser->ResultsStructs.IsValidIndex(RowIndex->RowIndex)) { // for special (negative) indices, move to the end NewRowIndex = Chooser->ResultsStructs.Num(); } else if (bDropAbove) { NewRowIndex = RowIndex->RowIndex; } else { NewRowIndex = RowIndex->RowIndex+1; } Editor->PasteInternal(Operation->RowData, NewRowIndex); GEditor->EndTransaction(); } else if (TSharedPtr ContentDragDropOp = DragDropEvent.GetOperationAs()) { FScopedTransaction ScopedTransaction(LOCTEXT("DragDropAssets","Drag and Drop Assets into Chooser")); Chooser->Modify(true); if (Chooser->ResultType == EObjectChooserResultType::ObjectResult) { int InsertRowIndex = RowIndex->RowIndex; if (!Chooser->ResultsStructs.IsValidIndex(RowIndex->RowIndex)) { // for special (negative) indices, move to the end InsertRowIndex = Chooser->ResultsStructs.Num(); } else if (!bDropAbove) { InsertRowIndex = RowIndex->RowIndex + 1; } if (bDropSupported) { TArray NewResults; const TArray& AssetList = ContentDragDropOp->GetAssets(); NewResults.Reserve(AssetList.Num()); for (const FAssetData& Asset : AssetList) { const UClass* AssetClass = Asset.GetClass(); NewResults.SetNum(NewResults.Num()+1); FInstancedStruct& NewResult = NewResults.Last(); if(AssetClass->IsChildOf(UChooserTable::StaticClass())) { NewResult.InitializeAs(FEvaluateChooser::StaticStruct()); NewResult.GetMutable().Chooser = Cast(Asset.GetAsset()); } else { UChooserTable* ContextOwner = Chooser->GetRootChooser(); if (ContextOwner->OutputObjectType == nullptr || ensure(AssetClass->IsChildOf(ContextOwner->OutputObjectType))) { NewResult.InitializeAs(FAssetChooser::StaticStruct()); NewResult.GetMutable().Asset = Asset.GetAsset(); } } } Chooser->ResultsStructs.Insert(NewResults, InsertRowIndex); // Make sure each column has the same number of row datas as there are results for(FInstancedStruct& ColumnData : Chooser->ColumnsStructs) { FChooserColumnBase& Column = ColumnData.GetMutable(); Column.InsertRows(InsertRowIndex, NewResults.Num()); } Editor->ClearSelectedRows(); Editor->RefreshAll(); TArray> RowsToSelect; RowsToSelect.Reserve(NewResults.Num()); for(int Index = InsertRowIndex; Index < InsertRowIndex + NewResults.Num(); Index++) { Editor->AutoPopulateRow(Index); RowsToSelect.Add(Index); } Editor->SelectRows(RowsToSelect); } } } return FReply::Handled(); } void SChooserTableRow::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { DragActiveCounter --; if (DragActiveCounter < 0) { DragActiveCounter = 0; bDragActive = false; } SMultiColumnTableRow>::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); } } #undef LOCTEXT_NAMESPACE