Files
UnrealEngineUWP/Engine/Source/Editor/VREditor/VREditorModule.cpp
zach brockway 08cd8da277 VR Editor: Introduce minimal UVREditorModeBase class.
This is the new parent class of UVREditorMode, and subsumes a small subset of its functionality (currently just the HMD stereo viewport rendering setup). IVREditorModule and FVREditorModeManager now use pointers to the base class type. Some deprecation annotations were added, but are commented out and will be enabled in a subsequent changelist due to the large number of external files that require accompanying fixups.

Other changes:
 - The analytics events have been moved from UVREditorMode into FVREditorModeManager (we always want these).
 - The use of UViewportWorldInteraction has been moved from FVREditorModeManager into UVREditorMode (this was really an implementation detail of UVREditorMode).
 - Support for the "Entering VR Mode - Experimental" warning, which was only used by UVREditorMode, was removed.
 - The UVREditorModeBase now has the UWorld as an outer, which makes it eligible for selection in the blueprint debugger.

#jira UE-167701
#rb jason.walter
#preflight 6351cc170313c24974e8f3ad

[CL 22695140 by zach brockway in ue5-main branch]
2022-10-21 14:15:29 -04:00

370 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VREditorModule.h"
#include "Modules/ModuleManager.h"
#include "Stats/Stats.h"
#include "HAL/IConsoleManager.h"
#include "IHeadMountedDisplay.h"
#include "ISettingsCategory.h"
#include "ISettingsContainer.h"
#include "ISettingsModule.h"
#include "ISettingsSection.h"
#include "IVREditorModule.h"
#include "IXRTrackingSystem.h"
#include "LevelEditorActions.h"
#include "ToolMenus.h"
#include "VREditorModeManager.h"
#include "VREditorStyle.h"
#include "VREditorMode.h"
#include "VRModeSettings.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "HeadMountedDisplayTypes.h"
#include "UI/VREditorFloatingUI.h"
#define LOCTEXT_NAMESPACE "VREditorModule"
DEFINE_LOG_CATEGORY(LogVREditor);
class FVREditorModule : public IVREditorModule
{
public:
FVREditorModule()
{
}
// FModuleInterface overrides
virtual void StartupModule() override;
virtual void ShutdownModule() override;
virtual void PostLoadCallback() override;
virtual bool SupportsDynamicReloading() override
{
return true;
}
// IVREditorModule overrides
virtual bool IsVREditorEnabled() const override;
virtual bool IsVREditorAvailable() const override;
virtual bool IsVREditorButtonActive() const override;
virtual void EnableVREditor( const bool bEnable, const bool bForceWithoutHMD ) override;
virtual bool IsVREditorModeActive() override;
virtual UVREditorModeBase* GetVRModeBase() override;
virtual UVREditorMode* GetVRMode() override;
virtual void UpdateActorPreview(TSharedRef<SWidget> InWidget, int32 Index, AActor *Actor, bool bIsPanelDetached = false) override;
virtual void UpdateExternalUMGUI(const FVREditorFloatingUICreationContext& CreationContext) override;
virtual void UpdateExternalSlateUI(TSharedRef<SWidget> InSlateWidget, FName Name, FVector2D InSize) override;
virtual TSharedPtr<FExtender> GetRadialMenuExtender() override
{
return RadialMenuExtender;
}
static void ToggleForceVRMode();
/** Return a multicast delegate which is executed when VR mode starts. */
virtual FOnVREditingModeEnter& OnVREditingModeEnter() override { return ModeManager.OnVREditingModeEnter(); }
/** Return a multicast delegate which is executed when VR mode stops. */
virtual FOnVREditingModeExit& OnVREditingModeExit() override { return ModeManager.OnVREditingModeExit(); }
private:
void ExtendToolbarMenu();
void HandleModeClassAdded(const FTopLevelAssetPath& ClassPath);
void AssignModeClassAndSave(const FTopLevelAssetPath& ClassPath);
private:
TSharedPtr<class FExtender> RadialMenuExtender;
/** Handles turning VR Editor mode on and off */
FVREditorModeManager ModeManager;
FDelegateHandle DeferredInitDelegate;
TWeakPtr<ISettingsSection> WeakSettingsSection;
};
namespace VREd
{
static FAutoConsoleCommand ForceVRMode( TEXT( "VREd.ForceVRMode" ), TEXT( "Toggles VREditorMode, even if not in immersive VR" ), FConsoleCommandDelegate::CreateStatic( &FVREditorModule::ToggleForceVRMode ) );
}
void FVREditorModule::StartupModule()
{
LLM_SCOPE_BYNAME(TEXT("VREditor"));
RadialMenuExtender = MakeShareable(new FExtender());
ModeManager.OnModeClassAdded.AddRaw(this, &FVREditorModule::HandleModeClassAdded);
// UToolMenus::RegisterStartupCallback is still too early.
DeferredInitDelegate = FCoreDelegates::OnBeginFrame.AddLambda(
[this]()
{
ExtendToolbarMenu();
// Cache pointer to VR Mode settings section, registered elsewhere.
ISettingsModule& SettingsModule = FModuleManager::GetModuleChecked<ISettingsModule>("Settings");
if (TSharedPtr<ISettingsContainer> EditorContainer = SettingsModule.GetContainer("Editor"))
{
if (TSharedPtr<ISettingsCategory> GeneralCategory = EditorContainer->GetCategory("General"))
{
WeakSettingsSection = GeneralCategory->GetSection("VR Mode");
}
}
// Must happen last; this implicitly deallocates this lambda's captures.
FCoreDelegates::OnBeginFrame.Remove(DeferredInitDelegate);
}
);
}
void FVREditorModule::ShutdownModule()
{
UToolMenus::UnregisterOwner(this);
if (GIsEditor)
{
FVREditorStyle::Shutdown();
}
}
void FVREditorModule::PostLoadCallback()
{
}
bool FVREditorModule::IsVREditorEnabled() const
{
return ModeManager.IsVREditorActive();
}
bool FVREditorModule::IsVREditorAvailable() const
{
return ModeManager.IsVREditorAvailable();
}
bool FVREditorModule::IsVREditorButtonActive() const
{
return ModeManager.IsVREditorButtonActive();
}
void FVREditorModule::EnableVREditor( const bool bEnable, const bool bForceWithoutHMD )
{
ModeManager.EnableVREditor( bEnable, bForceWithoutHMD );
}
bool FVREditorModule::IsVREditorModeActive()
{
// Deprecated method only returns true for legacy UVREditorMode
return GetVRMode() && ModeManager.IsVREditorActive();
}
UVREditorModeBase* FVREditorModule::GetVRModeBase()
{
return ModeManager.GetCurrentVREditorMode();
}
UVREditorMode* FVREditorModule::GetVRMode()
{
UVREditorModeBase* ModeBase = GetVRModeBase();
return Cast<UVREditorMode>(ModeBase);
}
void FVREditorModule::UpdateActorPreview(TSharedRef<SWidget> InWidget, int32 Index, AActor* Actor, bool bIsPanelDetached)
{
GetVRMode()->RefreshActorPreviewWidget(InWidget, Index, Actor, bIsPanelDetached);
}
void FVREditorModule::UpdateExternalUMGUI(const FVREditorFloatingUICreationContext& CreationContext)
{
GetVRMode()->UpdateExternalUMGUI(CreationContext);
}
void FVREditorModule::UpdateExternalSlateUI(TSharedRef<SWidget> InSlateWidget, FName Name, FVector2D InSize)
{
GetVRMode()->UpdateExternalSlateUI(InSlateWidget, Name, InSize);
}
void FVREditorModule::ToggleForceVRMode()
{
const bool bForceWithoutHMD = true;
FVREditorModule& Self = FModuleManager::GetModuleChecked< FVREditorModule >( TEXT( "VREditor" ) );
Self.EnableVREditor( !Self.IsVREditorEnabled(), bForceWithoutHMD );
}
void FVREditorModule::ExtendToolbarMenu()
{
FToolMenuOwnerScoped OwnerScoped(this);
UToolMenu* EditorToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.AssetsToolBar");
if (!ensure(EditorToolbarMenu))
{
return;
}
FToolMenuSection* EditorToolbarSection = EditorToolbarMenu->FindSection("Content");
if (!EditorToolbarSection)
{
return;
}
// The primary toggle button.
EditorToolbarSection->AddEntry(FToolMenuEntry::InitToolBarButton(
FLevelEditorCommands::Get().ToggleVR,
LOCTEXT("ToggleVR", "VR Mode")
));
// The "..." split, which summons the tool menu.
static const FName OptionsToolMenuName = "VrEditor.ToggleVrOptions";
EditorToolbarSection->AddEntry(FToolMenuEntry::InitComboButton(
"ToggleVrOptionsMenu",
FUIAction(),
FOnGetContent::CreateLambda([]() { return UToolMenus::Get()->GenerateWidget(OptionsToolMenuName, FToolMenuContext()); }),
TAttribute<FText>(),
LOCTEXT("ToggleVrOptions_ComboSplit_Tooltip", "Configure VR Editor Mode"),
TAttribute<FSlateIcon>(),
true
));
// Register the split options tool menu.
UToolMenu* OptionsMenu = UToolMenus::Get()->RegisterMenu(OptionsToolMenuName);
// The "Status" section indicates whether an HMD is available.
FToolMenuSection& StatusSection = OptionsMenu->AddSection("Status");
StatusSection.AddDynamicEntry("StatusEntry", FNewToolMenuSectionDelegate::CreateLambda(
[](FToolMenuSection& InSection)
{
const bool bHasHMDDevice = GEngine->XRSystem
&& GEngine->XRSystem->GetHMDDevice()
&& GEngine->XRSystem->GetHMDDevice()->IsHMDEnabled();
const FText HmdAvailableText = LOCTEXT("ToggleVrOptions_Status_HmdAvailable", "Headset available ({XrVersionString})");
const FText HmdNotAvailableText = LOCTEXT("ToggleVrOptions_Status_HmdNotAvailable", "No headset available");
const FText StatusText = bHasHMDDevice
? FText::FormatNamed(HmdAvailableText, TEXT("XrVersionString"), FText::FromString(GEngine->XRSystem->GetVersionString()))
: HmdNotAvailableText;
InSection.AddEntry(FToolMenuEntry::InitWidget(
"HeadsetStatus",
SNew(SBox)
.Padding(FMargin(16.0f, 3.0f))
[
SNew(STextBlock)
.ColorAndOpacity(FSlateColor::UseSubduedForeground())
.Text(StatusText)
],
FText::GetEmpty()
));
}));
// The "Modes" section enumerates the UVREditorMode-derived classes for radio selection.
OptionsMenu->AddDynamicSection("DynamicModesSection", FNewToolMenuDelegate::CreateLambda(
[this](UToolMenu* InMenu)
{
FToolMenuSection& Section = InMenu->AddSection("Modes", LOCTEXT("ToggleVrOptions_ModesSection_Label", "Modes"));
TSoftClassPtr<UVREditorModeBase> CurrentModeClassSoft = GetDefault<UVRModeSettings>()->ModeClass;
UClass* CurrentModeClass = CurrentModeClassSoft.LoadSynchronous();
TArray<UClass*> ModeClasses;
ModeManager.GetConcreteModeClasses(ModeClasses);
Algo::Sort(ModeClasses,
[](const UClass* Lhs, const UClass* Rhs)
{
return Lhs->GetDisplayNameText().CompareTo(Rhs->GetDisplayNameText()) < 0;
});
if (!ModeClasses.Contains(CurrentModeClass))
{
FToolUIAction UnavailableModeAction;
UnavailableModeAction.CanExecuteAction = FToolMenuCanExecuteAction::CreateLambda([](const FToolMenuContext&) { return false; });
UnavailableModeAction.GetActionCheckState = FToolMenuGetActionCheckState::CreateLambda([](const FToolMenuContext&) { return ECheckBoxState::Checked; });
Section.AddMenuEntry("UnavailableMode",
CurrentModeClassSoft.IsNull() ? LOCTEXT("NullModeClass", "(Unset)") : FText::AsCultureInvariant(CurrentModeClassSoft.ToString()),
TAttribute<FText>(),
TAttribute<FSlateIcon>(),
UnavailableModeAction,
EUserInterfaceActionType::RadioButton);
}
for (UClass* Mode : ModeClasses)
{
FToolUIAction SelectModeAction;
SelectModeAction.ExecuteAction = FToolMenuExecuteAction::CreateLambda(
[this, Mode]
(const FToolMenuContext&)
{
AssignModeClassAndSave(Mode->GetClassPathName());
});
SelectModeAction.CanExecuteAction = FToolMenuCanExecuteAction::CreateLambda(
[](const FToolMenuContext&)
{
return true;
});
SelectModeAction.GetActionCheckState = FToolMenuGetActionCheckState::CreateLambda(
[Mode](const FToolMenuContext&)
{
return Mode == GetDefault<UVRModeSettings>()->ModeClass.Get() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
});
Section.AddMenuEntry(Mode->GetFName(),
Mode->GetDisplayNameText(),
FText::AsCultureInvariant(Mode->GetClassPathName().ToString()),
TAttribute<FSlateIcon>(),
SelectModeAction,
EUserInterfaceActionType::RadioButton);
}
}));
FToolMenuSection& SettingsMenuSection = OptionsMenu->AddSection("Settings");
SettingsMenuSection.AddSeparator("SettingsStartSeparator");
SettingsMenuSection.AddMenuEntry(
"EditorSettings",
LOCTEXT("ToggleVrOptions_EditorSettings_Label", "Settings..."),
LOCTEXT("ToggleVrOptions_EditorSettings_Tooltip", "Jump to the VR Editor Mode section in the Editor Preferences"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.GameSettings"),
FUIAction(FExecuteAction::CreateLambda([]() { FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer("Editor", "General", "VR Mode"); }))
);
}
void FVREditorModule::HandleModeClassAdded(const FTopLevelAssetPath& ClassPath)
{
// If we don't have a mode class yet, assign the first one we discover.
if (GetDefault<UVRModeSettings>()->ModeClass.IsNull())
{
AssignModeClassAndSave(ClassPath);
}
}
void FVREditorModule::AssignModeClassAndSave(const FTopLevelAssetPath& ClassPath)
{
UVRModeSettings* Settings = GetMutableDefault<UVRModeSettings>();
Settings->ModeClass = FSoftObjectPath(ClassPath);
if (FProperty* ModeClassProperty = FindFProperty<FProperty>(Settings->GetClass(),
GET_MEMBER_NAME_CHECKED(UVRModeSettings, ModeClass)))
{
FPropertyChangedEvent PropertyUpdateStruct(ModeClassProperty, EPropertyChangeType::ValueSet);
Settings->PostEditChangeProperty(PropertyUpdateStruct);
}
if (TSharedPtr<ISettingsSection> SettingsSection = WeakSettingsSection.Pin())
{
SettingsSection->Save();
}
}
IMPLEMENT_MODULE( FVREditorModule, VREditor )
#undef LOCTEXT_NAMESPACE