text: // Copyright (c), Firelight Technologies Pty, Ltd. 2012-2020.
  
  #include "Sequencer/FMODChannelEditors.h"
  #include "ISequencerChannelInterface.h"
  #include "Widgets/DeclarativeSyntaxSupport.h"
  #include "Widgets/SCompoundWidget.h"
  #include "MovieSceneTimeHelpers.h"
  #include "MovieSceneToolHelpers.h"
  #include "ScopedTransaction.h"
  #include "EditorWidgets/Public/SEnumCombobox.h"
  #include "EditorStyleSet.h"
  
  class SFMODEventControlKeyEditor : public SCompoundWidget
  {
  public:
      SLATE_BEGIN_ARGS(SFMODEventControlKeyEditor) {}
      SLATE_END_ARGS();
  
      void Construct(const FArguments &InArgs, TMovieSceneChannelHandle<FMovieSceneByteChannel> InChannelHandle,
          TWeakObjectPtr<UMovieSceneSection> InWeakSection, TWeakPtr<ISequencer> InWeakSequencer, UEnum *InEnum)
      {
          ChannelHandle = InChannelHandle;
          WeakSection = InWeakSection;
          WeakSequencer = InWeakSequencer;
  
          ChildSlot[MovieSceneToolHelpers::MakeEnumComboBox(InEnum,
              TAttribute<int32>::Create(TAttribute<int32>::FGetter::CreateSP(this, &SFMODEventControlKeyEditor::OnGetCurrentValueAsInt)),
              SEnumComboBox::FOnEnumSelectionChanged::CreateSP(this, &SFMODEventControlKeyEditor::OnChangeKey))];
      }
  
  private:
      int32 OnGetCurrentValueAsInt() const
      {
          using namespace MovieScene;
  
          FMovieSceneByteChannel *Channel = ChannelHandle.Get();
          ISequencer *Sequencer = WeakSequencer.Pin().Get();
          UMovieSceneSection *OwningSection = WeakSection.Get();
          uint8 Result = 0;
  
          if (Channel && Sequencer && OwningSection)
          {
              const FFrameTime CurrentTime = MovieScene::ClampToDiscreteRange(Sequencer->GetLocalTime().Time, OwningSection->GetRange());
              EvaluateChannel(Channel, CurrentTime, Result);
          }
  
          return Result;
      }
  
      void SetValue(uint8 InValue)
      {
          using namespace MovieScene;
          using namespace Sequencer;
  
          UMovieSceneSection *OwningSection = WeakSection.Get();
          if (!OwningSection)
          {
              return;
          }
  
          OwningSection->SetFlags(RF_Transactional);
  
          FMovieSceneByteChannel *Channel = ChannelHandle.Get();
          ISequencer *Sequencer = WeakSequencer.Pin().Get();
  
          if (!OwningSection->TryModify() || !Channel || !Sequencer)
          {
              return;
          }
  
          const FFrameNumber CurrentTime = Sequencer->GetLocalTime().Time.FloorToFrame();
          const bool bAutoSetTrackDefaults = Sequencer->GetAutoSetTrackDefaults();
  
          EMovieSceneKeyInterpolation Interpolation = Sequencer->GetKeyInterpolation();
  
          TArray<FKeyHandle> KeysAtCurrentTime;
          Channel->GetKeys(TRange<FFrameNumber>(CurrentTime), nullptr, &KeysAtCurrentTime);
  
          if (KeysAtCurrentTime.Num() > 0)
          {
              AssignValue(Channel, KeysAtCurrentTime[0], InValue);
          }
          else
          {
              const bool bHasAnyKeys = Channel->GetNumKeys() != 0;
  
              if (bHasAnyKeys || bAutoSetTrackDefaults == false)
              {
                  // When auto setting track defaults are disabled, add a key even when it's empty so that the changed
                  // value is saved and is propagated to the property.
                  AddKeyToChannel(Channel, CurrentTime, InValue, Interpolation);
              }
  
              if (bHasAnyKeys)
              {
                  TRange<FFrameNumber> KeyRange = TRange<FFrameNumber>(CurrentTime);
                  TRange<FFrameNumber> SectionRange = OwningSection->GetRange();
  
                  if (!SectionRange.Contains(KeyRange))
                  {
                      OwningSection->SetRange(TRange<FFrameNumber>::Hull(KeyRange, SectionRange));
                  }
              }
          }
  
          // Always update the default value when auto-set default values is enabled so that the last changes
          // are always saved to the track.
          if (bAutoSetTrackDefaults)
          {
              SetChannelDefault(Channel, InValue);
          }
      }
  
      void OnChangeKey(int32 Selection, ESelectInfo::Type SelectionType)
      {
          FScopedTransaction Transaction(FText::FromString("Set FMOD Event Control Key Value"));
          SetValue(Selection);
          if (ISequencer *Sequencer = WeakSequencer.Pin().Get())
          {
              Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately);
          }
      }
  
      TMovieSceneChannelHandle<FMovieSceneByteChannel> ChannelHandle;
      TWeakObjectPtr<UMovieSceneSection> WeakSection;
      TWeakPtr<ISequencer> WeakSequencer;
  };
  
  bool CanCreateKeyEditor(const FMovieSceneByteChannel *Channel)
  {
      return true;
  }
  
  TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneByteChannel> &Channel, UMovieSceneSection *Section,
      const FGuid &InObjectBindingID, TWeakPtr<FTrackInstancePropertyBindings> PropertyBindings, TWeakPtr<ISequencer> InSequencer)
  {
      const FMovieSceneByteChannel *RawChannel = Channel.Get();
  
      if (!RawChannel)
      {
          return SNullWidget::NullWidget;
      }
  
      UEnum *Enum = RawChannel->GetEnum();
      return SNew(SFMODEventControlKeyEditor, Channel, Section, InSequencer, Enum);
  }
  
  TSharedPtr<FStructOnScope> GetKeyStruct(const TMovieSceneChannelHandle<FFMODEventControlChannel> &ChannelHandle, FKeyHandle InHandle)
  {
      FFMODEventControlChannel *Channel = ChannelHandle.Get();
      if (!Channel)
      {
          return nullptr;
      }
  
      TMovieSceneChannelData<uint8> ChannelData = Channel->GetData();
      const int32 KeyIndex = ChannelData.GetIndex(InHandle);
  
      if (KeyIndex == INDEX_NONE)
      {
          return nullptr;
      }
  
      TSharedPtr<FStructOnScope> KeyStruct = MakeShared<FStructOnScope>(FFMODEventControlKeyStruct::StaticStruct());
      FFMODEventControlKeyStruct *Struct = reinterpret_cast<FFMODEventControlKeyStruct *>(KeyStruct->GetStructMemory());
  
      Struct->Time = ChannelData.GetTimes()[KeyIndex];
      Struct->Value = (EFMODEventControlKey)ChannelData.GetValues()[KeyIndex];
  
      Struct->KeyStructInterop.Add(FMovieSceneChannelValueHelper(ChannelHandle, &Struct->Value, MakeTuple(InHandle, Struct->Time)));
      return KeyStruct;
  }
  
  void DrawKeys(FFMODEventControlChannel *Channel, TArrayView<const FKeyHandle> InKeyHandles, TArrayView<FKeyDrawParams> OutKeyDrawParams)
  {
      static const FName KeyLeftBrushName("Sequencer.KeyLeft");
      static const FName KeyRightBrushName("Sequencer.KeyRight");
      static const FName KeyDiamondBrushName("Sequencer.KeyDiamond");
  
      const FSlateBrush *LeftKeyBrush = FEditorStyle::GetBrush(KeyLeftBrushName);
      const FSlateBrush *RightKeyBrush = FEditorStyle::GetBrush(KeyRightBrushName);
      const FSlateBrush *DiamondBrush = FEditorStyle::GetBrush(KeyDiamondBrushName);
  
      TMovieSceneChannelData<uint8> ChannelData = Channel->GetData();
  
      for (int32 Index = 0; Index < InKeyHandles.Num(); ++Index)
      {
          FKeyHandle Handle = InKeyHandles[Index];
  
          FKeyDrawParams Params;
          Params.BorderBrush = Params.FillBrush = DiamondBrush;
  
          const int32 KeyIndex = ChannelData.GetIndex(Handle);
          if (KeyIndex != INDEX_NONE)
          {
              const EFMODEventControlKey Value = (EFMODEventControlKey)ChannelData.GetValues()[KeyIndex];
              if (Value == EFMODEventControlKey::Play)
              {
                  Params.BorderBrush = Params.FillBrush = LeftKeyBrush;
                  Params.FillOffset = FVector2D(-1.0f, 1.0f);
              }
              else if (Value == EFMODEventControlKey::Stop)
              {
                  Params.BorderBrush = Params.FillBrush = RightKeyBrush;
                  Params.FillOffset = FVector2D(1.0f, 1.0f);
              }
          }
  
          OutKeyDrawParams[Index] = Params;
      }
  }
  