// Copyright (c) Meta Platforms, Inc. and affiliates. #include "OculusXREditorModule.h" #include "AssetToolsModule.h" #include "OculusXRToolStyle.h" #include "OculusXRToolCommands.h" #include "OculusXRPlatformToolWidget.h" #include "OculusXRAssetDirectory.h" #include "OculusXRHMDRuntimeSettings.h" #include "IOculusXRProjectSetupModule.h" #include "OculusXRHMDTypes.h" #include "LevelEditor.h" #include "Modules/ModuleManager.h" #include "Widgets/Docking/SDockTab.h" #include "Widgets/Input/SButton.h" #include "PropertyEditorModule.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "DetailWidgetRow.h" #include "GeneralProjectSettings.h" #include "IAssetTools.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "ISettingsModule.h" #include "OculusXRPassthroughColorLutAsset.h" #include "OculusXRHMDModule.h" #include "OculusXRPrivacyNotification.h" #include "OculusXRSettingsToggle.h" #include "OculusXRTelemetryPrivacySettings.h" #include "OculusXRTelemetry.h" #include "OculusXRTelemetryEditorEvents.h" #include "SExternalImageReference.h" #include "AndroidRuntimeSettings.h" #include "SourceControlHelpers.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Editor/EditorPerformanceSettings.h" #define LOCTEXT_NAMESPACE "OculusXREditor" const FName FOculusXREditorModule::OculusPlatToolTabName = FName("OculusXRPlaformTool"); void FOculusXREditorModule::PostLoadCallback() { FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); } void FOculusXREditorModule::StartupModule() { bModuleValid = true; RegisterSettings(); FOculusAssetDirectory::LoadForCook(); if (!IsRunningCommandlet()) { FOculusToolStyle::Initialize(); FOculusToolStyle::ReloadTextures(); FOculusToolCommands::Register(); PluginCommands = MakeShareable(new FUICommandList); PluginCommands->MapAction( FOculusToolCommands::Get().OpenProjectSetupTool, FExecuteAction::CreateRaw(this, &FOculusXREditorModule::PluginOpenSetupToolWindow), FCanExecuteAction()); PluginCommands->MapAction( FOculusToolCommands::Get().OpenPlatWindow, FExecuteAction::CreateRaw(this, &FOculusXREditorModule::PluginOpenPlatWindow), FCanExecuteAction()); PluginCommands->MapAction( FOculusToolCommands::Get().ToggleDeploySo, FExecuteAction::CreateLambda([=]() { UOculusXRHMDRuntimeSettings* settings = GetMutableDefault(); settings->bDeploySoToDevice = !settings->bDeploySoToDevice; settings->Modify(true); settings->UpdateSinglePropertyInConfigFile(settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bDeploySoToDevice)), settings->GetDefaultConfigFilename()); }), FCanExecuteAction(), FIsActionChecked::CreateLambda([=]() { return GetMutableDefault()->bDeploySoToDevice; })); PluginCommands->MapAction( FOculusToolCommands::Get().ToggleMetaXRSim, FExecuteAction::CreateRaw(this, &FOculusXREditorModule::ToggleOpenXRRuntime), FCanExecuteAction(), FIsActionChecked::CreateLambda([=]() { return FOculusXRHMDModule::IsSimulatorActivated(); })); PluginCommands->MapAction( FOculusToolCommands::Get().LaunchGameRoom, FExecuteAction::CreateRaw(this, &FOculusXREditorModule::LaunchSESGameRoom), FCanExecuteAction()); PluginCommands->MapAction( FOculusToolCommands::Get().LaunchLivingRoom, FExecuteAction::CreateRaw(this, &FOculusXREditorModule::LaunchSESLivingRoom), FCanExecuteAction()); PluginCommands->MapAction( FOculusToolCommands::Get().LaunchBedroom, FExecuteAction::CreateRaw(this, &FOculusXREditorModule::LaunchSESBedroom), FCanExecuteAction()); PluginCommands->MapAction( FOculusToolCommands::Get().StopServer, FExecuteAction::CreateRaw(this, &FOculusXREditorModule::StopSESServer), FCanExecuteAction()); FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); // Adds an option to launch the tool to Window->Developer Tools. TSharedPtr MenuExtender = MakeShareable(new FExtender()); MenuExtender->AddMenuExtension("Miscellaneous", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FOculusXREditorModule::AddMenuExtension)); LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); // We add the Oculus menu on the toolbar TSharedPtr ToolbarExtender = MakeShareable(new FExtender); ToolbarExtender->AddToolBarExtension("Play", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FOculusXREditorModule::AddToolbarExtension)); LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); FGlobalTabmanager::Get()->RegisterNomadTabSpawner(OculusPlatToolTabName, FOnSpawnTab::CreateRaw(this, &FOculusXREditorModule::OnSpawnPlatToolTab)).SetDisplayName(LOCTEXT("FOculusPlatfToolTabTitle", "Meta XR Platform Tool")).SetMenuType(ETabSpawnerMenuType::Hidden); // Register asset types IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); AssetTools.RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_OculusXRPassthroughColorLut)); OculusXRTelemetry::PropagateTelemetryConsent(); // If needed, open a notification here. OculusXRTelemetry::SpawnNotification(); const UGeneralProjectSettings& ProjectSettings = *GetDefault(); const FString ProjectIdString = ProjectSettings.ProjectID.ToString(); const OculusXRTelemetry::TScopedMarker StartEvent; const auto& Annotated = StartEvent.AddAnnotation("project_hash", StringCast(*ProjectIdString).Get()); UEditorPerformanceSettings* EditorPerformanceSettings = GetMutableDefault(); if (EditorPerformanceSettings->bOverrideMaxViewportRenderingResolution) { UE_LOG(LogTemp, Warning, TEXT("Existing value for UEditorPerformanceSettings::MaxViewportRenderingResolution will be overriden.")); } UE_LOG(LogTemp, Log, TEXT("MetaXR ignores max viewport resolution in editor to support full HMD resolutions.")); EditorPerformanceSettings->bOverrideMaxViewportRenderingResolution = true; EditorPerformanceSettings->MaxViewportRenderingResolution = 0; FPropertyChangedEvent DisabledMaxResolutionEvent(EditorPerformanceSettings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UEditorPerformanceSettings, MaxViewportRenderingResolution)), EPropertyChangeType::ValueSet); EditorPerformanceSettings->PostEditChangeProperty(DisabledMaxResolutionEvent); } } void FOculusXREditorModule::ShutdownModule() { if (!bModuleValid) { return; } if (!IsRunningCommandlet()) { FOculusToolStyle::Shutdown(); FOculusToolCommands::Unregister(); FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(OculusPlatToolTabName); } FOculusAssetDirectory::ReleaseAll(); if (UObjectInitialized()) { UnregisterSettings(); } } TSharedRef FOculusXREditorModule::OnSpawnPlatToolTab(const FSpawnTabArgs& SpawnTabArgs) { /* clang-format off */ auto myTab = SNew(SDockTab) .TabRole(ETabRole::NomadTab) [ SNew(SOculusPlatformToolWidget) ]; /* clang-format on */ return myTab; } void FOculusXREditorModule::RegisterSettings() { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) { SettingsModule->RegisterSettings("Project", "Plugins", "OculusXR", LOCTEXT("RuntimeSettingsName", "Meta XR"), LOCTEXT("RuntimeSettingsDescription", "Configure the Meta XR plugin"), GetMutableDefault()); FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomClassLayout(UOculusXRHMDRuntimeSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FOculusXRHMDSettingsDetailsCustomization::MakeInstance)); SettingsModule->RegisterSettings("Editor", "Privacy", "OculusXR", LOCTEXT("PrivacyTelemetrySettingsName", "MetaXR Usage Data"), LOCTEXT("PrivacyTelemetrySettingsDescription", "Configure the way MetaXR usage information is handled."), GetMutableDefault()); PropertyModule.RegisterCustomClassLayout(UOculusXRTelemetryPrivacySettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FOculusXRSettingsToggle::MakeInstance)); } } void FOculusXREditorModule::UnregisterSettings() { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) { SettingsModule->UnregisterSettings("Project", "Plugins", "OculusXR"); SettingsModule->UnregisterSettings("Editor", "Privacy", "OculusXR"); } } FReply FOculusXREditorModule::PluginClickFn(bool text) { PluginOpenSetupToolWindow(); return FReply::Handled(); } void FOculusXREditorModule::PluginOpenSetupToolWindow() { IOculusXRProjectSetupToolModule::Get().ShowProjectSetupTool("Meta Menu"); } void FOculusXREditorModule::PluginOpenPlatWindow() { FGlobalTabmanager::Get()->TryInvokeTab(OculusPlatToolTabName); } void FOculusXREditorModule::ToggleOpenXRRuntime() { FOculusXRHMDModule::ToggleOpenXRRuntime(); } void FOculusXREditorModule::LaunchSESGameRoom() { FOculusXRHMDModule::LaunchEnvironment("GameRoom"); } void FOculusXREditorModule::LaunchSESLivingRoom() { FOculusXRHMDModule::LaunchEnvironment("LivingRoom"); } void FOculusXREditorModule::LaunchSESBedroom() { FOculusXRHMDModule::LaunchEnvironment("Bedroom"); } void FOculusXREditorModule::StopSESServer() { FOculusXRHMDModule::StopServer(); } void FOculusXREditorModule::AddMenuExtension(FMenuBuilder& Builder) { bool v = false; GConfig->GetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bAddMenuOption"), v, GEditorIni); if (v) { Builder.AddMenuEntry(FOculusToolCommands::Get().OpenProjectSetupTool); } } void FOculusXREditorModule::AddToolbarExtension(FToolBarBuilder& Builder) { Builder.SetLabelVisibility(EVisibility::All); Builder.AddComboButton( FUIAction(), FOnGetContent::CreateRaw(this, &FOculusXREditorModule::CreateToolbarEntryMenu, PluginCommands), LOCTEXT("OculusToolsToolBarCombo", "Meta XR Tools"), LOCTEXT("OculusToolsToolBarComboTooltip", "Meta XR tools"), TAttribute::CreateLambda([]() { return FSlateIcon(FOculusToolStyle::GetStyleSetName(), "OculusTool.MenuButton"); }), false); Builder.AddComboButton( FUIAction(), FOnGetContent::CreateRaw(this, &FOculusXREditorModule::CreateXrSimToolbarEntryMenu, PluginCommands), LOCTEXT("MetaXRSimulatorCombo", "Meta XR Simulator"), LOCTEXT("MetaXRSimulatorComboTooltip", "Meta XR Simulator"), TAttribute::CreateLambda([]() { return FSlateIcon(FOculusToolStyle::GetStyleSetName(), "OculusTool.MenuButton"); }), false); } // Add the entries to the OculusXR Tools toolbar menu button TSharedRef FOculusXREditorModule::CreateToolbarEntryMenu(TSharedPtr Commands) { FMenuBuilder MenuBuilder(true, Commands); MenuBuilder.BeginSection("OculusXRBuilds", LOCTEXT("OculusXRBuilds", "Builds")); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().ToggleDeploySo); MenuBuilder.EndSection(); MenuBuilder.BeginSection("OculusXRTools", LOCTEXT("OculusXRTools", "Tools")); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().OpenProjectSetupTool); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().OpenPlatWindow); MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } TSharedRef FOculusXREditorModule::CreateXrSimToolbarEntryMenu(TSharedPtr Commands) { FMenuBuilder MenuBuilder(true, Commands); MenuBuilder.BeginSection("MetaXRSimulator", LOCTEXT("MetaXRSimulator", "Toggle")); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().ToggleMetaXRSim); MenuBuilder.EndSection(); MenuBuilder.BeginSection("SES", LOCTEXT("SES", "SES")); MenuBuilder.AddSubMenu( LOCTEXT("Synthetic Environment Server", "Synthetic Environment Server"), LOCTEXT("Synthetic Environment Server", "Synthetic Environment Server"), FNewMenuDelegate::CreateRaw(this, &FOculusXREditorModule::CreateSESSubMenus)); MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } void FOculusXREditorModule::CreateSESSubMenus(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("Synthetic Environment Server", LOCTEXT("Synthetic Environment Server", "Synthetic Environment Server")); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().LaunchGameRoom); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().LaunchLivingRoom); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().LaunchBedroom); MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().StopServer); MenuBuilder.EndSection(); } FOculusXRHMDSettingsDetailsCustomization::FOculusXRHMDSettingsDetailsCustomization() : EngineAndroidPath(FPaths::EngineDir() + TEXT("Build/Android/Java")) , GameAndroidPath(FPaths::ProjectDir() + TEXT("Build/Android")) , LaunchImageLandscape(FPlatformIconInfo(TEXT("res/drawable/splashscreen_landscape.png"), LOCTEXT("SystemSplashImage", "System Splash Image"), FText::GetEmpty(), 640, 360, FPlatformIconInfo::Required)) , VRSplashPath(FPaths::ProjectDir() + TEXT("Build/Android/assets/vr_splash.png")) { } TSharedRef FOculusXRHMDSettingsDetailsCustomization::MakeInstance() { return MakeShareable(new FOculusXRHMDSettingsDetailsCustomization); } FReply FOculusXRHMDSettingsDetailsCustomization::PluginClickPerfFn(bool text) { IOculusXRProjectSetupToolModule::Get().ShowProjectSetupTool("Settings"); return FReply::Handled(); } FReply FOculusXRHMDSettingsDetailsCustomization::PluginClickPlatFn(bool text) { FGlobalTabmanager::Get()->TryInvokeTab(FOculusXREditorModule::OculusPlatToolTabName); return FReply::Handled(); } FReply FOculusXRHMDSettingsDetailsCustomization::DisableEngineSplash(bool text) { UOculusXRHMDRuntimeSettings* Settings = GetMutableDefault(); Settings->bAutoEnabled = false; Settings->SplashDescs.Empty(); Settings->TryUpdateDefaultConfigFile(); return FReply::Handled(); } FReply FOculusXRHMDSettingsDetailsCustomization::AddSplashImage(bool text) { const FString AutomaticImagePath = EngineAndroidPath / LaunchImageLandscape.IconPath; FText FailReason; if (!SourceControlHelpers::CopyFileUnderSourceControl(VRSplashPath, AutomaticImagePath, LOCTEXT("ImageDescription", "image"), FailReason)) { FNotificationInfo Info(FailReason); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); return FReply::Unhandled(); } return FReply::Handled(); } void FOculusXRHMDSettingsDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { SavedLayoutBuilder = &DetailLayout; // Labeled "General OculusXR" instead of "General" to enable searchability. The button "Launch Oculus Utilities Window" doesn't show up if you search for "Oculus" IDetailCategoryBuilder& CategoryBuilder = DetailLayout.EditCategory("General Meta XR", FText::GetEmpty(), ECategoryPriority::Important); /* clang-format off */ CategoryBuilder.AddCustomRow(LOCTEXT("General", "General")) .WholeRowContent() [ SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(2) [ SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth() [ SNew(SButton) .Text(LOCTEXT("LaunchTool", "Launch Meta XR Project Setup Tool")) .OnClicked(this, &FOculusXRHMDSettingsDetailsCustomization::PluginClickPerfFn, true) ] + SHorizontalBox::Slot().FillWidth(8) ] + SVerticalBox::Slot().AutoHeight().Padding(2) [ SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth() [ SNew(SButton) .Text(LOCTEXT("LaunchPlatTool", "Launch Meta XR Platform Window")) .OnClicked(this, &FOculusXRHMDSettingsDetailsCustomization::PluginClickPlatFn, true) ] + SHorizontalBox::Slot().FillWidth(8) ] ]; IDetailCategoryBuilder& CTXPTCategoryBuilder = DetailLayout.EditCategory("System SplashScreen", FText::GetEmpty(), ECategoryPriority::Important); static const FName WarningColorStyle("Colors.AccentYellow"); CTXPTCategoryBuilder.AddCustomRow(LOCTEXT("CTXPTWarning", "Contextual Passthrough Warning")) .Visibility(TAttribute(this, &FOculusXRHMDSettingsDetailsCustomization::GetContextualPassthroughWarningVisibility)) [ SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.f).VAlign(EVerticalAlignment::VAlign_Center) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .AutoWrapText(true) .Justification(ETextJustify::Center) .Text(LOCTEXT("CTXPT_EngineSplashWarning", "Engine Splash Screen is enabled, this will result in an inconsistent experience.")) .ColorAndOpacity(FAppStyle::Get().GetSlateColor(WarningColorStyle)) ] + SHorizontalBox::Slot().FillWidth(1.f).HAlign(EHorizontalAlignment::HAlign_Left) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .Text(LOCTEXT("DisableEngineSplashScreen", "Disable Engine Splash Screen")) .OnClicked(this, &FOculusXRHMDSettingsDetailsCustomization::DisableEngineSplash, true) ] ] ]; // Duplicate "Show Launch Image" and "Launch Landscape" properties from Android Settings CTXPTCategoryBuilder.AddCustomRow(LOCTEXT("ShowSystemSplashImageRow", "Show System Splash Image Row")) .NameContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FMargin(0, 1, 0, 1)) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("ShowSystemSplashImage", "Show System Splash Image")) .Font(DetailLayout.GetDetailFont()) .ToolTipText(LOCTEXT("ShowSystemSplashImageToolTip", "Same as \"Show Launch Image\" setting in the \"Platform > Android > Launch Images\" section. If set, the image will be presented by the Operating System at launch time")) ] ] .ValueContent() .MaxDesiredWidth(400.0f) .MinDesiredWidth(100.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew(SCheckBox) .IsChecked(TAttribute(this, &FOculusXRHMDSettingsDetailsCustomization::GetShowLaunchImageCheckBoxState)) .OnCheckStateChanged(this, &FOculusXRHMDSettingsDetailsCustomization::OnShowLaunchImageCheckStateChanged) ] ]; const FString AutomaticImagePath = EngineAndroidPath / LaunchImageLandscape.IconPath; const FString TargetImagePath = GameAndroidPath / LaunchImageLandscape.IconPath; const FVector2D LaunchImageMaxSize(150.0f, 150.0f); CTXPTCategoryBuilder.AddCustomRow(LaunchImageLandscape.IconName) .IsEnabled(TAttribute(this, &FOculusXRHMDSettingsDetailsCustomization::IsLaunchImageEnabled)) .NameContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FMargin(0, 1, 0, 1)) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LaunchImageLandscape.IconName) .Font(DetailLayout.GetDetailFont()) .ToolTipText(LOCTEXT("SystemSplashImageToolTip", "Same as \"Launch Landscape\" setting in the \"Platform > Android > Launch Images\" section. This is the image that will be presented by the Operating System at launch time")) ] ] .ValueContent() .MaxDesiredWidth(400.0f) .MinDesiredWidth(100.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew(SExternalImageReference, AutomaticImagePath, TargetImagePath) .FileDescription(LaunchImageLandscape.IconDescription) .MaxDisplaySize(LaunchImageMaxSize) .OnPostExternalImageCopy(FOnPostExternalImageCopy::CreateSP(this, &FOculusXRHMDSettingsDetailsCustomization::OnLaunchImageChanged)) ] ]; CTXPTCategoryBuilder.AddCustomRow(LOCTEXT("SystemSplashImageWarning", "System Splash Image warning")) .Visibility(TAttribute(this, &FOculusXRHMDSettingsDetailsCustomization::GetSystemSplashImageWarningVisibility)) [ SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.f).VAlign(EVerticalAlignment::VAlign_Center) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .AutoWrapText(true) .Justification(ETextJustify::Center) .Text(LOCTEXT("SystemSplashWarningText", "Splash Image is currently missing from project. Click button to add it.")) .ColorAndOpacity(FAppStyle::Get().GetSlateColor(WarningColorStyle)) ] + SHorizontalBox::Slot().FillWidth(1.f).HAlign(EHorizontalAlignment::HAlign_Left) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .Text(LOCTEXT("DisableEngineSplashScreen", "Add Splash Image file to project")) .OnClicked(this, &FOculusXRHMDSettingsDetailsCustomization::AddSplashImage, true) ] ] ]; /* clang-format on */ } EVisibility FOculusXRHMDSettingsDetailsCustomization::GetContextualPassthroughWarningVisibility() const { UOculusXRHMDRuntimeSettings* OculusSettings = GetMutableDefault(); return OculusSettings->SystemSplashBackground == ESystemSplashBackgroundType::Contextual && (OculusSettings->bAutoEnabled || !OculusSettings->SplashDescs.IsEmpty()) ? EVisibility::Visible : EVisibility::Collapsed; } ECheckBoxState FOculusXRHMDSettingsDetailsCustomization::GetShowLaunchImageCheckBoxState() const { UAndroidRuntimeSettings* AndroidSettings = GetMutableDefault(); return AndroidSettings->bShowLaunchImage ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool FOculusXRHMDSettingsDetailsCustomization::IsLaunchImageEnabled() const { UAndroidRuntimeSettings* AndroidSettings = GetMutableDefault(); return AndroidSettings->bShowLaunchImage; } void FOculusXRHMDSettingsDetailsCustomization::OnShowLaunchImageCheckStateChanged(const ECheckBoxState NewState) { UAndroidRuntimeSettings* AndroidSettings = GetMutableDefault(); AndroidSettings->bShowLaunchImage = NewState == ECheckBoxState::Checked; AndroidSettings->TryUpdateDefaultConfigFile(); } bool FOculusXRHMDSettingsDetailsCustomization::OnLaunchImageChanged(const FString& InChosenImage) { // This will refresh the launch image located in android settings as well SavedLayoutBuilder->ForceRefreshDetails(); FText FailReason; if (!SourceControlHelpers::CopyFileUnderSourceControl(VRSplashPath, InChosenImage, LOCTEXT("ImageDescription", "image"), FailReason)) { FNotificationInfo Info(FailReason); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); return false; } return true; } EVisibility FOculusXRHMDSettingsDetailsCustomization::GetSystemSplashImageWarningVisibility() const { IFileManager& FileManager = IFileManager::Get(); return !FileManager.FileExists(*VRSplashPath) ? EVisibility::Visible : EVisibility::Collapsed; } ////////////////////////////////////////////////////////////////////////// IMPLEMENT_MODULE(FOculusXREditorModule, OculusXREditor); ////////////////////////////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE