// Copyright Epic Games, Inc. All Rights Reserved. #include "LiveLinkFaceSourceCustomization.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "LiveLinkFaceSourceSettings.h" #include "CoreMinimal.h" #include "ObjectTools.h" #include "SLiveLinkFaceDiscoveryPanel.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "LiveLinkFaceSourceCustomization" FLiveLinkFaceSourceCustomization::FLiveLinkFaceSourceCustomization() : LiveLinkFaceDiscovery(MakeShared()) { LiveLinkFaceDiscovery->OnServersUpdated.BindLambda([this](const TSet& InServers) { ListServers.Reset(); for (const FLiveLinkFaceDiscovery::FServer& Server : InServers) { ListServers.Add(MakeShared(Server)); } ListServers.Sort([](const TSharedPtr& Server1, const TSharedPtr& Server2) { if (Server1->Name == Server2->Name) { return Server1->Id < Server2->Id; } else { return Server1->Name < Server2->Name; } }); if (DiscoveryPanel.IsValid()) { DiscoveryPanel->Refresh(); } }); LiveLinkFaceDiscovery->Start(); } FLiveLinkFaceSourceCustomization::~FLiveLinkFaceSourceCustomization() { LiveLinkFaceDiscovery->OnServersUpdated.Unbind(); LiveLinkFaceDiscovery->Stop(); } void FLiveLinkFaceSourceCustomization::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) { TArray> Objects; InDetailBuilder.GetObjectsBeingCustomized(Objects); check(Objects.Num() == 1); ULiveLinkFaceSourceSettings* Settings = Cast(Objects[0]); IDetailCategoryBuilder& CategoryBuilder = InDetailBuilder.EditCategory("Server", LOCTEXT("Server", "Server"), ECategoryPriority::Important); DiscoveryPanel = SNew(SLiveLinkFaceDiscoveryPanel) .Servers(&ListServers) .OnServerSingleClicked_Lambda([this, Settings](const FString& InServerHost, const uint16 InServerPort) { Settings->SetAddress(InServerHost); Settings->SetPort(InServerPort); Validate(Settings); }) .OnServerDoubleClicked_Lambda([this, Settings](const FString& InServerHost, const uint16 InServerPort) { Settings->SetAddress(InServerHost); Settings->SetPort(InServerPort); if (Validate(Settings)) { Settings->RequestConnect(); } }); CategoryBuilder.AddCustomRow(LOCTEXT("Discovery", "Discovery")) .WholeRowContent() [ DiscoveryPanel.ToSharedRef() ]; AddressTextBox = SNew(SEditableTextBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text_Lambda([Settings] { return FText::FromString(Settings->GetAddress()); }) .OnTextChanged_Lambda([this, Settings](const FText& InAddress) { Settings->SetAddress(InAddress.ToString()); Validate(Settings); }); const FText AddressLabel = LOCTEXT("Address", "Address"); CategoryBuilder.AddCustomRow(AddressLabel) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .ToolTipText(LOCTEXT("AddressTooltip", "The network address of the server.")) .Text(AddressLabel) ] .ValueContent() [ AddressTextBox.ToSharedRef() ]; PortEntryBox = SNew(SNumericEntryBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value_Lambda([Settings] { return Settings->GetPort(); }) .OnValueChanged_Lambda([this, Settings](const uint16 InPort) { Settings->SetPort(InPort); Validate(Settings); }); const FText PortLabel = LOCTEXT("Port", "Port"); CategoryBuilder.AddCustomRow(PortLabel) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .ToolTipText(LOCTEXT("PortTooltip", "The control port of the server.")) .Text(PortLabel) ] .ValueContent() [ PortEntryBox.ToSharedRef() ]; SubjectNameTextBox = SNew(SEditableTextBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text_Lambda([Settings] { return FText::FromString(Settings->GetSubjectName()); }) .OnTextChanged_Lambda([this, Settings](const FText& InSubjectName) { Settings->SetSubjectName(InSubjectName.ToString()); Validate(Settings); }); const FText SubjectNameLabel = LOCTEXT("SubjectName", "Subject Name"); CategoryBuilder.AddCustomRow(SubjectNameLabel) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .ToolTipText(LOCTEXT("SubjectNameTooltip", "The subject name to assign to this animation stream.")) .Text(SubjectNameLabel) ] .ValueContent() [ SubjectNameTextBox.ToSharedRef() ]; ButtonTextStyle = FCoreStyle::Get().GetWidgetStyle("ButtonText"); ButtonTextStyle.SetFont(IDetailLayoutBuilder::GetDetailFont()); CategoryBuilder.AddCustomRow(LOCTEXT("Connect", "Connect")) .ValueContent() [ SNew(SButton) .Text(LOCTEXT("Connect", "Connect")) .TextStyle(&ButtonTextStyle) .HAlign(HAlign_Center) .IsEnabled_Lambda([this] { return bIsConnectEnabled; }) .OnClicked_Lambda([Settings]() { Settings->RequestConnect(); return FReply::Handled(); }) ]; } TSharedRef FLiveLinkFaceSourceCustomization::MakeInstance() { return MakeShared(); } bool FLiveLinkFaceSourceCustomization::Validate(const ULiveLinkFaceSourceSettings* InSettings) { bool bIsAddressValid = false; bool bIsSubjectValid = false; if (ensure(IsValid(InSettings))) { bIsAddressValid = InSettings->IsAddressValid(); if (!bIsAddressValid) { AddressTextBox->SetError(LOCTEXT("InvalidAddress", "Invalid address")); } else { AddressTextBox->SetError(TEXT("")); } FText InvalidReason; const FName TestSubjectName(InSettings->GetSubjectName()); bIsSubjectValid = TestSubjectName.IsValidObjectName(InvalidReason); SubjectNameTextBox->SetError(InvalidReason); } bIsConnectEnabled = bIsAddressValid && bIsSubjectValid; return bIsConnectEnabled; } #undef LOCTEXT_NAMESPACE