3447 lines
109 KiB
C++
3447 lines
109 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MuCOE/SMutableCodeViewer.h"
|
|
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Framework/Views/TableViewMetadata.h"
|
|
#include "Misc/Paths.h"
|
|
#include "MuCO/UnrealConversionUtils.h"
|
|
#include "MuCO/UnrealToMutableTextureConversionUtils.h"
|
|
#include "MuCOE/SMutableBoolViewer.h"
|
|
#include "MuCOE/SMutableColorViewer.h"
|
|
#include "MuCOE/SMutableConstantsWidget.h"
|
|
#include "MuCOE/SMutableCurveViewer.h"
|
|
#include "MuCOE/SMutableImageViewer.h"
|
|
#include "MuCOE/SMutableIntViewer.h"
|
|
#include "MuCOE/SMutableLayoutViewer.h"
|
|
#include "MuCOE/SMutableMeshViewer.h"
|
|
#include "MuCOE/SMutableInstanceViewer.h"
|
|
#include "MuCOE/SMutableParametersWidget.h"
|
|
#include "MuCOE/SMutableProjectorViewer.h"
|
|
#include "MuCOE/SMutableScalarViewer.h"
|
|
#include "MuCOE/SMutableSkeletonViewer.h"
|
|
#include "MuCOE/SMutableStringViewer.h"
|
|
#include "MuCOE/UnrealEditorPortabilityHelpers.h"
|
|
#include "MuCOE/Widgets/MutableExpanderArrow.h"
|
|
#include "MuCO/CustomizableObject.h"
|
|
#include "MuCO/CustomizableObjectPrivate.h"
|
|
#include "MuCO/UnrealToMutableTextureConversionUtils.h"
|
|
#include "MuT/ErrorLog.h"
|
|
#include "MuT/TypeInfo.h"
|
|
#include "MuR/SystemPrivate.h"
|
|
#include "Widgets/SNullWidget.h"
|
|
#include "Widgets/Colors/SColorBlock.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SComboBox.h"
|
|
#include "Widgets/Input/SNumericEntryBox.h"
|
|
#include "Widgets/Layout/SScrollBox.h"
|
|
#include "Widgets/Views/STreeView.h"
|
|
#include "Widgets/Input/SSearchBox.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Internationalization/Regex.h"
|
|
|
|
class FExtender;
|
|
class FReferenceCollector;
|
|
class FUICommandList;
|
|
class SWidget;
|
|
namespace mu { struct FProjector; }
|
|
namespace mu { struct FShape; }
|
|
struct FGeometry;
|
|
struct FSlateBrush;
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "SMutableDebugger"
|
|
|
|
|
|
namespace MutableCodeTreeViewColumns
|
|
{
|
|
static const FName OperationsColumnID("Operations");
|
|
static const FName AdditionalDataColumnID("Flags");
|
|
};
|
|
|
|
/**
|
|
* Mutable tree row used to display the operations held on the Mutable model object.
|
|
*/
|
|
class SMutableCodeTreeRow final : public SMultiColumnTableRow<TSharedPtr<FMutableCodeTreeElement>>
|
|
{
|
|
public:
|
|
|
|
void Construct(const FArguments& Args, const TSharedRef<STableViewBase>& InOwnerTableView, const TSharedPtr<FMutableCodeTreeElement>& InRowItem)
|
|
{
|
|
RowItem = InRowItem;
|
|
|
|
SMultiColumnTableRow< TSharedPtr<FMutableCodeTreeElement> >::Construct(
|
|
STableRow::FArguments()
|
|
.ShowSelection(true)
|
|
, InOwnerTableView
|
|
);
|
|
}
|
|
|
|
FLinearColor OnGetExtraDataBoxColor() const
|
|
{
|
|
if (RowItem->bIsDynamicResource)
|
|
{
|
|
return DynamicResourceBoxColor;
|
|
}
|
|
else if (RowItem->bIsStateConstant)
|
|
{
|
|
return StateConstantBoxColor;
|
|
}
|
|
else
|
|
{
|
|
return ExtraDataBackgroundBoxDefaultColor;
|
|
}
|
|
}
|
|
|
|
FText OnGetExtraDataText() const
|
|
{
|
|
// DEBUG :Uncomment the next line in order to debug the current state being used by the element
|
|
// return FText::FromString(FString::FromInt(RowItem->GetStateIndex()));
|
|
|
|
if (RowItem->bIsDynamicResource)
|
|
{
|
|
return DynamicResourceText;
|
|
}
|
|
else if (RowItem->bIsStateConstant)
|
|
{
|
|
return StateConstantText;
|
|
}
|
|
else
|
|
{
|
|
return FText::FromString(FString(""));
|
|
}
|
|
}
|
|
|
|
/** Depending on the state of the row returns one color or another to be used by the highlighting system */
|
|
FLinearColor GetHighlightColor() const
|
|
{
|
|
if (bShouldBeHiglighted)
|
|
{
|
|
if (RowItem->DuplicatedOf)
|
|
{
|
|
return HighlightedDuplicatedBoxColor;
|
|
}
|
|
else
|
|
{
|
|
return HighlightedUniqueRowBoxColor;
|
|
}
|
|
}
|
|
|
|
return HighlightBoxDefaultColor;
|
|
}
|
|
|
|
/** Method intended with the generation of the wanted objects for each column*/
|
|
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override
|
|
{
|
|
// Primary column showing the name of the operation and tye type
|
|
if (ColumnName == MutableCodeTreeViewColumns::OperationsColumnID)
|
|
{
|
|
// Prepare a ui container for all the UI objects required by this row element
|
|
TSharedRef<SHorizontalBox> RowContainer = SNew(SHorizontalBox)
|
|
|
|
// First coll showing operation name and type
|
|
+ SHorizontalBox::Slot()
|
|
.HAlign(EHorizontalAlignment::HAlign_Fill)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SOverlay)
|
|
|
|
+ SOverlay::Slot()
|
|
[
|
|
SAssignNew(this->HighlightingColorBox, SColorBlock)
|
|
.Color(this, &SMutableCodeTreeRow::GetHighlightColor)
|
|
]
|
|
|
|
+ SOverlay::Slot()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SMutableExpanderArrow, SharedThis(this))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(RowItem->MainLabel))
|
|
.ColorAndOpacity(RowItem->LabelColor)
|
|
]
|
|
]
|
|
];
|
|
|
|
return RowContainer;
|
|
}
|
|
|
|
// Second column showing some extra data related with the operation being displayed
|
|
if (ColumnName == MutableCodeTreeViewColumns::AdditionalDataColumnID)
|
|
{
|
|
TSharedRef<SHorizontalBox> RowContainer = SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.HAlign(EHorizontalAlignment::HAlign_Left)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.MaxWidth(4.0f)
|
|
[
|
|
SNew(SColorBlock)
|
|
.Color(this,&SMutableCodeTreeRow::OnGetExtraDataBoxColor)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(4,1)
|
|
.AutoWidth()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this,&SMutableCodeTreeRow::OnGetExtraDataText)
|
|
]
|
|
];
|
|
|
|
return RowContainer;
|
|
}
|
|
|
|
// Invalid column name so no widget will be produced
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
/** Marks the row to be highlighted */
|
|
void Highlight()
|
|
{
|
|
bShouldBeHiglighted = true;
|
|
}
|
|
|
|
/** Resets the highlighting status */
|
|
void ResetHighlight()
|
|
{
|
|
bShouldBeHiglighted = false;
|
|
}
|
|
|
|
/** Returns a reference to the Element this row is representing */
|
|
TSharedPtr<FMutableCodeTreeElement>& GetItem()
|
|
{
|
|
return RowItem;
|
|
}
|
|
|
|
private:
|
|
|
|
/** Pointer to the element that did spawn this row */
|
|
TSharedPtr<FMutableCodeTreeElement> RowItem = nullptr;
|
|
|
|
/** Transparent color */
|
|
const FLinearColor TransparentColor = FLinearColor(0,0,0,0);
|
|
|
|
/*
|
|
* Operation Highlighting
|
|
*/
|
|
|
|
/** Custom Widget used to display a color. Used as the background of the text on the row to serve as highlighting Visual Element*/
|
|
TSharedPtr<SColorBlock> HighlightingColorBox = nullptr;
|
|
|
|
/** The color used to highlight the row if duplicated from another row */
|
|
const FLinearColor HighlightedDuplicatedBoxColor = FLinearColor(1, 1, 1, 0.15);
|
|
|
|
/** The color used to highlight elements that are originals (not duplicates) */
|
|
const FLinearColor HighlightedUniqueRowBoxColor = FLinearColor(1, 1, 1, 0.28);
|
|
|
|
/** Default color used when the row is not highlighted */
|
|
const FLinearColor HighlightBoxDefaultColor = TransparentColor;
|
|
|
|
/*
|
|
* Extra data objects
|
|
*/
|
|
|
|
// Text used to set the width of the color area in front of the extra data
|
|
const FText EmptyText = FText(INVTEXT(" "));
|
|
|
|
/** String printed on the UI when the operation is shown to be dynamic resource */
|
|
const FText DynamicResourceText = FText::FromString(FString("dyn"));
|
|
|
|
/** String printed on the UI when the operation is shown to be state constant */
|
|
const FText StateConstantText = FText::FromString(FString("const"));
|
|
|
|
/** Color used on the extra data column when no extra data is shown */
|
|
const FLinearColor ExtraDataBackgroundBoxDefaultColor = TransparentColor;
|
|
|
|
/** Color shown on the extra data column when the resource is found to be Dynamic */
|
|
const FLinearColor DynamicResourceBoxColor = FLinearColor(0,0,1,0.8);
|
|
|
|
/** Color shown on the extra data column when the resource is found to be State Constant */
|
|
const FLinearColor StateConstantBoxColor = FLinearColor(1,0,0,0.8);
|
|
|
|
bool bShouldBeHiglighted = false;
|
|
};
|
|
|
|
|
|
void SMutableCodeViewer::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
// Add UObjects here if we own any at some point
|
|
//Collector.AddReferencedObject(CustomizableObject);
|
|
}
|
|
|
|
|
|
FString SMutableCodeViewer::GetReferencerName() const
|
|
{
|
|
return TEXT("SMutableCodeViewer");
|
|
}
|
|
|
|
void SMutableCodeViewer::ClearSelectedTreeRow() const
|
|
{
|
|
check(TreeView);
|
|
TreeView->ClearSelection();
|
|
}
|
|
|
|
void SMutableCodeViewer::SetCurrentModel(const TSharedPtr<mu::FModel, ESPMode::ThreadSafe>& InMutableModel,
|
|
const TArray<TSoftObjectPtr<const UTexture>>& InReferencedTextures,
|
|
const TArray<TSoftObjectPtr<const UStreamableRenderAsset>>& InReferencedMeshes )
|
|
{
|
|
MutableModel = InMutableModel;
|
|
ReferencedTextures = InReferencedTextures;
|
|
ReferencedMeshes = InReferencedMeshes;
|
|
PreviewParameters = mu::FModel::NewParameters(MutableModel);
|
|
|
|
RootNodes.Empty();
|
|
RootNodeAddresses.Empty();
|
|
ItemCache.Empty();
|
|
MainItemPerOp.Empty();
|
|
TreeElements.Empty();
|
|
ExpandedElements.Empty();
|
|
FoundModelOperationTypeElements.Empty();
|
|
ModelOperationTypes.Empty();
|
|
ModelOperationTypeNames.Empty();
|
|
|
|
// Reset navigation by type / constant resource
|
|
NavigationElements.Empty();
|
|
NavigationIndex = -1;
|
|
|
|
// Reset navigation by string
|
|
NameBasedNavigationElements.Empty();
|
|
StringNavigationIndex = -1;
|
|
|
|
// Generate all elements before starting the tree UI so we have a deterministic set of unique and duplicated elements
|
|
GenerateAllTreeElements();
|
|
|
|
// Setup Navigation system
|
|
{
|
|
// Store the addresses of the root nodes so they can be used by operation search methods
|
|
CacheRootNodeAddresses();
|
|
|
|
// Cache the operation types that are present on the model
|
|
CacheOperationTypesPresentOnModel();
|
|
|
|
// Get an array of mutable types as an array of FStrings for the UI
|
|
GenerateNavigationOpTypeStrings();
|
|
|
|
// Generate list elements for the found operation types so we are able to search over them on our type dropdown
|
|
GenerateNavigationDropdownElements();
|
|
|
|
// Check we did find types (witch should always happen in a normal run) and select the NONE option as the default value
|
|
check(FoundModelOperationTypeElements.Num())
|
|
CurrentlySelectedOperationTypeElement = NoneOperationEntry;
|
|
}
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::Construct(const FArguments& InArgs, const TSharedPtr<mu::FModel, ESPMode::ThreadSafe>& InMutableModel,
|
|
const TArray<TSoftObjectPtr<const UTexture>>& InReferencedTextures,
|
|
const TArray<TSoftObjectPtr<const UStreamableRenderAsset>>& InReferencedMeshes )
|
|
{
|
|
// Min width allowed for the column. Needed to avoid having issues with the constants space being to small
|
|
// and then getting too tall on the y axis crashing the UI drawer.
|
|
constexpr float MinParametersCollWidth = 200;
|
|
|
|
SetCurrentModel(InMutableModel, InReferencedTextures, InReferencedMeshes);
|
|
|
|
FToolBarBuilder ToolbarBuilder(TSharedPtr<const FUICommandList>(), FMultiBoxCustomization::None, TSharedPtr<FExtender>(), true);
|
|
ToolbarBuilder.SetLabelVisibility(EVisibility::Visible);
|
|
ToolbarBuilder.SetStyle(&FAppStyle::Get(), "SlimToolBar");
|
|
|
|
ToolbarBuilder.AddWidget(
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(InArgs._DataTag)));
|
|
|
|
TSharedRef<SScrollBar> TreeVertScrollBar =
|
|
SNew(SScrollBar).
|
|
Orientation(EOrientation::Orient_Vertical).
|
|
AlwaysShowScrollbar(false);
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
ToolbarBuilder.MakeWidget()
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.VAlign(VAlign_Fill)
|
|
.Padding(5,2)
|
|
[
|
|
SNew(SSplitter)
|
|
.Orientation(EOrientation::Orient_Horizontal)
|
|
|
|
+ SSplitter::Slot()
|
|
.Value(0.35f)
|
|
.MinSize(520)
|
|
.Resizable(true)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Search box for tree operations
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
// Search by name
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("SelectedOperationByStringLabel","Search Operation by String :"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.MaxWidth(250)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SSearchBox)
|
|
.HintText(LOCTEXT("OperationToSearchHintText","Search OP"))
|
|
.SearchResultData(this,&SMutableCodeViewer::SearchResultsData)
|
|
.OnSearch(this, &SMutableCodeViewer::OnTreeStringSearch)
|
|
.OnTextChanged(this, &SMutableCodeViewer::OnTreeSearchTextChanged)
|
|
.OnTextCommitted(this, &SMutableCodeViewer::OnTreeSearchTextCommitted)
|
|
]
|
|
]
|
|
|
|
|
|
// Regex control for search by name
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(4,2)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("OperationToSearchRegexLabel","Is RegEx?"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SCheckBox)
|
|
.OnCheckStateChanged(this,&SMutableCodeViewer::OnRegexToggleChanged)
|
|
]
|
|
]
|
|
]
|
|
|
|
// Operation type filtering slot
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
// Box containing navigation elements
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(2,4)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("SelectedOperationTypeLabel","Search Operation Type :"))
|
|
]
|
|
|
|
// ComboBox used to select one or another Op_Type for tree navigation purposes
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(TargetedTypeSelector,SComboBox<TSharedPtr<const FMutableOperationElement>>)
|
|
.OptionsSource(&FoundModelOperationTypeElements)
|
|
.InitiallySelectedItem(CurrentlySelectedOperationTypeElement)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this,&SMutableCodeViewer::GetCurrentNavigationOpTypeText)
|
|
.ColorAndOpacity(this,&SMutableCodeViewer::GetCurrentNavigationOpTypeColor)
|
|
]
|
|
.OnGenerateWidget(this,&SMutableCodeViewer::OnGenerateOpNavigationDropDownWidget)
|
|
.OnSelectionChanged(this,&SMutableCodeViewer::OnNavigationSelectedOperationChanged)
|
|
]
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(4,0)
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("GoToPreviousOperationButton"," < "))
|
|
.OnClicked(this,&SMutableCodeViewer::OnGoToPreviousOperationButtonPressed)
|
|
.IsEnabled(this,&SMutableCodeViewer::CanInteractWithPreviousOperationButton)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(4,0)
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this,&SMutableCodeViewer::OnPrintNavigableObjectAddressesCount)
|
|
.Justification(ETextJustify::Center)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(4,0)
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("GoToNextOperationButton"," > "))
|
|
.OnClicked(this,&SMutableCodeViewer::OnGoToNextOperationButtonPressed)
|
|
.IsEnabled(this,&SMutableCodeViewer::CanInteractWithNextOperationButton)
|
|
]
|
|
]
|
|
]
|
|
|
|
// Tree operations slot
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(UE_MUTABLE_GET_BRUSH("ToolPanel.GroupBorder"))
|
|
.Padding(FMargin(4.0f, 4.0f))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.FillContentWidth(1)
|
|
[
|
|
SNew(SScrollBox)
|
|
.Orientation(EOrientation::Orient_Horizontal)
|
|
.ConsumeMouseWheel(EConsumeMouseWheel::Never)
|
|
|
|
+ SScrollBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.FillContentSize(1)
|
|
[
|
|
SAssignNew(TreeView, STreeView<TSharedPtr<FMutableCodeTreeElement>>)
|
|
.TreeItemsSource(&RootNodes)
|
|
.OnGenerateRow(this, &SMutableCodeViewer::GenerateRowForNodeTree)
|
|
.OnRowReleased(this, &SMutableCodeViewer::OnRowReleased)
|
|
.OnGetChildren(this, &SMutableCodeViewer::GetChildrenForInfo)
|
|
.OnSelectionChanged(this, &SMutableCodeViewer::OnSelectionChanged)
|
|
.OnSetExpansionRecursive(this, &SMutableCodeViewer::TreeExpandRecursive)
|
|
.OnContextMenuOpening(this, &SMutableCodeViewer::OnTreeContextMenuOpening)
|
|
.OnExpansionChanged(this, &SMutableCodeViewer::OnExpansionChanged)
|
|
.SelectionMode(ESelectionMode::Single)
|
|
.ExternalScrollbar(TreeVertScrollBar)
|
|
.HeaderRow
|
|
(
|
|
SNew(SHeaderRow)
|
|
.ResizeMode(ESplitterResizeMode::Fill)
|
|
|
|
+ SHeaderRow::Column(MutableCodeTreeViewColumns::OperationsColumnID)
|
|
.DefaultLabel(LOCTEXT("Operation", "Operation"))
|
|
|
|
+ SHeaderRow::Column(MutableCodeTreeViewColumns::AdditionalDataColumnID)
|
|
.DefaultLabel(LOCTEXT("OperationFlags", "Flags"))
|
|
.FixedWidth(50.0f)
|
|
)
|
|
]
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
TreeVertScrollBar
|
|
]
|
|
]
|
|
]
|
|
|
|
]
|
|
+ SSplitter::Slot()
|
|
.Value(0.75f)
|
|
[
|
|
SNew(SSplitter)
|
|
.Orientation(EOrientation::Orient_Horizontal)
|
|
|
|
+ SSplitter::Slot()
|
|
.Value(0.28f)
|
|
.MinSize(MinParametersCollWidth)
|
|
[
|
|
// Splitter managing both parameter and constant panels
|
|
SNew(SSplitter)
|
|
.Orientation(EOrientation::Orient_Vertical)
|
|
+SSplitter::Slot()
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("SkipMipsLabel", "Skip mips on generate :"))
|
|
.Visibility(this, &SMutableCodeViewer::IsMipSkipVisible)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(SNumericEntryBox<int32>)
|
|
.Visibility(this, &SMutableCodeViewer::IsMipSkipVisible)
|
|
.AllowSpin(true)
|
|
.MinValue(0)
|
|
.MaxValue(16)
|
|
.MinSliderValue(0)
|
|
.MaxSliderValue(16)
|
|
.Value(this, &SMutableCodeViewer::GetCurrentMipSkip)
|
|
.OnValueChanged(this, &SMutableCodeViewer::OnCurrentMipSkipChanged)
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
SNew(SScrollBox)
|
|
+ SScrollBox::Slot()
|
|
[
|
|
SAssignNew(ParametersWidget, SMutableParametersWidget)
|
|
.OnParametersValueChanged(this, &SMutableCodeViewer::OnPreviewParameterValueChanged)
|
|
.Parameters(PreviewParameters)
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SSplitter::Slot()
|
|
[
|
|
// Generate a new Constants panel to show the data stored on the current mutable program
|
|
SAssignNew(ConstantsWidget, SMutableConstantsWidget,
|
|
&(MutableModel->GetPrivate()->Program),
|
|
SharedThis(this))
|
|
]
|
|
]
|
|
+ SSplitter::Slot()
|
|
.Value(0.72f)
|
|
[
|
|
SAssignNew(PreviewBorder, SBorder)
|
|
.BorderImage(UE_MUTABLE_GET_BRUSH("ToolPanel.GroupBorder"))
|
|
.Padding(FMargin(4.0f, 4.0f))
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Set the tree expanded by default
|
|
// It does not recalculate states since the expansion of the instance will NOT expand duplicates witch means the widget position
|
|
// of the children of duplicated (or the original of an operation with duplicates) will not change.
|
|
TreeExpandInstance();
|
|
|
|
// Enable the recalculation of states once the tree has already been initially expanded since now we do not control
|
|
// how the user is point to interact with the view.
|
|
bShouldRecalculateStates = true;
|
|
// Now, on expansion or contraction the states will get recalculated
|
|
}
|
|
|
|
|
|
EVisibility SMutableCodeViewer::IsMipSkipVisible() const
|
|
{
|
|
return bSelectedOperationIsImage ? EVisibility::Visible : EVisibility::Hidden;
|
|
}
|
|
|
|
|
|
TOptional<int32> SMutableCodeViewer::GetCurrentMipSkip() const
|
|
{
|
|
return MipsToSkip;
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::OnCurrentMipSkipChanged(int32 NewValue)
|
|
{
|
|
MipsToSkip = NewValue;
|
|
bIsPreviewPendingUpdate = true;
|
|
}
|
|
|
|
#pragma region CodeTree operation name search
|
|
|
|
|
|
void SMutableCodeViewer::OnRegexToggleChanged(ECheckBoxState CheckBoxState)
|
|
{
|
|
const bool bPreChangeValue = bIsSearchStringRegularExpression;
|
|
bIsSearchStringRegularExpression = CheckBoxState == ECheckBoxState::Checked ? true : false;
|
|
|
|
if (bPreChangeValue != bIsSearchStringRegularExpression)
|
|
{
|
|
CacheOperationsMatchingStringPattern();
|
|
GoToNextOperation();
|
|
}
|
|
}
|
|
|
|
void SMutableCodeViewer::OnTreeStringSearch(SSearchBox::SearchDirection SearchDirection)
|
|
{
|
|
if (SearchDirection==SSearchBox::SearchDirection::Next)
|
|
{
|
|
GoToNextOperation();
|
|
}
|
|
else
|
|
{
|
|
GoToPreviousOperation();
|
|
}
|
|
}
|
|
|
|
void SMutableCodeViewer::GoToNextOperation()
|
|
{
|
|
// Contingency : Prevent a second scroll operation from being performed if still we do not have the first target in view
|
|
if (bWasScrollToTargetRequested)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (NameBasedNavigationElements.Num())
|
|
{
|
|
const int32 PreviousIndex = StringNavigationIndex;
|
|
|
|
// Focus on next target
|
|
StringNavigationIndex = StringNavigationIndex >= NameBasedNavigationElements.Num() - 1
|
|
? 0
|
|
: StringNavigationIndex + 1;
|
|
|
|
if (StringNavigationIndex != PreviousIndex)
|
|
{
|
|
FocusViewOnNavigationTarget(NameBasedNavigationElements[StringNavigationIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::GoToPreviousOperation()
|
|
{
|
|
// Contingency : Prevent a second scroll operation from being performed if still we do not have the first target in view
|
|
if (bWasScrollToTargetRequested)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (NameBasedNavigationElements.Num())
|
|
{
|
|
const int32 PreviousIndex = StringNavigationIndex;
|
|
|
|
// Focus on previous target
|
|
StringNavigationIndex = StringNavigationIndex <= 0 ? NameBasedNavigationElements.Num() -1 : StringNavigationIndex -1;
|
|
|
|
if (PreviousIndex != StringNavigationIndex)
|
|
{
|
|
FocusViewOnNavigationTarget(NameBasedNavigationElements[StringNavigationIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SMutableCodeViewer::GoToTargetOperation(const int32& InTargetIndex)
|
|
{
|
|
if (InTargetIndex == StringNavigationIndex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (NameBasedNavigationElements.Num() && InTargetIndex > 0 && InTargetIndex <= NameBasedNavigationElements.Num()-1)
|
|
{
|
|
// Focus on the target index
|
|
StringNavigationIndex = InTargetIndex;
|
|
FocusViewOnNavigationTarget(NameBasedNavigationElements[StringNavigationIndex]);
|
|
}
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::OnTreeSearchTextChanged(const FText& InUpdatedText)
|
|
{
|
|
SearchString = InUpdatedText.ToString();
|
|
}
|
|
|
|
|
|
TOptional<SSearchBox::FSearchResultData> SMutableCodeViewer::SearchResultsData() const
|
|
{
|
|
if (NameBasedNavigationElements.Num() == 0)
|
|
{
|
|
return TOptional<SSearchBox::FSearchResultData>();
|
|
}
|
|
return TOptional<SSearchBox::FSearchResultData>({ NameBasedNavigationElements.Num(), StringNavigationIndex + 1});
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::OnTreeSearchTextCommitted(const FText& InUpdatedText, ETextCommit::Type TextCommitType)
|
|
{
|
|
if (TextCommitType == ETextCommit::OnEnter)
|
|
{
|
|
check (InUpdatedText.ToString() == SearchString);
|
|
CacheOperationsMatchingStringPattern();
|
|
GoToNextOperation();
|
|
}
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::CacheOperationsMatchingStringPattern()
|
|
{
|
|
check(MutableModel);
|
|
check(RootNodeAddresses.Num());
|
|
|
|
if ( LastSearchedString == SearchString &&
|
|
bWasLastSearchRegEx == bIsSearchStringRegularExpression &&
|
|
LastSearchedModel == MutableModel )
|
|
{
|
|
// Do not perform a search again since the context has not changed
|
|
return;
|
|
}
|
|
|
|
if (!SearchString.IsEmpty())
|
|
{
|
|
UE_LOG(LogMutable,Display,TEXT("Starting string search with target string ""\"%s""\" "),*SearchString);
|
|
|
|
// Object containing all data required by the search operation to be able to be called recursively
|
|
FElementsSearchCache SearchPayload;
|
|
// Initialize the Search Payload with the root node addresses. This way the search will use them as the root nodes where
|
|
// to start searching
|
|
SearchPayload.SetupRootBatch(RootNodeAddresses);
|
|
|
|
const mu::FProgram& Program = MutableModel->GetPrivate()->Program;
|
|
GetOperationsMatchingStringPattern(SearchString,bIsSearchStringRegularExpression,SearchPayload, Program);
|
|
|
|
// Dump the located resources array onto the navigation array
|
|
NameBasedNavigationElements = MoveTemp(SearchPayload.FoundElements);
|
|
SortElementsByTreeIndex(NameBasedNavigationElements);
|
|
|
|
UE_LOG(LogMutable, Display, TEXT("Operations found with matching pattern ""\"%s""\" is %i"), *SearchString, NameBasedNavigationElements.Num());
|
|
}
|
|
else
|
|
{
|
|
NameBasedNavigationElements.Reset();
|
|
}
|
|
|
|
// Reset the search index
|
|
StringNavigationIndex = -1;
|
|
|
|
// Keep track of what context was used to perform the search to avoid doing it again if the context has not changed
|
|
LastSearchedString = SearchString;
|
|
bWasLastSearchRegEx = bIsSearchStringRegularExpression;
|
|
LastSearchedModel = MutableModel;
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::GetOperationsMatchingStringPattern(const FString& InStringPattern,const bool bIsRegularExpression ,FElementsSearchCache& SearchPayload,const mu::FProgram& InProgram)
|
|
{
|
|
// next batch of addresses to be explored
|
|
TArray<FItemCacheKey> NextBatchAddressesData;
|
|
|
|
for (int32 ParentIndex = 0; ParentIndex < SearchPayload.BatchData.Num(); ParentIndex++)
|
|
{
|
|
const FItemCacheKey CacheKey = SearchPayload.BatchData[ParentIndex];
|
|
const FString OperationDescriptiveText = GetOperationDescriptiveText(CacheKey);
|
|
|
|
bool bMatchesPattern = false;
|
|
if (!bIsRegularExpression)
|
|
{
|
|
// Check if the provided text is contained over the element identification text
|
|
bMatchesPattern = OperationDescriptiveText.Contains(InStringPattern);
|
|
}
|
|
else
|
|
{
|
|
FRegexPattern Pattern{InStringPattern};
|
|
FRegexMatcher RegexMatcher{Pattern,OperationDescriptiveText};
|
|
bMatchesPattern = RegexMatcher.FindNext();
|
|
}
|
|
|
|
// Get one of the previous run "children" and treat as a parent to get it's children and process them
|
|
const mu::OP::ADDRESS& ParentAddress = SearchPayload.BatchData[ParentIndex].Child;
|
|
|
|
if (bMatchesPattern)
|
|
{
|
|
SearchPayload.AddToFoundElements(ParentAddress,ParentIndex,ItemCache);
|
|
}
|
|
|
|
// Get all NON PROCESSED the children of this operation to later be able to process them (on next recursive call)
|
|
SearchPayload.CacheChildrenOfAddressIfNotProcessed(ParentAddress, InProgram, NextBatchAddressesData);
|
|
}
|
|
|
|
// At this point all the addresses to be computed on the next batch have already been set and will be computed on
|
|
// the next recursive call
|
|
|
|
// Explore children if found
|
|
if (NextBatchAddressesData.Num())
|
|
{
|
|
// Cache next batch data so the next invocations is able to locate the provided addresses on the itemsCache
|
|
SearchPayload.BatchData = MoveTemp(NextBatchAddressesData);
|
|
|
|
GetOperationsMatchingStringPattern(InStringPattern,bIsRegularExpression, SearchPayload, InProgram);
|
|
}
|
|
}
|
|
|
|
|
|
FString SMutableCodeViewer::GetOperationDescriptiveText(const FItemCacheKey& InItemCacheKey)
|
|
{
|
|
FString OperationDescriptiveText;
|
|
|
|
if (const TSharedPtr<FMutableCodeTreeElement>* Element = ItemCache.Find(InItemCacheKey))
|
|
{
|
|
OperationDescriptiveText = Element->Get()->MainLabel;
|
|
check (!OperationDescriptiveText.IsEmpty());
|
|
}
|
|
|
|
return OperationDescriptiveText;
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
|
|
#pragma region CodeTree operation search
|
|
|
|
FText SMutableCodeViewer::GetCurrentNavigationOpTypeText() const
|
|
{
|
|
check (CurrentlySelectedOperationTypeElement);
|
|
|
|
return CurrentlySelectedOperationTypeElement->OperationTypeText;
|
|
}
|
|
|
|
FSlateColor SMutableCodeViewer::GetCurrentNavigationOpTypeColor() const
|
|
{
|
|
check (CurrentlySelectedOperationTypeElement);
|
|
|
|
return CurrentlySelectedOperationTypeElement->OperationTextColor;
|
|
}
|
|
|
|
void SMutableCodeViewer::GenerateNavigationDropdownElements()
|
|
{
|
|
const int32 OperationTypesCount = ModelOperationTypes.Num();
|
|
|
|
// It must have at least one type, if not may be because we are running this before filling ModelOperationTypes
|
|
FoundModelOperationTypeElements.Empty(OperationTypesCount);
|
|
|
|
for (int32 OperationTypeIndex = 0; OperationTypeIndex < OperationTypesCount; OperationTypeIndex++)
|
|
{
|
|
// Get the type as a string to be able to print it on the UI
|
|
const FText OperationTypeName = FText::FromString(ModelOperationTypeNames[OperationTypeIndex]);
|
|
|
|
const mu::EOpType RepresentedType = ModelOperationTypes[OperationTypeIndex].Key;
|
|
const uint32 OperationTypeInstancesCount = ModelOperationTypes[OperationTypeIndex].Value;
|
|
|
|
// Get the Color to be used on the text that will represent the operation on the dropdown
|
|
const FSlateColor OperationColor = ColorPerComputationalCost[StaticCast<uint8>(GetOperationTypeComputationalCost(RepresentedType))];
|
|
|
|
// Generate an element to be used by the ComboBox handling the selection of the type to be used during navigation
|
|
TSharedPtr<FMutableOperationElement> OperationElement = MakeShared<FMutableOperationElement>(RepresentedType, OperationTypeName,OperationTypeInstancesCount,OperationColor);
|
|
FoundModelOperationTypeElements.Add(OperationElement);
|
|
}
|
|
|
|
// Add an entry for the NONE type of operation
|
|
{
|
|
const FText EntryName = FText::FromString("NONE");
|
|
const FSlateColor EntryColor = ColorPerComputationalCost[StaticCast<uint8>(EOperationComputationalCost::Standard)];
|
|
NoneOperationEntry = MakeShared<FMutableOperationElement>(mu::EOpType::NONE,EntryName,0,EntryColor);
|
|
|
|
// @warn While not visible this element must be part of the collection for the ComboBox to be able to work
|
|
// properly
|
|
FoundModelOperationTypeElements.Add(NoneOperationEntry);
|
|
}
|
|
|
|
// Add an extra operation type that will represent the constant resource based navigation type
|
|
{
|
|
const FText EntryName = FText::FromString("Selected Constant");
|
|
const FSlateColor EntryColor = FSlateColor(FLinearColor(0.35f ,0.35f,1.0f,1));
|
|
ConstantBasedNavigationEntry = MakeShared<FMutableOperationElement>(mu::EOpType::NONE,EntryName,0,EntryColor);
|
|
|
|
// @warn While not visible this element must be part of the collection for the ComboBox to be able to work
|
|
// properly
|
|
FoundModelOperationTypeElements.Add(ConstantBasedNavigationEntry);
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SMutableCodeViewer::OnGenerateOpNavigationDropDownWidget(
|
|
TSharedPtr<const FMutableOperationElement> MutableOperationElement) const
|
|
{
|
|
TSharedRef<STextBlock> NewSlateObject = SNew(STextBlock)
|
|
.Text(MutableOperationElement->OperationTypeText)
|
|
.ColorAndOpacity(MutableOperationElement->OperationTextColor);
|
|
|
|
// Set the visibility type for the UI object (currently will be hidden for the NONE type)
|
|
NewSlateObject->SetVisibility(MutableOperationElement->GetEntryVisibility());
|
|
|
|
return NewSlateObject;
|
|
}
|
|
|
|
void SMutableCodeViewer::OnNavigationSelectedOperationChanged(
|
|
TSharedPtr<const FMutableOperationElement, ESPMode::ThreadSafe> MutableOperationElement, ESelectInfo::Type Arg)
|
|
{
|
|
// Handle the case where we do not want an option selected, for example, when clearing the selected option.
|
|
TSharedPtr<const FMutableOperationElement, ESPMode::ThreadSafe> NewSelectedElement = MutableOperationElement;
|
|
if (!NewSelectedElement.IsValid())
|
|
{
|
|
NewSelectedElement = NoneOperationEntry;
|
|
}
|
|
|
|
check (NewSelectedElement.IsValid());
|
|
|
|
// Cache the currently selected operation set on the UI by the user
|
|
const mu::EOpType NewOperationType = NewSelectedElement->OperationType;
|
|
OperationTypeToSearch = NewOperationType;
|
|
CurrentlySelectedOperationTypeElement = NewSelectedElement;
|
|
|
|
// Only do the internal work if the type is one that makes sense searching
|
|
if (OperationTypeToSearch != mu::EOpType::NONE)
|
|
{
|
|
// Locate all operations on the mutable operations tree (not the visual one) that do share the same operation type
|
|
// as the one selected. This will fill the array with the elements we should be looking for during the navigation operation
|
|
CacheAddressesOfOperationsOfType();
|
|
}
|
|
// None can be set by the user or be an indication that we are navigating over constant related operations
|
|
// todo: Separate both operations in some way on the UI to avoid complications in the code and in the UI's UX
|
|
else
|
|
{
|
|
// Clear all the elements on the navigation addresses
|
|
NavigationElements.Empty();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::GenerateNavigationOpTypeStrings()
|
|
{
|
|
// Grab only the names from the operation types located during the caching of operation types of the model
|
|
for (const TTuple<mu::EOpType, uint32>& LocatedOperationType : ModelOperationTypes)
|
|
{
|
|
// Find the name of the Operation type
|
|
const uint16 OperationIndex = static_cast<uint16>(LocatedOperationType.Key);
|
|
const TCHAR* OpName = mu::s_opNames[OperationIndex];
|
|
|
|
// Remove trailing whitespaces adding noise and messing up concatenations with other strings
|
|
FString OperationNameString{OpName};
|
|
OperationNameString.RemoveSpacesInline();
|
|
|
|
// Save the name
|
|
ModelOperationTypeNames.Add( OperationNameString);
|
|
}
|
|
}
|
|
|
|
void SMutableCodeViewer::OnSelectedOperationTypeFromTree()
|
|
{
|
|
// We require to have only 1 element selected to avoid having inconsistencies during operation
|
|
check(TreeView->GetNumItemsSelected() == 1);
|
|
|
|
const TSharedPtr<FMutableCodeTreeElement> ReferenceOperationElement = TreeView->GetSelectedItems()[0];
|
|
|
|
const mu::EOpType OperationType =
|
|
MutableModel->GetPrivate()->Program.GetOpType(ReferenceOperationElement->MutableOperation);
|
|
|
|
// Find the operation type directly in our array of operation elements (from the drop down)
|
|
const TSharedPtr<const FMutableOperationElement>* RepresentativeElement = FoundModelOperationTypeElements.
|
|
FindByPredicate([OperationType](const TSharedPtr<const FMutableOperationElement> Other)
|
|
{
|
|
return Other->OperationType == OperationType;
|
|
});
|
|
|
|
// Ensure an element was found. Failing the next check would mean that we are not caching all the types present on
|
|
// the current operation's tree
|
|
check(RepresentativeElement != nullptr);
|
|
|
|
// Set the type operation type to be looking for -> Will invoke OnOptionTypeSelectionChanged
|
|
TargetedTypeSelector->SetSelectedItem(*RepresentativeElement);
|
|
|
|
// Reset the navigation index so it starts from scratch
|
|
NavigationIndex = -1;
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::SortElementsByTreeIndex(TArray<TSharedPtr<FMutableCodeTreeElement>>& InElementsArrayToSort)
|
|
{
|
|
// Sort the array from lower index to bigger index (0 , 1 , 2 ...)
|
|
InElementsArrayToSort.Sort([](const TSharedPtr<FMutableCodeTreeElement> A , const TSharedPtr<FMutableCodeTreeElement> B)
|
|
{
|
|
return A->IndexOnTree < B->IndexOnTree;
|
|
});
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::CacheAddressesOfOperationsOfType()
|
|
{
|
|
// Clear previous data
|
|
NavigationElements.Empty();
|
|
check(RootNodeAddresses.Num());
|
|
|
|
// Object containing all data required by the search operation to be able to be called recursively
|
|
FElementsSearchCache SearchPayload;
|
|
// Initialize the Search Payload with the root node addresses. This way the search will use them as the root nodes where
|
|
// to start searching
|
|
SearchPayload.SetupRootBatch(RootNodeAddresses);
|
|
|
|
// Main update procedure run for the targeted state and the targeted parameter values
|
|
const mu::FProgram& Program = MutableModel->GetPrivate()->Program;
|
|
GetOperationsOfType(OperationTypeToSearch,SearchPayload, Program);
|
|
|
|
if (!SearchPayload.FoundElements.IsEmpty())
|
|
{
|
|
// Cache the navigation addresses so we are able to navigate over them
|
|
NavigationElements = MoveTemp(SearchPayload.FoundElements);
|
|
SortElementsByTreeIndex(NavigationElements);
|
|
|
|
// Reset the navigation index
|
|
NavigationIndex = -1;
|
|
}
|
|
}
|
|
|
|
void SMutableCodeViewer::GetOperationsOfType(const mu::EOpType& TargetOperationType,
|
|
FElementsSearchCache& InSearchPayload,
|
|
const mu::FProgram& InProgram)
|
|
{
|
|
// next batch of addresses to be explored
|
|
TArray<FItemCacheKey> NextBatchAddressesData;
|
|
|
|
for (int32 ParentIndex = 0 ; ParentIndex < InSearchPayload.BatchData.Num(); ParentIndex++)
|
|
{
|
|
// Get one of the previous run "children" and treat as a parent to get it's children and process them
|
|
const mu::OP::ADDRESS& CurrentAddress = InSearchPayload.BatchData[ParentIndex].Child;
|
|
|
|
// Cache if same data type and we share the same address (means this op is pointing at the provided resource)
|
|
// It will cache duplicated entries
|
|
if ( InProgram.GetOpType(CurrentAddress) == TargetOperationType)
|
|
{
|
|
// Since this element is of the type we are looking for then cache it on InSearchPayload.FoundElements
|
|
InSearchPayload.AddToFoundElements(CurrentAddress,ParentIndex,ItemCache);
|
|
}
|
|
|
|
// Get all NON PROCESSED the children of this operation to later be able to process them (on next recursive call)
|
|
InSearchPayload.CacheChildrenOfAddressIfNotProcessed(CurrentAddress, InProgram, NextBatchAddressesData);
|
|
}
|
|
|
|
// Explore children if found
|
|
if (NextBatchAddressesData.Num())
|
|
{
|
|
// Cache next batch data so the next invocations are able to locate the provided addresses on the itemsCache
|
|
InSearchPayload.BatchData = MoveTemp(NextBatchAddressesData);
|
|
|
|
// Process the children of this object
|
|
GetOperationsOfType(TargetOperationType, InSearchPayload, InProgram);
|
|
}
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::CacheOperationTypesPresentOnModel()
|
|
{
|
|
check(MutableModel)
|
|
|
|
// Initialize NodeOperationTypes with empty TPair for each possible mutable operation type
|
|
{
|
|
constexpr uint32 OperationTypesCount = static_cast<uint16>(mu::EOpType::COUNT);
|
|
ModelOperationTypes.Empty(OperationTypesCount);
|
|
for (uint32 Index = 0; Index < OperationTypesCount ; Index++)
|
|
{
|
|
mu::EOpType TargetType = StaticCast<mu::EOpType>(Index);
|
|
ModelOperationTypes.Add(TPair<mu::EOpType,uint32>{TargetType,0});
|
|
}
|
|
}
|
|
|
|
// Locate all operation types found on the provided model program data structure and count how many instances of each
|
|
// there are
|
|
{
|
|
// Get the types and the amount of instances of each unique operation on the mutable model
|
|
const mu::FProgram& Program = MutableModel->GetPrivate()->Program;
|
|
const uint32 ProgramAddressesCount = Program.OpAddress.Num();
|
|
|
|
// Ensure first operation type is NONE since we are skipping it due to it having to have that type
|
|
check (Program.GetOpType(Program.OpAddress[0]) == mu::EOpType::NONE);
|
|
|
|
// Iterate over the addresses of the program and count how many instances each type has.
|
|
for (uint32 ProgramAddressesIndex = 1 ; ProgramAddressesIndex < ProgramAddressesCount; ProgramAddressesIndex++)
|
|
{
|
|
// Locate what is the position (index) of the operation type of the address on our collection of types found until now
|
|
const mu::EOpType OperationType = Program.GetOpType(ProgramAddressesIndex);
|
|
|
|
// Increase the counter for this operation type
|
|
const uint16 TypeAsInteger = StaticCast<uint16>(OperationType);
|
|
ModelOperationTypes[TypeAsInteger].Value++;
|
|
}
|
|
}
|
|
|
|
// Remove all operation types that do have no operations present on the model
|
|
{
|
|
ModelOperationTypes.RemoveAll(
|
|
[](const TPair<mu::EOpType, uint32>& Current)
|
|
{
|
|
return Current.Value == 0;
|
|
});
|
|
}
|
|
|
|
// Sort the contents of the array of mutable operation types alphabetically
|
|
ModelOperationTypes.StableSort([&](const TPair<mu::EOpType,uint32>& A, const TPair<mu::EOpType,uint32>& B)
|
|
{
|
|
// Find the name
|
|
FString AString;
|
|
{
|
|
const uint16 OperationIndex = static_cast<uint16>(A.Key);
|
|
const TCHAR* OpName = mu::s_opNames[OperationIndex];
|
|
AString = FString(OpName);
|
|
}
|
|
|
|
// Find out the name of the first element
|
|
FString BString;
|
|
{
|
|
const uint16 OperationIndex = static_cast<uint16>(B.Key);
|
|
const TCHAR* OpName = mu::s_opNames[OperationIndex];
|
|
BString = FString(OpName);
|
|
}
|
|
|
|
// Then the name of the second element
|
|
return AString < BString;
|
|
});
|
|
|
|
// ModelOperationTypes is now an array with all the types found on the operations tree in alphabetical order
|
|
}
|
|
|
|
|
|
|
|
FText SMutableCodeViewer::OnPrintNavigableObjectAddressesCount() const
|
|
{
|
|
FString OutputString = "";
|
|
if (const int32 NavigationElementsCount = NavigationElements.Num())
|
|
{
|
|
// Show the index if the index showing adds information
|
|
if (NavigationIndex >= 0)
|
|
{
|
|
OutputString.Append( FString::FromInt( NavigationIndex+1));
|
|
OutputString.Append(" / ");
|
|
}
|
|
|
|
OutputString.Append( FString::FromInt(NavigationElementsCount));
|
|
|
|
// Format : 1 / 12 or 12
|
|
}
|
|
|
|
// Depending on the amount of navigable objects (addresses, not actual elements) display the amount there are
|
|
return FText::FromString(OutputString);
|
|
}
|
|
|
|
|
|
bool SMutableCodeViewer::CanInteractWithPreviousOperationButton() const
|
|
{
|
|
// Only navigable if there are more than 0 elements to traverse and we are not scrolling
|
|
return NavigationElements.Num() > 0 && NavigationIndex > 0 && (!bWasScrollToTargetRequested && !bWasUniqueExpansionInvokedForNavigation);
|
|
}
|
|
|
|
bool SMutableCodeViewer::CanInteractWithNextOperationButton() const
|
|
{
|
|
// Only navigable if there are more than 0 elements to traverse and we are not scrolling
|
|
return NavigationElements.Num() > 0 && NavigationIndex < NavigationElements.Num() -1 && (!bWasScrollToTargetRequested && !bWasUniqueExpansionInvokedForNavigation);
|
|
}
|
|
|
|
|
|
FReply SMutableCodeViewer::OnGoToPreviousOperationButtonPressed()
|
|
{
|
|
// Focus on previous target
|
|
NavigationIndex = NavigationIndex<=0 ? 0 : NavigationIndex - 1;
|
|
FocusViewOnNavigationTarget(NavigationElements[NavigationIndex]);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SMutableCodeViewer::OnGoToNextOperationButtonPressed()
|
|
{
|
|
// Focus on next target
|
|
NavigationIndex = NavigationIndex>=NavigationElements.Num() -1 ? NavigationElements.Num() -1 : NavigationIndex + 1;
|
|
FocusViewOnNavigationTarget(NavigationElements[NavigationIndex]);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void SMutableCodeViewer::FocusViewOnNavigationTarget(TSharedPtr<FMutableCodeTreeElement> InTargetElement)
|
|
{
|
|
// Stage 1 : Expand all tree so all navigable elements get to be reachable
|
|
if (!bWasUniqueExpansionInvokedForNavigation && !bWasScrollToTargetRequested)
|
|
{
|
|
TreeExpandUnique();
|
|
bWasUniqueExpansionInvokedForNavigation = true;
|
|
|
|
// Cache the current navigation target so after the update we can focus it
|
|
ToFocusElement = InTargetElement;
|
|
|
|
// Early exit, this method will get called again later after tree update
|
|
return;
|
|
}
|
|
|
|
// Stage 2 : Try to get to the targeted element. if not visible scroll into view
|
|
check (InTargetElement.IsValid());
|
|
|
|
// If required scroll to the area where we know the element is going to be in view
|
|
// a way to ensure this happens is by calling
|
|
if (TreeView->IsItemVisible(InTargetElement))
|
|
{
|
|
// Stage 3-b : Select the element we have provided since now is sure to be in view
|
|
|
|
// This line selects the element with at the same time updates the UI to show the row representing this element selected
|
|
TreeView->SetSelection(InTargetElement);
|
|
ToFocusElement.Reset(); // We have focused the target so we no longer need to keep a reference to it
|
|
|
|
// Done!
|
|
// We have the element in view and we have selected it!
|
|
}
|
|
else
|
|
{
|
|
// Stage 3-a (optional) : Ask for the provided element to be scrolled into view.
|
|
|
|
// Failing this check would mean we have performed a scroll but we are still not able to view the element
|
|
check (!bWasScrollToTargetRequested);
|
|
|
|
// Request the tree to show us the target element we want to get focused
|
|
TreeView->RequestScrollIntoView(InTargetElement);
|
|
|
|
// Read this variable after the update and then select the object (easy at this point)
|
|
// You may want to just call again this method after refresh since the element will be on view
|
|
bWasScrollToTargetRequested = true;
|
|
|
|
// Early exit, this method will get called again later after tree update once the scroll has been completed
|
|
return;
|
|
}
|
|
|
|
// Reset the control flag so we do not expand all tree again if not required
|
|
bWasUniqueExpansionInvokedForNavigation = false;
|
|
bWasScrollToTargetRequested = false;
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Operation Cost Color Hints
|
|
|
|
void SMutableCodeViewer::GenerateAllTreeElements()
|
|
{
|
|
// By generating all tree elements prior to usage we are able to :
|
|
// - Compute the index of each one to aid on navigation
|
|
// - Remove non-deterministic assignation of the "Duplicated" state of elements. It was due to user interaction with the tree
|
|
// Only unique elements, their children and duplicated elements will be generated. Children of duplicates will
|
|
// be ignored due to how we handle them when expanding and contracting elements (OnExpansionChanged)
|
|
|
|
// Generate all root nodes
|
|
const mu::FModel::Private* ModelPrivate = MutableModel->GetPrivate();
|
|
check(ModelPrivate);
|
|
const mu::FProgram& Program = ModelPrivate->Program;
|
|
const uint32 StateCount = Program.States.Num();
|
|
for ( uint32 StateIndex = 0; StateIndex < StateCount; ++StateIndex )
|
|
{
|
|
const mu::FProgram::FState& State = Program.States[StateIndex];
|
|
const FString Caption = FString::Printf( TEXT("state [%s]"), *State.Name );
|
|
|
|
const FSlateColor LabelColor = ColorPerComputationalCost[StaticCast<uint8>(GetOperationTypeComputationalCost(
|
|
Program.GetOpType(State.Root)))];
|
|
|
|
// Locate the "original" tree element :
|
|
// This may happen if for some reason the state is duplicated (should never happen)
|
|
const TSharedPtr<FMutableCodeTreeElement>* MainItemPtr = MainItemPerOp.Find(State.Root);
|
|
|
|
// Create a new root element and add it to the collection of root nodes
|
|
TSharedPtr<FMutableCodeTreeElement> RootNodeElement = MakeShareable(new FMutableCodeTreeElement(ItemCache.Num(),StateIndex, MutableModel, State.Root, Caption,LabelColor, MainItemPtr));
|
|
RootNodes.Add(RootNodeElement);
|
|
|
|
// Add the element to the cache so we keep the indices straight.
|
|
constexpr mu::OP::ADDRESS CommonParent = 0;
|
|
const FItemCacheKey Key = { CommonParent, State.Root, StateIndex };
|
|
ItemCache.Add(Key, RootNodeElement);
|
|
|
|
if (!MainItemPtr)
|
|
{
|
|
// Cache this node as it may be duplicated of another state. Check the "MainItemPtr" initialization for more info
|
|
MainItemPerOp.Add(State.Root, RootNodeElement);
|
|
|
|
// Iterate over each root node and generate all the elements in a human-readable pattern (Z Pattern)
|
|
GenerateElementRecursive(StateIndex,State.Root,Program);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SMutableCodeViewer::GenerateElementRecursive(const int32& InStateIndex, mu::OP::ADDRESS InParentAddress, const mu::FProgram& InProgram)
|
|
{
|
|
// This will be used to add operations
|
|
uint32 ChildIndex = 0;
|
|
auto AddOpFunc = [this, InParentAddress, &InProgram, &ChildIndex, &InStateIndex](mu::OP::ADDRESS ChildAddress, const FString& Caption)
|
|
{
|
|
{
|
|
const FItemCacheKey Key = { InParentAddress, ChildAddress, ChildIndex };
|
|
const TSharedPtr<FMutableCodeTreeElement>* CachedItem = ItemCache.Find(Key);
|
|
|
|
// If not already cached then process it
|
|
if (ensure(!CachedItem))
|
|
{
|
|
// Locate the "original" tree element
|
|
const TSharedPtr<FMutableCodeTreeElement>* MainItemPtr = MainItemPerOp.Find(ChildAddress);
|
|
|
|
// Provide the color this element should be using for the displayed text
|
|
const FSlateColor LabelColor = ColorPerComputationalCost[StaticCast<uint8>(GetOperationTypeComputationalCost(InProgram.GetOpType(ChildAddress)))];
|
|
|
|
const TSharedPtr<FMutableCodeTreeElement> Item = MakeShareable(new FMutableCodeTreeElement(ItemCache.Num(),InStateIndex, MutableModel, ChildAddress, Caption, LabelColor, MainItemPtr));
|
|
|
|
// Cache this element for later access
|
|
ItemCache.Add(Key, Item);
|
|
|
|
// It is not a duplicated of another one, then we can continue searching
|
|
if (!MainItemPtr)
|
|
{
|
|
MainItemPerOp.Add(ChildAddress, Item);
|
|
|
|
GenerateElementRecursive(InStateIndex,ChildAddress, InProgram);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMutable, Error, TEXT("An already processed operation is being re-processed in order to generate a tree row. "
|
|
"ParentAddress : %u , ChildAddress : %u , ChildIndex : %u "), InParentAddress, ChildAddress, ChildIndex);
|
|
}
|
|
|
|
}
|
|
++ChildIndex;
|
|
};
|
|
|
|
|
|
// For some specific parent operation types we create more detailed subtrees.
|
|
bool bUseGeneric = false;
|
|
const mu::EOpType ParentOperationType = InProgram.GetOpType(InParentAddress);
|
|
switch (ParentOperationType)
|
|
{
|
|
case mu::EOpType::IM_CONDITIONAL:
|
|
case mu::EOpType::LA_CONDITIONAL:
|
|
case mu::EOpType::ME_CONDITIONAL:
|
|
case mu::EOpType::CO_CONDITIONAL:
|
|
case mu::EOpType::SC_CONDITIONAL:
|
|
case mu::EOpType::NU_CONDITIONAL:
|
|
case mu::EOpType::IN_CONDITIONAL:
|
|
case mu::EOpType::ED_CONDITIONAL:
|
|
{
|
|
mu::OP::ConditionalArgs Args = InProgram.GetOpArgs<mu::OP::ConditionalArgs>(InParentAddress);
|
|
AddOpFunc(Args.condition, TEXT("cond "));
|
|
AddOpFunc(Args.yes, TEXT("true "));
|
|
AddOpFunc(Args.no, TEXT("false "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_SWITCH:
|
|
case mu::EOpType::LA_SWITCH:
|
|
case mu::EOpType::ME_SWITCH:
|
|
case mu::EOpType::CO_SWITCH:
|
|
case mu::EOpType::SC_SWITCH:
|
|
case mu::EOpType::NU_SWITCH:
|
|
case mu::EOpType::IN_SWITCH:
|
|
case mu::EOpType::ED_SWITCH:
|
|
{
|
|
const uint8* OpData = InProgram.GetOpArgsPointer(InParentAddress);
|
|
|
|
mu::OP::ADDRESS VarAddress;
|
|
FMemory::Memcpy(&VarAddress, OpData, sizeof(mu::OP::ADDRESS));
|
|
OpData += sizeof(mu::OP::ADDRESS);
|
|
AddOpFunc(VarAddress, TEXT("var "));
|
|
|
|
mu::OP::ADDRESS DefAddress;
|
|
FMemory::Memcpy(&DefAddress, OpData, sizeof(mu::OP::ADDRESS));
|
|
OpData += sizeof(mu::OP::ADDRESS);
|
|
AddOpFunc(DefAddress, TEXT("def "));
|
|
|
|
uint32 CaseCount;
|
|
FMemory::Memcpy(&CaseCount, OpData, sizeof(uint32));
|
|
OpData += sizeof(uint32);
|
|
|
|
for (uint32 C = 0; C < CaseCount; ++C)
|
|
{
|
|
int32 Condition;
|
|
FMemory::Memcpy(&Condition, OpData, sizeof(int32));
|
|
OpData += sizeof(int32);
|
|
|
|
mu::OP::ADDRESS At;
|
|
FMemory::Memcpy(&At, OpData, sizeof(mu::OP::ADDRESS));
|
|
OpData += sizeof(mu::OP::ADDRESS);
|
|
|
|
FString Caption = FString::Printf(TEXT("case %d "), Condition);
|
|
AddOpFunc(At, Caption);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_SWIZZLE:
|
|
{
|
|
mu::OP::ImageSwizzleArgs Args = InProgram.GetOpArgs<mu::OP::ImageSwizzleArgs>(InParentAddress);
|
|
for (int32 Channel = 0; Channel < 4; ++Channel)
|
|
{
|
|
FString Caption = FString::Printf(TEXT("%d is %d from "), Channel, Args.sourceChannels[Channel]);
|
|
AddOpFunc(Args.sources[Channel], Caption);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::CO_SWIZZLE:
|
|
{
|
|
mu::OP::ColourSwizzleArgs Args = InProgram.GetOpArgs<mu::OP::ColourSwizzleArgs>(InParentAddress);
|
|
for (int32 Channel = 0; Channel < 4; ++Channel)
|
|
{
|
|
FString Caption = FString::Printf(TEXT("%d is %d from "), Channel, Args.sourceChannels[Channel]);
|
|
AddOpFunc(Args.sources[Channel], Caption);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_LAYER:
|
|
{
|
|
mu::OP::ImageLayerArgs Args = InProgram.GetOpArgs<mu::OP::ImageLayerArgs>(InParentAddress);
|
|
AddOpFunc(Args.base, TEXT("base "));
|
|
AddOpFunc(Args.mask, TEXT("mask "));
|
|
AddOpFunc(Args.blended, TEXT("blended "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
mu::OP::ImageLayerColourArgs Args = InProgram.GetOpArgs<mu::OP::ImageLayerColourArgs>(InParentAddress);
|
|
AddOpFunc(Args.base, TEXT("base "));
|
|
AddOpFunc(Args.mask, TEXT("mask "));
|
|
AddOpFunc(Args.colour, TEXT("colour "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_MULTILAYER:
|
|
{
|
|
mu::OP::ImageMultiLayerArgs Args = InProgram.GetOpArgs<mu::OP::ImageMultiLayerArgs>(InParentAddress);
|
|
AddOpFunc(Args.rangeSize, TEXT("range "));
|
|
AddOpFunc(Args.base, TEXT("base "));
|
|
AddOpFunc(Args.mask, TEXT("mask "));
|
|
AddOpFunc(Args.blended, TEXT("blended "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::ME_ADDMETADATA:
|
|
{
|
|
mu::OP::MeshAddMetadataArgs Args = InProgram.GetOpArgs<mu::OP::MeshAddMetadataArgs>(InParentAddress);
|
|
|
|
using OpEnumFlags = mu::OP::MeshAddMetadataArgs::EnumFlags;
|
|
|
|
// Set count to 1 if is not a list.
|
|
int32 TagCount = int32(!EnumHasAnyFlags(Args.Flags, OpEnumFlags::IsTagList));
|
|
int32 ResourceCount = int32(!EnumHasAnyFlags(Args.Flags, OpEnumFlags::IsResourceList));
|
|
int32 SkeletonCount = int32(!EnumHasAnyFlags(Args.Flags, OpEnumFlags::IsSkeletonList));
|
|
|
|
if (!TagCount)
|
|
{
|
|
TagCount = InProgram.ConstantUInt32Lists.IsValidIndex(Args.Tags.ListAddress)
|
|
? InProgram.ConstantUInt32Lists[Args.Tags.ListAddress].Num()
|
|
: 0;
|
|
}
|
|
|
|
if (!ResourceCount)
|
|
{
|
|
ResourceCount = InProgram.ConstantUInt64Lists.IsValidIndex(Args.ResourceIds.ListAddress)
|
|
? InProgram.ConstantUInt64Lists[Args.ResourceIds.ListAddress].Num()
|
|
: 0;
|
|
}
|
|
|
|
if (!SkeletonCount)
|
|
{
|
|
SkeletonCount = InProgram.ConstantUInt32Lists.IsValidIndex(Args.SkeletonIds.ListAddress)
|
|
? InProgram.ConstantUInt32Lists[Args.SkeletonIds.ListAddress].Num()
|
|
: 0;
|
|
}
|
|
|
|
FString Caption = FString::Printf(TEXT("add %d tags %d resources and %d skeletons to "), TagCount, ResourceCount, SkeletonCount);
|
|
AddOpFunc(Args.Source, Caption);
|
|
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::ME_APPLYLAYOUT:
|
|
{
|
|
mu::OP::MeshApplyLayoutArgs Args = InProgram.GetOpArgs<mu::OP::MeshApplyLayoutArgs>(InParentAddress);
|
|
AddOpFunc(Args.Layout, TEXT("layout "));
|
|
AddOpFunc(Args.Mesh, TEXT("mesh "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::ME_PREPARELAYOUT:
|
|
{
|
|
mu::OP::MeshPrepareLayoutArgs Args = InProgram.GetOpArgs<mu::OP::MeshPrepareLayoutArgs>(InParentAddress);
|
|
AddOpFunc(Args.Layout, TEXT("layout "));
|
|
AddOpFunc(Args.Mesh, TEXT("mesh "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::ME_DIFFERENCE:
|
|
{
|
|
const uint8* data = InProgram.GetOpArgsPointer(InParentAddress);
|
|
|
|
mu::OP::ADDRESS BaseAt = 0;
|
|
FMemory::Memcpy(&BaseAt, data, sizeof(mu::OP::ADDRESS));
|
|
AddOpFunc(BaseAt, TEXT("base "));
|
|
data += sizeof(mu::OP::ADDRESS);
|
|
|
|
mu::OP::ADDRESS TargetAt = 0;
|
|
FMemory::Memcpy(&TargetAt, data, sizeof(mu::OP::ADDRESS));
|
|
AddOpFunc(TargetAt, TEXT("target "));
|
|
data += sizeof(mu::OP::ADDRESS);
|
|
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::ME_MORPH:
|
|
{
|
|
const uint8* Data = InProgram.GetOpArgsPointer(InParentAddress);
|
|
|
|
mu::OP::ADDRESS FactorAt = 0;
|
|
FMemory::Memcpy(&FactorAt, Data, sizeof(mu::OP::ADDRESS));
|
|
AddOpFunc(FactorAt, TEXT("factor "));
|
|
Data += sizeof(mu::OP::ADDRESS);
|
|
|
|
mu::OP::ADDRESS BaseAt = 0;
|
|
FMemory::Memcpy(&BaseAt, Data, sizeof(mu::OP::ADDRESS));
|
|
AddOpFunc(BaseAt, TEXT("base "));
|
|
Data += sizeof(mu::OP::ADDRESS);
|
|
|
|
mu::OP::ADDRESS TargetAt = 0;
|
|
FMemory::Memcpy(&TargetAt, Data, sizeof(mu::OP::ADDRESS));
|
|
AddOpFunc(TargetAt, TEXT("target "));
|
|
Data += sizeof(mu::OP::ADDRESS);
|
|
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::SC_CURVE:
|
|
{
|
|
mu::OP::ScalarCurveArgs Args = InProgram.GetOpArgs<mu::OP::ScalarCurveArgs>(InParentAddress);
|
|
AddOpFunc(Args.time, TEXT("time "));
|
|
AddOpFunc(Args.curve, TEXT("curve "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::CO_SAMPLEIMAGE:
|
|
{
|
|
mu::OP::ColourSampleImageArgs Args = InProgram.GetOpArgs<mu::OP::ColourSampleImageArgs>(InParentAddress);
|
|
AddOpFunc(Args.Image, TEXT("image "));
|
|
AddOpFunc(Args.X, TEXT("x "));
|
|
AddOpFunc(Args.Y, TEXT("y "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_RESIZELIKE:
|
|
{
|
|
mu::OP::ImageResizeLikeArgs Args = InProgram.GetOpArgs<mu::OP::ImageResizeLikeArgs>(InParentAddress);
|
|
AddOpFunc(Args.Source, TEXT("src "));
|
|
AddOpFunc(Args.SizeSource, TEXT("sizeSrc "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IN_ADDMESH:
|
|
case mu::EOpType::IN_ADDIMAGE:
|
|
case mu::EOpType::IN_ADDVECTOR:
|
|
case mu::EOpType::IN_ADDSCALAR:
|
|
case mu::EOpType::IN_ADDSTRING:
|
|
case mu::EOpType::IN_ADDCOMPONENT:
|
|
case mu::EOpType::IN_ADDSURFACE:
|
|
{
|
|
mu::OP::InstanceAddArgs Args = InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(InParentAddress);
|
|
AddOpFunc(Args.instance, TEXT("instance "));
|
|
AddOpFunc(Args.value, TEXT("value "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_COMPOSE:
|
|
{
|
|
mu::OP::ImageComposeArgs Args = InProgram.GetOpArgs<mu::OP::ImageComposeArgs>(InParentAddress);
|
|
AddOpFunc(Args.layout, TEXT("layout "));
|
|
AddOpFunc(Args.base, TEXT("base "));
|
|
AddOpFunc(Args.blockImage, TEXT("blockImage "));
|
|
AddOpFunc(Args.mask, TEXT("mask "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_INTERPOLATE:
|
|
{
|
|
mu::OP::ImageInterpolateArgs Args = InProgram.GetOpArgs<mu::OP::ImageInterpolateArgs>(InParentAddress);
|
|
AddOpFunc(Args.Factor, TEXT("factor "));
|
|
|
|
int32 TargetIndex = 0;
|
|
for (const mu::OP::ADDRESS& Operation : Args.Targets)
|
|
{
|
|
AddOpFunc(Operation, FString::Printf(TEXT("target %i "), TargetIndex++));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_SATURATE:
|
|
{
|
|
mu::OP::ImageSaturateArgs Args = InProgram.GetOpArgs<mu::OP::ImageSaturateArgs>(InParentAddress);
|
|
AddOpFunc(Args.Base, TEXT("base "));
|
|
AddOpFunc(Args.Factor, TEXT("factor "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_COLOURMAP:
|
|
{
|
|
mu::OP::ImageColourMapArgs Args = InProgram.GetOpArgs<mu::OP::ImageColourMapArgs>(InParentAddress);
|
|
AddOpFunc(Args.Base, TEXT("base "));
|
|
AddOpFunc(Args.Mask, TEXT("mask "));
|
|
AddOpFunc(Args.Map, TEXT("map "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_BINARISE:
|
|
{
|
|
mu::OP::ImageBinariseArgs Args = InProgram.GetOpArgs<mu::OP::ImageBinariseArgs>(InParentAddress);
|
|
AddOpFunc(Args.Base, TEXT("base "));
|
|
AddOpFunc(Args.Threshold, TEXT("threshold "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_PATCH:
|
|
{
|
|
mu::OP::ImagePatchArgs Args = InProgram.GetOpArgs<mu::OP::ImagePatchArgs>(InParentAddress);
|
|
AddOpFunc(Args.base, TEXT("base "));
|
|
AddOpFunc(Args.patch, TEXT("patch "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_RASTERMESH:
|
|
{
|
|
mu::OP::ImageRasterMeshArgs Args = InProgram.GetOpArgs<mu::OP::ImageRasterMeshArgs>(InParentAddress);
|
|
AddOpFunc(Args.mesh, TEXT("mesh "));
|
|
AddOpFunc(Args.image, TEXT("image "));
|
|
AddOpFunc(Args.mask, TEXT("mask "));
|
|
AddOpFunc(Args.angleFadeProperties, TEXT("angleFadeProperties "));
|
|
AddOpFunc(Args.projector, TEXT("projector "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_DISPLACE:
|
|
{
|
|
mu::OP::ImageDisplaceArgs Args = InProgram.GetOpArgs<mu::OP::ImageDisplaceArgs>(InParentAddress);
|
|
AddOpFunc(Args.Source, TEXT("src "));
|
|
AddOpFunc(Args.DisplacementMap, TEXT("displacementMap "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_NORMALCOMPOSITE:
|
|
{
|
|
mu::OP::ImageNormalCompositeArgs Args = InProgram.GetOpArgs<mu::OP::ImageNormalCompositeArgs>(InParentAddress);
|
|
AddOpFunc(Args.base, TEXT("base "));
|
|
AddOpFunc(Args.normal, TEXT("normal "));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_TRANSFORM:
|
|
{
|
|
mu::OP::ImageTransformArgs Args = InProgram.GetOpArgs<mu::OP::ImageTransformArgs>(InParentAddress);
|
|
AddOpFunc(Args.Base, TEXT("base "));
|
|
AddOpFunc(Args.OffsetX, TEXT("offsetX "));
|
|
AddOpFunc(Args.OffsetY, TEXT("offsetY "));
|
|
AddOpFunc(Args.ScaleX, TEXT("scaleX "));
|
|
AddOpFunc(Args.ScaleY, TEXT("scaleY "));
|
|
AddOpFunc(Args.Rotation, TEXT("rotation "));
|
|
break;
|
|
}
|
|
|
|
// Add here more operation types to define how they are exposed in the tree (set a Caption)
|
|
|
|
default:
|
|
{
|
|
// Generic list of child operations
|
|
bUseGeneric = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bUseGeneric)
|
|
{
|
|
// Find children of the provided element without adding any extra string to better identify what each of them represents
|
|
mu::ForEachReference(InProgram, InParentAddress, [this, &InProgram, AddOpFunc](mu::OP::ADDRESS ChildAddress)
|
|
{
|
|
AddOpFunc(ChildAddress,TEXT(""));
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Validate in case there is a mismatch in the custom processing of children and the generic one, which would cause problems.
|
|
ChildIndex = 0;
|
|
|
|
auto ValidateOpFunc = [this, InParentAddress, ParentOperationType, &ChildIndex](mu::OP::ADDRESS ChildAddress)
|
|
{
|
|
const FItemCacheKey Key = { InParentAddress, ChildAddress, ChildIndex };
|
|
const TSharedPtr<FMutableCodeTreeElement>* CachedItem = ItemCache.Find(Key);
|
|
|
|
// If this check fails could mean that in the switch above one type of OP processes its children in one
|
|
// order but in "ForEachReference" that same operation processes the same children in another order.
|
|
check(CachedItem);
|
|
|
|
++ChildIndex;
|
|
};
|
|
|
|
mu::ForEachReference(InProgram, InParentAddress, [this, ValidateOpFunc](mu::OP::ADDRESS ChildAddress)
|
|
{
|
|
ValidateOpFunc(ChildAddress);
|
|
});
|
|
}
|
|
}
|
|
|
|
SMutableCodeViewer::EOperationComputationalCost SMutableCodeViewer::GetOperationTypeComputationalCost(mu::EOpType OperationType) const
|
|
{
|
|
if (VeryExpensiveOperationTypes.Contains(OperationType))
|
|
{
|
|
return EOperationComputationalCost::VeryExpensive;
|
|
}
|
|
else if (ExpensiveOperationTypes.Contains(OperationType))
|
|
{
|
|
return EOperationComputationalCost::Expensive;
|
|
}
|
|
else
|
|
{
|
|
return EOperationComputationalCost::Standard;
|
|
}
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
|
|
|
|
#pragma region CodeTree Callbacks
|
|
|
|
|
|
TSharedRef<ITableRow> SMutableCodeViewer::GenerateRowForNodeTree(TSharedPtr<FMutableCodeTreeElement> InTreeNode, const TSharedRef<STableViewBase>& InOwnerTable)
|
|
{
|
|
// Save the node for later access
|
|
TreeElements.Add(InTreeNode);
|
|
|
|
// Generate a row element
|
|
TSharedRef<SMutableCodeTreeRow> Row = SNew(SMutableCodeTreeRow, InOwnerTable, InTreeNode);
|
|
|
|
// Determine if a row should be painted as highlighted based on the selected item
|
|
if (TreeView->GetNumItemsSelected())
|
|
{
|
|
const TSharedPtr<FMutableCodeTreeElement> SelectedElement = TreeView->GetSelectedItems()[0];
|
|
if (SelectedElement != InTreeNode &&
|
|
SelectedElement->MutableOperation > 0 &&
|
|
InTreeNode->MutableOperation == SelectedElement->MutableOperation)
|
|
{
|
|
Row->Highlight();
|
|
}
|
|
}
|
|
|
|
return Row;
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::GetChildrenForInfo(TSharedPtr<FMutableCodeTreeElement> InInfo, TArray<TSharedPtr<FMutableCodeTreeElement>>& OutChildren)
|
|
{
|
|
if (!InInfo->MutableModel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(MutableModel);
|
|
const mu::FProgram& Program = MutableModel->GetPrivate()->Program;
|
|
|
|
mu::OP::ADDRESS ParentAddress = InInfo->MutableOperation;
|
|
|
|
// Generic case for unnamed children traversal.
|
|
uint32 ChildIndex = 0;
|
|
mu::ForEachReference(Program, InInfo->MutableOperation, [this, ParentAddress, &ChildIndex, &OutChildren](mu::OP::ADDRESS ChildAddress)
|
|
{
|
|
{
|
|
const FItemCacheKey Key = { ParentAddress, ChildAddress, ChildIndex };
|
|
const TSharedPtr<FMutableCodeTreeElement>* CachedItem = ItemCache.Find(Key);
|
|
|
|
if (CachedItem)
|
|
{
|
|
OutChildren.Add(*CachedItem);
|
|
}
|
|
else
|
|
{
|
|
// if all elements have been already cached this should never happen
|
|
checkNoEntry();
|
|
}
|
|
}
|
|
++ChildIndex;
|
|
});
|
|
}
|
|
|
|
void SMutableCodeViewer::OnExpansionChanged(TSharedPtr<FMutableCodeTreeElement> InItem, bool bInExpanded)
|
|
{
|
|
// Update expanded state of the provided element
|
|
InItem->bIsExpanded = bInExpanded;
|
|
|
|
// If an element gets expanded then contract (if found) the other element that uses the same address
|
|
if (bInExpanded)
|
|
{
|
|
const mu::OP::ADDRESS MutableOperation = InItem->MutableOperation;
|
|
const TSharedPtr<FMutableCodeTreeElement>* PreviouslyExpandedElement = ExpandedElements.Find(MutableOperation);
|
|
if (PreviouslyExpandedElement)
|
|
{
|
|
TreeView->SetItemExpansion(*PreviouslyExpandedElement, false);
|
|
}
|
|
|
|
// Only do this if in a situation where it may be required (do not do it if the tree has not been interacted with yet)
|
|
if (bShouldRecalculateStates)
|
|
{
|
|
// Find all the children (recursive) of this item.
|
|
TSet<TSharedPtr<FMutableCodeTreeElement>> FoundChildren;
|
|
GetVisibleChildren(InItem, FoundChildren);
|
|
for (const TSharedPtr<FMutableCodeTreeElement>& Child : FoundChildren)
|
|
{
|
|
// For each of the children found set it's state to be the one found on the expanded element
|
|
Child->SetElementCurrentState(InItem->GetStateIndex());
|
|
}
|
|
}
|
|
|
|
// Cache this element as one currently expanded
|
|
ExpandedElements.Add(MutableOperation, InItem);
|
|
}
|
|
else
|
|
{
|
|
// Remove this element from the cache of expanded elements
|
|
ExpandedElements.Remove(InItem->MutableOperation);
|
|
}
|
|
}
|
|
|
|
void SMutableCodeViewer::GetVisibleChildren(TSharedPtr<FMutableCodeTreeElement> InInfo, TSet<TSharedPtr<FMutableCodeTreeElement>>& OutChildren)
|
|
{
|
|
check(MutableModel);
|
|
const mu::FProgram& MutableProgram = MutableModel->GetPrivate()->Program;
|
|
|
|
TArray<TSharedPtr<FMutableCodeTreeElement>> ToSearchForChildren;
|
|
ToSearchForChildren.Add(InInfo);
|
|
while (!ToSearchForChildren.IsEmpty())
|
|
{
|
|
// Grab the first element in order to check for it's children
|
|
const TSharedPtr<FMutableCodeTreeElement> ToCheck = ToSearchForChildren[0];
|
|
ToSearchForChildren.RemoveAt(0);
|
|
|
|
const mu::OP::ADDRESS ParentAddress = ToCheck->MutableOperation;
|
|
|
|
// Generic case for unnamed children traversal.
|
|
uint32 ChildIndex = 0;
|
|
mu::ForEachReference(MutableProgram, ParentAddress, [this, ParentAddress, &ChildIndex, &OutChildren, &ToSearchForChildren ](mu::OP::ADDRESS ChildAddress)
|
|
{
|
|
{
|
|
const FItemCacheKey Key = { ParentAddress, ChildAddress, ChildIndex };
|
|
const TSharedPtr<FMutableCodeTreeElement> CachedItem = *ItemCache.Find(Key);
|
|
|
|
// Since we have already generated all elements CachedItem should be therefore always a valid pointer
|
|
check (CachedItem);
|
|
|
|
// If the address has not been yet found then save it as one of the children affected
|
|
if (!OutChildren.Contains(CachedItem))
|
|
{
|
|
OutChildren.Add(CachedItem);
|
|
|
|
// And if the children is found to be expanded then also process it later to later return only the
|
|
// elements that are expanded in the tree view (using data manually set on each tree element)
|
|
if (CachedItem->bIsExpanded )
|
|
{
|
|
// Add for processing
|
|
ToSearchForChildren.Add(CachedItem);
|
|
}
|
|
}
|
|
}
|
|
++ChildIndex;
|
|
});
|
|
}
|
|
|
|
// Debug
|
|
// UE_LOG(LogTemp,Warning,TEXT("Found a total of %i children elements "),OutChildren.Num());
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::OnSelectionChanged(TSharedPtr<FMutableCodeTreeElement> InNode, ESelectInfo::Type InSelectInfo)
|
|
{
|
|
if (bIsElementHighlighted)
|
|
{
|
|
ClearHighlightedItems();
|
|
}
|
|
|
|
TArray<TSharedPtr<FMutableCodeTreeElement>> SelectedNodes;
|
|
TreeView->GetSelectedItems(SelectedNodes);
|
|
|
|
PreviewBorder->ClearContent();
|
|
|
|
SelectedOperationAddress = 0;
|
|
bSelectedOperationIsImage = false;
|
|
|
|
if (SelectedNodes.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Clear all selected items in the constant resources widget
|
|
ConstantsWidget->ClearSelectedConstantItems();
|
|
|
|
// Find the duplicates for the selected tree element and highlight them
|
|
if (InNode)
|
|
{
|
|
HighlightDuplicatesOfEntry(InNode);
|
|
}
|
|
|
|
bIsPreviewPendingUpdate = true;
|
|
|
|
SelectedOperationAddress = SelectedNodes[0]->MutableOperation;
|
|
const mu::EOpType OperationType = MutableModel->GetPrivate()->Program.GetOpType(SelectedOperationAddress);
|
|
const mu::EDataType OperationDataType = mu::GetOpDataType(OperationType);
|
|
|
|
switch (OperationDataType)
|
|
{
|
|
case mu::EDataType::Layout:
|
|
{
|
|
// Create or reuse the UI
|
|
PrepareLayoutViewer();
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Image:
|
|
{
|
|
// Create or reuse the UI
|
|
bSelectedOperationIsImage = true;
|
|
PrepareImageViewer();
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Mesh:
|
|
{
|
|
// Create or reuse the UI
|
|
PrepareMeshViewer();
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Instance:
|
|
{
|
|
// Create or reuse the UI
|
|
PrepareInstanceViewer();
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Scalar:
|
|
{
|
|
// Create or reuse the UI
|
|
if (!PreviewScalarViewer)
|
|
{
|
|
PreviewScalarViewer = SNew(SMutableScalarViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewScalarViewer.ToSharedRef());
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::String:
|
|
{
|
|
// Create or reuse the UI
|
|
PrepareStringViewer();
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Color:
|
|
{
|
|
// Create or reuse the UI
|
|
if (!PreviewColorViewer)
|
|
{
|
|
PreviewColorViewer = SNew(SMutableColorViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewColorViewer.ToSharedRef());
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Int:
|
|
{
|
|
// Create or reuse the UI
|
|
if (!PreviewIntViewer)
|
|
{
|
|
PreviewIntViewer = SNew(SMutableIntViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewIntViewer.ToSharedRef());
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Bool:
|
|
{
|
|
// Create or reuse the UI
|
|
if (!PreviewBoolViewer)
|
|
{
|
|
PreviewBoolViewer = SNew(SMutableBoolViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewBoolViewer.ToSharedRef());
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Projector:
|
|
{
|
|
// Create or reuse the UI
|
|
PrepareProjectorViewer();
|
|
break;
|
|
}
|
|
|
|
|
|
default:
|
|
// There is no viewer for this type yet.
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SWidget> SMutableCodeViewer::OnTreeContextMenuOpening()
|
|
{
|
|
FMenuBuilder MenuBuilder(true, nullptr);
|
|
|
|
// Only show the Ui for operations different from "None" or "0"
|
|
if (TreeView->GetSelectedItems().Num() && TreeView->GetSelectedItems()[0]->MutableOperation > 0)
|
|
{
|
|
if (TreeView->GetSelectedItems().Num() == 1)
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("Set_as_search_operation_type","Set as search Operation"),
|
|
LOCTEXT("Set_as_search_operation_type_Tooltip", "Sets the type of this operation as the type to be looking for when searching for operations on the tree view"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &SMutableCodeViewer::OnSelectedOperationTypeFromTree)
|
|
)
|
|
);
|
|
}
|
|
|
|
MenuBuilder.AddMenuSeparator();
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("Code_Expand_Selected", "Expand Selected Operation"),
|
|
LOCTEXT("Code_Expand_Selected_Tooltip", "Expands only the selected Operation and leaves the other as they are."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &SMutableCodeViewer::TreeExpandSelected)
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("Code_Expand_Instance", "Expand Instance-Level Operations"),
|
|
LOCTEXT("Code_Expand_Instance_Tooltip", "Expands all the operations in the tree that are instance operations (not images, meshes, booleans, etc.)."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &SMutableCodeViewer::TreeExpandInstance)
|
|
//, FCanExecuteAction::CreateSP(this, &SMutableCodeViewer::HasAnyItemInPalette)
|
|
)
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("Code_Expand_Unique", "Expand All Unique Operations"),
|
|
LOCTEXT("Code_Expand_Unique_Tooltip", "Expands all the operations in the tree that have not been expanded yet."),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateSP(this, &SMutableCodeViewer::TreeExpandUnique)
|
|
)
|
|
);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void SMutableCodeViewer::TreeExpandRecursive(TSharedPtr<FMutableCodeTreeElement> InInfo, bool bExpand)
|
|
{
|
|
if (bExpand)
|
|
{
|
|
TreeExpandUnique();
|
|
}
|
|
}
|
|
|
|
void SMutableCodeViewer::OnRowReleased(const TSharedRef<ITableRow>& InTreeRow)
|
|
{
|
|
SMutableCodeTreeRow* CastedTableRow = static_cast<SMutableCodeTreeRow*>(&InTreeRow.Get());
|
|
const TSharedPtr<FMutableCodeTreeElement>& RowElement = CastedTableRow->GetItem();
|
|
TreeElements.Remove(RowElement);
|
|
}
|
|
#pragma endregion
|
|
|
|
|
|
#pragma region Highlight Methods
|
|
void SMutableCodeViewer::HighlightDuplicatesOfEntry(const TSharedPtr<FMutableCodeTreeElement>& InTargetEntry)
|
|
{
|
|
if (bIsElementHighlighted)
|
|
{
|
|
ClearHighlightedItems();
|
|
}
|
|
|
|
// Do not highlight empty entries
|
|
if (InTargetEntry->MutableOperation == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Highlight the elements related to the currently selected item of the tree
|
|
HighlightedOperation = InTargetEntry->MutableOperation;
|
|
|
|
for (const TSharedPtr<FMutableCodeTreeElement>& TreeItem : TreeElements)
|
|
{
|
|
if (TreeItem.Get() != InTargetEntry.Get() && TreeItem->MutableOperation == HighlightedOperation)
|
|
{
|
|
TSharedPtr<ITableRow> TableRow = TreeView->WidgetFromItem(TreeItem);
|
|
SMutableCodeTreeRow* MutableRow = static_cast<SMutableCodeTreeRow*>(TableRow.Get());
|
|
MutableRow->Highlight();
|
|
}
|
|
}
|
|
|
|
bIsElementHighlighted = true;
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::ClearHighlightedItems()
|
|
{
|
|
// Clear the previously highlighted elements
|
|
for (const TSharedPtr<FMutableCodeTreeElement>& HighlightedElement : TreeElements)
|
|
{
|
|
if (HighlightedElement->MutableOperation == HighlightedOperation)
|
|
{
|
|
TSharedPtr<ITableRow> TableRow = TreeView->WidgetFromItem(HighlightedElement);
|
|
|
|
if (TableRow.IsValid())
|
|
{
|
|
SMutableCodeTreeRow* MutableRow = static_cast<SMutableCodeTreeRow*>(TableRow.Get());
|
|
MutableRow->ResetHighlight();
|
|
}
|
|
}
|
|
}
|
|
|
|
bIsElementHighlighted = false;
|
|
}
|
|
#pragma endregion
|
|
|
|
#pragma region Element Expansion Llogic
|
|
|
|
|
|
void SMutableCodeViewer::TreeExpandElements(TArray<TSharedPtr<FMutableCodeTreeElement>>& InElementsToExpand,
|
|
bool bForceExpandDuplicates /*= false*/,
|
|
mu::EDataType FilteringDataType /*= mu::EDataType::None*/,
|
|
TSharedPtr<FProcessedOperationsBuffer> InExpandedOperationsBuffer /* = nullptr */)
|
|
{
|
|
if (InElementsToExpand.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Initialization of recursive elements if this is the first invocation of method
|
|
{
|
|
if (!InExpandedOperationsBuffer)
|
|
{
|
|
InExpandedOperationsBuffer = MakeShared<FProcessedOperationsBuffer>();
|
|
}
|
|
}
|
|
|
|
// Load references to the arrays containing all the operations already worked on during another recursive call to this
|
|
// method
|
|
TArray<mu::OP::ADDRESS>& AlreadyExpandedOriginalOperations = InExpandedOperationsBuffer->ExpandedOriginalOperations;
|
|
TArray<mu::OP::ADDRESS>& AlreadyExpandedDuplicatedOperations = InExpandedOperationsBuffer->ExpandedDuplicatedOperations;
|
|
|
|
// Array containing the children object found on Item.
|
|
TArray<TSharedPtr<FMutableCodeTreeElement>> Children;
|
|
|
|
// Index of the current element being processed
|
|
int32 CurrentElementIndex = 0;
|
|
while (CurrentElementIndex < InElementsToExpand.Num() )
|
|
{
|
|
// Grab the current element to process and move the index forward once
|
|
TSharedPtr<FMutableCodeTreeElement> Item = InElementsToExpand[CurrentElementIndex++];
|
|
check(Item);
|
|
|
|
// Identifier of the element. May be repeated if there are elements duplicating another element
|
|
const mu::OP::ADDRESS OperationAddress = Item->MutableOperation;
|
|
|
|
// Filter the elements being expanded if the user has defined a desired EDataType
|
|
if (FilteringDataType != mu::EDataType::None)
|
|
{
|
|
const mu::EOpType OperationType = Item->MutableModel->GetPrivate()->Program.GetOpType(OperationAddress);
|
|
const mu::EDataType OperationDataType = mu::GetOpDataType(OperationType);
|
|
|
|
// If it is not of the desired type then ignore it and continue to the next pending element
|
|
if (OperationDataType != FilteringDataType)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Reset the children array
|
|
Children.SetNum(0);
|
|
|
|
// If not duplicated expand it and grab the children to be also expanded on the next loop
|
|
if (Item->DuplicatedOf == nullptr )
|
|
{
|
|
// Was this unique element expanded before (only valid if also expanding duplicates)
|
|
bool bHasBeenExpandedPreviously = false;
|
|
|
|
// Mind duplicated original elements if dealing with duplicated operation expansions.
|
|
if (bForceExpandDuplicates)
|
|
{
|
|
// Make sure we have not already expanded this item to avoid recursive expansions of the same item and
|
|
// children
|
|
bHasBeenExpandedPreviously = AlreadyExpandedOriginalOperations.Contains(OperationAddress);
|
|
}
|
|
|
|
// Only check for duplicated original elements when working with duplicates
|
|
if (!bHasBeenExpandedPreviously )
|
|
{
|
|
// Get the children of this unique element and prepare them for processing
|
|
GetChildrenForInfo(Item, Children);
|
|
|
|
// Call for the expansion of the children first
|
|
TreeExpandElements(Children, bForceExpandDuplicates, FilteringDataType,InExpandedOperationsBuffer);
|
|
|
|
// At this point all the children objects that needed expansion are already expanded so we can proceed with
|
|
// the expansion of this element
|
|
|
|
{
|
|
// If we do expect to expand duplicates make sure we record this object as being expanded to be later able
|
|
// to block the expansion of duplicates of this object
|
|
if (bForceExpandDuplicates)
|
|
{
|
|
// Register this node as expanded so other nodes are able to check if it has already been worked with
|
|
AlreadyExpandedOriginalOperations.Add(OperationAddress);
|
|
}
|
|
|
|
// Only ask for the expansion of the element if we know it can be expanded due to it having
|
|
// children
|
|
if (!Children.IsEmpty())
|
|
{
|
|
// Expand this unique element
|
|
TreeView->SetItemExpansion(Item, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
// If it is a duplicated node
|
|
else
|
|
{
|
|
// Special behavior where we expand duplicates if parent is not found to be expanded
|
|
if (bForceExpandDuplicates)
|
|
{
|
|
// Was this element expanded as an original operation? We only want to expand the duplicate if the original
|
|
// was not duplicated before
|
|
bool bOriginalElementHasBeenExpanded = false;
|
|
|
|
// Was this element expanded on a duplicated element? we only want to expand the first duplicate!
|
|
const bool bOtherDuplicateOfSameOpWasExpandedBefore = AlreadyExpandedDuplicatedOperations.Contains(OperationAddress);
|
|
|
|
// Only check if there is another original element with the same operation if we know that there is not another
|
|
// duplicated element using this operation
|
|
if (!bOtherDuplicateOfSameOpWasExpandedBefore)
|
|
{
|
|
bOriginalElementHasBeenExpanded = AlreadyExpandedOriginalOperations.Contains(OperationAddress);
|
|
}
|
|
|
|
// Was this operation expanded before?
|
|
const bool bWasOperationExpandedPreviously = bOriginalElementHasBeenExpanded || bOtherDuplicateOfSameOpWasExpandedBefore;
|
|
|
|
// If this operation have not yet been expanded then expand it!
|
|
// Duplicates do not have priority over original elements.
|
|
if ( !bWasOperationExpandedPreviously )
|
|
{
|
|
// Mark the children to be expanded later if conditions are met
|
|
GetChildrenForInfo(Item, Children);
|
|
|
|
// Expand the children objects
|
|
TreeExpandElements(Children, bForceExpandDuplicates, FilteringDataType,InExpandedOperationsBuffer);
|
|
|
|
// At this point all the children objects that needed expansion are already expanded so we can proceed with
|
|
// the expansion of this element
|
|
|
|
{
|
|
// Record this node being expanded
|
|
AlreadyExpandedDuplicatedOperations.Add(OperationAddress);
|
|
|
|
// Only ask for the expansion of the element if we know it can be expanded due to it having
|
|
// children
|
|
if (!Children.IsEmpty())
|
|
{
|
|
// Expand the current element since we know it is from a operation not yet expanded
|
|
TreeView->SetItemExpansion(Item, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::TreeExpandSelected()
|
|
{
|
|
// Get the selected items and expand them excluding the duplicates
|
|
TArray<TSharedPtr<FMutableCodeTreeElement>> SelectedItems = TreeView->GetSelectedItems();
|
|
TreeExpandElements(SelectedItems,true);
|
|
}
|
|
|
|
void SMutableCodeViewer::TreeExpandUnique()
|
|
{
|
|
// Expand the tree from the root and do not expand the duplicated elements
|
|
TreeExpandElements(RootNodes);
|
|
}
|
|
|
|
void SMutableCodeViewer::TreeExpandInstance()
|
|
{
|
|
// Expand only the items that match the datatype provided
|
|
TreeExpandElements(RootNodes,false, mu::EDataType::Instance);
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Caching of operations related to constant resource
|
|
|
|
void SMutableCodeViewer::CacheRootNodeAddresses()
|
|
{
|
|
check (MutableModel);
|
|
check (RootNodeAddresses.IsEmpty())
|
|
|
|
TArray<mu::OP::ADDRESS> FoundRootNodeAddresses;
|
|
|
|
const mu::FModel::Private* ModelPrivate = MutableModel->GetPrivate();
|
|
const int32 StateCount = ModelPrivate->Program.States.Num();
|
|
for ( int32 StateIndex=0; StateIndex < StateCount; ++StateIndex )
|
|
{
|
|
const mu::FProgram::FState& State = ModelPrivate->Program.States[StateIndex];
|
|
FoundRootNodeAddresses.Add(State.Root);
|
|
}
|
|
|
|
RootNodeAddresses = MoveTemp(FoundRootNodeAddresses);
|
|
}
|
|
|
|
void SMutableCodeViewer::CacheAddressesRelatedWithConstantResource(const mu::EDataType ConstantDataType,
|
|
const int32 IndexOnConstantsArray)
|
|
{
|
|
check(MutableModel);
|
|
check(RootNodeAddresses.Num());
|
|
|
|
if (IndexOnConstantsArray < 0)
|
|
{
|
|
// Not valid index.
|
|
UE_LOG(LogTemp,Error,TEXT("The provided index [%d] is not valid."),IndexOnConstantsArray );
|
|
return;
|
|
}
|
|
|
|
// Object containing all data required by the search operation to be able to be called recursively
|
|
FElementsSearchCache SearchPayload;
|
|
// Initialize the Search Payload with the root node addresses. This way the search will use them as the root nodes where
|
|
// to start searching
|
|
SearchPayload.SetupRootBatch(RootNodeAddresses);
|
|
|
|
// Main update procedure run for the targeted state and the targeted parameter values
|
|
const mu::FProgram& Program = MutableModel->GetPrivate()->Program;
|
|
GetOperationsReferencingConstantResource(ConstantDataType,IndexOnConstantsArray,SearchPayload, Program);
|
|
|
|
// At this point we did get all the addresses of operations that do involve the usage of our resource
|
|
if (SearchPayload.FoundElements.Num() > 0)
|
|
{
|
|
// Set the type operation type to CONST_BASED_NAVIGATION (used to tell the user what is happening)
|
|
TargetedTypeSelector->SetSelectedItem(ConstantBasedNavigationEntry);
|
|
|
|
// Dump the located resources array onto the navigation array since we have content to navigate over
|
|
NavigationElements = MoveTemp(SearchPayload.FoundElements);
|
|
SortElementsByTreeIndex(NavigationElements);
|
|
}
|
|
else
|
|
{
|
|
TargetedTypeSelector->SetSelectedItem(NoneOperationEntry);
|
|
NavigationElements.Reset();
|
|
UE_LOG(LogTemp,Error,TEXT("The provided constant index does not seem to be used anywhere : Make sure the index is valid and that IsConstantResourceUsedByOperation() switch is up to date"));
|
|
}
|
|
|
|
// Reset the navigation index
|
|
NavigationIndex = -1;
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::GetOperationsReferencingConstantResource(
|
|
const mu::EDataType ConstantDataType,
|
|
const int32 IndexOnConstantsArray,
|
|
FElementsSearchCache& InSearchPayload,
|
|
const mu::FProgram& InProgram)
|
|
{
|
|
// next batch of addresses to be explored
|
|
TArray<FItemCacheKey> NextBatchAddressesData;
|
|
|
|
for (int32 ParentIndex = 0 ; ParentIndex < InSearchPayload.BatchData.Num(); ParentIndex++)
|
|
{
|
|
// Get one of the previous run "children" and treat as a parent to get it's children and process them
|
|
const mu::OP::ADDRESS& ParentAddress = InSearchPayload.BatchData[ParentIndex].Child;
|
|
|
|
// Cache if same data type and we share the same address (means this op is pointing at the provided resource)
|
|
// It will cache duplicated entries
|
|
if (IsConstantResourceUsedByOperation(IndexOnConstantsArray, ConstantDataType, ParentAddress,InProgram))
|
|
{
|
|
// Since this element is related with the provided constant resource cache it on InSearchPayload.FoundElements
|
|
InSearchPayload.AddToFoundElements(ParentAddress,ParentIndex,ItemCache);
|
|
}
|
|
|
|
// Get all NON PROCESSED the children of this operation to later be able to process them (on next recursive call)
|
|
InSearchPayload.CacheChildrenOfAddressIfNotProcessed(ParentAddress, InProgram, NextBatchAddressesData);
|
|
}
|
|
|
|
// At this point all the addresses to be computed on the next batch have already been set and will be computed on
|
|
// the next recursive call
|
|
|
|
// Explore children if found
|
|
if (NextBatchAddressesData.Num())
|
|
{
|
|
// Cache next batch data so the next invocations is able to locate the provided addresses on the itemsCache
|
|
InSearchPayload.BatchData = MoveTemp(NextBatchAddressesData);
|
|
|
|
GetOperationsReferencingConstantResource(ConstantDataType, IndexOnConstantsArray, InSearchPayload, InProgram);
|
|
}
|
|
}
|
|
|
|
bool SMutableCodeViewer::IsConstantResourceUsedByOperation(const int32 IndexOnConstantsArray,
|
|
const mu::EDataType ConstantDataType, const mu::OP::ADDRESS OperationAddress, const mu::FProgram& InProgram) const
|
|
{
|
|
// Cache the current operation type to know where to look and what to check
|
|
const mu::EOpType OperationType = InProgram.GetOpType(OperationAddress);
|
|
|
|
// Making usage of the operation data type is not valid since some operations while return one type do, in fact,
|
|
// contain data from other types (like the mesh constant for example that contains mesh, skeleton and physics asset)
|
|
// const mu::EDataType DataType = mu::GetOpDataType(OperationType);
|
|
// if (DataType != ConstantDataType)
|
|
// {
|
|
// return false;
|
|
// }
|
|
|
|
// Is this operation referencing (by an index) the index we are providing from a constants array
|
|
bool bResourceLocated = false;
|
|
|
|
// Check if the operation data type is compatible with the type of resources we are providing
|
|
switch (ConstantDataType)
|
|
{
|
|
case mu::EDataType::String:
|
|
{
|
|
// TIP: To know if they represent a constant value check the code on code runner to see if they read from the constants array
|
|
if (OperationType == mu::EOpType::ST_CONSTANT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::ResourceConstantArgs>(OperationAddress).value;
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_ADDSTRING)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(OperationAddress).name;
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_ADDMESH)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(OperationAddress).name;
|
|
}
|
|
else if(OperationType == mu::EOpType::IN_ADDIMAGE)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(OperationAddress).name;
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_ADDVECTOR)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(OperationAddress).name;
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_ADDSCALAR)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(OperationAddress).name;
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_ADDCOMPONENT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(OperationAddress).name;
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_ADDSURFACE)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddArgs>(OperationAddress).name;
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_CONDITIONAL)
|
|
{
|
|
mu::OP::ConditionalArgs Arguments = InProgram.GetOpArgs<mu::OP::ConditionalArgs>(OperationAddress);
|
|
|
|
if (IndexOnConstantsArray == Arguments.condition)
|
|
{
|
|
bResourceLocated = true;
|
|
break;
|
|
}
|
|
|
|
if (IndexOnConstantsArray == Arguments.yes)
|
|
{
|
|
bResourceLocated = true;
|
|
break;
|
|
}
|
|
|
|
if (IndexOnConstantsArray == Arguments.no)
|
|
{
|
|
bResourceLocated = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (OperationType == mu::EOpType::IN_ADDEXTENSIONDATA)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::InstanceAddExtensionDataArgs>(OperationAddress).ExtensionDataName;
|
|
}
|
|
else if (OperationType == mu::EOpType::ME_BINDSHAPE)
|
|
{
|
|
mu::OP::MeshBindShapeArgs Arguments = InProgram.GetOpArgs<mu::OP::MeshBindShapeArgs>(OperationAddress);
|
|
const uint8_t* Data = InProgram.GetOpArgsPointer(OperationAddress);
|
|
|
|
// Bones are stored after the args
|
|
Data += sizeof(Arguments);
|
|
|
|
// Iterate over the bones and check if they point to the same index on the string constants array
|
|
int32 NumBones;
|
|
FMemory::Memcpy(&NumBones, Data, sizeof(int32));
|
|
Data += sizeof(int32);
|
|
|
|
for (int32 Bone = 0; Bone < NumBones; ++Bone)
|
|
{
|
|
// Exit once we know that the data is pointing to the index provided
|
|
if (*Data == IndexOnConstantsArray)
|
|
{
|
|
bResourceLocated = true;
|
|
continue;
|
|
}
|
|
|
|
Data += sizeof(int32);
|
|
}
|
|
}
|
|
else if (OperationType == mu::EOpType::ME_ADDMETADATA)
|
|
{
|
|
const uint8* OpData = InProgram.GetOpArgsPointer(OperationAddress);
|
|
|
|
mu::OP::ADDRESS SourceAddress;
|
|
FMemory::Memcpy(&SourceAddress, OpData, sizeof(mu::OP::ADDRESS));
|
|
OpData += sizeof(mu::OP::ADDRESS);
|
|
|
|
uint16 TagCount;
|
|
FMemory::Memcpy(&TagCount, OpData, sizeof(uint16));
|
|
OpData += sizeof(uint16);
|
|
|
|
for (int32 TagIndex = 0; TagIndex < TagCount; ++TagIndex)
|
|
{
|
|
// Exit once we know that the data is pointing to the index provided
|
|
if (*OpData == IndexOnConstantsArray)
|
|
{
|
|
bResourceLocated = true;
|
|
continue;
|
|
}
|
|
|
|
OpData += sizeof(uint16);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Image:
|
|
{
|
|
if (OperationType == mu::EOpType::IM_CONSTANT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::ResourceConstantArgs>(OperationAddress).value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Mesh:
|
|
{
|
|
if (OperationType == mu::EOpType::ME_CONSTANT)
|
|
{
|
|
mu::OP::MeshConstantArgs Args = InProgram.GetOpArgs<mu::OP::MeshConstantArgs>(OperationAddress);
|
|
mu::FConstantResourceIndex ResourceIndex = *reinterpret_cast<const mu::FConstantResourceIndex*>(&Args.Value);
|
|
|
|
if (!ResourceIndex.Streamable)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == ResourceIndex.Index;
|
|
}
|
|
else
|
|
{
|
|
int32 DebuggerIndexOffset = InProgram.ConstantMeshesPermanent.Num();
|
|
for (const TPair<uint32, TSharedPtr<const mu::FMesh>>& Entry : InProgram.ConstantMeshesStreamed)
|
|
{
|
|
if (Entry.Key == ResourceIndex.Index)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == DebuggerIndexOffset;
|
|
break;
|
|
}
|
|
++DebuggerIndexOffset;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Layout:
|
|
{
|
|
if (OperationType == mu::EOpType::LA_CONSTANT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::ResourceConstantArgs>(OperationAddress).value;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Projector:
|
|
{
|
|
if (OperationType == mu::EOpType::PR_CONSTANT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::ResourceConstantArgs>(OperationAddress).value;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Matrix:
|
|
{
|
|
if (OperationType == mu::EOpType::ME_TRANSFORM)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::MeshTransformArgs>(OperationAddress).matrix;
|
|
}
|
|
else if (OperationType == mu::EOpType::ME_TRANSFORMWITHMESH)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::MeshTransformWithinMeshArgs>(OperationAddress).matrix;
|
|
}
|
|
else if (OperationType == mu::EOpType::MA_CONSTANT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::MatrixConstantArgs>(OperationAddress).value;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Shape:
|
|
{
|
|
if (OperationType == mu::EOpType::ME_CLIPMORPHPLANE)
|
|
{
|
|
const mu::OP::MeshClipMorphPlaneArgs Arguments = InProgram.GetOpArgs<mu::OP::MeshClipMorphPlaneArgs>(OperationAddress);
|
|
|
|
// Morph shape
|
|
bResourceLocated = IndexOnConstantsArray == Arguments.MorphShape;
|
|
if (bResourceLocated)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (Arguments.VertexSelectionType == EClipVertexSelectionType::Shape)
|
|
{
|
|
// Selection Shape
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::MeshClipMorphPlaneArgs>(OperationAddress).VertexSelectionShapeOrBone;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Curve:
|
|
{
|
|
if (OperationType == mu::EOpType::SC_CURVE)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::ScalarCurveArgs>(OperationAddress).curve;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Skeleton:
|
|
{
|
|
if (OperationType == mu::EOpType::ME_CONSTANT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::MeshConstantArgs>(OperationAddress).Skeleton;
|
|
}
|
|
else if (OperationType == mu::EOpType::ME_SETSKELETON)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::MeshSetSkeletonArgs>(OperationAddress).Skeleton;
|
|
}
|
|
break;
|
|
}
|
|
case mu::EDataType::PhysicsAsset:
|
|
{
|
|
if (OperationType == mu::EOpType::ME_CONSTANT)
|
|
{
|
|
bResourceLocated = IndexOnConstantsArray == InProgram.GetOpArgs<mu::OP::MeshConstantArgs>(OperationAddress).Value;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Invalid types
|
|
case mu::EDataType::None:
|
|
default:
|
|
{
|
|
checkNoEntry();
|
|
}
|
|
}
|
|
|
|
|
|
return bResourceLocated;
|
|
}
|
|
|
|
|
|
|
|
#pragma endregion
|
|
|
|
|
|
namespace
|
|
{
|
|
/** Test implementation to provide image parameters. It will generate some images of a fixed size and format. */
|
|
class TestResourceProvider : public mu::FExternalResourceProvider
|
|
{
|
|
static inline const mu::FImageDesc IMAGE_DESC =
|
|
mu::FImageDesc(mu::FImageSize(1024, 1024), mu::EImageFormat::RGBA_UByte, 1);
|
|
|
|
public:
|
|
|
|
/** */
|
|
TArray<TSoftObjectPtr<const UTexture>> ReferencedTextures;
|
|
TArray<TSoftObjectPtr<const UStreamableRenderAsset>> ReferencedMeshes;
|
|
|
|
public:
|
|
|
|
|
|
virtual TTuple<UE::Tasks::FTask, TFunction<void()>> GetImageAsync(UTexture* Texture, uint8 MipmapsToSkip, TFunction<void(TSharedPtr<mu::FImage>)>& ResultCallback) override
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(TestImageProvider_GetImage);
|
|
|
|
int32 Size = IMAGE_DESC.m_size[0];
|
|
Size = FMath::Max(4, Size / (1 << MipmapsToSkip));
|
|
|
|
TSharedPtr<mu::FImage> Image = MakeShared<mu::FImage>(
|
|
Size, Size, IMAGE_DESC.m_lods,
|
|
IMAGE_DESC.m_format,
|
|
mu::EInitializationType::NotInitialized);
|
|
|
|
// Generate an alpha-tested circle with an horizontal gradient color.
|
|
uint8* Data = Image->GetLODData(0);
|
|
int32 CircleRadius = (Size * 2) / 5;
|
|
int32 CircleRadius2 = CircleRadius * CircleRadius;
|
|
int32 Color[3] = {255, 128, 0};
|
|
|
|
int32 LogSize = FMath::CeilLogTwo(Size);
|
|
|
|
int32 HalfSize = Size >> 1;
|
|
for (int32 RadY = -HalfSize; RadY < HalfSize; ++RadY)
|
|
{
|
|
int32 RadY2 = RadY * RadY;
|
|
for (int32 x = 0; x < Size; ++x)
|
|
{
|
|
int32 RadX = (x - HalfSize);
|
|
int32 R2 = RadX * RadX + RadY2;
|
|
int32 Opacity = FMath::Clamp(((CircleRadius2 - R2) * 512) / CircleRadius2 - 64, 0, 255);
|
|
Data[0] = uint8((Color[0] * x) >> LogSize);
|
|
Data[1] = uint8((Color[1] * x) >> LogSize);
|
|
Data[2] = uint8((Color[2] * x) >> LogSize);
|
|
Data[3] = uint8(Opacity);
|
|
Data += 4;
|
|
}
|
|
}
|
|
|
|
ResultCallback(Image);
|
|
|
|
return MakeTuple(UE::Tasks::MakeCompletedTask<void>(), []() -> void {});
|
|
}
|
|
|
|
virtual mu::FExtendedImageDesc GetImageDesc(UTexture* Texture) override
|
|
{
|
|
return mu::FExtendedImageDesc{ IMAGE_DESC };
|
|
}
|
|
|
|
|
|
virtual TTuple<UE::Tasks::FTask, TFunction<void()>> GetReferencedImageAsync(const void* ModelPtr, int32 Id, uint8 MipmapsToSkip, TFunction<void(TSharedPtr<mu::FImage>)>& ResultCallback)
|
|
{
|
|
check(ReferencedTextures.IsValidIndex(Id));
|
|
|
|
const UTexture* Texture = ReferencedTextures[Id].Get();
|
|
if (!Texture)
|
|
{
|
|
UE_LOG(LogMutable, Warning, TEXT("Failed to load Referenced Image [%i]. Nullptr."), Id);
|
|
return MakeTuple(UE::Tasks::MakeCompletedTask<void>(), []() -> void {});
|
|
}
|
|
|
|
const UTexture2D* Texture2D = Cast<UTexture2D>(Texture);
|
|
if (!Texture2D)
|
|
{
|
|
UE_LOG(LogMutable, Warning, TEXT("Invalid Referenced Image [%i, %s]. Is not a UTexture2D."), Id, *Texture->GetName());
|
|
return MakeTuple(UE::Tasks::MakeCompletedTask<void>(), []() -> void {});
|
|
}
|
|
|
|
// In the editor the src data can be directly accessed
|
|
int32 MipIndex = (MipmapsToSkip < Texture2D->GetPlatformData()->Mips.Num()) ? MipmapsToSkip : Texture2D->GetPlatformData()->Mips.Num() - 1;
|
|
check(MipIndex >= 0);
|
|
|
|
TSharedPtr<mu::FImage> ResultImage = MakeShared<mu::FImage>();
|
|
|
|
FMutableSourceTextureData Tex(*Texture2D);
|
|
EUnrealToMutableConversionError Error = ConvertTextureUnrealSourceToMutable(ResultImage.Get(), Tex, MipmapsToSkip);
|
|
|
|
if (Error != EUnrealToMutableConversionError::Success)
|
|
{
|
|
// This could happen in the editor, because some source textures may have changed while there was a background compilation.
|
|
// We just show a warning and move on. This cannot happen during cooks, so it is fine.
|
|
UE_LOG(LogMutable, Warning, TEXT("Failed to load some source texture data for Referenced Image [%i, %s]. Some textures may be corrupted."), Id, *Texture->GetName());
|
|
}
|
|
|
|
ResultCallback(ResultImage);
|
|
|
|
return MakeTuple(UE::Tasks::MakeCompletedTask<void>(), []() -> void {});
|
|
}
|
|
|
|
|
|
virtual TTuple<UE::Tasks::FTask, TFunction<void()>> GetMeshAsync(USkeletalMesh* SkeletalMesh, int32 LODIndex, int32 SectionIndex, TFunction<void(TSharedPtr<mu::FMesh>)>& ResultCallback) override
|
|
{
|
|
// Thread: worker
|
|
MUTABLE_CPUPROFILER_SCOPE(FUnrealMutableImageProvider::GetMeshAsync);
|
|
|
|
check(SkeletalMesh);
|
|
|
|
TSharedPtr<mu::FMesh> Result = MakeShared<mu::FMesh>();
|
|
|
|
UE::Tasks::FTask ConversionTask = UnrealConversionUtils::ConvertSkeletalMeshFromRuntimeData(SkeletalMesh, LODIndex, SectionIndex, nullptr, Result.Get());
|
|
|
|
return MakeTuple(
|
|
UE::Tasks::Launch( TEXT("FinalizeGetMesh"),
|
|
[Result, ResultCallback]()
|
|
{
|
|
ResultCallback(Result);
|
|
},
|
|
ConversionTask)
|
|
,
|
|
[]() -> void {}
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
|
{
|
|
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
|
|
|
// After the tick we do know the tree has been refreshed, so all expansion and contraction operations have been
|
|
// completed and the new data has been loaded onto our listening arrays. Then its safe to expect the widgets to be
|
|
// there to be selected or inspected.
|
|
if (!TreeView->IsPendingRefresh())
|
|
{
|
|
/** If we have expanded the tree elements in order to reach one of them then continue the operation */
|
|
if (bWasUniqueExpansionInvokedForNavigation || bWasScrollToTargetRequested)
|
|
{
|
|
FocusViewOnNavigationTarget(ToFocusElement);
|
|
}
|
|
}
|
|
|
|
|
|
if (!bIsPreviewPendingUpdate)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bIsPreviewPendingUpdate = false;
|
|
|
|
const mu::EOpType OperationType = MutableModel->GetPrivate()->Program.GetOpType(SelectedOperationAddress);
|
|
const mu::EDataType OperationDataType = mu::GetOpDataType(OperationType);
|
|
|
|
mu::FSettings Settings;
|
|
const TSharedPtr<mu::FSystem> System = MakeShared<mu::FSystem>(Settings);
|
|
|
|
TSharedPtr<TestResourceProvider> ExternalResourceProvider = MakeShared<TestResourceProvider>();
|
|
ExternalResourceProvider->ReferencedTextures = ReferencedTextures;
|
|
ExternalResourceProvider->ReferencedMeshes = ReferencedMeshes;
|
|
System->SetExternalResourceProvider(ExternalResourceProvider);
|
|
|
|
System->GetPrivate()->BeginBuild(MutableModel);
|
|
|
|
switch (OperationDataType)
|
|
{
|
|
case mu::EDataType::Layout:
|
|
{
|
|
check(PreviewLayoutViewer);
|
|
TSharedPtr<const mu::FLayout> MutableLayout = System->GetPrivate()->BuildLayout(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
PreviewLayoutViewer->SetLayout(MutableLayout);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Image:
|
|
{
|
|
check(PreviewImageViewer);
|
|
TSharedPtr<const mu::FImage> MutableImage = System->GetPrivate()->BuildImage(MutableModel, PreviewParameters.Get(), SelectedOperationAddress, MipsToSkip, 0);
|
|
PreviewImageViewer->SetImage(MutableImage, 0);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Mesh:
|
|
{
|
|
check(PreviewMeshViewer);
|
|
TSharedPtr<const mu::FMesh> MutableMesh = System->GetPrivate()->BuildMesh(MutableModel, PreviewParameters.Get(), SelectedOperationAddress, mu::EMeshContentFlags::AllFlags);
|
|
PreviewMeshViewer->SetMesh(MutableMesh);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Instance:
|
|
{
|
|
check(PreviewInstanceViewer);
|
|
TSharedPtr<const mu::FInstance> MutableInstance = System->GetPrivate()->BuildInstance(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
PreviewInstanceViewer->SetInstance(MutableInstance, MutableModel, PreviewParameters, *System);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Bool:
|
|
{
|
|
check(PreviewBoolViewer);
|
|
const bool MutableBool = System->GetPrivate()->BuildBool(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
PreviewBoolViewer->SetBool(MutableBool);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Int:
|
|
{
|
|
check(PreviewIntViewer);
|
|
const int32 MutableInt = System->GetPrivate()->BuildInt(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
PreviewIntViewer->SetInt(MutableInt);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Scalar:
|
|
{
|
|
check(PreviewScalarViewer);
|
|
const float MutableScalar = System->GetPrivate()->BuildScalar(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
PreviewScalarViewer->SetScalar(MutableScalar);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::String:
|
|
{
|
|
check(PreviewStringViewer);
|
|
TSharedPtr<const mu::String> MutableString = System->GetPrivate()->BuildString(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
const FText MutableText = FText::FromString(FString(MutableString->GetValue()));
|
|
PreviewStringViewer->SetString(MutableText);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Color:
|
|
{
|
|
check(PreviewColorViewer);
|
|
FVector4f Color = System->GetPrivate()->BuildColour(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
PreviewColorViewer->SetColor(Color);
|
|
break;
|
|
}
|
|
|
|
case mu::EDataType::Projector:
|
|
{
|
|
check (PreviewProjectorViewer);
|
|
mu::FProjector Projector = System->GetPrivate()->BuildProjector(MutableModel, PreviewParameters.Get(), SelectedOperationAddress);
|
|
PreviewProjectorViewer->SetProjector(Projector);
|
|
}
|
|
|
|
default:
|
|
#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
|
|
UE_LOG(LogMutable, Log, TEXT("There is no previewer for the selected type of Mutable object"))
|
|
#endif
|
|
// There is no viewer for this type.
|
|
break;
|
|
}
|
|
|
|
System->GetPrivate()->EndBuild();
|
|
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::OnPreviewParameterValueChanged(int32 ParamIndex)
|
|
{
|
|
// This is deferred to the tick to avoid multiple updates per frame.
|
|
bIsPreviewPendingUpdate = true;
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PrepareStringViewer()
|
|
{
|
|
if (!PreviewStringViewer)
|
|
{
|
|
PreviewStringViewer = SNew(SMutableStringViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewStringViewer.ToSharedRef());
|
|
}
|
|
|
|
void SMutableCodeViewer::PrepareImageViewer()
|
|
{
|
|
if (!PreviewImageViewer)
|
|
{
|
|
PreviewImageViewer = SNew(SMutableImageViewer)
|
|
.GridSize(FIntPoint(8, 8));
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewImageViewer.ToSharedRef());
|
|
}
|
|
|
|
void SMutableCodeViewer::PrepareMeshViewer()
|
|
{
|
|
if (!PreviewMeshViewer)
|
|
{
|
|
PreviewMeshViewer = SNew(SMutableMeshViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewMeshViewer.ToSharedRef());
|
|
}
|
|
|
|
void SMutableCodeViewer::PrepareInstanceViewer()
|
|
{
|
|
if (!PreviewInstanceViewer)
|
|
{
|
|
PreviewInstanceViewer = SNew(SMutableInstanceViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewInstanceViewer.ToSharedRef());
|
|
}
|
|
|
|
void SMutableCodeViewer::PrepareLayoutViewer()
|
|
{
|
|
if (!PreviewLayoutViewer)
|
|
{
|
|
PreviewLayoutViewer = SNew(SMutableLayoutViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewLayoutViewer.ToSharedRef());
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PrepareProjectorViewer()
|
|
{
|
|
if (!PreviewProjectorViewer)
|
|
{
|
|
PreviewProjectorViewer = SNew(SMutableProjectorViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewProjectorViewer.ToSharedRef());
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PreviewMutableString(const FString& InString)
|
|
{
|
|
// Prepare the previewer object to receive data
|
|
PrepareStringViewer();
|
|
|
|
// Provide the desired data to the previewer object
|
|
const FText TextToShow = FText::FromString(InString);
|
|
PreviewStringViewer->SetString(TextToShow);
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PreviewMutableImage(TSharedPtr<const mu::FImage> InImagePtr)
|
|
{
|
|
PrepareImageViewer();
|
|
PreviewImageViewer->SetImage(InImagePtr,0);
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PreviewMutableMesh(TSharedPtr<const mu::FMesh> InMeshPtr)
|
|
{
|
|
PrepareMeshViewer();
|
|
PreviewMeshViewer->SetMesh(InMeshPtr);
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PreviewMutableLayout(TSharedPtr<const mu::FLayout> Layout)
|
|
{
|
|
PrepareLayoutViewer();
|
|
PreviewLayoutViewer->SetLayout(Layout);
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PreviewMutableProjector(const mu::FProjector* Projector)
|
|
{
|
|
if (!Projector)
|
|
{
|
|
UE_LOG(LogTemp,Error,TEXT("Unable to preview data on null Projector pointer."))
|
|
return;
|
|
}
|
|
|
|
PrepareProjectorViewer();
|
|
PreviewProjectorViewer->SetProjector(*Projector);
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PreviewMutableSkeleton(TSharedPtr<const mu::FSkeleton> Skeleton)
|
|
{
|
|
if (!PreviewSkeletonViewer)
|
|
{
|
|
PreviewSkeletonViewer = SNew(SMutableSkeletonViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewSkeletonViewer.ToSharedRef());
|
|
|
|
PreviewSkeletonViewer->SetSkeleton(Skeleton);
|
|
}
|
|
|
|
|
|
void SMutableCodeViewer::PreviewMutableCurve(const FRichCurve& Curve)
|
|
{
|
|
if (!PreviewCurveViewer)
|
|
{
|
|
PreviewCurveViewer = SNew(SMutableCurveViewer);
|
|
}
|
|
|
|
PreviewBorder->SetContent(PreviewCurveViewer.ToSharedRef());
|
|
|
|
PreviewCurveViewer->SetCurve(Curve);
|
|
}
|
|
|
|
// TODO: Implement physics viewer
|
|
void SMutableCodeViewer::PreviewMutablePhysics(TSharedPtr<const mu::FPhysicsBody> Physics)
|
|
{
|
|
UE_LOG(LogMutable, Warning, TEXT("Previewer for Mutable Physics not yet implemented"))
|
|
}
|
|
|
|
// TODO: Implement matrix viewer
|
|
void SMutableCodeViewer::PreviewMutableMatrix(const FMatrix44f& Mat)
|
|
{
|
|
UE_LOG(LogMutable, Warning, TEXT("Previewer for Mutable Matrices not yet implemented"))
|
|
}
|
|
|
|
// TODO: Implement shape viewer
|
|
void SMutableCodeViewer::PreviewMutableShape(const mu::FShape* Shape)
|
|
{
|
|
UE_LOG(LogMutable, Warning, TEXT("Previewer for Mutable Shapes not yet implemented"))
|
|
}
|
|
|
|
|
|
FMutableCodeTreeElement::FMutableCodeTreeElement(int32 InIndexOnTree, const int32& InMutableStateIndex, const TSharedPtr<mu::FModel, ESPMode::ThreadSafe>& InModel, mu::OP::ADDRESS InOperation, const FString& InCaption, const FSlateColor InLabelColor, const TSharedPtr<FMutableCodeTreeElement>* InDuplicatedOf)
|
|
{
|
|
MutableModel = InModel;
|
|
MutableOperation = InOperation;
|
|
Caption = InCaption;
|
|
LabelColor = InLabelColor;
|
|
|
|
// Use a special color to denote "none" entries instead of the provided one
|
|
if (MutableOperation == 0)
|
|
{
|
|
LabelColor = FColor(FColorList::DimGrey);
|
|
}
|
|
|
|
IndexOnTree = InIndexOnTree;
|
|
if (InDuplicatedOf)
|
|
{
|
|
DuplicatedOf = *InDuplicatedOf;
|
|
}
|
|
|
|
// Generate the label to be used to display this operation in the operation tree
|
|
GenerateLabelText();
|
|
|
|
// Process the data that can be extracted from the current state
|
|
SetElementCurrentState(InMutableStateIndex);
|
|
}
|
|
|
|
|
|
void FMutableCodeTreeElement::SetElementCurrentState(const int32& InStateIndex)
|
|
{
|
|
// Skip operation if state is the same
|
|
if (InStateIndex == CurrentMutableStateIndex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for an out of bounds value
|
|
check(MutableModel);
|
|
mu::FProgram& MutableProgram = MutableModel->GetPrivate()->Program;
|
|
check(InStateIndex >= 0 && InStateIndex < MutableProgram.States.Num());
|
|
|
|
CurrentMutableStateIndex = InStateIndex;
|
|
const mu::FProgram::FState& CurrentState = MutableProgram.States[CurrentMutableStateIndex];
|
|
|
|
// Check if it is a dynamic resource
|
|
for (auto& DynamicResource : CurrentState.m_dynamicResources)
|
|
{
|
|
// If the operation gets located then mark it as dynamic resource
|
|
if (DynamicResource.Key == MutableOperation)
|
|
{
|
|
bIsDynamicResource = true;
|
|
break;
|
|
}
|
|
}
|
|
// Early exit: A dynamic resource can not be at the same time a state constant
|
|
if (bIsDynamicResource)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if it is a state constant
|
|
bIsStateConstant = CurrentState.m_updateCache.Contains(MutableOperation);
|
|
}
|
|
|
|
|
|
void FMutableCodeTreeElement::GenerateLabelText()
|
|
{
|
|
const mu::FProgram& Program = MutableModel->GetPrivate()->Program;
|
|
const mu::EOpType OperationType = Program.GetOpType(MutableOperation);
|
|
FString OpName = mu::s_opNames[static_cast<int32>(OperationType)];
|
|
OpName.TrimEndInline();
|
|
|
|
// See if the operation type accepts additional information in the label
|
|
switch (OperationType)
|
|
{
|
|
case mu::EOpType::BO_PARAMETER:
|
|
case mu::EOpType::NU_PARAMETER:
|
|
case mu::EOpType::SC_PARAMETER:
|
|
case mu::EOpType::CO_PARAMETER:
|
|
case mu::EOpType::PR_PARAMETER:
|
|
case mu::EOpType::IM_PARAMETER:
|
|
case mu::EOpType::ST_PARAMETER:
|
|
{
|
|
mu::OP::ParameterArgs Args = Program.GetOpArgs<mu::OP::ParameterArgs>(MutableOperation);
|
|
OpName += TEXT(" ");
|
|
OpName += Program.Parameters[int32(Args.variable)].Name;
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::ME_PARAMETER:
|
|
{
|
|
mu::OP::MeshParameterArgs Args = Program.GetOpArgs<mu::OP::MeshParameterArgs>(MutableOperation);
|
|
OpName += FString::Printf( TEXT(" LOD %d Section %d of "), Args.LOD, Args.Section );
|
|
OpName += Program.Parameters[int32(Args.variable)].Name;
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_SWIZZLE:
|
|
{
|
|
mu::OP::ImageSwizzleArgs Args = Program.GetOpArgs<mu::OP::ImageSwizzleArgs>(MutableOperation);
|
|
OpName += TEXT(" ");
|
|
OpName += StringCast<TCHAR>(mu::TypeInfo::s_imageFormatName[int32(Args.format)]).Get();
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_PIXELFORMAT:
|
|
{
|
|
mu::OP::ImagePixelFormatArgs Args = Program.GetOpArgs<mu::OP::ImagePixelFormatArgs>(MutableOperation);
|
|
OpName += TEXT(" ");
|
|
OpName += StringCast<TCHAR>(mu::TypeInfo::s_imageFormatName[int32(Args.format)]).Get();
|
|
OpName += TEXT(" or ");
|
|
OpName += StringCast<TCHAR>(mu::TypeInfo::s_imageFormatName[int32(Args.formatIfAlpha)]).Get();
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_MIPMAP:
|
|
{
|
|
mu::OP::ImageMipmapArgs Args = Program.GetOpArgs<mu::OP::ImageMipmapArgs>(MutableOperation);
|
|
OpName += FString::Printf(TEXT(" levels: %d-%d tail: %d"), Args.levels, Args.blockLevels, int32(Args.onlyTail));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_RESIZE:
|
|
{
|
|
mu::OP::ImageResizeArgs Args = Program.GetOpArgs<mu::OP::ImageResizeArgs>(MutableOperation);
|
|
OpName += FString::Printf(TEXT(" %d x %d"), int32(Args.Size[0]), int32(Args.Size[1]));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_RESIZEREL:
|
|
{
|
|
mu::OP::ImageResizeRelArgs Args = Program.GetOpArgs<mu::OP::ImageResizeRelArgs>(MutableOperation);
|
|
OpName += FString::Printf(TEXT(" %.3f x %.3f"), Args.Factor[0], Args.Factor[1]);
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_MULTILAYER:
|
|
{
|
|
mu::OP::ImageMultiLayerArgs Args = Program.GetOpArgs<mu::OP::ImageMultiLayerArgs>(MutableOperation);
|
|
OpName += TEXT(" rgb: ");
|
|
OpName += mu::TypeInfo::s_blendModeName[int32(Args.blendType)];
|
|
OpName += TEXT(", a: ");
|
|
OpName += mu::TypeInfo::s_blendModeName[int32(Args.blendTypeAlpha)];
|
|
OpName += FString::Printf(TEXT(" a from %d "), Args.BlendAlphaSourceChannel);
|
|
OpName += FString::Printf(TEXT(" range-id: %d"), Args.rangeId);
|
|
OpName += FString::Printf(TEXT(" mask-from-alpha: %d"), int32(Args.bUseMaskFromBlended));
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_LAYER:
|
|
{
|
|
mu::OP::ImageLayerArgs Args = Program.GetOpArgs<mu::OP::ImageLayerArgs>(MutableOperation);
|
|
OpName += TEXT(" rgb: ");
|
|
OpName += mu::TypeInfo::s_blendModeName[int32(Args.blendType)];
|
|
OpName += TEXT(", a: ");
|
|
OpName += mu::TypeInfo::s_blendModeName[int32(Args.blendTypeAlpha)];
|
|
OpName += FString::Printf(TEXT(" a from %d "), Args.BlendAlphaSourceChannel);
|
|
OpName += FString::Printf(TEXT(" flags %d"), Args.flags);
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
mu::OP::ImageLayerColourArgs Args = Program.GetOpArgs<mu::OP::ImageLayerColourArgs>(MutableOperation);
|
|
OpName += TEXT(" rgb: ");
|
|
OpName += mu::TypeInfo::s_blendModeName[int32(Args.blendType)];
|
|
OpName += TEXT(" a: ");
|
|
OpName += mu::TypeInfo::s_blendModeName[int32(Args.blendTypeAlpha)];
|
|
OpName += TEXT(" a from ");
|
|
OpName += FString::Printf(TEXT(" a from %d "), Args.BlendAlphaSourceChannel);
|
|
OpName += FString::Printf(TEXT(" flags %d"), Args.flags);
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IM_PLAINCOLOUR:
|
|
{
|
|
mu::OP::ImagePlainColorArgs Args = Program.GetOpArgs<mu::OP::ImagePlainColorArgs>(MutableOperation);
|
|
OpName += TEXT(" format: ");
|
|
OpName += StringCast<TCHAR>(mu::TypeInfo::s_imageFormatName[int32(Args.Format)]).Get();
|
|
OpName += FString::Printf(TEXT(" size %d x %d"), Args.Size[0], Args.Size[1]);
|
|
OpName += FString::Printf(TEXT(" mips %d"), Args.LODs);
|
|
break;
|
|
}
|
|
|
|
case mu::EOpType::IN_ADDIMAGE:
|
|
{
|
|
mu::OP::InstanceAddArgs Args = Program.GetOpArgs<mu::OP::InstanceAddArgs>(MutableOperation);
|
|
if (Program.ConstantStrings.IsValidIndex(Args.name))
|
|
{
|
|
OpName += TEXT(" name: ");
|
|
OpName += Program.ConstantStrings[Args.name];
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
OpName = OpName.TrimStartAndEnd();
|
|
|
|
// Prepare the text shown on the UI side of the operation tree
|
|
if (!Caption.IsEmpty())
|
|
{
|
|
Caption = Caption.TrimStartAndEnd();
|
|
MainLabel = FString::Printf(TEXT("%d : [ %s ] %s"), int32(MutableOperation), *Caption, *OpName);
|
|
}
|
|
else
|
|
{
|
|
MainLabel = FString::Printf(TEXT("%d : %s"), int32(MutableOperation), *OpName);
|
|
}
|
|
|
|
|
|
// DEBUG :
|
|
// FString IndexOnTree = FString::FromInt(IndexOnTree);
|
|
// IndexOnTree.Append(TEXT("- "));
|
|
// MainLabel.InsertAt(0,IndexOnTree);
|
|
|
|
// DEBUG :
|
|
// FString RowStateIndex = FString::FromInt(GetStateIndex());
|
|
// RowStateIndex.Append(TEXT(" st "));
|
|
// MainLabel.InsertAt(0,RowStateIndex);
|
|
|
|
// Ignore the special case of operations of type "None"
|
|
if (MutableOperation > 0 && DuplicatedOf)
|
|
{
|
|
MainLabel.Append(TEXT(" (duplicated)"));
|
|
}
|
|
}
|
|
|
|
|
|
int32 FMutableCodeTreeElement::GetStateIndex() const
|
|
{
|
|
return CurrentMutableStateIndex;
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|