// Copyright Epic Games, Inc. All Rights Reserved. #include "Sections/ThumbnailSection.h" #include "AnimatedRange.h" #include "Rendering/DrawElements.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "GameFramework/Actor.h" #include "ISequencer.h" #include "MVVM/ViewModels/SequencerEditorViewModel.h" #include "MVVM/ViewModels/ViewDensity.h" #include "Modules/ModuleManager.h" #include "Application/ThrottleManager.h" #include "Widgets/Layout/SBox.h" #include "SequencerSectionPainter.h" #include "Styling/AppStyle.h" #include "LevelEditorViewport.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "PropertyEditorModule.h" #include "IDetailsView.h" #include "IVREditorModule.h" #include "MovieScene.h" #include "MovieSceneTimeHelpers.h" #include "TrackEditorThumbnail/TrackThumbnailUtils.h" #define LOCTEXT_NAMESPACE "FThumbnailSection" namespace ThumbnailSectionConstants { const uint32 ThumbnailHeight = 90; const uint32 TrackWidth = 90; const float SectionGripSize = 4.0f; } /* FThumbnailSection structors *****************************************************************************/ FThumbnailSection::FThumbnailSection(TSharedPtr InSequencer, TSharedPtr InThumbnailPool, IViewportThumbnailClient* InViewportThumbanilClient, UMovieSceneSection& InSection) : Section(&InSection) , SequencerPtr(InSequencer) , ThumbnailCache(InThumbnailPool, InViewportThumbanilClient) , AdditionalDrawEffect(ESlateDrawEffect::None) , TimeSpace(ETimeSpace::Global) { WhiteBrush = FAppStyle::GetBrush("WhiteBrush"); RedrawThumbnailDelegateHandle = GetMutableDefault()->OnForceRedraw().AddRaw(this, &FThumbnailSection::RedrawThumbnails); } FThumbnailSection::FThumbnailSection(TSharedPtr InSequencer, TSharedPtr InThumbnailPool, ICustomThumbnailClient* InCustomThumbnailClient, UMovieSceneSection& InSection) : Section(&InSection) , SequencerPtr(InSequencer) , ThumbnailCache(InThumbnailPool, InCustomThumbnailClient) , AdditionalDrawEffect(ESlateDrawEffect::None) , TimeSpace(ETimeSpace::Global) { WhiteBrush = FAppStyle::GetBrush("WhiteBrush"); RedrawThumbnailDelegateHandle = GetMutableDefault()->OnForceRedraw().AddRaw(this, &FThumbnailSection::RedrawThumbnails); } FThumbnailSection::~FThumbnailSection() { GetMutableDefault()->OnForceRedraw().Remove(RedrawThumbnailDelegateHandle); } void FThumbnailSection::RedrawThumbnails() { ThumbnailCache.ForceRedraw(); } /* ISequencerSection interface *****************************************************************************/ TSharedRef FThumbnailSection::GenerateSectionWidget() { return SNew(SBox) .HAlign(HAlign_Left) .VAlign(VAlign_Top) .Padding_Lambda([&]() { return GetContentPadding(); }) [ SAssignNew(NameWidget, SInlineEditableTextBlock) .ToolTipText(CanRename() ? LOCTEXT("RenameThumbnail", "Click or hit F2 to rename") : FText::GetEmpty()) .Text(this, &FThumbnailSection::HandleThumbnailTextBlockText) .ShadowOffset(FVector2D(1,1)) .OnTextCommitted(this, &FThumbnailSection::HandleThumbnailTextBlockTextCommitted) .IsReadOnly(!CanRename()) .Visibility(this, &FThumbnailSection::GetRenameVisibility) ]; } EVisibility FThumbnailSection::GetRenameVisibility() const { if (NameWidget.IsValid() && NameWidget->IsInEditMode()) { return EVisibility::Visible; } return EVisibility::Hidden; } void FThumbnailSection::EnterRename() { if (NameWidget.IsValid()) { NameWidget->SetReadOnly(false); NameWidget->EnterEditingMode(); NameWidget->SetReadOnly(!CanRename()); } } void FThumbnailSection::BuildSectionContextMenu(FMenuBuilder& MenuBuilder, const FGuid& ObjectBinding) { MenuBuilder.BeginSection(NAME_None, LOCTEXT("ViewMenuText", "View")); { MenuBuilder.AddSubMenu( LOCTEXT("ThumbnailsMenu", "Thumbnails"), FText(), FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InMenuBuilder) { BuildThumbnailsMenu(InMenuBuilder); }) ); } MenuBuilder.EndSection(); } void FThumbnailSection::BuildThumbnailsMenu(FMenuBuilder& InMenuBuilder) { const TSharedPtr Sequencer = SequencerPtr.Pin(); if (!Sequencer.IsValid()) { return; } const FText CurrentTime = FText::FromString(Sequencer->GetNumericTypeInterface()->ToString(Sequencer->GetLocalTime().Time.GetFrame().Value)); InMenuBuilder.BeginSection(TEXT("Thumbnails"), LOCTEXT("ThumbnailsMenuSection", "Thumbnails")); { InMenuBuilder.AddMenuEntry( LOCTEXT("RefreshText", "Refresh"), LOCTEXT("RefreshTooltip", "Refresh this section's thumbnails"), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &FThumbnailSection::RedrawThumbnails)) ); InMenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("SetSingleTime", "Set Thumbnail Time To {0}"), CurrentTime), LOCTEXT("SetSingleTimeTooltip", "Defines the time at which this section should draw its single thumbnail to the current cursor position"), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this] { const TSharedPtr Sequencer = SequencerPtr.Pin(); if (!Sequencer.IsValid()) { return; } SetSingleTime(Sequencer->GetLocalTime().AsSeconds()); GetMutableDefault()->bDrawSingleThumbnails = true; GetMutableDefault()->SaveConfig(); }) ) ); InMenuBuilder.AddMenuEntry( LOCTEXT("RefreshAllText", "Refresh All"), LOCTEXT("RefreshAllTooltip", "Refresh all sections' thumbnails"), FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([] { GetDefault()->BroadcastRedrawThumbnails(); })) ); FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); FDetailsViewArgs Args; Args.bAllowSearch = false; Args.NameAreaSettings = FDetailsViewArgs::HideNameArea; TSharedRef DetailView = PropertyModule.CreateDetailView(Args); DetailView->SetObject(GetMutableDefault()); InMenuBuilder.AddWidget(DetailView, FText(), true); } InMenuBuilder.EndSection(); } void FThumbnailSection::BuildSectionSidebarMenu(FMenuBuilder& MenuBuilder, const FGuid& ObjectBinding) { BuildThumbnailsMenu(MenuBuilder); } float FThumbnailSection::GetSectionGripSize() const { return ThumbnailSectionConstants::SectionGripSize; } float FThumbnailSection::GetSectionHeight(const UE::Sequencer::FViewDensityInfo& ViewDensity) const { auto* Settings = GetDefault(); if (Settings->bDrawThumbnails) { return GetDefault()->ThumbnailSize.Y; } else { return FAppStyle::GetFontStyle("NormalFont").Size + 8.f; } } UMovieSceneSection* FThumbnailSection::GetSectionObject() { return Section; } FText FThumbnailSection::GetSectionTitle() const { return FText::GetEmpty(); } int32 FThumbnailSection::OnPaintSection( FSequencerSectionPainter& InPainter ) const { if (!GetDefault()->bDrawThumbnails) { return InPainter.LayerId; } static const float SectionThumbnailPadding = 4.f; ESlateDrawEffect DrawEffects = InPainter.bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; int32 LayerId = InPainter.LayerId; const FGeometry& HeaderGeometry = InPainter.HeaderGeometry; // @todo Sequencer: Need a way to visualize the key here const TRange VisibleRange = GetVisibleRange(); const TRange GenerationRange = GetTotalRange(); const float TimePerPx = GenerationRange.Size() / HeaderGeometry.GetLocalSize().X; const FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); const double SectionEaseInDuration = TickResolution.AsSeconds(Section->Easing.GetEaseInDuration()) / TimePerPx; const double SectionEaseOutDuration = TickResolution.AsSeconds(Section->Easing.GetEaseOutDuration()) / TimePerPx; const FSlateRect ThumbnailClipRect = HeaderGeometry.GetLayoutBoundingRect() .InsetBy(FMargin(SectionThumbnailPadding, 0.f)) .InsetBy(FMargin(SectionEaseInDuration, 0.f, SectionEaseOutDuration, 0.f)) .IntersectionWith(InPainter.SectionClippingRect); for (const TSharedPtr& Thumbnail : ThumbnailCache.GetThumbnails()) { const float Fade = Thumbnail->bHasFinishedDrawing ? Thumbnail->GetFadeInCurve() : 1.f; if (Fade >= 1.f) { continue; } FIntPoint ThumbnailRTSize = Thumbnail->GetSize(); FIntPoint ThumbnailCropSize = Thumbnail->GetDesiredSize(); const float ThumbnailScale = float(ThumbnailCropSize.Y) / ThumbnailRTSize.Y; const float HorizontalCropOffset = (ThumbnailRTSize.X*ThumbnailScale - ThumbnailCropSize.X) * 0.5f; // Calculate the paint geometry for this thumbnail TOptional SingleReferenceFrame = ThumbnailCache.GetSingleReferenceFrame(); // Single thumbnails are always drawn at the start of the section, clamped to the visible range // Thumbnail sequences draw relative to their actual position in the sequence/section const float PositionX = SingleReferenceFrame.IsSet() ? FMath::Max(float(VisibleRange.GetLowerBoundValue() - GenerationRange.GetLowerBoundValue()) / TimePerPx, 0.f) + SectionThumbnailPadding : (Thumbnail->GetTimeRange().GetLowerBoundValue() - GenerationRange.GetLowerBoundValue()) / TimePerPx; const float PositionY = (HeaderGeometry.GetLocalSize().Y - ThumbnailCropSize.Y)*.5f; FPaintGeometry PaintGeometry = HeaderGeometry.ToPaintGeometry( ThumbnailRTSize, FSlateLayoutTransform(ThumbnailScale, FVector2D(PositionX-HorizontalCropOffset, PositionY)) ); if (IVREditorModule::Get().IsVREditorModeActive()) { // In VR editor every widget is in the world and gamma corrected by the scene renderer. Thumbnails will have already been gamma // corrected and so they need to be reversed DrawEffects |= ESlateDrawEffect::ReverseGamma; } else { DrawEffects |= ESlateDrawEffect::NoGamma; } if (Thumbnail->bIgnoreAlpha) { DrawEffects |= ESlateDrawEffect::IgnoreTextureAlpha; } FGeometry ClipGeometry = HeaderGeometry.MakeChild( ThumbnailCropSize, FSlateLayoutTransform( FVector2D(PositionX, PositionY) ) ); FSlateRect ThisThumbnailClipRect = ThumbnailClipRect.IntersectionWith(ClipGeometry.GetLayoutBoundingRect()); FSlateClippingZone ClippingZone(ThisThumbnailClipRect); InPainter.DrawElements.PushClip(ClippingZone); FSlateDrawElement::MakeViewport( InPainter.DrawElements, LayerId, PaintGeometry, Thumbnail, DrawEffects | AdditionalDrawEffect, FLinearColor(1.f, 1.f, 1.f, 1.f - Fade) ); InPainter.DrawElements.PopClip(); } return LayerId + 2; } TRange FThumbnailSection::GetVisibleRange() const { const FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); TRange GlobalVisibleRange = SequencerPtr.Pin()->GetViewRange(); TRange SectionRange = Section->GetRange() / TickResolution; if (TimeSpace == ETimeSpace::Global) { return GlobalVisibleRange; } TRange Intersection = TRange::Intersection(GlobalVisibleRange, SectionRange); return TRange( Intersection.GetLowerBoundValue() - SectionRange.GetLowerBoundValue(), Intersection.GetUpperBoundValue() - SectionRange.GetLowerBoundValue() ); } TRange FThumbnailSection::GetTotalRange() const { TRange SectionRange = Section->GetRange(); FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); if (TimeSpace == ETimeSpace::Global) { return SectionRange / TickResolution; } else { const bool bHasDiscreteSize = SectionRange.GetLowerBound().IsClosed() && SectionRange.GetUpperBound().IsClosed(); TRangeBound UpperBound = bHasDiscreteSize ? TRangeBound::Exclusive(FFrameNumber(UE::MovieScene::DiscreteSize(SectionRange)) / TickResolution) : TRangeBound::Open(); return TRange(0, UpperBound); } } void FThumbnailSection::Tick(const FGeometry& AllottedGeometry, const FGeometry& ParentGeometry, const double InCurrentTime, const float InDeltaTime) { using namespace UE::Sequencer; TSharedPtr Sequencer = SequencerPtr.Pin(); if (Sequencer && FSlateThrottleManager::Get().IsAllowingExpensiveTasks() && GetDefault()->bDrawThumbnails) { const UMovieSceneUserThumbnailSettings* Settings = GetDefault(); FViewDensityInfo ViewDensity = Sequencer->GetViewModel()->GetViewDensity(); const float Height = GetSectionHeight(ViewDensity); FIntPoint AllocatedSize = AllottedGeometry.GetLocalSize().IntPoint(); AllocatedSize.X = FMath::Max(AllocatedSize.X, 1); AllocatedSize.Y = FMath::RoundToInt(Height); ThumbnailCache.Update(GetTotalRange(), GetVisibleRange(), AllocatedSize, Settings->ThumbnailSize, Settings->Quality, InCurrentTime); } } FViewportThumbnailSection::FViewportThumbnailSection(TSharedPtr InSequencer, TSharedPtr InThumbnailPool, UMovieSceneSection& InSection) : FThumbnailSection(InSequencer, InThumbnailPool, this, InSection) { } void FViewportThumbnailSection::PreDraw(FTrackEditorThumbnail& Thumbnail) { TSharedPtr Sequencer = SequencerPtr.Pin(); if (Sequencer.IsValid()) { UE::MoveSceneTools::PreDrawThumbnailSetupSequencer(*Sequencer, Thumbnail.GetEvalPosition()); } } void FViewportThumbnailSection::PostDraw(FTrackEditorThumbnail& Thumbnail) { TSharedPtr Sequencer = SequencerPtr.Pin(); if (Sequencer.IsValid()) { Thumbnail.SetupFade(Sequencer->GetSequencerWidget()); UE::MoveSceneTools::PostDrawThumbnailCleanupSequencer(*Sequencer); } } #undef LOCTEXT_NAMESPACE