// Copyright Epic Games, Inc. All Rights Reserved. #include "XRDeviceVisualizationDetails.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Features/IModularFeatures.h" // for GetModularFeatureImplementations() #include "IXRSystemAssets.h" #include "MotionControllerComponent.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/SListView.h" #include "Widgets/Views/STableRow.h" #include "XRDeviceVisualizationComponent.h" class FProperty; #define LOCTEXT_NAMESPACE "XRDeviceVisualizationDetails" TMap< FName, TSharedPtr > FXRDeviceVisualizationDetails::CustomSourceNames; TSharedRef FXRDeviceVisualizationDetails::MakeInstance() { return MakeShareable(new FXRDeviceVisualizationDetails); } void FXRDeviceVisualizationDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { DetailLayout.GetObjectsBeingCustomized(SelectedObjects); IDetailCategoryBuilder& VisualizationDetails = DetailLayout.EditCategory("Visualization"); TSharedRef ModelSrcProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UXRDeviceVisualizationComponent, DisplayModelSource)); if (ensure(ModelSrcProperty->IsValidHandle())) { XRSourceProperty = ModelSrcProperty; } TSharedRef CustomMeshProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UXRDeviceVisualizationComponent, CustomDisplayMesh)); TSharedRef CustomMaterialsProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UXRDeviceVisualizationComponent, DisplayMeshMaterialOverrides)); TArray> VisualizationProperties; VisualizationDetails.GetDefaultProperties(VisualizationProperties); for (TSharedRef VisProp : VisualizationProperties) { IDetailPropertyRow& PropertyRow = VisualizationDetails.AddProperty(VisProp); FProperty* RawProperty = VisProp->GetProperty(); if (RawProperty == ModelSrcProperty->GetProperty()) { CustomizeModelSourceRow(ModelSrcProperty, PropertyRow); } else if (RawProperty == CustomMeshProperty->GetProperty()) { CustomizeCustomMeshRow(PropertyRow); } } } void FXRDeviceVisualizationDetails::RefreshXRSourceList() { TSharedPtr DefaultOption; TSharedPtr CustomOption; if (XRSourceNames.Num() > 0) { DefaultOption = XRSourceNames[0]; CustomOption = XRSourceNames.Last(); } TArray XRAssetSystems = IModularFeatures::Get().GetModularFeatureImplementations(IXRSystemAssets::GetModularFeatureName()); XRSourceNames.Empty(XRAssetSystems.Num() + CustomSourceNames.Num() + 2); if (!DefaultOption.IsValid()) { DefaultOption = MakeShareable(new FName()); } XRSourceNames.Add(DefaultOption); TArray ListedNames; TArray StaleCustomNames; for (auto& CustomNameIt : CustomSourceNames) { UObject* CustomizedController = FindObject(/*Outer =*/nullptr, *CustomNameIt.Key.ToString()); if (CustomizedController) { if (!CustomNameIt.Value->IsNone() && *CustomNameIt.Value != UXRDeviceVisualizationComponent::CustomModelSourceId) { ListedNames.AddUnique(*CustomNameIt.Value); XRSourceNames.AddUnique(CustomNameIt.Value); } } else { StaleCustomNames.Add(CustomNameIt.Key); } } for (FName& DeadEntry : StaleCustomNames) { CustomSourceNames.Remove(DeadEntry); } for (IXRSystemAssets* AssetSys : XRAssetSystems) { FName SystemName = AssetSys->GetSystemName(); if (!ListedNames.Contains(SystemName)) { ListedNames.Add(SystemName); // @TODO: shouldn't be continuously creating these TSharedPtr SystemNamePtr = MakeShareable(new FName(SystemName)); XRSourceNames.AddUnique(SystemNamePtr); } } if (!CustomOption.IsValid()) { CustomOption = MakeShareable(new FName(UXRDeviceVisualizationComponent::CustomModelSourceId)); } XRSourceNames.AddUnique(CustomOption); } void FXRDeviceVisualizationDetails::SetSourcePropertyValue(const FName NewSystemName) { if (XRSourceProperty.IsValid()) { XRSourceProperty->SetValue(NewSystemName); } } void FXRDeviceVisualizationDetails::UpdateSourceSelection(TSharedPtr NewSelection) { for (TWeakObjectPtr SelectedObj : SelectedObjects) { if (SelectedObj.IsValid()) { CustomSourceNames.Add(*SelectedObj->GetPathName(), NewSelection); } } SetSourcePropertyValue(*NewSelection); } void FXRDeviceVisualizationDetails::CustomizeModelSourceRow(TSharedRef& Property, IDetailPropertyRow& PropertyRow) { const FText ToolTip = Property->GetToolTipText(); FResetToDefaultOverride ResetOverride = FResetToDefaultOverride::Create( FIsResetToDefaultVisible::CreateSP(this, &FXRDeviceVisualizationDetails::IsSourceValueModified), FResetToDefaultHandler::CreateSP(this, &FXRDeviceVisualizationDetails::OnResetSourceValue) ); PropertyRow.OverrideResetToDefault(ResetOverride); PropertyRow.CustomWidget() .NameContent() [ Property->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SComboButton) .ContentPadding(FMargin(0,0,5,0)) .OnComboBoxOpened(this, &FXRDeviceVisualizationDetails::OnSourceMenuOpened) .ButtonContent() [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("NoBorder")) .Padding(FMargin(0, 0, 5, 0)) [ SNew(SEditableTextBox) .Text(this, &FXRDeviceVisualizationDetails::OnGetSelectedSourceText) .OnTextCommitted(this, &FXRDeviceVisualizationDetails::OnSourceNameCommited) .ToolTipText(ToolTip) .SelectAllTextWhenFocused(true) .RevertTextOnEscape(true) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] .MenuContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ SNew(SListView< TSharedPtr >) .ListItemsSource(&XRSourceNames) .OnGenerateRow(this, &FXRDeviceVisualizationDetails::MakeSourceSelectionWidget) .OnSelectionChanged(this, &FXRDeviceVisualizationDetails::OnSourceSelectionChanged) ] ] ]; } void FXRDeviceVisualizationDetails::OnResetSourceValue(TSharedPtr /*PropertyHandle*/) { TSharedPtr DefaultOption; if (XRSourceNames.Num() > 0) { DefaultOption = XRSourceNames[0]; } else { DefaultOption = MakeShareable(new FName()); XRSourceNames.Add(DefaultOption); } UpdateSourceSelection(DefaultOption); } bool FXRDeviceVisualizationDetails::IsSourceValueModified(TSharedPtr PropertyHandle) { FName CurrentValue; PropertyHandle->GetValue(CurrentValue); return !CurrentValue.IsNone(); } void FXRDeviceVisualizationDetails::OnSourceMenuOpened() { RefreshXRSourceList(); } FText FXRDeviceVisualizationDetails::OnGetSelectedSourceText() const { FText DisplayText = LOCTEXT("DefaultModelSrc", "Default"); if (XRSourceProperty.IsValid()) { FName PropertyValue; XRSourceProperty->GetValue(PropertyValue); if (PropertyValue == UXRDeviceVisualizationComponent::CustomModelSourceId) { DisplayText = LOCTEXT("CustomModelSrc", "Custom..."); } else if (!PropertyValue.IsNone()) { DisplayText = FText::FromName(PropertyValue); } } return DisplayText; } void FXRDeviceVisualizationDetails::OnSourceNameCommited(const FText& NewText, ETextCommit::Type InTextCommit) { if (InTextCommit == ETextCommit::OnEnter || InTextCommit == ETextCommit::OnUserMovedFocus) { const FName NewName = *NewText.ToString(); RefreshXRSourceList(); if (NewText.IsEmpty()) { UpdateSourceSelection(XRSourceNames[0]); return; } else if (NewName == UXRDeviceVisualizationComponent::CustomModelSourceId) { UpdateSourceSelection(XRSourceNames.Last()); return; } TSharedPtr SelectedName; for (const TSharedPtr& SystemName : XRSourceNames) { if (*SystemName == NewName) { SelectedName = SystemName; } } if (!SelectedName.IsValid()) { SelectedName = MakeShareable(new FName(NewName)); } UpdateSourceSelection(SelectedName); } } TSharedRef FXRDeviceVisualizationDetails::MakeSourceSelectionWidget(TSharedPtr Item, const TSharedRef& OwnerTable) { FText DisplayText; if (Item.IsValid() && !Item->IsNone()) { DisplayText = FText::FromName(*Item); } return SNew(STableRow>, OwnerTable) [ SNew(STextBlock) .Text(DisplayText) ]; } void FXRDeviceVisualizationDetails::OnSourceSelectionChanged(TSharedPtr NewSelection, ESelectInfo::Type /*SelectInfo*/) { UpdateSourceSelection(NewSelection); } void FXRDeviceVisualizationDetails::CustomizeCustomMeshRow(IDetailPropertyRow& PropertyRow) { if (!UseCustomMeshAttr.IsBound()) { UseCustomMeshAttr.Bind(this, &FXRDeviceVisualizationDetails::IsCustomMeshPropertyEnabled); } PropertyRow.EditCondition(UseCustomMeshAttr, nullptr); } bool FXRDeviceVisualizationDetails::IsCustomMeshPropertyEnabled() const { FName SourceSetting; if (XRSourceProperty.IsValid()) { XRSourceProperty->GetValue(SourceSetting); } return SourceSetting == UXRDeviceVisualizationComponent::CustomModelSourceId; } #undef LOCTEXT_NAMESPACE