// Copyright Epic Games, Inc. All Rights Reserved. #include "MediaPlateCustomization.h" #include "CineCameraSettings.h" #include "Components/StaticMeshComponent.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "IMediaAssetsModule.h" #include "LevelEditorViewport.h" #include "MediaPlate.h" #include "MediaPlateComponent.h" #include "MediaPlateEditorModule.h" #include "MediaPlateEditorStyle.h" #include "MediaPlayer.h" #include "MediaPlayerEditorModule.h" #include "PropertyCustomizationHelpers.h" #include "ScopedTransaction.h" #include "Styling/AppStyle.h" #include "Subsystems/AssetEditorSubsystem.h" #include "ThumbnailRendering/ThumbnailManager.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Input/SSegmentedControl.h" #include "Widgets/SMediaPlateEditorMediaDetails.h" #define LOCTEXT_NAMESPACE "FMediaPlateCustomization" /* IDetailCustomization interface *****************************************************************************/ FMediaPlateCustomization::FMediaPlateCustomization() { } FMediaPlateCustomization::~FMediaPlateCustomization() { } void FMediaPlateCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { TWeakPtr WeakSelf = StaticCastWeakPtr(AsWeak()); // Is this the media plate editor window? bool bIsMediaPlateWindow = false; if (TSharedPtr DetailsView = DetailBuilder.GetDetailsViewSharedPtr()) { TSharedPtr HostTabManager = DetailsView->GetHostTabManager(); bIsMediaPlateWindow = (HostTabManager.IsValid() == false); } // Get style. const ISlateStyle* Style = &FMediaPlateEditorStyle::Get().Get(); CustomizeCategories(DetailBuilder); IDetailCategoryBuilder& ControlCategory = DetailBuilder.EditCategory("Control"); IDetailCategoryBuilder& PlaylistCategory = DetailBuilder.EditCategory("Playlist"); IDetailCategoryBuilder& GeometryCategory = DetailBuilder.EditCategory("Geometry"); IDetailCategoryBuilder& MediaDetailsCategory = DetailBuilder.EditCategory("MediaDetails"); // Get objects we are editing. TArray> Objects; DetailBuilder.GetObjectsBeingCustomized(Objects); MediaPlatesList.Reserve(Objects.Num()); for (TWeakObjectPtr& Obj : Objects) { TWeakObjectPtr MediaPlate = Cast(Obj.Get()); if (MediaPlate.IsValid()) { MediaPlatesList.Add(MediaPlate); MeshMode = MediaPlate->GetVisibleMipsTilesCalculations(); } } // Add mesh customization. AddMeshCustomization(GeometryCategory); // Add media plate source MediaPlateResourcePropertyHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMediaPlateComponent, MediaPlateResource)); if (MediaPlateResourcePropertyHandle) { PlaylistCategory.AddProperty(MediaPlateResourcePropertyHandle); } // Add media player playback slider if (IMediaPlayerEditorModule* MediaPlayerEditorModule = FModuleManager::LoadModulePtr("MediaPlayerEditor")) { const TSharedRef MediaPlayerSlider = MediaPlayerEditorModule->CreateMediaPlayerSliderWidget(GetMediaPlayers()); MediaPlayerSlider->SetSliderHandleColor(FSlateColor(EStyleColor::AccentBlue)); MediaPlayerSlider->SetVisibleWhenInactive(EVisibility::Visible); ControlCategory.AddCustomRow(LOCTEXT("MediaPlatePlaybackPosition", "Playback Position")) [ MediaPlayerSlider ]; } // Add media control buttons. ControlCategory.AddCustomRow(LOCTEXT("MediaPlateControls", "MediaPlate Controls")) [ SNew(SHorizontalBox) // Rewind button. + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Center) .IsEnabled_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { return Self->IsButtonEventAllowedForAnyPlate(EMediaPlateEventState::Rewind); } return false; }) .OnClicked_Lambda([WeakSelf]() -> FReply { if (const TSharedPtr Self = WeakSelf.Pin()) { Self->OnButtonEvent(EMediaPlateEventState::Rewind); return FReply::Handled(); } return FReply::Unhandled(); }) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Style->GetBrush("MediaPlateEditor.RewindMedia.Small")) .ToolTipText(LOCTEXT("Rewind", "Rewind the media to the beginning")) ] ] // Reverse button. + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Center) .IsEnabled_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { return Self->IsButtonEventAllowedForAnyPlate(EMediaPlateEventState::Reverse); } return false; }) .OnClicked_Lambda([WeakSelf]() -> FReply { if (const TSharedPtr Self = WeakSelf.Pin()) { Self->OnButtonEvent(EMediaPlateEventState::Reverse); return FReply::Handled(); } return FReply::Unhandled(); }) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Style->GetBrush("MediaPlateEditor.ReverseMedia.Small")) .ToolTipText(LOCTEXT("Reverse", "Reverse media playback")) ] ] // Play button. + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Center) .IsEnabled_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { return Self->IsButtonEventAllowedForAnyPlate(EMediaPlateEventState::Play); } return false; }) .Visibility_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { const bool bAllPlaying = Self->IsTrueForAllPlayers([](const UMediaPlayer* InMediaPlayer) { return InMediaPlayer->IsPlaying(); }); return bAllPlaying ? EVisibility::Collapsed : EVisibility::Visible; } return EVisibility::Visible; }) .OnClicked_Lambda([WeakSelf]() -> FReply { if (const TSharedPtr Self = WeakSelf.Pin()) { Self->OnButtonEvent(EMediaPlateEventState::Play); return FReply::Handled(); } return FReply::Unhandled(); }) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Style->GetBrush("MediaPlateEditor.PlayMedia.Small")) .ToolTipText(LOCTEXT("Play", "Start media playback")) ] ] // Pause button. + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Center) .IsEnabled_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { return Self->IsButtonEventAllowedForAnyPlate(EMediaPlateEventState::Pause); } return false; }) .Visibility_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { // We want this logic to be as mutually exclusive with the play button visibility as possible so // they don't show at the same time and cause other buttons to move around. const bool bAllPaused = Self->IsTrueForAllPlayers([](const UMediaPlayer* InMediaPlayer) { return !InMediaPlayer->IsPlaying(); // Not using IsPaused() as it is not the logical inverse of IsPlaying. }); return bAllPaused ? EVisibility::Collapsed : EVisibility::Visible; } return EVisibility::Collapsed; }) .OnClicked_Lambda([WeakSelf]() -> FReply { if (const TSharedPtr Self = WeakSelf.Pin()) { Self->OnButtonEvent(EMediaPlateEventState::Pause); return FReply::Handled(); } return FReply::Unhandled(); }) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Style->GetBrush("MediaPlateEditor.PauseMedia.Small")) .ToolTipText(LOCTEXT("Pause", "Pause media playback")) ] ] // Forward button. + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Center) .IsEnabled_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { return Self->IsButtonEventAllowedForAnyPlate(EMediaPlateEventState::Forward); } return false; }) .OnClicked_Lambda([WeakSelf]() -> FReply { if (const TSharedPtr Self = WeakSelf.Pin()) { Self->OnButtonEvent(EMediaPlateEventState::Forward); return FReply::Handled(); } return FReply::Unhandled(); }) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Style->GetBrush("MediaPlateEditor.ForwardMedia.Small")) .ToolTipText(LOCTEXT("Forward", "Fast forward media playback")) ] ] // Open button. + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Center) .IsEnabled_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { return Self->IsButtonEventAllowedForAnyPlate(EMediaPlateEventState::Open); } return true; }) .OnClicked_Lambda([WeakSelf]() -> FReply { if (const TSharedPtr Self = WeakSelf.Pin()) { Self->OnButtonEvent(EMediaPlateEventState::Open); return FReply::Handled(); } return FReply::Unhandled(); }) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Style->GetBrush("MediaPlateEditor.OpenMedia.Small")) .ToolTipText(LOCTEXT("Open", "Open the current media")) ] ] // Close button. + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Center) .IsEnabled_Lambda([WeakSelf] { if (const TSharedPtr Self = WeakSelf.Pin()) { return Self->IsButtonEventAllowedForAnyPlate(EMediaPlateEventState::Close); } return false; }) .OnClicked_Lambda([WeakSelf]() -> FReply { if (const TSharedPtr Self = WeakSelf.Pin()) { Self->OnButtonEvent(EMediaPlateEventState::Close); return FReply::Handled(); } return FReply::Unhandled(); }) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(Style->GetBrush("MediaPlateEditor.CloseMedia.Small")) .ToolTipText(LOCTEXT("Close", "Close the currently opened media")) ] ] ]; // Add button to open the media plate editor. if (bIsMediaPlateWindow == false) { PlaylistCategory.AddCustomRow(LOCTEXT("OpenMediaPlate", "Open Media Plate")) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.f) .Padding(0, 5, 10, 5) [ SNew(SButton) .ContentPadding(3.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .OnClicked(this, &FMediaPlateCustomization::OnOpenMediaPlate) .Text(LOCTEXT("OpenMediaPlate", "Open Media Plate")) ] ]; // Get the first media plate. UMediaPlateComponent* FirstMediaPlate = nullptr; for (TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { FirstMediaPlate = MediaPlate; break; } } if (FirstMediaPlate != nullptr) { MediaDetailsCategory.AddCustomRow(FText::FromString(TEXT("Media Details"))) [ SNew(SMediaPlateEditorMediaDetails, *FirstMediaPlate) ]; } } } void FMediaPlateCustomization::AddMeshCustomization(IDetailCategoryBuilder& InParentCategory) { TWeakPtr WeakSelf = StaticCastWeakPtr(AsWeak()); // Add radio buttons for mesh type. InParentCategory.AddCustomRow(FText::FromString("Mesh Selection")) [ SNew(SSegmentedControl) .Value_Lambda([WeakSelf]() { const TSharedPtr Self = WeakSelf.Pin(); return Self.IsValid() ? Self->MeshMode : EMediaTextureVisibleMipsTiles::None; }) .OnValueChanged(this, &FMediaPlateCustomization::SetMeshMode) + SSegmentedControl::Slot(EMediaTextureVisibleMipsTiles::Plane) .Text(LOCTEXT("Plane", "Plane")) .ToolTip(LOCTEXT("Plane_ToolTip", "Select this if you want to use a standard plane for the mesh.")) + SSegmentedControl::Slot(EMediaTextureVisibleMipsTiles::Sphere) .Text(LOCTEXT("Sphere", "Sphere")) .ToolTip(LOCTEXT("Sphere_ToolTip", "Select this if you want to use a spherical object for the mesh.")) + SSegmentedControl::Slot(EMediaTextureVisibleMipsTiles::None) .Text(LOCTEXT("Custom", "Custom")) .ToolTip(LOCTEXT("Custom_ToolTip", "Select this if you want to provide your own mesh.")) ]; // Visibility attributes. TAttribute MeshCustomVisibility(this, &FMediaPlateCustomization::ShouldShowMeshCustomWidgets); TAttribute MeshPlaneVisibility(this, &FMediaPlateCustomization::ShouldShowMeshPlaneWidgets); TAttribute MeshSphereVisibility(this, &FMediaPlateCustomization::ShouldShowMeshSphereWidgets); // Add aspect ratio. InParentCategory.AddCustomRow(FText::FromString("Mesh Selection")) .Visibility(MeshPlaneVisibility) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("AspectRatio", "Aspect Ratio")) .ToolTipText(LOCTEXT("AspectRatio_ToolTip", "Sets the aspect ratio of the plane showing the media.\nChanging this will change the scale of the mesh component.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SHorizontalBox) // Presets button. + SHorizontalBox::Slot() .AutoWidth() [ SNew(SComboButton) .OnGetMenuContent(this, &FMediaPlateCustomization::OnGetAspectRatios) .ContentPadding(2.0f) .ButtonContent() [ SNew(STextBlock) .ToolTipText(LOCTEXT("Presets_ToolTip", "Select one of the presets for the aspect ratio.")) .Text(LOCTEXT("Presets", "Presets")) ] ] // Numeric entry box. + SHorizontalBox::Slot() .AutoWidth() [ SNew(SSpinBox) .Value(this, &FMediaPlateCustomization::GetAspectRatio) .MinValue(0.0f) .OnValueChanged(this, &FMediaPlateCustomization::SetAspectRatio) ] ]; // Add letterbox aspect ratio. InParentCategory.AddCustomRow(FText::FromString("Aspect Ratio")) .Visibility(MeshPlaneVisibility) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("LetterboxAspectRatio", "Letterbox Aspect Ratio")) .ToolTipText(LOCTEXT("LetterboxAspectRatio_ToolTip", "Sets the aspect ratio of the whole screen.\n" "If the screen is larger than the media then letterboxes will be added.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SHorizontalBox) // Presets button. + SHorizontalBox::Slot() .AutoWidth() [ SNew(SComboButton) .OnGetMenuContent(this, &FMediaPlateCustomization::OnGetLetterboxAspectRatios) .ContentPadding(2.0f) .ButtonContent() [ SNew(STextBlock) .ToolTipText(LOCTEXT("Presets_ToolTip", "Select one of the presets for the aspect ratio.")) .Text(LOCTEXT("Presets", "Presets")) ] ] // Numeric entry box. + SHorizontalBox::Slot() .AutoWidth() [ SNew(SSpinBox) .Value(this, &FMediaPlateCustomization::GetLetterboxAspectRatio) .MinValue(0.0f) .OnValueChanged(this, &FMediaPlateCustomization::SetLetterboxAspectRatio) ] ]; // Add auto aspect ratio. InParentCategory.AddCustomRow(FText::FromString("Aspect Ratio")) .Visibility(MeshPlaneVisibility) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("AutoAspectRatio", "Auto Aspect Ratio")) .ToolTipText(LOCTEXT("AutoAspectRatio_ToolTip", "Sets the aspect ratio to match the media.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMediaPlateCustomization::IsAspectRatioAuto) .OnCheckStateChanged(this, &FMediaPlateCustomization::SetIsAspectRatioAuto) ]; // Add sphere horizontal arc. InParentCategory.AddCustomRow(FText::FromString("Horizontal Arc")) .Visibility(MeshSphereVisibility) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("HorizontalArc", "Horizontal Arc")) .ToolTipText(LOCTEXT("HorizontalArc_ToolTip", "Sets the horizontal arc size of the sphere in degrees.\nFor example 360 for a full circle, 180 for a half circle.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SNumericEntryBox) .Value(this, &FMediaPlateCustomization::GetMeshHorizontalRange) .OnValueChanged(this, &FMediaPlateCustomization::SetMeshHorizontalRange) ]; // Add sphere vertical arc. InParentCategory.AddCustomRow(FText::FromString("Vertical Arc")) .Visibility(MeshSphereVisibility) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("VerticalArc", "Vertical Arc")) .ToolTipText(LOCTEXT("VerticalArc_ToolTip", "Sets the vertical arc size of the sphere in degrees.\nFor example 180 for a half circle, 90 for a quarter circle.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SNumericEntryBox) .Value(this, &FMediaPlateCustomization::GetMeshVerticalRange) .OnValueChanged(this, &FMediaPlateCustomization::SetMeshVerticalRange) ]; // Add static mesh. InParentCategory.AddCustomRow(FText::FromString("Static Mesh")) .Visibility(MeshCustomVisibility) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("StaticMesh", "Static Mesh")) .ToolTipText(LOCTEXT("StaticMesh_Tooltip", "The static mesh to use.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SObjectPropertyEntryBox) .AllowedClass(UStaticMesh::StaticClass()) .ThumbnailPool(UThumbnailManager::Get().GetSharedThumbnailPool()) .ObjectPath(this, &FMediaPlateCustomization::GetStaticMeshPath) .OnObjectChanged(this, &FMediaPlateCustomization::OnStaticMeshChanged) ]; } EVisibility FMediaPlateCustomization::ShouldShowMeshCustomWidgets() const { return (MeshMode == EMediaTextureVisibleMipsTiles::None) ? EVisibility::Visible : EVisibility::Hidden; } EVisibility FMediaPlateCustomization::ShouldShowMeshPlaneWidgets() const { return (MeshMode == EMediaTextureVisibleMipsTiles::Plane) ? EVisibility::Visible : EVisibility::Hidden; } EVisibility FMediaPlateCustomization::ShouldShowMeshSphereWidgets() const { return (MeshMode == EMediaTextureVisibleMipsTiles::Sphere) ? EVisibility::Visible : EVisibility::Hidden; } void FMediaPlateCustomization::SetMeshMode(EMediaTextureVisibleMipsTiles InMode) { if (MeshMode != InMode) { const FScopedTransaction Transaction(LOCTEXT("SetMeshMode", "Media Plate Mesh Changed")); MeshMode = InMode; for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { // Update the setting in the media plate. MediaPlate->SetVisibleMipsTilesCalculations(MeshMode); // Set the appropriate mesh. if (MeshMode == EMediaTextureVisibleMipsTiles::Plane) { MeshCustomization.SetPlaneMesh(MediaPlate); } else { // Letterboxes are only for planes. SetLetterboxAspectRatio(0.0f); if (MeshMode == EMediaTextureVisibleMipsTiles::Sphere) { SetSphereMesh(MediaPlate); } } } } } } void FMediaPlateCustomization::SetSphereMesh(UMediaPlateComponent* MediaPlate) { MeshCustomization.SetSphereMesh(MediaPlate); } ECheckBoxState FMediaPlateCustomization::IsAspectRatioAuto() const { ECheckBoxState State = ECheckBoxState::Undetermined; for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { ECheckBoxState NewState = MediaPlate->GetIsAspectRatioAuto() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; if (State == ECheckBoxState::Undetermined) { State = NewState; } else if (State != NewState) { // If the media plates have different states then return undetermined. State = ECheckBoxState::Undetermined; break; } } } return State; } void FMediaPlateCustomization::SetIsAspectRatioAuto(ECheckBoxState State) { bool bEnable = (State == ECheckBoxState::Checked); // Loop through all our objects. for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { MediaPlate->SetIsAspectRatioAuto(bEnable); } } } TSharedRef FMediaPlateCustomization::OnGetAspectRatios() { FMenuBuilder MenuBuilder(true, NULL); AddAspectRatiosToMenuBuilder(MenuBuilder, &FMediaPlateCustomization::SetAspectRatio); return MenuBuilder.MakeWidget(); } TSharedRef FMediaPlateCustomization::OnGetLetterboxAspectRatios() { FMenuBuilder MenuBuilder(true, NULL); AddAspectRatiosToMenuBuilder(MenuBuilder, &FMediaPlateCustomization::SetLetterboxAspectRatio); FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &FMediaPlateCustomization::SetLetterboxAspectRatio, 0.0f)); MenuBuilder.AddMenuEntry(LOCTEXT("Disable", "Disable"), FText(), FSlateIcon(), Action); return MenuBuilder.MakeWidget(); } void FMediaPlateCustomization::AddAspectRatiosToMenuBuilder(FMenuBuilder& MenuBuilder, void (FMediaPlateCustomization::* Func)(float)) { TArray const& Presets = UCineCameraSettings::GetFilmbackPresets(); for (const FNamedFilmbackPreset& Preset : Presets) { FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, Func, Preset.FilmbackSettings.SensorAspectRatio)); MenuBuilder.AddMenuEntry(FText::FromString(Preset.Name), FText(), FSlateIcon(), Action); } } void FMediaPlateCustomization::SetAspectRatio(float AspectRatio) { // Loop through all our objects. for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { MediaPlate->SetAspectRatio(AspectRatio); } } // Invalidate the viewport so we can see the mesh change. if (GCurrentLevelEditingViewportClient != nullptr) { GCurrentLevelEditingViewportClient->Invalidate(); } } float FMediaPlateCustomization::GetAspectRatio() const { // Loop through our objects. for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { return MediaPlate->GetAspectRatio(); } } return 1.0f; } void FMediaPlateCustomization::SetLetterboxAspectRatio(float AspectRatio) { for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { MediaPlate->SetLetterboxAspectRatio(AspectRatio); } } // Invalidate the viewport so we can see the mesh change. if (GCurrentLevelEditingViewportClient != nullptr) { GCurrentLevelEditingViewportClient->Invalidate(); } } float FMediaPlateCustomization::GetLetterboxAspectRatio() const { for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { return MediaPlate->GetLetterboxAspectRatio(); } } return 0.0f; } void FMediaPlateCustomization::SetMeshHorizontalRange(float HorizontalRange) { HorizontalRange = FMath::Clamp(HorizontalRange, 1.0f, 360.0f); TOptional VerticalRange = GetMeshVerticalRange(); if (VerticalRange.IsSet()) { FVector2D MeshRange = FVector2D(HorizontalRange, VerticalRange.GetValue()); SetMeshRange(MeshRange); } } TOptional FMediaPlateCustomization::GetMeshHorizontalRange() const { // Loop through our objects. for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { return static_cast(MediaPlate->GetMeshRange().X); } } return TOptional(); } void FMediaPlateCustomization::SetMeshVerticalRange(float VerticalRange) { VerticalRange = FMath::Clamp(VerticalRange, 1.0f, 180.0f); TOptional HorizontalRange = GetMeshHorizontalRange(); if (HorizontalRange.IsSet()) { FVector2D MeshRange = FVector2D(HorizontalRange.GetValue(), VerticalRange); SetMeshRange(MeshRange); } } TOptional FMediaPlateCustomization::GetMeshVerticalRange() const { // Loop through our objects. for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { return static_cast(MediaPlate->GetMeshRange().Y); } } return TOptional(); } void FMediaPlateCustomization::SetMeshRange(FVector2D Range) { const FScopedTransaction Transaction(LOCTEXT("SetMeshRange", "Media Plate Set Mesh Range")); // Loop through all our objects. for (const TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { if (MediaPlate->GetMeshRange() != Range) { MediaPlate->Modify(); MediaPlate->SetMeshRange(Range); SetSphereMesh(MediaPlate); } } } } FString FMediaPlateCustomization::GetStaticMeshPath() const { FString Path; // Get the first media plate. if (MediaPlatesList.Num() > 0) { UMediaPlateComponent* MediaPlate = MediaPlatesList[0].Get(); if (MediaPlate != nullptr) { UStaticMeshComponent* StaticMeshComponent = MediaPlate->StaticMeshComponent; if (StaticMeshComponent != nullptr) { UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh(); if (StaticMesh != nullptr) { Path = StaticMesh->GetPathName(); } } } } return Path; } void FMediaPlateCustomization::OnStaticMeshChanged(const FAssetData& AssetData) { const FScopedTransaction Transaction(LOCTEXT("OnStaticMeshChanged", "Media Plate Custom Mesh Changed")); // Update the static mesh. UStaticMesh* StaticMesh = Cast(AssetData.GetAsset()); for (TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { MeshCustomization.SetCustomMesh(MediaPlate, StaticMesh); } } } bool FMediaPlateCustomization::IsButtonEventAllowedForAnyPlate(EMediaPlateEventState InState) const { // Returns true if any of the selected plate allows the action. for (const TWeakObjectPtr& MediaPlateWeak : MediaPlatesList) { if (UMediaPlateComponent* MediaPlate = MediaPlateWeak.Get()) { if (MediaPlate->IsEventStateChangeAllowed(InState) && IsButtonEventAllowedForPlayer(InState, MediaPlate->GetMediaPlayer())) { return true; } } } return false; } bool FMediaPlateCustomization::IsButtonEventAllowedForPlayer(EMediaPlateEventState InState, UMediaPlayer* InMediaPlayer) const { // Note: centralize the state switch conditions here to make it easier to maintain. if (!InMediaPlayer) { return false; } switch (InState) { case EMediaPlateEventState::Play: // Is player paused or fast forwarding/rewinding? return InMediaPlayer->IsReady() && (!InMediaPlayer->IsPlaying() || InMediaPlayer->GetRate() != 1.0f); case EMediaPlateEventState::Open: return true; // The condition is implemented by the media plate already. case EMediaPlateEventState::Close: return !InMediaPlayer->GetUrl().IsEmpty(); case EMediaPlateEventState::Pause: return InMediaPlayer->CanPause() && !InMediaPlayer->IsPaused(); case EMediaPlateEventState::Reverse: return InMediaPlayer->IsReady() && InMediaPlayer->SupportsRate(UMediaPlateComponent::GetReverseRate(InMediaPlayer), false); case EMediaPlateEventState::Forward: return InMediaPlayer->IsReady() && InMediaPlayer->SupportsRate(UMediaPlateComponent::GetForwardRate(InMediaPlayer), false); case EMediaPlateEventState::Rewind: return InMediaPlayer->IsReady() && InMediaPlayer->SupportsSeeking() && InMediaPlayer->GetTime() > FTimespan::Zero(); //case EMediaPlateEventState::Next: // was not implemented //case EMediaPlateEventState::Previous: // was not implemented default: return true; } } void FMediaPlateCustomization::OnButtonEvent(EMediaPlateEventState InState) { IMediaAssetsModule* MediaAssets = FModuleManager::LoadModulePtr("MediaAssets"); TArray ActorsPathNames; ActorsPathNames.Reserve(MediaPlatesList.Num()); for (const TWeakObjectPtr& MediaPlateWeak : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlateWeak.Get(); // Note: because of multi-selection and the possibility of different player states, we need check restrictions per plate again. if (!MediaPlate || !MediaPlate->IsEventStateChangeAllowed(InState) || !IsButtonEventAllowedForPlayer(InState, MediaPlate->GetMediaPlayer())) { continue; } ActorsPathNames.Add(MediaPlate->GetOwner()->GetPathName()); if (InState == EMediaPlateEventState::Open) { // Tell the editor module that this media plate is playing. FMediaPlateEditorModule* EditorModule = FModuleManager::LoadModulePtr("MediaPlateEditor"); if (EditorModule != nullptr) { EditorModule->MediaPlateStartedPlayback(MediaPlate); } } MediaPlate->SwitchStates(InState); } MediaAssets->BroadcastOnMediaStateChangedEvent(ActorsPathNames, (uint8)InState); } FReply FMediaPlateCustomization::OnOpenMediaPlate() { // Get all our objects. TArray AssetArray; for (TWeakObjectPtr& MediaPlatePtr : MediaPlatesList) { UMediaPlateComponent* MediaPlate = MediaPlatePtr.Get(); if (MediaPlate != nullptr) { AssetArray.Add(MediaPlate); } } // Open the editor. if (GEditor && AssetArray.Num() > 0) { GEditor->GetEditorSubsystem()->OpenEditorForAssets(AssetArray); } return FReply::Handled(); } void FMediaPlateCustomization::StopMediaPlates() { OnButtonEvent(EMediaPlateEventState::Close); } TArray> FMediaPlateCustomization::GetMediaPlayers() const { TArray> MediaPlayers; MediaPlayers.Reserve(MediaPlatesList.Num()); for (const TWeakObjectPtr& MediaPlateWeak : MediaPlatesList) { if (UMediaPlateComponent* MediaPlate = MediaPlateWeak.Get()) { if (UMediaPlayer* MediaPlayer = MediaPlate->GetMediaPlayer()) { MediaPlayers.Add(MediaPlayer); } } } return MediaPlayers; } bool FMediaPlateCustomization::IsTrueForAllPlayers(TFunctionRef InPredicate) const { bool bPredicateCalled = false; for (const TWeakObjectPtr& MediaPlateWeak : MediaPlatesList) { if (UMediaPlateComponent* MediaPlate = MediaPlateWeak.Get()) { if (const UMediaPlayer* MediaPlayer = MediaPlate->GetMediaPlayer()) { if (!InPredicate(MediaPlayer)) { return false; } bPredicateCalled = true; } } } return bPredicateCalled; } void FMediaPlateCustomization::CustomizeCategories(IDetailLayoutBuilder& InDetailBuilder) { static const FName PropertyEditor("PropertyEditor"); FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked(PropertyEditor); // Rearrange Categories const FName MediaPlateComponentName = UMediaPlateComponent::StaticClass()->GetFName(); const TSharedRef GeneralSection = PropertyModule.FindOrCreateSection(MediaPlateComponentName, TEXT("General"), LOCTEXT("General", "General")); GeneralSection->AddCategory(TEXT("Control")); GeneralSection->AddCategory(TEXT("Geometry")); GeneralSection->AddCategory(TEXT("Playlist")); GeneralSection->AddCategory(TEXT("MediaDetails")); GeneralSection->AddCategory(TEXT("MediaTexture")); GeneralSection->AddCategory(TEXT("Materials")); GeneralSection->AddCategory(TEXT("EXR Tiles & Mips")); GeneralSection->AddCategory(TEXT("Media Cache")); GeneralSection->AddCategory(TEXT("Advanced")); const TSharedRef MediaSection = PropertyModule.FindOrCreateSection(MediaPlateComponentName, TEXT("Media"), LOCTEXT("Media", "Media")); MediaSection->AddCategory(TEXT("Playlist")); MediaSection->AddCategory(TEXT("MediaDetails")); MediaSection->AddCategory(TEXT("Media Cache")); const TSharedRef EXRSection = PropertyModule.FindOrCreateSection(MediaPlateComponentName, TEXT("EXR"), LOCTEXT("EXR", "EXR")); EXRSection->AddCategory(TEXT("MediaDetails")); EXRSection->AddCategory(TEXT("EXR Tiles & Mips")); EXRSection->AddCategory(TEXT("Media Cache")); const TSharedRef RenderingSection = PropertyModule.FindOrCreateSection(MediaPlateComponentName, TEXT("Rendering"), LOCTEXT("Rendering", "Rendering")); RenderingSection->AddCategory(TEXT("Geometry")); RenderingSection->AddCategory(TEXT("Materials")); RenderingSection->AddCategory(TEXT("MediaTexture")); RenderingSection->AddCategory(TEXT("Mobility")); RenderingSection->AddCategory(TEXT("Transform")); RenderingSection->AddCategory(TEXT("TransformCommon")); RenderingSection->RemoveCategory(TEXT("Lighting")); RenderingSection->AddCategory(TEXT("MediaTexture")); RenderingSection->RemoveCategory(TEXT("MaterialParameters")); RenderingSection->RemoveCategory(TEXT("Mobile")); RenderingSection->RemoveCategory(TEXT("RayTracing")); RenderingSection->RemoveCategory(TEXT("Rendering")); RenderingSection->RemoveCategory(TEXT("TextureStreaming")); RenderingSection->RemoveCategory(TEXT("VirtualTexture")); // Hide unwanted Categories const FName MediaPlateName = AMediaPlate::StaticClass()->GetFName(); const TSharedRef MediaPlateMiscSection = PropertyModule.FindOrCreateSection(MediaPlateName, TEXT("Misc"), LOCTEXT("Misc", "Misc")); MediaPlateMiscSection->RemoveCategory("AssetUserData"); MediaPlateMiscSection->RemoveCategory("Cooking"); MediaPlateMiscSection->RemoveCategory("Input"); MediaPlateMiscSection->RemoveCategory("Navigation"); MediaPlateMiscSection->RemoveCategory("Replication"); MediaPlateMiscSection->RemoveCategory("Tags"); const TSharedRef MediaPlateStreamingSection = PropertyModule.FindOrCreateSection(MediaPlateName, TEXT("Streaming"), LOCTEXT("Streaming", "Streaming")); MediaPlateStreamingSection->RemoveCategory("Data Layers"); MediaPlateStreamingSection->RemoveCategory("HLOD"); MediaPlateStreamingSection->RemoveCategory("World Partition"); const TSharedRef MediaPlateLODSection = PropertyModule.FindOrCreateSection(MediaPlateName, TEXT("LOD"), LOCTEXT("LOD", "LOD")); MediaPlateLODSection->RemoveCategory("HLOD"); MediaPlateLODSection->RemoveCategory("LOD"); const TSharedRef MediaPlatePhysicsSection = PropertyModule.FindOrCreateSection(MediaPlateName, TEXT("Physics"), LOCTEXT("Physics", "Physics")); MediaPlatePhysicsSection->RemoveCategory("Collision"); MediaPlatePhysicsSection->RemoveCategory("Physics"); // Hide the static mesh. IDetailCategoryBuilder& StaticMeshCategory = InDetailBuilder.EditCategory("StaticMesh"); StaticMeshCategory.SetCategoryVisibility(false); IDetailCategoryBuilder& ControlCategory = InDetailBuilder.EditCategory("Control"); IDetailCategoryBuilder& MediaDetailsCategory = InDetailBuilder.EditCategory("MediaDetails"); IDetailCategoryBuilder& PlaylistCategory = InDetailBuilder.EditCategory("Playlist"); IDetailCategoryBuilder& GeometryCategory = InDetailBuilder.EditCategory("Geometry"); IDetailCategoryBuilder& MediaTextureCategory = InDetailBuilder.EditCategory("MediaTexture"); IDetailCategoryBuilder& MaterialsCategory = InDetailBuilder.EditCategory("Materials"); IDetailCategoryBuilder& TilesMipsCategory = InDetailBuilder.EditCategory("EXR Tiles & Mips"); IDetailCategoryBuilder& MediaCacheCategory = InDetailBuilder.EditCategory("Media Cache"); // Rename Media Cache category and look ahead property MediaCacheCategory.SetDisplayName(FText::FromString(TEXT("Cache"))); const TSharedRef CacheSettingsProperty = InDetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMediaPlateComponent, CacheSettings)); if (const auto LookAheadTimeProperty = CacheSettingsProperty->GetChildHandle(TEXT("TimeToLookAhead"))) { LookAheadTimeProperty->SetPropertyDisplayName(FText::FromString("Look Ahead Time")); } // Start from a Priority value which places these categories after the Transform one uint32 Priority = 2010; ControlCategory.SetSortOrder(Priority++); GeometryCategory.SetSortOrder(Priority++); PlaylistCategory.SetSortOrder(Priority++); MediaDetailsCategory.SetSortOrder(Priority++); MediaTextureCategory.SetSortOrder(Priority++); MaterialsCategory.SetSortOrder(Priority++); TilesMipsCategory.SetSortOrder(Priority++); MediaCacheCategory.SetSortOrder(Priority); } #undef LOCTEXT_NAMESPACE