Files
Andron666 9c38e93fa4 part7
2022-12-05 20:31:35 +05:00

2042 lines
60 KiB
C++

/*
* Copyright (c) <2021> Side Effects Software Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. The name of Side Effects Software may not be used to endorse or
* promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "HoudiniEngineDetails.h"
#include "HoudiniEngineEditorPrivatePCH.h"
#include "HoudiniAssetComponent.h"
#include "HoudiniAssetComponentDetails.h"
#include "HoudiniAssetActor.h"
#include "HoudiniAsset.h"
#include "HoudiniParameter.h"
#include "HoudiniEngineUtils.h"
#include "HoudiniEngineRuntime.h"
#include "HoudiniEngineBakeUtils.h"
#include "HoudiniPackageParams.h"
#include "HoudiniEngineEditor.h"
#include "HoudiniEngineEditorUtils.h"
#include "CoreMinimal.h"
#include "DetailCategoryBuilder.h"
#include "IDetailGroup.h"
#include "DetailWidgetRow.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Input/SMultiLineEditableTextBox.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "Brushes/SlateImageBrush.h"
#include "Widgets/Input/SComboBox.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ActorPickerMode.h"
#include "SceneOutlinerModule.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IMainFrameModule.h"
#include "AssetThumbnail.h"
#include "DetailLayoutBuilder.h"
#include "SAssetDropTarget.h"
#include "PropertyCustomizationHelpers.h"
#include "ScopedTransaction.h"
#include "SEnumCombobox.h"
#include "HAL/FileManager.h"
#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE
#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1
#define HOUDINI_ENGINE_UI_SECTION_BAKE 2
#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3
#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4
#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f
#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate"
#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake"
#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options"
#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug"
void
SHoudiniAssetLogWidget::Construct(const FArguments & InArgs)
{
this->ChildSlot
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background")))
.Content()
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
SNew(SMultiLineEditableTextBox)
.Text(FText::FromString(InArgs._LogText))
.AutoWrapText(true)
.IsReadOnly(true)
]
]
];
}
void
FHoudiniEngineDetails::CreateWidget(
IDetailCategoryBuilder& HoudiniEngineCategoryBuilder,
TArray<UHoudiniAssetComponent*>& InHACs)
{
if (InHACs.Num() <= 0)
return;
UHoudiniAssetComponent* MainHAC = InHACs[0];
if (!MainHAC || MainHAC->IsPendingKill())
return;
// 0. Houdini Engine Icon
FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs);
// 1. Houdini Engine Session Status
FHoudiniAssetComponentDetails::AddSessionStatusRow(HoudiniEngineCategoryBuilder);
// 2. Create Generate Category
FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs);
// 3. Create Bake Category
FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs);
// 4. Create Asset Options Category
FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs);
// 5. Create Help and Debug Category
FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs);
}
void
FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(
IDetailCategoryBuilder& HoudiniEngineCategoryBuilder,
TArray<UHoudiniAssetComponent*>& InHACs)
{
if (InHACs.Num() <= 0)
return;
UHoudiniAssetComponent* MainHAC = InHACs[0];
if (!MainHAC || MainHAC->IsPendingKill())
return;
// Skip drawing the icon if the icon image is not loaded correctly.
TSharedPtr<FSlateDynamicImageBrush> HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush();
if (!HoudiniEngineUIIconBrush.IsValid())
return;
FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedRef<SHorizontalBox> Box = SNew(SHorizontalBox);
TSharedPtr<SImage> Image;
Box->AddSlot()
.AutoWidth()
.Padding(0.0f, 5.0f, 5.0f, 10.0f)
.HAlign(HAlign_Left)
[
SNew(SBox)
.HeightOverride(30)
.WidthOverride(208)
[
SAssignNew(Image, SImage)
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
Image->SetImage(
TAttribute<const FSlateBrush*>::Create(
TAttribute<const FSlateBrush*>::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() {
return HoudiniEngineUIIconBrush.Get();
})));
Row.WholeRowWidget.Widget = Box;
Row.IsEnabled(false);
}
void
FHoudiniEngineDetails::CreateGenerateWidgets(
IDetailCategoryBuilder& HoudiniEngineCategoryBuilder,
TArray<UHoudiniAssetComponent*>& InHACs)
{
if (InHACs.Num() <= 0)
return;
UHoudiniAssetComponent* MainHAC = InHACs[0];
if (!MainHAC || MainHAC->IsPendingKill())
return;
auto OnReBuildClickedLambda = [InHACs]()
{
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->MarkAsNeedRebuild();
}
return FReply::Handled();
};
auto OnRecookClickedLambda = [InHACs]()
{
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->MarkAsNeedCook();
}
return FReply::Handled();
};
auto ShouldEnableResetParametersButtonLambda = [InHACs]()
{
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
// Reset parameters to default values?
for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n)
{
UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n);
if (NextParm && !NextParm->IsDefault())
return true;
}
}
return false;
};
auto OnResetParametersClickedLambda = [InHACs]()
{
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
// Reset parameters to default values?
for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n)
{
UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n);
if (NextParm && !NextParm->IsDefault())
{
NextParm->RevertToDefault();
}
}
}
return FReply::Handled();
};
auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType)
{
if (!MainHAC || MainHAC->IsPendingKill())
return;
FString NewPathStr = Val.ToString();
if (NewPathStr.IsEmpty())
return;
if (NewPathStr.StartsWith("Game/"))
{
NewPathStr = "/" + NewPathStr;
}
FString AbsolutePath;
if (NewPathStr.StartsWith("/Game/"))
{
FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6);
AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath);
}
else
{
AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr);
}
if (!FPaths::DirectoryExists(AbsolutePath))
{
HOUDINI_LOG_WARNING(TEXT("Invalid path"));
FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true);
return;
}
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr))
continue;
NextHAC->TemporaryCookFolder.Path = NewPathStr;
}
};
FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE);
// Button Row (draw only if expanded)
if (!MainHAC->bGenerateMenuExpanded)
return;
TSharedPtr<FSlateDynamicImageBrush> HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush();
TSharedPtr<FSlateDynamicImageBrush> HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush();
TSharedPtr<FSlateDynamicImageBrush> HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush();
FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedRef<SHorizontalBox> ButtonHorizontalBox = SNew(SHorizontalBox);
// Recook button
TSharedPtr<SButton> RecookButton;
TSharedPtr<SHorizontalBox> RecookButtonHorizontalBox;
ButtonHorizontalBox->AddSlot()
.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
//.Padding(2.0f, 0.0f, 0.0f, 2.0f)
[
SNew(SBox)
.WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SAssignNew(RecookButton, SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook."))
//.Text(FText::FromString("Recook"))
.Visibility(EVisibility::Visible)
.OnClicked_Lambda(OnRecookClickedLambda)
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Center)
[
SAssignNew(RecookButtonHorizontalBox, SHorizontalBox)
]
]
]
];
if (HoudiniEngineUIRecookIconBrush.IsValid())
{
TSharedPtr<SImage> RecookImage;
RecookButtonHorizontalBox->AddSlot()
.MaxWidth(16.0f)
//.Padding(23.0f, 0.0f, 3.0f, 0.0f)
[
SNew(SBox)
.WidthOverride(16.0f)
.HeightOverride(16.0f)
[
SAssignNew(RecookImage, SImage)
//.ColorAndOpacity(FSlateColor::UseForeground())
]
];
RecookImage->SetImage(
TAttribute<const FSlateBrush*>::Create(
TAttribute<const FSlateBrush*>::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() {
return HoudiniEngineUIRecookIconBrush.Get();
})));
}
RecookButtonHorizontalBox->AddSlot()
.Padding(5.0, 0.0, 0.0, 0.0)
//.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(STextBlock)
.Text(FText::FromString("Recook"))
];
// Rebuild button
TSharedPtr<SButton> RebuildButton;
TSharedPtr<SHorizontalBox> RebuildButtonHorizontalBox;
ButtonHorizontalBox->AddSlot()
.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
//.Padding(15.0f, 0.0f, 0.0f, 2.0f)
//.Padding(2.0f, 0.0f, 0.0f, 2.0f)
[
SNew(SBox)
.WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SAssignNew(RebuildButton, SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook."))
//.Text(FText::FromString("Rebuild"))
.Visibility(EVisibility::Visible)
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Center)
[
SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox)
]
]
.OnClicked_Lambda(OnReBuildClickedLambda)
]
];
if (HoudiniEngineUIRebuildIconBrush.IsValid())
{
TSharedPtr<SImage> RebuildImage;
RebuildButtonHorizontalBox->AddSlot()
//.Padding(25.0f, 0.0f, 3.0f, 0.0f)
//.Padding(2.0f, 0.0f, 0.0f, 2.0f)
.MaxWidth(16.0f)
[
SNew(SBox)
.WidthOverride(16.0f)
.HeightOverride(16.0f)
[
SAssignNew(RebuildImage, SImage)
//.ColorAndOpacity(FSlateColor::UseForeground())
]
];
RebuildImage->SetImage(
TAttribute<const FSlateBrush*>::Create(
TAttribute<const FSlateBrush*>::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() {
return HoudiniEngineUIRebuildIconBrush.Get();
})));
}
RebuildButtonHorizontalBox->AddSlot()
.VAlign(VAlign_Center)
.AutoWidth()
.Padding(5.0, 0.0, 0.0, 0.0)
[
SNew(STextBlock)
.Text(FText::FromString("Rebuild"))
];
ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox;
ButtonRow.IsEnabled(false);
// Reset Parameters button
TSharedPtr<SButton> ResetParametersButton;
TSharedPtr<SHorizontalBox> ResetParametersButtonHorizontalBox;
ButtonHorizontalBox->AddSlot()
.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
//.Padding(2.0f, 0.0f, 0.0f, 2.0f)
[
SNew(SBox)
.WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SAssignNew(ResetParametersButton, SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values."))
//.Text(FText::FromString("Reset Parameters"))
.IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda)
.Visibility(EVisibility::Visible)
.OnClicked_Lambda(OnResetParametersClickedLambda)
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Center)
[
SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox)
]
]
]
];
if (HoudiniEngineUIResetParametersIconBrush.IsValid())
{
TSharedPtr<SImage> ResetParametersImage;
ResetParametersButtonHorizontalBox->AddSlot()
.MaxWidth(16.0f)
//.Padding(0.0f, 0.0f, 3.0f, 0.0f)
[
SNew(SBox)
.WidthOverride(16.0f)
.HeightOverride(16.0f)
[
SAssignNew(ResetParametersImage, SImage)
//.ColorAndOpacity(FSlateColor::UseForeground())
]
];
ResetParametersImage->SetImage(
TAttribute<const FSlateBrush*>::Create(
TAttribute<const FSlateBrush*>::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() {
return HoudiniEngineUIResetParametersIconBrush.Get();
})));
}
ResetParametersButtonHorizontalBox->AddSlot()
.Padding(5.0, 0.0, 0.0, 0.0)
//.FillWidth(4.2f)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(STextBlock)
//.MinDesiredWidth(160.f)
.Text(FText::FromString("Reset Parameters"))
];
// Temp Cook Folder Row
FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedRef<SHorizontalBox> TempCookFolderRowHorizontalBox = SNew(SHorizontalBox);
TempCookFolderRowHorizontalBox->AddSlot()
//.Padding(30.0f, 0.0f, 6.0f, 0.0f)
.MaxWidth(155.0f)
[
SNew(SBox)
.WidthOverride(155.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder"))
.ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook."))
]
];
TempCookFolderRowHorizontalBox->AddSlot()
.MaxWidth(235.0f)
[
SNew(SBox)
.WidthOverride(235.0f)
[
SNew(SEditableTextBox)
.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH)
.ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook."))
.HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder"))
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
.Text(FText::FromString(MainHAC->TemporaryCookFolder.Path))
.OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda)
]
];
TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox;
}
void
FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC)
{
if (!IsValid(InHAC))
return;
if (!bInState)
{
if (InHAC->GetOnPostCookBakeDelegate().IsBound())
InHAC->GetOnPostCookBakeDelegate().Unbind();
}
else
{
InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC)
{
return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(
HAC,
HAC->bReplacePreviousBake,
HAC->HoudiniEngineBakeOption,
HAC->bRemoveOutputAfterBake,
HAC->bRecenterBakedActors);
});
}
}
void
FHoudiniEngineDetails::CreateBakeWidgets(
IDetailCategoryBuilder& HoudiniEngineCategoryBuilder,
TArray<UHoudiniAssetComponent*>& InHACs)
{
if (InHACs.Num() <= 0)
return;
UHoudiniAssetComponent * MainHAC = InHACs[0];
if (!MainHAC || MainHAC->IsPendingKill())
return;
FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE);
if (!MainHAC->bBakeMenuExpanded)
return;
auto OnBakeButtonClickedLambda = [InHACs, MainHAC]()
{
for (auto & NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(
NextHAC,
MainHAC->bReplacePreviousBake,
MainHAC->HoudiniEngineBakeOption,
MainHAC->bRemoveOutputAfterBake,
MainHAC->bRecenterBakedActors);
}
return FReply::Handled();
};
auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType)
{
if (!MainHAC || MainHAC->IsPendingKill())
return;
FString NewPathStr = Val.ToString();
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
if (NextHAC->BakeFolder.Path.Equals(NewPathStr))
continue;
NextHAC->BakeFolder.Path = NewPathStr;
}
};
// Button Row
FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedRef<SHorizontalBox> ButtonRowHorizontalBox = SNew(SHorizontalBox);
// Bake Button
TSharedPtr<SButton> BakeButton;
TSharedPtr<SHorizontalBox> BakeButtonHorizontalBox;
ButtonRowHorizontalBox->AddSlot()
.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
//.Padding(15.f, 0.0f, 0.0f, 0.0f)
[
SNew(SBox)
.WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SAssignNew(BakeButton, SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s)."))
//.Text(FText::FromString("Recook"))
.Visibility(EVisibility::Visible)
.OnClicked_Lambda(OnBakeButtonClickedLambda)
.Content()
[
SAssignNew(BakeButtonHorizontalBox, SHorizontalBox)
]
]
];
TSharedPtr<FSlateDynamicImageBrush> BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush();
if (BakeIconBrush.IsValid())
{
TSharedPtr<SImage> BakeImage;
BakeButtonHorizontalBox->AddSlot()
.MaxWidth(16.0f)
//.Padding(23.0f, 0.0f, 3.0f, 0.0f)
[
SNew(SBox)
.WidthOverride(16.0f)
.HeightOverride(16.0f)
[
SAssignNew(BakeImage, SImage)
]
];
BakeImage->SetImage(
TAttribute<const FSlateBrush*>::Create(
TAttribute<const FSlateBrush*>::FGetter::CreateLambda([BakeIconBrush]() {
return BakeIconBrush.Get();
})));
}
BakeButtonHorizontalBox->AddSlot()
.Padding(5.0, 0.0, 0.0, 0.0)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(STextBlock)
.Text(FText::FromString("Bake"))
];
// Bake Type ComboBox
TSharedPtr<SComboBox<TSharedPtr<FString>>> TypeComboBox;
TArray<TSharedPtr<FString>>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels();
TSharedPtr<FString> IntialSelec;
if (OptionSource)
{
IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption];
}
ButtonRowHorizontalBox->AddSlot()
/*.AutoWidth()*/
.Padding(3.0, 0.0, 4.0f, 0.0f)
//.MaxWidth(103.f)
.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SNew(SBox)
//.WidthOverride(103.f)
.WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SAssignNew(TypeComboBox, SComboBox<TSharedPtr<FString>>)
.OptionsSource(OptionSource)
.InitiallySelectedItem(IntialSelec)
.OnGenerateWidget_Lambda(
[](TSharedPtr< FString > InItem)
{
FText ChoiceEntryText = FText::FromString(*InItem);
return SNew(STextBlock)
.Text(ChoiceEntryText)
.ToolTipText(ChoiceEntryText)
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")));
})
.OnSelectionChanged_Lambda(
[MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType)
{
if (!NewChoice.IsValid())
return;
const EHoudiniEngineBakeOption NewOption =
FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get());
for (auto & NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
MainHAC->HoudiniEngineBakeOption = NewOption;
}
FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true);
})
[
SNew(STextBlock)
.Text_Lambda([MainHAC]()
{
return FText::FromString(
FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption));
})
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
]
];
ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox;
// Clear Output After Baking Row
FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedRef<SHorizontalBox> AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox);
// Remove Output Checkbox
TSharedPtr<SCheckBox> CheckBoxRemoveOutput;
TSharedPtr<SCheckBox> CheckBoxAutoBake;
TSharedPtr<SCheckBox> CheckBoxRecenterBakedActors;
TSharedPtr<SCheckBox> CheckBoxReplacePreviousBake;
TSharedPtr<SVerticalBox> LeftColumnVerticalBox;
TSharedPtr<SVerticalBox> RightColumnVerticalBox;
AdditionalBakeSettingsRowHorizontalBox->AddSlot()
.Padding(30.0f, 5.0f, 0.0f, 0.0f)
.MaxWidth(200.f)
[
SNew(SBox)
.WidthOverride(200.f)
[
SAssignNew(LeftColumnVerticalBox, SVerticalBox)
]
];
AdditionalBakeSettingsRowHorizontalBox->AddSlot()
.Padding(20.0f, 5.0f, 0.0f, 0.0f)
.MaxWidth(200.f)
[
SNew(SBox)
[
SAssignNew(RightColumnVerticalBox, SVerticalBox)
]
];
LeftColumnVerticalBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 3.5f)
[
SNew(SBox)
.WidthOverride(160.f)
[
SAssignNew(CheckBoxRemoveOutput, SCheckBox)
.Content()
[
SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake"))
.ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed."))
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
.IsChecked_Lambda([MainHAC]()
{
return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState)
{
const bool bNewState = (NewState == ECheckBoxState::Checked);
for (auto & NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bRemoveOutputAfterBake = bNewState;
}
// FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true);
})
]
];
LeftColumnVerticalBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 3.5f)
[
SNew(SBox)
.WidthOverride(160.f)
[
SAssignNew(CheckBoxRecenterBakedActors, SCheckBox)
.Content()
[
SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors"))
.ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center."))
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
.IsChecked_Lambda([MainHAC]()
{
return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState)
{
const bool bNewState = (NewState == ECheckBoxState::Checked);
for (auto & NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bRecenterBakedActors = bNewState;
}
// FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true);
})
]
];
// TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate
// We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn
// Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access
// the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and
// managing the delegate in this way).
for (auto & NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
const bool bState = NextHAC->IsBakeAfterNextCookEnabled();
NextHAC->SetBakeAfterNextCookEnabled(bState);
OnBakeAfterCookChangedHelper(bState, NextHAC);
}
RightColumnVerticalBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 3.5f)
[
SNew(SBox)
.WidthOverride(160.f)
[
SAssignNew(CheckBoxAutoBake, SCheckBox)
.Content()
[
SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake"))
.ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook."))
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
.IsChecked_Lambda([MainHAC]()
{
return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState)
{
const bool bNewState = (NewState == ECheckBoxState::Checked);
for (auto & NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->SetBakeAfterNextCookEnabled(bNewState);
OnBakeAfterCookChangedHelper(bNewState, NextHAC);
}
// FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true);
})
]
];
// Replace Checkbox
RightColumnVerticalBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 3.5f)
[
SNew(SBox)
.WidthOverride(160.f)
[
SAssignNew(CheckBoxReplacePreviousBake, SCheckBox)
.Content()
[
SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake"))
.ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects."))
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
.IsChecked_Lambda([MainHAC]()
{
return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState)
{
const bool bNewState = (NewState == ECheckBoxState::Checked);
for (auto & NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
MainHAC->bReplacePreviousBake = bNewState;
}
FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true);
})
]
];
ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox;
// Bake Folder Row
FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedRef<SHorizontalBox> BakeFolderRowHorizontalBox = SNew(SHorizontalBox);
BakeFolderRowHorizontalBox->AddSlot()
/*.AutoWidth()*/
.Padding(30.0f, 0.0f, 6.0f, 0.0f)
.MaxWidth(155.0f)
[
SNew(SBox)
.WidthOverride(155.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder"))
.ToolTipText(LOCTEXT(
"HoudiniEngineBakeFolderTooltip",
"The folder used to store the objects that are generated by this Houdini Asset when baking, if the "
"unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the "
"plugin settings is used."))
]
];
BakeFolderRowHorizontalBox->AddSlot()
/*.AutoWidth()*/
.MaxWidth(235.0)
[
SNew(SBox)
.WidthOverride(235.0f)
[
SNew(SEditableTextBox)
.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH)
.ToolTipText(LOCTEXT(
"HoudiniEngineBakeFolderTooltip",
"The folder used to store the objects that are generated by this Houdini Asset when baking, if the "
"unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the "
"plugin settings is used."))
.HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder"))
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
.Text_Lambda([MainHAC]()
{
if (!IsValid(MainHAC))
return FText();
return FText::FromString(MainHAC->BakeFolder.Path);
})
.OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda)
]
];
BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox;
switch (MainHAC->HoudiniEngineBakeOption)
{
case EHoudiniEngineBakeOption::ToActor:
{
if (MainHAC->bReplacePreviousBake)
{
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip",
"Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result."));
}
else
{
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip",
"Bake this Houdini Asset Actor and its components to native unreal actors and components."));
}
}
break;
case EHoudiniEngineBakeOption::ToBlueprint:
{
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip",
"Bake this Houdini Asset Actor to a blueprint."));
}
break;
case EHoudiniEngineBakeOption::ToFoliage:
{
if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC))
{
// If the HAC does not have instanced output, disable Bake to Foliage
BakeButton->SetEnabled(false);
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip",
"The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage."));
}
else
{
if (MainHAC->bReplacePreviousBake)
{
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip",
"Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor."));
}
else
{
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip",
"Add this Houdini Asset Actor's instancers to the current level's Foliage."));
}
}
}
break;
case EHoudiniEngineBakeOption::ToWorldOutliner:
{
if (MainHAC->bReplacePreviousBake)
{
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip",
"Not implemented."));
}
else
{
BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip",
"Not implemented."));
}
}
break;
}
// Todo: remove me!
if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner)
BakeButton->SetEnabled(false);
}
void
FHoudiniEngineDetails::CreateAssetOptionsWidgets(
IDetailCategoryBuilder& HoudiniEngineCategoryBuilder,
TArray<UHoudiniAssetComponent*>& InHACs)
{
if (InHACs.Num() <= 0)
return;
UHoudiniAssetComponent * MainHAC = InHACs[0];
if (!MainHAC || MainHAC->IsPendingKill())
return;
// Header Row
FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS);
if (!MainHAC->bAssetOptionMenuExpanded)
return;
auto IsCheckedParameterChangedLambda = [MainHAC]()
{
if (!MainHAC || MainHAC->IsPendingKill())
return ECheckBoxState::Unchecked;
return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState)
{
bool bChecked = (NewState == ECheckBoxState::Checked);
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bCookOnParameterChange = bChecked;
}
};
auto IsCheckedTransformChangeLambda = [MainHAC]()
{
if (!MainHAC || MainHAC->IsPendingKill())
return ECheckBoxState::Unchecked;
return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState)
{
bool bChecked = (NewState == ECheckBoxState::Checked);
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bCookOnTransformChange = bChecked;
NextHAC->MarkAsNeedCook();
}
};
auto IsCheckedAssetInputCookLambda = [MainHAC]()
{
if (!MainHAC || MainHAC->IsPendingKill())
return ECheckBoxState::Unchecked;
return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState)
{
bool bChecked = (NewState == ECheckBoxState::Checked);
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bCookOnAssetInputCook = bChecked;
}
};
auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]()
{
if (!MainHAC || MainHAC->IsPendingKill())
return ECheckBoxState::Unchecked;
return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState)
{
bool bChecked = (NewState == ECheckBoxState::Checked);
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bUploadTransformsToHoudiniEngine = bChecked;
NextHAC->MarkAsNeedCook();
}
};
auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]()
{
if (!MainHAC || MainHAC->IsPendingKill())
return ECheckBoxState::Unchecked;
return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState)
{
bool bChecked = (NewState == ECheckBoxState::Checked);
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bOutputless = bChecked;
NextHAC->MarkAsNeedCook();
}
};
auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]()
{
if (!MainHAC || MainHAC->IsPendingKill())
return ECheckBoxState::Unchecked;
return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState)
{
bool bChecked = (NewState == ECheckBoxState::Checked);
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bOutputTemplateGeos = bChecked;
NextHAC->MarkAsNeedCook();
}
};
auto IsCheckedUseOutputNodesLambda = [MainHAC]()
{
if (!MainHAC || MainHAC->IsPendingKill())
return ECheckBoxState::Unchecked;
return MainHAC->bUseOutputNodes ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
auto OnCheckStateChangedUseOutputNodesLambda = [InHACs](ECheckBoxState NewState)
{
bool bChecked = (NewState == ECheckBoxState::Checked);
for (auto& NextHAC : InHACs)
{
if (!NextHAC || NextHAC->IsPendingKill())
continue;
NextHAC->bUseOutputNodes = bChecked;
NextHAC->MarkAsNeedCook();
}
};
// Checkboxes row
FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedPtr<SVerticalBox> FirstLeftColumnVerticalBox;
TSharedPtr<SVerticalBox> FirstRightColumnVerticalBox;
TSharedPtr<SVerticalBox> SecondLeftColumnVerticalBox;
TSharedPtr<SVerticalBox> SecondRightColumnVerticalBox;
TSharedRef<SHorizontalBox> WidgetBox = SNew(SHorizontalBox);
WidgetBox->AddSlot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
//First Line
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(30.0f, 5.0f, 0.0f, 0.0f)
.MaxWidth(200.f)
[
// First Left
SNew(SBox)
.WidthOverride(200.f)
[
SAssignNew(FirstLeftColumnVerticalBox, SVerticalBox)
]
]
+ SHorizontalBox::Slot()
.Padding(20.0f, 5.0f, 0.0f, 0.0f)
.MaxWidth(200.f)
[
// First Right
SNew(SBox)
[
SAssignNew(FirstRightColumnVerticalBox, SVerticalBox)
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
//Second Line
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(30.0f, 5.0f, 0.0f, 0.0f)
.MaxWidth(200.f)
[
// Second Left
SNew(SBox)
.WidthOverride(200.f)
[
SAssignNew(SecondLeftColumnVerticalBox, SVerticalBox)
]
]
+ SHorizontalBox::Slot()
.Padding(20.0f, 5.0f, 0.0f, 0.0f)
.MaxWidth(200.f)
[
// Second Right
SNew(SBox)
[
SAssignNew(SecondRightColumnVerticalBox, SVerticalBox)
]
]
]
];
//
// First line - left
//
FirstLeftColumnVerticalBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 3.5f)
[
SNew(SBox)
.WidthOverride(160.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers"))
]
];
// Parameter change check box
FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini.");
FirstLeftColumnVerticalBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(4.0f)
[
SNew(STextBlock)
.MinDesiredWidth(160.f)
.Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change"))
.ToolTipText(TooltipText)
]
+ SHorizontalBox::Slot()
[
SNew(SCheckBox)
.OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda)
.IsChecked_Lambda(IsCheckedParameterChangedLambda)
.ToolTipText(TooltipText)
]
];
// Transform change check box
TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform.");
FirstLeftColumnVerticalBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(4.0f)
[
SNew(STextBlock)
.MinDesiredWidth(160.f)
.Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change"))
.ToolTipText(TooltipText)
]
+ SHorizontalBox::Slot()
[
SNew(SCheckBox)
.OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda)
.IsChecked_Lambda(IsCheckedTransformChangeLambda)
.ToolTipText(TooltipText)
]
];
// Triggers Downstream cook checkbox
TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking.");
FirstLeftColumnVerticalBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(4.0f)
[
SNew(STextBlock)
.MinDesiredWidth(160.f)
.Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook"))
.ToolTipText(TooltipText)
]
+ SHorizontalBox::Slot()
[
SNew(SCheckBox)
.OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda)
.IsChecked_Lambda(IsCheckedAssetInputCookLambda)
.ToolTipText(TooltipText)
]
];
//
// First line - right
//
FirstRightColumnVerticalBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 3.5f)
[
SNew(STextBlock)
.Text(LOCTEXT("HoudiniEngineOutputLabel", "Outputs"))
];
// Do not generate output check box
TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs.");
FirstRightColumnVerticalBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(4.0f)
[
SNew(STextBlock)
.MinDesiredWidth(160.f)
.Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs"))
.ToolTipText(TooltipText)
]
+ SHorizontalBox::Slot()
[
SNew(SCheckBox)
.OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda)
.IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda)
.ToolTipText(TooltipText)
]
];
// Use Output Nodes geos check box
TooltipText = LOCTEXT("HoudiniEnginUseOutputNodesTooltip", "If enabled, Output nodes found in this Houdini asset will be used alongside the Display node to create outputs.");
FirstRightColumnVerticalBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(4.0f)
[
SNew(STextBlock)
.MinDesiredWidth(160.f)
.Text(LOCTEXT("HoudiniEnginUseOutputNodesCheckBoxLabel", "Use Output Nodes"))
.ToolTipText(TooltipText)
]
+ SHorizontalBox::Slot()
[
SNew(SCheckBox)
.OnCheckStateChanged_Lambda(OnCheckStateChangedUseOutputNodesLambda)
.IsChecked_Lambda(IsCheckedUseOutputNodesLambda)
.ToolTipText(TooltipText)
]
];
// Output templated geos check box
TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed.");
FirstRightColumnVerticalBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(4.0f)
[
SNew(STextBlock)
.MinDesiredWidth(160.f)
.Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Use Templated Geos"))
.ToolTipText(TooltipText)
]
+ SHorizontalBox::Slot()
[
SNew(SCheckBox)
.OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda)
.IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda)
.ToolTipText(TooltipText)
]
];
//
// Second line
//
SecondLeftColumnVerticalBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 3.5f)
[
SNew(STextBlock)
.Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous"))
];
// Push Transform to Houdini check box
TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini.");
SecondLeftColumnVerticalBox->AddSlot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(4.0f)
[
SNew(STextBlock)
.MinDesiredWidth(160.f)
.Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini"))
.ToolTipText(TooltipText)
]
+ SHorizontalBox::Slot()
[
SNew(SCheckBox)
.OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda)
.IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda)
.ToolTipText(TooltipText)
]
];
// Use whole widget
CheckBoxesRow.WholeRowWidget.Widget = WidgetBox;
}
void
FHoudiniEngineDetails::CreateHelpAndDebugWidgets(
IDetailCategoryBuilder& HoudiniEngineCategoryBuilder,
TArray<UHoudiniAssetComponent*>& InHACs)
{
if (InHACs.Num() <= 0)
return;
UHoudiniAssetComponent * MainHAC = InHACs[0];
if (!MainHAC || MainHAC->IsPendingKill())
return;
// Header Row
FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG);
if (!MainHAC->bHelpAndDebugMenuExpanded)
return;
auto OnFetchCookLogButtonClickedLambda = [InHACs]()
{
return ShowCookLog(InHACs);
};
auto OnHelpButtonClickedLambda = [MainHAC]()
{
return ShowAssetHelp(MainHAC);
};
// Button Row
FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedRef<SHorizontalBox> ButtonRowHorizontalBox = SNew(SHorizontalBox);
TSharedPtr<SHorizontalBox> CookLogButtonHorizontalBox = SNew(SHorizontalBox);
// Fetch Cook Log button
ButtonRowHorizontalBox->AddSlot()
//.Padding(15.0f, 0.0f, 0.0f, 0.0f)
.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SNew(SBox)
.WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SNew(SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor."))
//.Text(FText::FromString("Fetch Cook Log"))
.Visibility(EVisibility::Visible)
.OnClicked_Lambda(OnFetchCookLogButtonClickedLambda)
.Content()
[
SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox)
]
]
];
TSharedPtr<FSlateDynamicImageBrush> CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush();
if (CookLogIconBrush.IsValid())
{
TSharedPtr<SImage> CookImage;
CookLogButtonHorizontalBox->AddSlot()
.MaxWidth(16.0f)
//.Padding(23.0f, 0.0f, 3.0f, 0.0f)
[
SNew(SBox)
.WidthOverride(16.0f)
.HeightOverride(16.0f)
[
SAssignNew(CookImage, SImage)
]
];
CookImage->SetImage(
TAttribute<const FSlateBrush*>::Create(
TAttribute<const FSlateBrush*>::FGetter::CreateLambda([CookLogIconBrush]() {
return CookLogIconBrush.Get();
})));
}
CookLogButtonHorizontalBox->AddSlot()
.Padding(5.0, 0.0, 0.0, 0.0)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(STextBlock)
.Text(FText::FromString("Show Cook Logs"))
];
// Asset Help Button
TSharedPtr<SHorizontalBox> AssetHelpButtonHorizontalBox;
ButtonRowHorizontalBox->AddSlot()
//.Padding(4.0, 0.0f, 0.0f, 0.0f)
.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SNew(SBox)
.WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH)
[
SNew(SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help."))
//.Text(FText::FromString("Asset Help"))
.Visibility(EVisibility::Visible)
.OnClicked_Lambda(OnHelpButtonClickedLambda)
.Content()
[
SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox)
]
]
];
TSharedPtr<FSlateDynamicImageBrush> AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush();
if (AssetHelpIconBrush.IsValid())
{
TSharedPtr<SImage> AssetHelpImage;
AssetHelpButtonHorizontalBox->AddSlot()
.MaxWidth(16.0f)
//.Padding(23.0f, 0.0f, 3.0f, 0.0f)
[
SNew(SBox)
.WidthOverride(16.0f)
.HeightOverride(16.0f)
[
SAssignNew(AssetHelpImage, SImage)
]
];
AssetHelpImage->SetImage(
TAttribute<const FSlateBrush*>::Create(
TAttribute<const FSlateBrush*>::FGetter::CreateLambda([AssetHelpIconBrush]() {
return AssetHelpIconBrush.Get();
})));
}
AssetHelpButtonHorizontalBox->AddSlot()
.Padding(5.0, 0.0, 0.0, 0.0)
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(STextBlock)
.Text(FText::FromString("Asset Help"))
];
ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox;
}
FMenuBuilder
FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker()
{
auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor)
{
if (!Actor)
return false;
// Only return HoudiniAssetActors, but not our HAA
if (!Actor->IsA<AHoudiniAssetActor>())
return false;
return true;
};
auto OnActorSelected = [](AActor* Actor)
{
UE_LOG(LogTemp, Warning, TEXT("Actor Selected"));
return;
};
FMenuBuilder MenuBuilder(true, nullptr);
FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset"));
{
FSceneOutlinerModule & SceneOutlinerModule =
FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner"));
SceneOutliner::FInitializationOptions InitOptions;
{
InitOptions.Mode = ESceneOutlinerMode::ActorPicker;
InitOptions.Filters->AddFilterPredicate(ActorFilter);
InitOptions.bFocusSearchBoxWhenOpened = true;
InitOptions.bShowCreateNewFolder = false;
// Add the gutter so we can change the selection's visibility
InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0));
InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10));
InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20));
}
static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f);
TSharedRef<SWidget> MenuWidget =
SNew(SBox)
.WidthOverride(SceneOutlinerWindowSize.X)
.HeightOverride(SceneOutlinerWindowSize.Y)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Menu.Background"))
[
SceneOutlinerModule.CreateSceneOutliner(
InitOptions,
FOnActorPicked::CreateLambda(OnActorSelected))
]
];
MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true);
}
MenuBuilder.EndSection();
return MenuBuilder;
}
const FSlateBrush *
FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const
{
if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered())
return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight");
else
return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow");
}
/*
TSharedRef< SWidget >
FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray<UHoudiniAssetComponent*> InHACs)
{
TArray< const UClass * > AllowedClasses;
AllowedClasses.Add(UHoudiniAsset::StaticClass());
TArray< UFactory * > NewAssetFactories;
UHoudiniAsset * HoudiniAsset = nullptr;
if (InHACs.Num() > 0)
{
UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0];
HoudiniAsset = HoudiniAssetComponent->HoudiniAsset;
}
auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor)
{
if (!Actor)
return false;
// Only return HoudiniAssetActors, but not our HAA
if (!Actor->IsA<AHoudiniAssetActor>())
return false;
return true;
};
// Delegate for filtering Houdini assets.
FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda);
return PropertyCustomizationHelpers::MakeAssetPickerWithMenu(
FAssetData(HoudiniAsset), true,
AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset,
FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}),
FSimpleDelegate::CreateLambda([]() { })
);
}
*/
FReply
FHoudiniEngineDetails::ShowCookLog(TArray<UHoudiniAssetComponent *> InHACS)
{
TSharedPtr< SWindow > ParentWindow;
FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS);
// Check if the main frame is loaded. When using the old main frame it may not be.
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
{
IMainFrameModule & MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
ParentWindow = MainFrame.GetParentWindow();
}
if (ParentWindow.IsValid())
{
TSharedPtr<SHoudiniAssetLogWidget> HoudiniAssetCookLog;
TSharedRef<SWindow> Window =
SNew(SWindow)
.Title(LOCTEXT("WindowTitle", "Houdini Cook Log"))
.ClientSize(FVector2D(640, 480));
Window->SetContent(
SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget)
.LogText(CookLog));
if (FSlateApplication::IsInitialized())
FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false);
}
return FReply::Handled();
}
FReply
FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC)
{
if (!InHAC)
return FReply::Handled();
FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC);
TSharedPtr< SWindow > ParentWindow;
// Check if the main frame is loaded. When using the old main frame it may not be.
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
{
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked< IMainFrameModule >("MainFrame");
ParentWindow = MainFrame.GetParentWindow();
}
if (ParentWindow.IsValid())
{
TSharedPtr<SHoudiniAssetLogWidget> HoudiniAssetHelpLog;
TSharedRef<SWindow> Window =
SNew(SWindow)
.Title(LOCTEXT("WindowTitle", "Houdini Asset Help"))
.ClientSize(FVector2D(640, 480));
Window->SetContent(
SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget)
.LogText(AssetHelp));
if (FSlateApplication::IsInitialized())
FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false);
}
return FReply::Handled();
}
void
FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection)
{
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
return;
FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]()
{
switch (MenuSection)
{
case HOUDINI_ENGINE_UI_SECTION_GENERATE:
HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded;
break;
case HOUDINI_ENGINE_UI_SECTION_BAKE:
HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded;
break;
case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS:
HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded;
break;
case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG:
HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded;
}
FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true);
return FReply::Handled();
});
TFunction<FText(void)> GetText = [MenuSection]()
{
switch (MenuSection)
{
case HOUDINI_ENGINE_UI_SECTION_GENERATE:
return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT);
break;
case HOUDINI_ENGINE_UI_SECTION_BAKE:
return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT);
break;
case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS:
return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT);
break;
case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG:
return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT);
break;
}
return FText::FromString("");
};
TFunction<const FSlateBrush*(SButton* InExpanderArrow)> GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow)
{
FName ResourceName;
bool bMenuExpanded = false;
switch (MenuSection)
{
case HOUDINI_ENGINE_UI_SECTION_GENERATE:
bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded;
break;
case HOUDINI_ENGINE_UI_SECTION_BAKE:
bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded;
break;
case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS:
bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded;
break;
case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG:
bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded;
}
if (bMenuExpanded)
{
ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded";
}
else
{
ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed";
}
return FEditorStyle::GetBrush(ResourceName);
};
return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush);
}
void
FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection)
{
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
return;
FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]()
{
// Record a transaction for undo/redo
FScopedTransaction Transaction(
TEXT(HOUDINI_MODULE_RUNTIME),
LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"),
InPDGAssetLink);
switch (MenuSection)
{
case HOUDINI_ENGINE_UI_SECTION_BAKE:
InPDGAssetLink->Modify();
InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded;
FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(
GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink);
break;
}
//FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true);
return FReply::Handled();
});
TFunction<FText(void)> GetText = [MenuSection]()
{
switch (MenuSection)
{
case HOUDINI_ENGINE_UI_SECTION_BAKE:
return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT);
break;
}
return FText::FromString("");
};
TFunction<const FSlateBrush*(SButton* InExpanderArrow)> GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow)
{
FName ResourceName;
bool bMenuExpanded = false;
switch (MenuSection)
{
case HOUDINI_ENGINE_UI_SECTION_BAKE:
bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded;
break;
}
if (bMenuExpanded)
{
ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded";
}
else
{
ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed";
}
return FEditorStyle::GetBrush(ResourceName);
};
return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush);
}
void
FHoudiniEngineDetails::AddHeaderRow(
IDetailCategoryBuilder& HoudiniEngineCategoryBuilder,
FOnClicked& InOnExpanderClick,
TFunction<FText(void)>& InGetText,
TFunction<const FSlateBrush*(SButton* InExpanderArrow)>& InGetExpanderBrush)
{
// Header Row
FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty());
TSharedPtr<SHorizontalBox> HeaderHorizontalBox;
HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox);
TSharedPtr<SImage> ExpanderImage;
TSharedPtr<SButton> ExpanderArrow;
HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth()
[
SAssignNew(ExpanderArrow, SButton)
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
.ClickMethod(EButtonClickMethod::MouseDown)
.Visibility(EVisibility::Visible)
.OnClicked(InOnExpanderClick)
[
SAssignNew(ExpanderImage, SImage)
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth()
[
SNew(STextBlock)
.Text_Lambda([InGetText](){return InGetText(); })
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
];
ExpanderImage->SetImage(
TAttribute<const FSlateBrush*>::Create(
[ExpanderArrow, InGetExpanderBrush]()
{
return InGetExpanderBrush(ExpanderArrow.Get());
}));
}
#undef LOCTEXT_NAMESPACE