VRTowerDef/Plugins/MetaXR/Source/OculusXRProjectSetupTool/Private/Widget/OculusXRProjectSetupToolWid...

1027 lines
32 KiB
C++
Raw Normal View History

2024-05-29 08:53:41 +00:00
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRProjectSetupToolWidget.h"
#include "DetailColumnSizeData.h"
#include "OculusXRProjectSetupToolModule.h"
#include "OculusXRPSTEvents.h"
#include "OculusXRPSTSettings.h"
#include "OculusXRPSTUtils.h"
#include "OculusXRTelemetry.h"
#include "SWarningOrErrorBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Input/SButton.h"
#include "Styling/SlateStyle.h"
#define LOCTEXT_NAMESPACE "OculusXRProjectSetupToolWidget"
/**
* Construct the layout for the project setup tool tab
*
* @param InArgs [in] the arguments associated with this tool
*/
void SOculusXRProjectSetupToolWidget::Construct(const FArguments& InArgs)
{
Refresh();
// Populate the platform filter
PlatformFilters.Add(MetaQuest_All);
PlatformFilters.Add(ESetupRulePlatform::MetaLink);
PlatformFilters.Add(ESetupRulePlatform::MetaQuest_3);
PlatformFilters.Add(ESetupRulePlatform::MetaQuest_Pro);
PlatformFilters.Add(ESetupRulePlatform::MetaQuest_2);
const auto Settings = GetMutableDefault<UOculusXRPSTSettings>();
UpdateActiveTimer(Settings->bBackGroundChecks);
// Apply the starting platform filter.
const auto& Platform = static_cast<ESetupRulePlatform>(Settings->CurrentPlatform);
CurrentPlatformFilterIndex = PlatformFilters.Find(Platform);
if (CurrentPlatformFilterIndex == INDEX_NONE)
{
CurrentPlatformFilterIndex = 0;
}
// Setup the column information for the layout
ColumnSizeData = MakeShared<FDetailColumnSizeData>();
ColumnSizeData->SetValueColumnWidth(0.75f);
ColumnSizeData->SetRightColumnMinWidth(150);
// Construct the layout
RootContainerWidget = SNew(SVerticalBox);
BuildLayout(RootContainerWidget);
ChildSlot
[RootContainerWidget.ToSharedRef()];
}
/**
* Build the layout for the main window
*/
void SOculusXRProjectSetupToolWidget::BuildLayout(const TSharedPtr<SVerticalBox>& RootContainer)
{
// Clear all existing contents
RootContainer->ClearChildren();
const TSharedPtr<SVerticalBox> HeaderContainer = SNew(SVerticalBox);
// Section to contain the title bar
BuildTitleSectionLayout(HeaderContainer);
// Section to contain the filters
BuildFilterSectionLayout(HeaderContainer, CurrentPlatformFilterIndex);
const TSharedPtr<SScrollBox> RulesContainer = SNew(SScrollBox);
// Section to contain the required rules
BuildRequiredRulesSectionLayout(RulesContainer);
// Section to contain the recommended rules
BuildRecommendedRulesSectionLayout(RulesContainer);
// Section to contain the applied rules
BuildAppliedRulesSectionLayout(RulesContainer);
// Section to contain the ignored rules
BuildIgnoredRulesSectionLayout(RulesContainer);
RootContainer->AddSlot()
.AutoHeight()
.Padding(0)
[HeaderContainer.ToSharedRef()];
RootContainer->AddSlot()
.Padding(0)
[RulesContainer.ToSharedRef()];
// Section to show warning about restarting editor
RootContainer->AddSlot()
.AutoHeight()
[SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(18.0f, 20.0f, 18.0f, 16.0f))
[SNew(SWarningOrErrorBox)
.Visibility(this, &SOculusXRProjectSetupToolWidget::OnRestartEditorNoticeVisibility)
.MessageStyle(EMessageStyle::Warning)
.Message(LOCTEXT("PluginSettingsRestartNotice", "You must restart Unreal Editor for your changes to take effect."))
[SNew(SButton)
.OnClicked(this, &SOculusXRProjectSetupToolWidget::OnRestartEditorButtonClicked)
.TextStyle(FAppStyle::Get(), "NormalText")
.Text(LOCTEXT("PluginSettingsRestartEditor", "Restart Now"))]]];
}
/**
* Build the title section layout
*/
void SOculusXRProjectSetupToolWidget::BuildTitleSectionLayout(const TSharedPtr<SVerticalBox>& RootContainer)
{
const TSharedPtr<SHorizontalBox> TitleWidget = SNew(SHorizontalBox)
.Clipping(EWidgetClipping::OnDemand);
ProjectStatusWidget = SNew(STextBlock)
.Font(FAppStyle::Get().GetFontStyle(TEXT("DetailsView.CategoryFontStyle")))
.TextStyle(FAppStyle::Get(), "DetailsView.CategoryTextStyle")
.Text(FText());
UpdateProjectStatus();
TitleWidget->AddSlot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(8, 0, 0, 0)
[ProjectStatusWidget.ToSharedRef()];
const TSharedPtr<SHorizontalBox> ButtonWidget = SNew(SHorizontalBox);
auto Settings = GetMutableDefault<UOculusXRPSTSettings>();
FMenuBuilder DetailViewOptions(true, nullptr);
DetailViewOptions.AddMenuEntry(
LOCTEXT("BackGroundChecks", "Enable background rule checks"),
LOCTEXT("BackGroundChecks_ToolTip", "If enabled background rule validity will be performed every 30 seconds."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Settings] {
Settings->bBackGroundChecks = !Settings->bBackGroundChecks;
Settings->SaveConfig();
const OculusXRTelemetry::TScopedMarker<OculusXRTelemetry::Events::FProjectSetupToolOption> OptionEvent;
const auto& Annotated = OptionEvent
.AddAnnotation(OculusXRTelemetry::Annotations::Uid, "bBackGroundChecks")
.AddAnnotation(OculusXRTelemetry::Annotations::Value,
Settings->bBackGroundChecks ? "true" : "false");
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([Settings]() -> bool {
return Settings->bBackGroundChecks;
})),
NAME_None,
EUserInterfaceActionType::ToggleButton);
DetailViewOptions.AddMenuEntry(
LOCTEXT("StopBuildOnUnappliedRequredItem", "Stop build on unapplied items"),
LOCTEXT("StopBuildOnUnappliedRequredItem_ToolTip", "Stop build if required items are not applied or ignored."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Settings] {
Settings->bStopBuildOnUnAppliedCriticalItems = !Settings->bStopBuildOnUnAppliedCriticalItems;
Settings->SaveConfig();
const OculusXRTelemetry::TScopedMarker<OculusXRTelemetry::Events::FProjectSetupToolOption> OptionEvent;
const auto& Annotated = OptionEvent
.AddAnnotation(OculusXRTelemetry::Annotations::Uid, "bStopBuildOnUnAppliedCriticalItems")
.AddAnnotation(OculusXRTelemetry::Annotations::Value,
Settings->bStopBuildOnUnAppliedCriticalItems ? "true" : "false");
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([Settings]() -> bool {
return Settings->bStopBuildOnUnAppliedCriticalItems;
})),
NAME_None,
EUserInterfaceActionType::ToggleButton);
ButtonWidget->AddSlot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(FMargin(8.f, 0.f, 8.f, 0.f))
[SNew(SComboButton)
.ContentPadding(0)
.HasDownArrow(false)
.ForegroundColor(FSlateColor::UseForeground())
.ComboButtonStyle(FAppStyle::Get(), "SimpleComboButton")
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ViewOptions")))
.MenuContent()
[DetailViewOptions.MakeWidget()]
.ButtonContent()
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
[SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Toolbar.Settings"))
.ColorAndOpacity(FSlateColor::UseForeground())]]];
// Top level container for the whole row
const TSharedPtr<SHorizontalBox> RowWidget = SNew(SHorizontalBox);
RowWidget->AddSlot()
.HAlign(HAlign_Left)
[TitleWidget.ToSharedRef()];
RowWidget->AddSlot()
.HAlign(HAlign_Right)
[ButtonWidget.ToSharedRef()];
// Add into the root container
RootContainer->AddSlot()
.AutoHeight()
.Padding(0)
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.GridLine"))
.Padding(FMargin(0, 0, 0, 1))
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.CategoryTop"))
.BorderBackgroundColor(FSlateColor(FLinearColor::White))
.Padding(0)
[SNew(SBox)
.MinDesiredHeight(26.0f)
[RowWidget.ToSharedRef()]]]]];
}
/**
* Build the filter section layout
*/
void SOculusXRProjectSetupToolWidget::BuildFilterSectionLayout(const TSharedPtr<SVerticalBox>& RootContainer, const uint32 PlatformFilterIndex)
{
// Top level container for the whole row
const TSharedPtr<SHorizontalBox> RowWidget = SNew(SHorizontalBox);
uint32 index = 0;
for (const auto& Platform : PlatformFilters)
{
const FSlateBrush* BorderBrush = FAppStyle::Get().GetBrush("DetailsView.GridLine");
if (index == PlatformFilterIndex)
{
BorderBrush = FAppStyle::Get().GetBrush("DetailsView.CategoryTop");
}
++index;
RowWidget->AddSlot()
.Padding(FMargin(0, 1, 1, 1))
[SNew(SBorder)
.BorderImage(BorderBrush)
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ContentPadding(0)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
.OnClicked(this, &SOculusXRProjectSetupToolWidget::OnPlatformFilterChanged, Platform)
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0, 3, 3, 3)
[SNew(SImage)
.Image(
FOculusXRProjectSetupToolModule::GetSlateStyle()
->GetBrush(OculusXRPSTUtils::GetDisplayName(
Platform)))
.ColorAndOpacity(FSlateColor::UseForeground())]
+ SHorizontalBox::Slot()
.Padding(3, 0, 0, 0)
.AutoWidth()
.VAlign(VAlign_Center)
[SNew(STextBlock)
.Text(FText::FromString(OculusXRPSTUtils::GetDisplayName(Platform)))]]]]];
}
// Add into the root container
RootContainer->AddSlot()
.AutoHeight()
.Padding(0)
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.GridLine"))
.Padding(FMargin(0, 0, 0, 1))
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.CategoryTop"))
.BorderBackgroundColor(FSlateColor(FLinearColor::White))
.Padding(0)
[SNew(SBox)
.MinDesiredHeight(26.0f)
[RowWidget.ToSharedRef()]]]]];
}
/**
* Build a container for a rules section
*/
TSharedPtr<SVerticalBox> SOculusXRProjectSetupToolWidget::BuildRulesContainerLayout(const TSharedPtr<SScrollBox>& RootContainer, ERulesSection Section, const FText& SectionTitle)
{
// Top level container for the whole row
const TSharedPtr<SHorizontalBox> TitleAndExpanderWidget = SNew(SHorizontalBox)
.Clipping(EWidgetClipping::OnDemand);
TitleAndExpanderWidget->AddSlot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(8, 0, 8, 0)
.AutoWidth()
[SNew(SHorizontalBox)
.Visibility_Static(&SOculusXRProjectSetupToolWidget::OnHeaderExpanderVisibility, Section)
+ SHorizontalBox::Slot()
[SNew(SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ClickMethod(EButtonClickMethod::MouseDown)
.OnClicked(this, &SOculusXRProjectSetupToolWidget::OnHeaderExpanderClicked, Section)
.ContentPadding(0)
.IsFocusable(false)
[SNew(SImage)
.Image(this, &SOculusXRProjectSetupToolWidget::GetHeaderExpanderImage, Section)
.ColorAndOpacity(FSlateColor::UseSubduedForeground())]]];
TitleAndExpanderWidget->AddSlot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(0, 0, 0, 0)
[SNew(STextBlock)
.Font(FAppStyle::Get().GetFontStyle(TEXT("DetailsView.CategoryFontStyle")))
.TextStyle(FAppStyle::Get(), "DetailsView.CategoryTextStyle")
.Text(SectionTitle)];
const TSharedPtr<SHorizontalBox> EmptyDescriptionWidget = SNew(SHorizontalBox);
const TSharedPtr<SHorizontalBox> ButtonWidget = SNew(SHorizontalBox);
if (Section == ERulesSection::Required || Section == ERulesSection::Recommended)
{
ButtonWidget->AddSlot()
.Padding(FMargin(6, 3, 3, 3))
[SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ButtonStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("PrimaryButton"))
.Text(LOCTEXT("ApplyAllRules", "Apply All"))
.OnClicked(this, &SOculusXRProjectSetupToolWidget::OnApplyAllRulesClicked, Section)
.IsEnabled_Raw(this, &SOculusXRProjectSetupToolWidget::OnApplyAllRulesEnabled, Section)];
// This is not actually needed but we add it to ensure correct alignment
ButtonWidget->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(FMargin(4.f, 0.f, 8.f, 0.f))
[SNew(SComboButton)
.ContentPadding(0)
.Visibility_Lambda([this]() -> EVisibility { return EVisibility::Hidden; })
.HasDownArrow(false)
.ForegroundColor(FSlateColor::UseForeground())
.ComboButtonStyle(FAppStyle::Get(), "SimpleComboButton")
.ButtonContent()
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
[SNew(SImage)
.Image(FAppStyle::Get().GetBrush("PropertyWindow.Button_Ellipsis"))
.ColorAndOpacity(FSlateColor::UseForeground())]]];
}
// Splitter so each row can contain Name | Description | Buttons
const TSharedPtr<SSplitter> Splitter = SNew(SSplitter)
.Style(FAppStyle::Get(), "DetailsView.Splitter")
.PhysicalSplitterHandleSize(1.0f)
.HitDetectionSplitterHandleSize(5.0f);
// Add the widgets to the splitter
Splitter->AddSlot()
.Value(ColumnSizeData->GetNameColumnWidth())
.OnSlotResized(ColumnSizeData->GetOnNameColumnResized())
[TitleAndExpanderWidget.ToSharedRef()];
Splitter->AddSlot()
.Value(ColumnSizeData->GetValueColumnWidth())
.OnSlotResized(ColumnSizeData->GetOnValueColumnResized())
[EmptyDescriptionWidget.ToSharedRef()];
Splitter->AddSlot()
.Value(ColumnSizeData->GetRightColumnWidth())
.OnSlotResized(ColumnSizeData->GetOnRightColumnResized())
.MinSize(ColumnSizeData->GetRightColumnMinWidth())
[ButtonWidget.ToSharedRef()];
// Top level container for the whole row
const TSharedPtr<SWidget> RowWidget = SNew(SBox)
.Padding(0)
[Splitter.ToSharedRef()];
// Add into the root container
RootContainer->AddSlot()
.Padding(0)
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.GridLine"))
.Padding(FMargin(0, 0, 0, 1))
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.CategoryTop"))
.BorderBackgroundColor(FSlateColor(FLinearColor::White))
.Padding(0)
[SNew(SBox)
.MinDesiredHeight(26.0f)
[RowWidget.ToSharedRef()]]]]];
// Contents
TSharedPtr<SVerticalBox> SectionContentContainer = SNew(SVerticalBox);
RootContainer->AddSlot()
.Padding(0)
[SectionContentContainer.ToSharedRef()];
return SectionContentContainer;
}
/**
* Build the layout for a single row (filter or rule row)
*/
void SOculusXRProjectSetupToolWidget::BuildRowItemLayout(const TSharedPtr<SVerticalBox>& SectionContentContainer, ERulesSection Section, const SetupRulePtr& Rule, const uint32 PlatformFilterIndex)
{
// RowWidget -> Splitter -> (NameWidget | DescriptionWidget | ButtonWidget)
const bool bShouldHideApplyButton = Section == ERulesSection::Applied;
const FSlateBrush* IconBrush = nullptr;
if (Rule != nullptr)
{
if (Section == ERulesSection::Applied)
{
IconBrush = FAppStyle::Get().GetBrush("Icons.SuccessWithColor");
}
else if (Rule->GetSeverity() == ESetupRuleSeverity::Critical)
{
IconBrush = FAppStyle::Get().GetBrush("Icons.ErrorWithColor");
}
else
{
IconBrush = FAppStyle::Get().GetBrush("Icons.WarningWithColor");
}
}
// Name widget
const TSharedPtr<SHorizontalBox> NameWidget = SNew(SHorizontalBox)
.Clipping(EWidgetClipping::OnDemand);
if (IconBrush != nullptr)
{
NameWidget->AddSlot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(8, 0, 8, 0)
.AutoWidth()
[SNew(SImage)
.Image(IconBrush)];
}
NameWidget->AddSlot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(0, 0, 0, 0)
[SNew(STextBlock)
.Font(FAppStyle::Get().GetFontStyle(TEXT("PropertyWindow.NormalFont")))
.ColorAndOpacity(FSlateColor::UseStyle())
.Text(Rule != nullptr ? Rule->GetDisplayName() : FText::GetEmpty())];
// Description widget
const TSharedPtr<SHorizontalBox> DescriptionWidget = SNew(SHorizontalBox)
.Clipping(EWidgetClipping::OnDemand);
DescriptionWidget->AddSlot()
.VAlign(VAlign_Center)
.Padding(6, 0)
[SNew(STextBlock)
.Font(FAppStyle::Get().GetFontStyle(TEXT("PropertyWindow.NormalFont")))
.ColorAndOpacity(FSlateColor::UseStyle())
.Text(Rule != nullptr ? Rule->GetDescription() : FText::GetEmpty())];
// Button widget. There are two buttons slots that are customised based on section
const TSharedPtr<SHorizontalBox> ButtonWidget = SNew(SHorizontalBox);
ButtonWidget->AddSlot()
.Padding(FMargin(6, 3, 3, 3))
[SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Text(LOCTEXT("ApplyRule", "Apply"))
.Visibility_Lambda([bShouldHideApplyButton]() -> EVisibility { return bShouldHideApplyButton ? EVisibility::Hidden : EVisibility::Visible; })
.OnClicked(this, &SOculusXRProjectSetupToolWidget::OnApplyRuleClicked, Rule)
.IsEnabled_Static(&SOculusXRProjectSetupToolWidget::OnApplyRuleEnabled, Section)];
FMenuBuilder EllipsisMenuBuilder(true, nullptr);
const auto IgnoreButtonText = Section == ERulesSection::Ignored ? LOCTEXT("UnignoreRule", "Unignore") : LOCTEXT("IgnoreRule", "Ignore");
const auto IgnoreButtonTooltip = Section == ERulesSection::Ignored ? LOCTEXT("UnignoreRule_Tooltip", "Unignore this rule") : LOCTEXT("IgnoreRule_Tooltip", "Ignore this rule");
const FString SupportURL(TEXT("https://forums.oculusvr.com/developer"));
EllipsisMenuBuilder.AddMenuEntry(
IgnoreButtonText,
IgnoreButtonTooltip,
FSlateIcon(),
FUIAction(
FExecuteAction::CreateRaw(this, &SOculusXRProjectSetupToolWidget::OnIgnoreRuleClicked, Rule, Section),
FCanExecuteAction::CreateStatic(&SOculusXRProjectSetupToolWidget::OnIgnoreRuleEnabled, Section)));
EllipsisMenuBuilder.AddMenuEntry(
LOCTEXT("SupportButton", "Support"),
LOCTEXT("SupportButton_ToolTip", "Get more information about this rule."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Comment"),
FUIAction(
FExecuteAction::CreateLambda([SupportURL] { FPlatformProcess::LaunchURL(*SupportURL, nullptr, nullptr); }),
FCanExecuteAction()));
ButtonWidget->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(FMargin(4.f, 0.f, 8.f, 0.f))
[SNew(SComboButton)
.ContentPadding(0)
.HasDownArrow(false)
.Visibility_Lambda([bShouldHideApplyButton]() -> EVisibility { return bShouldHideApplyButton ? EVisibility::Hidden : EVisibility::Visible; })
.ForegroundColor(FSlateColor::UseForeground())
.ComboButtonStyle(FAppStyle::Get(), "SimpleComboButton")
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ViewOptions")))
.MenuContent()
[EllipsisMenuBuilder.MakeWidget()]
.ButtonContent()
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
[SNew(SImage)
.Image(FAppStyle::Get().GetBrush("PropertyWindow.Button_Ellipsis"))
.ColorAndOpacity(FSlateColor::UseForeground())]]];
// Splitter so each row can contain Name | Description | Buttons
const TSharedPtr<SSplitter> Splitter = SNew(SSplitter)
.Style(FAppStyle::Get(), "DetailsView.Splitter")
.PhysicalSplitterHandleSize(1.0f)
.HitDetectionSplitterHandleSize(5.0f);
// Add the widgets to the splitter
Splitter->AddSlot()
.Value(ColumnSizeData->GetNameColumnWidth())
.OnSlotResized(ColumnSizeData->GetOnNameColumnResized())
[NameWidget.ToSharedRef()];
Splitter->AddSlot()
.Value(ColumnSizeData->GetValueColumnWidth())
.OnSlotResized(ColumnSizeData->GetOnValueColumnResized())
[DescriptionWidget.ToSharedRef()];
Splitter->AddSlot()
.Value(ColumnSizeData->GetRightColumnWidth())
.OnSlotResized(ColumnSizeData->GetOnRightColumnResized())
.MinSize(ColumnSizeData->GetRightColumnMinWidth())
[ButtonWidget.ToSharedRef()];
// Top level container for the whole row
const TSharedPtr<SWidget> RowWidget = SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.CategoryMiddle"))
.BorderBackgroundColor(FAppStyle::Get().GetSlateColor("Colors.Panel"))
.Padding(0)
[Splitter.ToSharedRef()];
// Add a slot into the content container for this item and add the row widget to it
SectionContentContainer->AddSlot()
.AutoHeight()
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.GridLine"))
.Padding(FMargin(0, 0, 0, 1))
.Clipping(EWidgetClipping::ClipToBounds)
.Visibility(this, &SOculusXRProjectSetupToolWidget::OnRowVisibility, Section, PlatformFilters[CurrentPlatformFilterIndex], Rule)
[SNew(SBox)
.MinDesiredHeight(26.0f)
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.Highlight"))
.Padding(0)
[SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("DetailsView.CategoryMiddle"))
.BorderBackgroundColor(FAppStyle::Get().GetSlateColor("Colors.Panel"))
.Padding(0)
[RowWidget.ToSharedRef()]]]]]];
}
/**
* Build the layout for the required rules
*/
void SOculusXRProjectSetupToolWidget::BuildRequiredRulesSectionLayout(const TSharedPtr<SScrollBox>& RootContainer)
{
const UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
if (RuleProcessorSubsystem == nullptr)
{
return;
}
const TSharedPtr<SVerticalBox> SectionContentContainer = BuildRulesContainerLayout(RootContainer, ERulesSection::Required, LOCTEXT("RequiredRules_Title", "Required Rules"));
for (const auto& Rule : RuleProcessorSubsystem->GetRules())
{
if (Rule->GetSeverity() == ESetupRuleSeverity::Critical)
{
BuildRowItemLayout(SectionContentContainer, ERulesSection::Required, Rule, 0);
}
}
}
/**
* Build the layout for the recommended rules
*/
void SOculusXRProjectSetupToolWidget::BuildRecommendedRulesSectionLayout(const TSharedPtr<SScrollBox>& RootContainer)
{
const UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
if (RuleProcessorSubsystem == nullptr)
{
return;
}
const TSharedPtr<SVerticalBox> SectionContentContainer = BuildRulesContainerLayout(RootContainer, ERulesSection::Recommended, LOCTEXT("RecommendedRules_Title", "Recommended Rules"));
for (const auto& Rule : RuleProcessorSubsystem->GetRules())
{
if (Rule->GetSeverity() < ESetupRuleSeverity::Critical)
{
BuildRowItemLayout(SectionContentContainer, ERulesSection::Recommended, Rule, 0);
}
}
}
/**
* Build the layout for the applied rules
*/
void SOculusXRProjectSetupToolWidget::BuildAppliedRulesSectionLayout(const TSharedPtr<SScrollBox>& RootContainer)
{
const UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
if (RuleProcessorSubsystem == nullptr)
{
return;
}
const TSharedPtr<SVerticalBox> SectionContentContainer = BuildRulesContainerLayout(RootContainer, ERulesSection::Applied, LOCTEXT("AppliedRules_Title", "Applied Rules"));
// Add all rules and let the visibility function take care of which ones to show
for (const auto& Rule : RuleProcessorSubsystem->GetRules())
{
BuildRowItemLayout(SectionContentContainer, ERulesSection::Applied, Rule, 0);
}
}
/**
* Build the layout for the ignored rules
*/
void SOculusXRProjectSetupToolWidget::BuildIgnoredRulesSectionLayout(const TSharedPtr<SScrollBox>& RootContainer)
{
const UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
if (RuleProcessorSubsystem == nullptr)
{
return;
}
const TSharedPtr<SVerticalBox> SectionContentContainer = BuildRulesContainerLayout(RootContainer, ERulesSection::Ignored, LOCTEXT("IgnoredRules_Title", "Ignored Rules"));
// Add all rules and let the visibility function take care of which ones to show
for (const auto& Rule : RuleProcessorSubsystem->GetRules())
{
BuildRowItemLayout(SectionContentContainer, ERulesSection::Ignored, Rule, 0);
}
}
/**
* Is an un-applied rule visible?
*/
EVisibility SOculusXRProjectSetupToolWidget::OnRowVisibility(ERulesSection Section, ESetupRulePlatform CurrentPlatform, SetupRulePtr Rule) const
{
if (Section == ERulesSection::Filter)
{
return EVisibility::Visible;
}
if (!BIsSectionExpanded[static_cast<uint32>(Section)])
{
return EVisibility::Collapsed;
}
if (Rule == nullptr)
{
return EVisibility::Collapsed;
}
if (!Rule->IsValid())
{
return EVisibility::Collapsed;
}
if ((Rule->GetPlatform() & CurrentPlatform) != CurrentPlatform)
{
return EVisibility::Collapsed;
}
switch (Section)
{
case ERulesSection::Required:
case ERulesSection::Recommended:
{
if (Rule->IsApplied())
{
return EVisibility::Collapsed;
}
return Rule->IsIgnored() ? EVisibility::Collapsed : EVisibility::Visible;
}
case ERulesSection::Applied:
{
return Rule->IsApplied() ? EVisibility::Visible : EVisibility::Collapsed;
}
case ERulesSection::Ignored:
{
// Applied rules always show in the Applied section even if ignored
if (Rule->IsApplied())
{
return EVisibility::Collapsed;
}
return Rule->IsIgnored() ? EVisibility::Visible : EVisibility::Collapsed;
}
default:
{
break;
}
}
return EVisibility::Collapsed;
}
EVisibility SOculusXRProjectSetupToolWidget::OnRestartEditorNoticeVisibility() const
{
return bShowButtonToRestart ? EVisibility::Visible : EVisibility::Collapsed;
}
FReply SOculusXRProjectSetupToolWidget::OnRestartEditorButtonClicked()
{
bShowButtonToRestart = false;
FUnrealEdMisc::Get().RestartEditor(false);
return FReply::Handled();
}
void SOculusXRProjectSetupToolWidget::Refresh()
{
UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
if (RuleProcessorSubsystem != nullptr)
{
RuleProcessorSubsystem->Refresh();
}
}
void SOculusXRProjectSetupToolWidget::UpdateActiveTimer(bool Register)
{
if (!Register)
{
if (!ActiveTimerHandle.IsValid())
{
return;
}
UnRegisterActiveTimer(ActiveTimerHandle.Pin().ToSharedRef());
return;
}
if (ActiveTimerHandle.IsValid())
{
UnRegisterActiveTimer(ActiveTimerHandle.Pin().ToSharedRef());
}
ActiveTimerHandle = RegisterActiveTimer(
30.f,
FWidgetActiveTimerDelegate::CreateLambda([this](double /*InCurrentTime*/, float /*InDeltaTime*/) {
Refresh();
UpdateProjectStatus();
return EActiveTimerReturnType::Continue;
}));
}
/**
* Apply a new platform filter
*/
FReply SOculusXRProjectSetupToolWidget::OnPlatformFilterChanged(ESetupRulePlatform ItemSelected)
{
const UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
if (RuleProcessorSubsystem == nullptr)
{
return FReply::Handled();
}
const int32 Index = PlatformFilters.Find(ItemSelected);
if (Index != INDEX_NONE)
{
CurrentPlatformFilterIndex = Index;
GetMutableDefault<UOculusXRPSTSettings>()->CurrentPlatform = static_cast<uint32>(ItemSelected);
BuildLayout(RootContainerWidget);
}
return FReply::Handled();
}
/**
* Apply rule button callback
*/
FReply SOculusXRProjectSetupToolWidget::OnApplyRuleClicked(SetupRulePtr Rule)
{
if (Rule == nullptr)
{
return FReply::Handled();
}
bool bShouldRestartEditor = false;
Rule->Apply(bShouldRestartEditor);
bShowButtonToRestart |= bShouldRestartEditor;
UpdateProjectStatus();
return FReply::Handled();
}
/**
* Apply rule enabled callback
*/
bool SOculusXRProjectSetupToolWidget::OnApplyRuleEnabled(ERulesSection Section)
{
return Section != ERulesSection::Applied && Section != ERulesSection::Ignored;
}
/**
* Ignore/Unignore rule button callback
*/
void SOculusXRProjectSetupToolWidget::OnIgnoreRuleClicked(SetupRulePtr Rule, ERulesSection Section)
{
if (Rule == nullptr)
{
return;
}
if (Section != ERulesSection::Ignored)
{
Rule->SetIgnoreRule(true);
}
else
{
Rule->SetIgnoreRule(false);
}
UpdateProjectStatus();
}
/**
* Apply rule button callback
*/
FReply SOculusXRProjectSetupToolWidget::OnApplyAllRulesClicked(ERulesSection Section)
{
bool bShouldRestartEditor = false;
const UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
for (auto& Rule : RuleProcessorSubsystem->GetRules())
{
// Only apply rules that are critical and are in the required section or non-critical and are in the recommended section
bool bShouldApplyRule = Rule->GetSeverity() == ESetupRuleSeverity::Critical && Section == ERulesSection::Required;
bShouldApplyRule = bShouldApplyRule || Rule->GetSeverity() != ESetupRuleSeverity::Critical && Section == ERulesSection::Recommended;
// Only apply rules that in the current platform
bShouldApplyRule = bShouldApplyRule && (Rule->GetPlatform() & PlatformFilters[CurrentPlatformFilterIndex]) == PlatformFilters[CurrentPlatformFilterIndex];
// Only apply rules that are valid
bShouldApplyRule = bShouldApplyRule && Rule->IsValid();
// Only apply rules that are not applied yet
bShouldApplyRule = bShouldApplyRule && !Rule->IsApplied();
// Only apply rules that are not ignored
bShouldApplyRule = bShouldApplyRule && !Rule->IsIgnored();
if (!bShouldApplyRule)
{
continue;
}
Rule->Apply(bShouldRestartEditor);
bShowButtonToRestart |= bShouldRestartEditor;
}
UpdateProjectStatus();
return FReply::Handled();
}
/**
* Apply rule enabled callback
*/
bool SOculusXRProjectSetupToolWidget::OnApplyAllRulesEnabled(ERulesSection Section) const
{
switch (Section)
{
case ERulesSection::Required:
return RuleStatus.PendingRequiredRulesCount > 0;
case ERulesSection::Recommended:
return RuleStatus.PendingRecommendedRulesCount > 0;
default:
return false;
}
}
/**
* Ignore rule enabled callback
*/
bool SOculusXRProjectSetupToolWidget::OnIgnoreRuleEnabled(ERulesSection Section)
{
return Section != ERulesSection::Applied;
}
/**
* Expander visibility
*/
EVisibility SOculusXRProjectSetupToolWidget::OnHeaderExpanderVisibility(ERulesSection Section)
{
return Section == ERulesSection::Filter ? EVisibility::Collapsed : EVisibility::Visible;
}
/**
* Expander image
*/
const FSlateBrush* SOculusXRProjectSetupToolWidget::GetHeaderExpanderImage(ERulesSection Section) const
{
const bool bIsHeaderExpanded = BIsSectionExpanded[static_cast<uint32>(Section)];
FName ResourceName;
if (bIsHeaderExpanded)
{
static const FName ExpandedName = "TreeArrow_Expanded";
ResourceName = ExpandedName;
}
else
{
static const FName CollapsedName = "TreeArrow_Collapsed";
ResourceName = CollapsedName;
}
return FAppStyle::Get().GetBrush(ResourceName);
}
/**
* Expander clicked
*/
FReply SOculusXRProjectSetupToolWidget::OnHeaderExpanderClicked(ERulesSection Section)
{
const uint8 index = static_cast<uint8>(Section);
BIsSectionExpanded[index] = !BIsSectionExpanded[index];
return FReply::Handled();
}
void SOculusXRProjectSetupToolWidget::UpdateProjectStatus()
{
const UOculusXRRuleProcessorSubsystem* RuleProcessorSubsystem = GEngine->GetEngineSubsystem<UOculusXRRuleProcessorSubsystem>();
if (RuleProcessorSubsystem != nullptr)
{
RuleStatus = RuleProcessorSubsystem->UnAppliedRulesStatus(PlatformFilters[CurrentPlatformFilterIndex]);
}
UpdateProjectStatusString();
}
/**
* Build the project status string
*/
void SOculusXRProjectSetupToolWidget::UpdateProjectStatusString() const
{
if (ProjectStatusWidget == nullptr)
{
return;
}
FString Status;
if (RuleStatus.PendingRequiredRulesCount == 0 && RuleStatus.PendingRecommendedRulesCount == 0)
{
Status = TEXT("Current Project Status: All rules have been applied");
}
else if (RuleStatus.PendingRequiredRulesCount == 0)
{
Status = FString::Printf(TEXT("Current Project Status: There are %d recommended rules to apply"), RuleStatus.PendingRecommendedRulesCount);
}
else if (RuleStatus.PendingRecommendedRulesCount == 0)
{
Status = FString::Printf(TEXT("Current Project Status: There are %d required rules to apply"), RuleStatus.PendingRequiredRulesCount);
}
else
{
Status = FString::Printf(TEXT("Current Project Status: There are %d required rules and %d recommended rules to apply"), RuleStatus.PendingRequiredRulesCount, RuleStatus.PendingRecommendedRulesCount);
}
ProjectStatusWidget->SetText(FText::FromString(Status));
}
#undef LOCTEXT_NAMESPACE