bool UBlendSpaceBase::IsValidAdditiveInternal(EAdditiveAnimationType AdditiveType) const { TOptional<bool> bIsAdditive; for (int32 I=0; I<SampleData.Num(); ++I) { const UAnimSequence* Animation = SampleData[I].Animation; // test animation to see if it matched additive type bool bAdditiveAnim = ( Animation && Animation->IsValidAdditive() && Animation->AdditiveAnimType == AdditiveType ); // if already has value, but it does not match if ( bIsAdditive.IsSet() ) { // it's inconsistent, we ignore this if (bIsAdditive.GetValue() != bAdditiveAnim) { return false; } } else { bIsAdditive = TOptional<bool>(bAdditiveAnim); } } return (bIsAdditive.IsSet() && bIsAdditive.GetValue()); }
void FSequencerTimeSliderController::SetViewRange( float NewRangeMin, float NewRangeMax, EViewRangeInterpolation Interpolation ) { TOptional<float> LocalClampMin = TimeSliderArgs.ClampMin.Get(); TOptional<float> LocalClampMax = TimeSliderArgs.ClampMax.Get(); // Clamp the range if clamp values are set if ( LocalClampMin.IsSet() && NewRangeMin < LocalClampMin.GetValue() ) { NewRangeMin = LocalClampMin.GetValue(); } if ( LocalClampMax.IsSet() && NewRangeMax > LocalClampMax.GetValue() ) { NewRangeMax = LocalClampMax.GetValue(); } const TRange<float> NewRange(NewRangeMin, NewRangeMax); TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound( NewRange, Interpolation ); if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves (no animation) TimeSliderArgs.ViewRange.Set( NewRange ); } }
TOptional<FExpressionError> ParseEscapedChar(FExpressionTokenConsumer& Consumer) { FTokenStream& Stream = Consumer.GetStream(); TOptional<FStringToken> Token = Stream.ParseSymbol(EscapeChar); if (!Token.IsSet()) { return TOptional<FExpressionError>(); } FStringToken& TokenValue = Token.GetValue(); // Accumulate the next character into the token TOptional<FStringToken> EscapedChar = Consumer.GetStream().ParseSymbol(&TokenValue); if (!EscapedChar.IsSet()) { return TOptional<FExpressionError>(); } // Check for a valid escape character const TCHAR Character = *EscapedChar->GetTokenStartPos(); if (IsValidEscapeChar(Character)) { // Add the token to the consumer - this moves the read position in the stream to the end of the token. Consumer.Add(TokenValue, FEscapedCharacter(Character)); } return TOptional<FExpressionError>(); }
void FWidgetNavigationCustomization::SetNav(UWidget* Widget, EUINavigation Nav, TOptional<EUINavigationRule> Rule, TOptional<FName> WidgetToFocus) { Widget->Modify(); UWidgetNavigation* WidgetNavigation = Widget->Navigation; if (!Widget->Navigation) { WidgetNavigation = NewObject<UWidgetNavigation>(Widget); } FWidgetNavigationData* DirectionNavigation = nullptr; switch ( Nav ) { case EUINavigation::Left: DirectionNavigation = &WidgetNavigation->Left; break; case EUINavigation::Right: DirectionNavigation = &WidgetNavigation->Right; break; case EUINavigation::Up: DirectionNavigation = &WidgetNavigation->Up; break; case EUINavigation::Down: DirectionNavigation = &WidgetNavigation->Down; break; case EUINavigation::Next: DirectionNavigation = &WidgetNavigation->Next; break; case EUINavigation::Previous: DirectionNavigation = &WidgetNavigation->Previous; break; default: // Should not be possible. check(false); return; } if ( Rule.IsSet() ) { DirectionNavigation->Rule = Rule.GetValue(); } if ( WidgetToFocus.IsSet() ) { DirectionNavigation->WidgetToFocus = WidgetToFocus.GetValue(); } if ( WidgetNavigation->IsDefault() ) { // If the navigation rules are all set to the defaults, remove the navigation // information from the widget. Widget->Navigation = nullptr; } else { Widget->Navigation = WidgetNavigation; } }
void FAssetSourceFilenameCache::HandleOnAssetRenamed(const FAssetData& AssetData, const FString& OldPath) { TOptional<FAssetImportInfo> ImportData = ExtractAssetImportInfo(AssetData.TagsAndValues); if (ImportData.IsSet()) { FName OldPathName = *OldPath; for (auto& SourceFile : ImportData->SourceFiles) { FString CleanFilename = FPaths::GetCleanFilename(SourceFile.RelativeFilename); if (auto* Objects = SourceFileToObjectPathCache.Find(CleanFilename)) { Objects->Remove(OldPathName); if (Objects->Num() == 0) { SourceFileToObjectPathCache.Remove(CleanFilename); } } SourceFileToObjectPathCache.FindOrAdd(CleanFilename).Add(AssetData.ObjectPath); } } AssetRenamedEvent.Broadcast(AssetData, OldPath); }
FCursorReply SWidget::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const { TOptional<EMouseCursor::Type> TheCursor = Cursor.Get(); return ( TheCursor.IsSet() ) ? FCursorReply::Cursor( TheCursor.GetValue() ) : FCursorReply::Unhandled(); }
bool LocalizationCommandletTasks::ImportTextForTargets(const TSharedRef<SWindow>& ParentWindow, const TArray<ULocalizationTarget*>& Targets, const TOptional<FString> DirectoryPath) { TArray<LocalizationCommandletExecution::FTask> Tasks; for (ULocalizationTarget* Target : Targets) { const bool ShouldUseProjectFile = !Target->IsMemberOfEngineTargetSet(); FFormatNamedArguments Arguments; Arguments.Add(TEXT("TargetName"), FText::FromString(Target->Settings.Name)); const FText ImportTaskName = FText::Format(LOCTEXT("ImportTaskNameFormat", "Import Translations for {TargetName}"), Arguments); const FString ImportScriptPath = LocalizationConfigurationScript::GetImportTextConfigPath(Target, TOptional<FString>()); const TOptional<FString> DirectoryPathForTarget = DirectoryPath.IsSet() ? DirectoryPath.GetValue() / Target->Settings.Name : TOptional<FString>(); LocalizationConfigurationScript::GenerateImportTextConfigFile(Target, TOptional<FString>(), DirectoryPathForTarget).Write(ImportScriptPath); Tasks.Add(LocalizationCommandletExecution::FTask(ImportTaskName, ImportScriptPath, ShouldUseProjectFile)); const FText ReportTaskName = FText::Format(LOCTEXT("ReportTaskNameFormat", "Generate Reports for {TargetName}"), Arguments); const FString ReportScriptPath = LocalizationConfigurationScript::GetWordCountReportConfigPath(Target); LocalizationConfigurationScript::GenerateWordCountReportConfigFile(Target).Write(ReportScriptPath); Tasks.Add(LocalizationCommandletExecution::FTask(ReportTaskName, ReportScriptPath, ShouldUseProjectFile)); } return LocalizationCommandletExecution::Execute(ParentWindow, LOCTEXT("ImportForAllTargetsWindowTitle", "Import Translations for All Targets"), Tasks); }
/** Parse anything until we find an unescaped { */ TOptional<FExpressionError> ParseLiteral(FExpressionTokenConsumer& Consumer, bool bEmitErrors) { // Include a leading { character - if it was a valid argument token it would have been picked up by a previous token definition bool bFirstChar = true; TOptional<FStringToken> Token = Consumer.GetStream().ParseToken([&](TCHAR C){ if (C == '{' && !bFirstChar) { return EParseState::StopBefore; } else if (C == EscapeChar) { return EParseState::StopBefore; } else { bFirstChar = false; // Keep consuming return EParseState::Continue; } }); if (Token.IsSet()) { // Add the token to the consumer. This moves the read position in the stream to the end of the token. Consumer.Add(Token.GetValue(), FStringLiteral(Token.GetValue())); } return TOptional<FExpressionError>(); }
TOptional<FExpressionError> ParseLiteral(FExpressionTokenConsumer& Consumer) { FTokenStream& Stream = Consumer.GetStream(); TOptional<FStringToken> Token; { bool bFirstChar = true; Token = Stream.ParseToken([&](TCHAR C) { // Always include the first character, since if it was the start of a valid token then it would have been picked up by a higher priority token parser if (bFirstChar) { bFirstChar = false; return EParseState::Continue; } else if (!IsLiteralBreakChar(C)) { return EParseState::Continue; } else { return EParseState::StopBefore; } }); } if (Token.IsSet()) { // Add the token to the consumer - this moves the read position in the stream to the end of the token FStringToken& TokenValue = Token.GetValue(); Consumer.Add(TokenValue, FStringLiteral(TokenValue)); } return TOptional<FExpressionError>(); }
FReply FSequencerTimeSliderController::OnMouseWheel( TSharedRef<SWidget> WidgetOwner, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( TimeSliderArgs.AllowZoom ) { const float ZoomDelta = -0.1f * MouseEvent.GetWheelDelta(); { TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float OutputViewSize = LocalViewRangeMax - LocalViewRangeMin; const float OutputChange = OutputViewSize * ZoomDelta; float NewViewOutputMin = LocalViewRangeMin - (OutputChange * 0.5f); float NewViewOutputMax = LocalViewRangeMax + (OutputChange * 0.5f); if( FMath::Abs( OutputChange ) > 0.01f && NewViewOutputMin < NewViewOutputMax ) { TOptional<float> LocalClampMin = TimeSliderArgs.ClampMin.Get(); TOptional<float> LocalClampMax = TimeSliderArgs.ClampMax.Get(); // Clamp the range if clamp values are set if ( LocalClampMin.IsSet() && NewViewOutputMin < LocalClampMin.GetValue() ) { NewViewOutputMin = LocalClampMin.GetValue(); } if ( LocalClampMax.IsSet() && NewViewOutputMax > LocalClampMax.GetValue() ) { NewViewOutputMax = LocalClampMax.GetValue(); } TimeSliderArgs.OnViewRangeChanged.ExecuteIfBound(TRange<float>(NewViewOutputMin, NewViewOutputMax)); if( !TimeSliderArgs.ViewRange.IsBound() ) { // The output is not bound to a delegate so we'll manage the value ourselves TimeSliderArgs.ViewRange.Set( TRange<float>( NewViewOutputMin, NewViewOutputMax ) ); } } } return FReply::Handled(); } return FReply::Unhandled(); }
TOptional<FVector4> FVectorPropertySection::GetPropertyValueAsVector4() const { if (ChannelsUsed == 2) { TOptional<FVector2D> Vector = GetPropertyValue<FVector2D>(); return Vector.IsSet() ? TOptional<FVector4>(FVector4(Vector.GetValue().X, Vector.GetValue().Y, 0, 0)) : TOptional<FVector4>(); } else if (ChannelsUsed == 3) { TOptional<FVector> Vector = GetPropertyValue<FVector>(); return Vector.IsSet() ? TOptional<FVector4>(FVector4(Vector.GetValue().X, Vector.GetValue().Y, Vector.GetValue().Z, 0)) : TOptional<FVector4>(); } else // ChannelsUsed == 4 { return GetPropertyValue<FVector4>(); } }
TOptional<FGuid> FActorReferencePropertySection::GetActorGuid() const { TOptional<AActor*> CurrentActor = GetPropertyValue<AActor*>(); if (CurrentActor.IsSet() && CurrentActor.GetValue() != nullptr) { return TOptional<FGuid>(GetSequencer()->GetFocusedMovieSceneSequenceInstance()->FindObjectId(*CurrentActor.GetValue())); } return TOptional<FGuid>(FGuid()); }
/** Generate a config from the specified options, to pass to FFileCache on construction */ DirectoryWatcher::FFileCacheConfig GenerateFileCacheConfig(const FString& InPath, const DirectoryWatcher::FMatchRules& InMatchRules, const FString& InMountedContentPath) { FString Directory = FPaths::ConvertRelativePathToFull(InPath); const FString& HashString = InMountedContentPath.IsEmpty() ? Directory : InMountedContentPath; const uint32 CRC = FCrc::MemCrc32(*HashString, HashString.Len()*sizeof(TCHAR)); FString CacheFilename = FPaths::ConvertRelativePathToFull(FPaths::GameIntermediateDir()) / TEXT("ReimportCache") / FString::Printf(TEXT("%u.bin"), CRC); DirectoryWatcher::FFileCacheConfig Config(Directory, MoveTemp(CacheFilename)); Config.Rules = InMatchRules; // We always store paths inside content folders relative to the folder Config.PathType = DirectoryWatcher::EPathType::Relative; Config.bDetectChangesSinceLastRun = GetDefault<UEditorLoadingSavingSettings>()->bDetectChangesOnStartup; // It's safe to assume the asset registry is not re-loadable IAssetRegistry* Registry = &FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get(); Config.CustomChangeLogic = [Directory, Registry](const DirectoryWatcher::FImmutableString& InRelativePath, const DirectoryWatcher::FFileData& FileData) -> TOptional<bool> { int32 TotalNumReferencingAssets = 0; TArray<FAssetData> Assets = FAssetSourceFilenameCache::Get().GetAssetsPertainingToFile(*Registry, Directory / InRelativePath.Get()); if (Assets.Num() == 0) { return TOptional<bool>(); } // We need to consider this as a changed file if the hash doesn't match any asset imported from that file for (FAssetData& Asset : Assets) { TOptional<FAssetImportInfo> Info = FAssetSourceFilenameCache::ExtractAssetImportInfo(Asset.TagsAndValues); // Check if the source file that this asset last imported was the same as the one we're going to reimport. // If it is, there's no reason to auto-reimport it if (Info.IsSet() && Info->SourceFiles.Num() == 1) { if (Info->SourceFiles[0].FileHash != FileData.FileHash) { return true; } } } return TOptional<bool>(); }; // We only detect changes for when the file *contents* have changed (not its timestamp) Config .DetectMoves(true) .DetectChangesFor(DirectoryWatcher::FFileCacheConfig::Timestamp, false) .DetectChangesFor(DirectoryWatcher::FFileCacheConfig::FileHash, true); return Config; }
void FAssetSourceFilenameCache::HandleOnAssetAdded(const FAssetData& AssetData) { TOptional<FAssetImportInfo> ImportData = ExtractAssetImportInfo(AssetData.TagsAndValues); if (ImportData.IsSet()) { for (const auto& SourceFile : ImportData->SourceFiles) { SourceFileToObjectPathCache.FindOrAdd(FPaths::GetCleanFilename(SourceFile.RelativeFilename)).Add(AssetData.ObjectPath); } } }
LexResultType Lex(const TCHAR* InExpression, const FTokenDefinitions& TokenDefinitions) { FExpressionTokenConsumer TokenConsumer(InExpression); TOptional<FExpressionError> Error = TokenDefinitions.ConsumeTokens(TokenConsumer); if (Error.IsSet()) { return MakeError(Error.GetValue()); } return MakeValue(TokenConsumer.Extract()); }
EPopupMethod QueryPopupMethod(const FWidgetPath& PathToQuery) { for (int32 WidgetIndex = PathToQuery.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex) { TOptional<EPopupMethod> PopupMethod = PathToQuery.Widgets[WidgetIndex].Widget->OnQueryPopupMethod(); if (PopupMethod.IsSet()) { return PopupMethod.GetValue(); } } return EPopupMethod::CreateNewWindow; }
/** Consume a number from the specified consumer's stream, if one exists at the current read position */ TOptional<FExpressionError> ConsumeNumber(FExpressionTokenConsumer& Consumer) { auto& Stream = Consumer.GetStream(); TOptional<FStringToken> NumberToken = ExpressionParser::ParseNumber(Stream); if (NumberToken.IsSet()) { Consumer.Add(NumberToken.GetValue(), FExpressionNode(FTextToken(NumberToken.GetValue().GetString(), ETextFilterTextComparisonMode::Partial, FTextToken::EInvertResult::No))); } return TOptional<FExpressionError>(); }
void FCameraDetails::UpdateAspectTextFromProperty() { // Called whenever the actual aspect ratio property changes - clears the text box if the value no longer matches the current text TOptional<float> Value = GetAspectRatio(); if (!Value.IsSet() || Value.GetValue() < LastParsedAspectRatioValue - DELTA || Value.GetValue() > LastParsedAspectRatioValue + DELTA) { LastParsedAspectRatioValue = -1.0f; if (!AspectTextBox->GetText().IsEmpty()) { AspectTextBox->SetText(FText::GetEmpty()); } } }
/** Parse an escaped character */ TOptional<FExpressionError> ParseEscapedChar(FExpressionTokenConsumer& Consumer, bool bEmitErrors) { static const TCHAR* ValidEscapeChars = TEXT("{`"); TOptional<FStringToken> Token = Consumer.GetStream().ParseSymbol(EscapeChar); if (!Token.IsSet()) { return TOptional<FExpressionError>(); } // Accumulate the next character into the token TOptional<FStringToken> EscapedChar = Consumer.GetStream().ParseSymbol(&Token.GetValue()); if (!EscapedChar.IsSet()) { return TOptional<FExpressionError>(); } // Check for a valid escape character const TCHAR Character = *EscapedChar->GetTokenStartPos(); if (FCString::Strchr(ValidEscapeChars, Character)) { // Add the token to the consumer. This moves the read position in the stream to the end of the token. Consumer.Add(Token.GetValue(), FEscapedCharacter(Character)); return TOptional<FExpressionError>(); } else if (bEmitErrors) { FString CharStr; CharStr += Character; FFormatOrderedArguments Args; Args.Add(FText::FromString(CharStr)); return FExpressionError(FText::Format(LOCTEXT("InvalidEscapeCharacter", "Invalid escape character '{0}'"), Args)); } else { return TOptional<FExpressionError>(); } }
TOptional<FSequencerSnapField::FSnapResult> FSequencerSnapField::Snap(const TArray<float>& InTimes, float Threshold) const { TOptional<FSnapResult> ProspectiveSnap; float SnapDelta = 0.f; for (float Time : InTimes) { TOptional<float> ThisSnap = Snap(Time, Threshold); if (!ThisSnap.IsSet()) { continue; } float ThisSnapDelta = ThisSnap.GetValue() - Time; if (!ProspectiveSnap.IsSet() || FMath::Abs(ThisSnapDelta) < FMath::Abs(SnapDelta)) { ProspectiveSnap = FSnapResult{ Time, ThisSnap.GetValue() }; SnapDelta = ThisSnapDelta; } } return ProspectiveSnap; }
FString GetExportScriptPath(const FLocalizationTargetSettings& Target, const TOptional<FString> CultureName) { const FString ConfigFileDirectory = GetScriptDirectory(); FString ConfigFilePath; if (CultureName.IsSet()) { ConfigFilePath = ConfigFileDirectory / FString::Printf( TEXT("%s_Export_%s.%s"), *Target.Name, *CultureName.GetValue(), TEXT("ini") ); } else { ConfigFilePath = ConfigFileDirectory / FString::Printf( TEXT("%s_Export.%s"), *Target.Name, TEXT("ini") ); } return ConfigFilePath; }
TOptional<FExpressionError> ConsumeOperator(FExpressionTokenConsumer& Consumer) { auto& Stream = Consumer.GetStream(); for (const TCHAR* Moniker : TSymbol::Monikers) { TOptional<FStringToken> OperatorToken = Stream.ParseToken(Moniker); if (OperatorToken.IsSet()) { Consumer.Add(OperatorToken.GetValue(), TSymbol()); } } return TOptional<FExpressionError>(); }
TOptional<FExpressionError> ParseArgument(FExpressionTokenConsumer& Consumer) { // An argument token looks like {ArgName} FTokenStream& Stream = Consumer.GetStream(); TOptional<FStringToken> OpeningChar = Stream.ParseSymbol(ArgStartChar); if (!OpeningChar.IsSet()) { return TOptional<FExpressionError>(); } FStringToken& EntireToken = OpeningChar.GetValue(); // Parse out the argument name TOptional<FStringToken> Identifier = Stream.ParseToken([](TCHAR InC) { if (InC == ArgEndChar) { return EParseState::StopBefore; } else { return EParseState::Continue; } }, &EntireToken); if (!Identifier.IsSet() || !Stream.ParseSymbol(ArgEndChar, &EntireToken).IsSet()) { return TOptional<FExpressionError>(); } // Add the token to the consumer - this moves the read position in the stream to the end of the token FStringToken& IdentifierValue = Identifier.GetValue(); Consumer.Add(EntireToken, FArgumentTokenSpecifier(IdentifierValue)); return TOptional<FExpressionError>(); }
EActiveTimerReturnType SProgressBar::ActiveTick(double InCurrentTime, float InDeltaTime) { MarqueeOffset = InCurrentTime - FMath::FloorToDouble(InCurrentTime); TOptional<float> PrecentFracton = Percent.Get(); if (PrecentFracton.IsSet()) { SetActiveTimerTickRate(MinimumTickRate); } else { SetActiveTimerTickRate(0.0f); } return EActiveTimerReturnType::Continue; }
/** Tick this state machine with the given time limit. Will continuously enumerate the machine until TimeLimit is reached */ void Tick(const FTimeLimit& TimeLimit) { while (!TimeLimit.Exceeded()) { const auto& State = Nodes[CurrentState]; TOptional<TState> NewState = State.Endpoint(TimeLimit); if (NewState.IsSet()) { CurrentState = NewState.GetValue(); } else if (State.Type == EStateMachineNode::CallOnce) { break; } } }
TOptional<FExpressionError> FTokenDefinitions::ConsumeToken(FExpressionTokenConsumer& Consumer) const { auto& Stream = Consumer.GetStream(); // Skip over whitespace if (bIgnoreWhitespace) { TOptional<FStringToken> Whitespace = Stream.ParseWhitespace(); if (Whitespace.IsSet()) { Stream.SetReadPos(Whitespace.GetValue()); } } if (Stream.IsEmpty()) { // Trailing whitespace in the expression. return TOptional<FExpressionError>(); } const auto* Pos = Stream.GetRead(); // Try each token in turn. First come first served. for (const auto& Def : Definitions) { // Call the token definition auto Error = Def(Consumer); if (Error.IsSet()) { return Error; } // If the stream has moved on, the definition added one or more tokens, so else if (Stream.GetRead() != Pos) { return TOptional<FExpressionError>(); } } // No token definition matched the stream at its current position - fatal error FFormatOrderedArguments Args; Args.Add(FText::FromString(Consumer.GetStream().GetErrorContext())); Args.Add(Consumer.GetStream().GetPosition()); return FExpressionError(FText::Format(LOCTEXT("LexicalError", "Unrecognized token '{0}' at character {1}"), Args)); }
void FAssetSourceFilenameCache::HandleOnAssetRemoved(const FAssetData& AssetData) { TOptional<FAssetImportInfo> ImportData = ExtractAssetImportInfo(AssetData.TagsAndValues); if (ImportData.IsSet()) { for (auto& SourceFile : ImportData->SourceFiles) { FString CleanFilename = FPaths::GetCleanFilename(SourceFile.RelativeFilename); if (auto* Objects = SourceFileToObjectPathCache.Find(CleanFilename)) { Objects->Remove(AssetData.ObjectPath); if (Objects->Num() == 0) { SourceFileToObjectPathCache.Remove(CleanFilename); } } } } }
FCursorReply FUMGDragDropOp::OnCursorQuery() { FCursorReply CursorReply = FGameDragDropOperation::OnCursorQuery(); if ( !CursorReply.IsEventHandled() ) { CursorReply = CursorReply.Cursor(EMouseCursor::Default); } if ( GameViewport ) { TOptional<TSharedRef<SWidget>> CursorWidget = GameViewport->MapCursor(nullptr, CursorReply); if ( CursorWidget.IsSet() ) { CursorReply.SetCursorWidget(GameViewport->GetWindow(), CursorWidget.GetValue()); } } return CursorReply; }
void UAssetImportData::Serialize(FArchive& Ar) { if (Ar.UE4Ver() >= VER_UE4_ASSET_IMPORT_DATA_AS_JSON) { FString Json; if (Ar.IsLoading()) { Ar << Json; TOptional<FAssetImportInfo> Copy = FromJson(MoveTemp(Json)); if (Copy.IsSet()) { CopyFrom(Copy.GetValue()); } } else if (Ar.IsSaving()) { Json = ToJson(); Ar << Json; } } Super::Serialize(Ar); }
TOptional<FKeyHandle> FGroupedKeyArea::DuplicateKey(FKeyHandle KeyToDuplicate) { FKeyGrouping* Group = FindGroup(KeyToDuplicate); if (!Group) { return TOptional<FKeyHandle>(); } const float Time = Group->RepresentativeTime; const int32 NewGroupIndex = Groups.Num(); Groups.Emplace(Time); for (const FKeyGrouping::FKeyIndex& Key : Group->Keys) { TOptional<FKeyHandle> NewKeyHandle = KeyAreas[Key.AreaIndex]->DuplicateKey(Key.KeyHandle); if (NewKeyHandle.IsSet()) { Groups[NewGroupIndex].Keys.Emplace(Key.AreaIndex, NewKeyHandle.GetValue()); } } // Update the global index with our new key FIndexEntry* IndexEntry = GlobalIndex.Find(IndexKey); if (IndexEntry) { FKeyHandle ThisGroupKeyHandle; IndexEntry->GroupHandles.Add(ThisGroupKeyHandle); IndexEntry->HandleToGroup.Add(ThisGroupKeyHandle, NewGroupIndex); IndexEntry->RepresentativeTimes.Add(Time); return ThisGroupKeyHandle; } return TOptional<FKeyHandle>(); }