// Copyright Epic Games, Inc. All Rights Reserved. #include "Sections/MovieSceneSectionHelpers.h" #include "Sections/MovieSceneColorSection.h" #include "Channels/MovieSceneChannelProxy.h" #include "Channels/MovieSceneFloatChannel.h" #include "ISequencer.h" #include "Misc/FrameNumber.h" #include "Widgets/Colors/SColorPicker.h" #include "ScopedTransaction.h" #include "Framework/Application/SlateApplication.h" #include "Engine/Engine.h" #include "TimeToPixel.h" void MovieSceneSectionHelpers::ConsolidateColorCurves( TArray< TTuple >& OutColorKeys, const FLinearColor& DefaultColor, TArrayView ColorChannels, const FTimeToPixel& TimeConverter ) { // @todo Sequencer Optimize - This could all get cached, instead of recalculating everything every OnPaint if (!ensure(ColorChannels.Num() == 4)) { return; } // Gather all the channels with keys TArray, TInlineAllocator<4>> ChannelTimes; for (int32 Index = 0; Index < 4; ++Index) { if (ColorChannels[Index]->GetTimes().Num()) { ChannelTimes.Add(ColorChannels[Index]->GetTimes()); } } // Keep adding color stops for similar times until we have nothing left while ( ChannelTimes.Num() ) { // Find the earliest time from the remaining channels FFrameNumber Time = TNumericLimits::Max(); for (const TArrayView& Channel : ChannelTimes) { Time = FMath::Min(Time, Channel[0]); } // Slice the channels until we no longer match the next time for (TArrayView& Channel : ChannelTimes) { int32 SliceIndex = 0; while (SliceIndex < Channel.Num() && Time == Channel[SliceIndex]) { ++SliceIndex; } if (SliceIndex > 0) { int32 NewNum = Channel.Num() - SliceIndex; Channel = NewNum > 0 ? Channel.Slice(SliceIndex, NewNum) : TArrayView(); } } // Remove empty channels with no keys left for (int32 Index = ChannelTimes.Num()-1; Index >= 0; --Index) { if (ChannelTimes[Index].Num() == 0) { ChannelTimes.RemoveAt(Index, EAllowShrinking::No); } } FLinearColor ColorAtTime = DefaultColor; ColorChannels[0]->Evaluate(Time, ColorAtTime.R); ColorChannels[1]->Evaluate(Time, ColorAtTime.G); ColorChannels[2]->Evaluate(Time, ColorAtTime.B); ColorChannels[3]->Evaluate(Time, ColorAtTime.A); OutColorKeys.Add(MakeTuple(float(Time / TimeConverter.GetTickResolution()), ColorAtTime)); } // Enforce at least one key for the default value if (OutColorKeys.Num() == 0) { OutColorKeys.Add(MakeTuple(0.f, DefaultColor)); } } FFrameNumber FMovieSceneKeyColorPicker::KeyTime = FFrameNumber(); FLinearColor FMovieSceneKeyColorPicker::InitialColor = FLinearColor(); bool FMovieSceneKeyColorPicker::bColorPickerWasCancelled = false; FMovieSceneKeyColorPicker::FMovieSceneKeyColorPicker(UMovieSceneSection* Section, FMovieSceneFloatChannel* RChannel, FMovieSceneFloatChannel* GChannel, FMovieSceneFloatChannel* BChannel, FMovieSceneFloatChannel* AChannel, const TArray& KeyHandles, TWeakPtr InSequencer) { // One key was clicked on, find which from the channels int32 KeyIndex = INDEX_NONE; for (FKeyHandle KeyHandle : KeyHandles) { if (RChannel->GetData().GetIndex(KeyHandle) != INDEX_NONE) { KeyIndex = RChannel->GetData().GetIndex(KeyHandle); RChannel->GetKeyTime(KeyHandle, KeyTime); break; } if (GChannel->GetData().GetIndex(KeyHandle) != INDEX_NONE) { KeyIndex = GChannel->GetData().GetIndex(KeyHandle); GChannel->GetKeyTime(KeyHandle, KeyTime); break; } if (BChannel->GetData().GetIndex(KeyHandle) != INDEX_NONE) { KeyIndex = BChannel->GetData().GetIndex(KeyHandle); BChannel->GetKeyTime(KeyHandle, KeyTime); break; } if (AChannel->GetData().GetIndex(KeyHandle) != INDEX_NONE) { KeyIndex = AChannel->GetData().GetIndex(KeyHandle); AChannel->GetKeyTime(KeyHandle, KeyTime); break; } } if (KeyIndex == INDEX_NONE) { return; } float R, G, B, A = 0.f; RChannel->Evaluate(KeyTime, R); GChannel->Evaluate(KeyTime, G); BChannel->Evaluate(KeyTime, B); AChannel->Evaluate(KeyTime, A); InitialColor = FLinearColor(R, G, B, A); bColorPickerWasCancelled = false; FColorPickerArgs PickerArgs; PickerArgs.bUseAlpha = false; PickerArgs.DisplayGamma = TAttribute::Create(TAttribute::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); PickerArgs.InitialColor = InitialColor; PickerArgs.ParentWidget = FSlateApplication::Get().GetActiveTopLevelWindow(); PickerArgs.OptionalOwningDetailsView = PickerArgs.ParentWidget; PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateRaw(this, &FMovieSceneKeyColorPicker::OnColorPickerPicked, Section, RChannel, GChannel, BChannel, AChannel, InSequencer); PickerArgs.OnColorPickerWindowClosed = FOnWindowClosed::CreateRaw(this, &FMovieSceneKeyColorPicker::OnColorPickerClosed, Section, RChannel, GChannel, BChannel, AChannel, InSequencer); PickerArgs.OnColorPickerCancelled = FOnColorPickerCancelled::CreateRaw(this, &FMovieSceneKeyColorPicker::OnColorPickerCancelled, Section, RChannel, GChannel, BChannel, AChannel, InSequencer); OpenColorPicker(PickerArgs); } void UpdateOrAddKey(FMovieSceneFloatChannel* Channel, FFrameNumber KeyTime, float Value) { int32 KeyHandleIndex = Channel->GetData().FindKey(KeyTime); if (KeyHandleIndex == INDEX_NONE) { Channel->GetData().UpdateOrAddKey(KeyTime, FMovieSceneFloatValue(Value)); } else { Channel->GetData().GetValues()[KeyHandleIndex].Value = Value; } } void FMovieSceneKeyColorPicker::OnColorPickerPicked(FLinearColor NewColor, UMovieSceneSection* Section, FMovieSceneFloatChannel* RChannel, FMovieSceneFloatChannel* GChannel, FMovieSceneFloatChannel* BChannel, FMovieSceneFloatChannel* AChannel, TWeakPtr WeakSequencer) { Section->Modify(); UpdateOrAddKey(RChannel, KeyTime, NewColor.R); UpdateOrAddKey(GChannel, KeyTime, NewColor.G); UpdateOrAddKey(BChannel, KeyTime, NewColor.B); UpdateOrAddKey(AChannel, KeyTime, NewColor.A); TSharedPtr Sequencer = WeakSequencer.Pin(); if (Sequencer) { Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged); } } void FMovieSceneKeyColorPicker::OnColorPickerClosed(const TSharedRef& Window, UMovieSceneSection* Section, FMovieSceneFloatChannel* RChannel, FMovieSceneFloatChannel* GChannel, FMovieSceneFloatChannel* BChannel, FMovieSceneFloatChannel* AChannel, TWeakPtr WeakSequencer) { // Under Unreal UX terms, closing the Color Picker (via the UI) is the same as confirming it since we've been live updating // the color. The track already has the latest color change so we undo the change before calling Modify so that Undo sets us // to the original color. This is also called in the event of pressing cancel so we need to detect if it was canceled or not. if (!bColorPickerWasCancelled) { const FScopedTransaction Transaction(NSLOCTEXT("Sequencer", "SetKeyColor", "Set Key Color")); float R, G, B, A = 0.f; RChannel->Evaluate(KeyTime, R); GChannel->Evaluate(KeyTime, G); BChannel->Evaluate(KeyTime, B); AChannel->Evaluate(KeyTime, A); UpdateOrAddKey(RChannel, KeyTime, InitialColor.R); UpdateOrAddKey(GChannel, KeyTime, InitialColor.G); UpdateOrAddKey(BChannel, KeyTime, InitialColor.B); UpdateOrAddKey(AChannel, KeyTime, InitialColor.A); Section->Modify(); UpdateOrAddKey(RChannel, KeyTime, R); UpdateOrAddKey(GChannel, KeyTime, G); UpdateOrAddKey(BChannel, KeyTime, B); UpdateOrAddKey(AChannel, KeyTime, A); TSharedPtr Sequencer = WeakSequencer.Pin(); if (Sequencer) { Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged); } } } void FMovieSceneKeyColorPicker::OnColorPickerCancelled(FLinearColor NewColor, UMovieSceneSection* Section, FMovieSceneFloatChannel* RChannel, FMovieSceneFloatChannel* GChannel, FMovieSceneFloatChannel* BChannel, FMovieSceneFloatChannel* AChannel, TWeakPtr WeakSequencer) { bColorPickerWasCancelled = true; Section->Modify(); // Restore the original color. No transaction will be created when the OnColorPickerClosed callback is called. UpdateOrAddKey(RChannel, KeyTime, InitialColor.R); UpdateOrAddKey(GChannel, KeyTime, InitialColor.G); UpdateOrAddKey(BChannel, KeyTime, InitialColor.B); UpdateOrAddKey(AChannel, KeyTime, InitialColor.A); TSharedPtr Sequencer = WeakSequencer.Pin(); if (Sequencer) { Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged); } }