FText UAnimGraphNode_BoneDrivenController::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if ((Node.SourceBone.BoneName == NAME_None) && (Node.TargetBone.BoneName == NAME_None) && (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle))
	{
		return GetControllerDescription();
	}
	// @TODO: the bone can be altered in the property editor, so we have to 
	//        choose to mark this dirty when that happens for this to properly work
	else //if (!CachedNodeTitles.IsTitleCached(TitleType, this))
	{
		FFormatNamedArguments Args;
		Args.Add(TEXT("ControllerDesc"), GetControllerDescription());
		Args.Add(TEXT("SourceBone"), FText::FromName(Node.SourceBone.BoneName));
		Args.Add(TEXT("TargetBone"), FText::FromName(Node.TargetBone.BoneName));

		if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
		{
			Args.Add(TEXT("Delim"), FText::FromString(TEXT(" - ")));
		}
		else
		{
			Args.Add(TEXT("Delim"), FText::FromString(TEXT("\n")));
		}

		// FText::Format() is slow, so we cache this to save on performance
		CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("AnimGraphNode_BoneDrivenController_Title", "{ControllerDesc}{Delim}Driving Bone: {SourceBone}{Delim}Driven Bone: {TargetBone}"), Args), this);
	}	
	return CachedNodeTitles[TitleType];
}
void UAnimGraphNode_SkeletalControlBase::GetMenuEntries(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
	UAnimGraphNode_Base* TemplateNode = NewObject<UAnimGraphNode_Base>(GetTransientPackage(), GetClass());

	FString Category = TemplateNode->GetNodeCategory();
	FText MenuDesc = GetControllerDescription();
	FString Tooltip = GetControllerDescription().ToString();
	FString Keywords = TemplateNode->GetKeywords();

	TSharedPtr<FEdGraphSchemaAction_K2NewNode> NodeAction = FK2ActionMenuBuilder::AddNewNodeAction(ContextMenuBuilder, Category, MenuDesc, Tooltip, 0, Keywords);
	NodeAction->NodeTemplate = TemplateNode;
}
FString UAnimGraphNode_SpringBone::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	FString Result = *GetControllerDescription();
	Result += (TitleType == ENodeTitleType::ListView) ? TEXT(" - ") : TEXT("\n");
	Result += FString::Printf(TEXT("Bone: %s"), *Node.SpringBone.BoneName.ToString());
	return Result;
}
FText UAnimGraphNode_CopyBoneDelta::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if((TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle) && (Node.TargetBone.BoneName == NAME_None) && (Node.SourceBone.BoneName == NAME_None))
	{
		return GetControllerDescription();
	}
	else
	{
		FFormatNamedArguments Args;
		Args.Add(TEXT("Description"), GetControllerDescription());
		Args.Add(TEXT("Source"), FText::FromName(Node.SourceBone.BoneName));
		Args.Add(TEXT("Target"), FText::FromName(Node.TargetBone.BoneName));

		if(TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
		{
			return FText::Format(LOCTEXT("ListTitle", "{Description} - Source Bone: {Source} - Target Bone: {Target}"), Args);
		}
		else
		{
			return FText::Format(LOCTEXT("Title", "{Description}\nSource Bone: {Source}\nTarget Bone: {Target}"), Args);
		}
	}
}
FText UAnimGraphNode_WheelHandler::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	FText NodeTitle;
	if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
	{
		NodeTitle = GetControllerDescription();
	}
	else
	{
		// we don't have any run-time information, so it's limited to print  
		// anymore than what it is it would be nice to print more data such as 
		// name of bones for wheels, but it's not available in Persona
		NodeTitle = FText(LOCTEXT("AnimGraphNode_WheelHandler_Title", "Wheel Handler"));
	}	
	return NodeTitle;
}
FText UAnimGraphNode_FootPlacementIK::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	FFormatNamedArguments Args;
	Args.Add(TEXT("ControllerDescription"), GetControllerDescription());
	Args.Add(TEXT("BoneName"), FText::FromName(Node.IKBone.BoneName));

	if(TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
	{
		if (Node.IKBone.BoneName == NAME_None)
		{
			return FText::Format(LOCTEXT("FootPlacementIK_MenuTitle", "{ControllerDescription}"), Args);
		}
		return FText::Format(LOCTEXT("FootPlacementIK_ListTitle", "{ControllerDescription} - Bone: {BoneName}"), Args);
	}
	else
	{
		return FText::Format(LOCTEXT("FootPlacementIK_FullTitle", "{ControllerDescription}\nBone: {BoneName}"), Args);
	}
}
FText UAnimGraphNode_TwistCorrectiveNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if ((Node.BaseFrame.Bone.BoneName == NAME_None) && (Node.TwistFrame.Bone.BoneName == NAME_None) 
		&& ((TitleType == ENodeTitleType::ListView) || (TitleType == ENodeTitleType::MenuTitle)))
	{
		return GetControllerDescription();
	}
	else
	{
		FText FormattedText;

		FFormatNamedArguments Args;
		Args.Add(TEXT("BaseBone"), FText::FromName(Node.BaseFrame.Bone.BoneName));
		Args.Add(TEXT("TwistBone"), FText::FromName(Node.TwistFrame.Bone.BoneName));
		Args.Add(TEXT("CurveName"), FText::FromName(Node.Curve.Name));

		FormattedText = FText::Format(LOCTEXT("AnimGraphNode_TwistCorrectiveNode_Title", "Twist {CurveName} = {BaseBone}:{TwistBone} "), Args);
		return FormattedText;
	}	
}
FText UAnimGraphNode_BoneDrivenController::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if ((Node.SourceBone.BoneName == NAME_None) && (Node.TargetBone.BoneName == NAME_None) && ((TitleType == ENodeTitleType::ListView) || (TitleType == ENodeTitleType::MenuTitle)))
	{
		return GetControllerDescription();
	}
	else
	{
		// Determine the mapping
		FText FinalSourceExpression;
		{
			FFormatNamedArguments SourceArgs;
			SourceArgs.Add(TEXT("SourceBone"), FText::FromName(Node.SourceBone.BoneName));
			SourceArgs.Add(TEXT("SourceComponent"), ComponentTypeToText(Node.SourceComponent));

			if (Node.DrivingCurve != nullptr)
			{
				FinalSourceExpression = LOCTEXT("BoneDrivenByCurve", "curve({SourceBone}.{SourceComponent})");
			}
			else
			{
				if (Node.bUseRange)
				{
					if (Node.Multiplier == 1.0f)
					{
						FinalSourceExpression = LOCTEXT("WithRangeBoneMultiplierIs1", "remap({SourceBone}.{SourceComponent})");
					}
					else
					{
						SourceArgs.Add(TEXT("Multiplier"), FText::AsNumber(Node.Multiplier));
						FinalSourceExpression = LOCTEXT("WithRangeNonUnityMultiplier", "remap({SourceBone}.{SourceComponent}) * {Multiplier}");
					}
				}
				else
				{
					if (Node.Multiplier == 1.0f)
					{
						FinalSourceExpression = LOCTEXT("BoneMultiplierIs1", "{SourceBone}.{SourceComponent}");
					}
					else
					{
						SourceArgs.Add(TEXT("Multiplier"), FText::AsNumber(Node.Multiplier));
						FinalSourceExpression = LOCTEXT("NonUnityMultiplier", "{SourceBone}.{SourceComponent} * {Multiplier}");
					}
				}
			}

			FinalSourceExpression = FText::Format(FinalSourceExpression, SourceArgs);
		}

		FFormatNamedArguments Args;
		Args.Add(TEXT("ControllerDesc"), GetControllerDescription());
		Args.Add(TEXT("TargetBone"), FText::FromName(Node.TargetBone.BoneName));

		// Determine the target component
		int32 NumComponents = 0;
		EComponentType::Type TargetComponent = EComponentType::None;

		#define UE_CHECK_TARGET_COMPONENT(ComponentProperty, ComponentChoice) if (ComponentProperty) { ++NumComponents; TargetComponent = ComponentChoice; }
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetTranslationX, EComponentType::TranslationX);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetTranslationY, EComponentType::TranslationY);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetTranslationZ, EComponentType::TranslationZ);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetRotationX, EComponentType::RotationX);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetRotationY, EComponentType::RotationY);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetRotationZ, EComponentType::RotationZ);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetScaleX, EComponentType::ScaleX);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetScaleY, EComponentType::ScaleY);
		UE_CHECK_TARGET_COMPONENT(Node.bAffectTargetScaleZ, EComponentType::ScaleZ);
		Args.Add(TEXT("TargetComponents"), (NumComponents <= 1) ? ComponentTypeToText(TargetComponent) : LOCTEXT("MultipleTargetComponents", "multiple"));

		if ((TitleType == ENodeTitleType::ListView) || (TitleType == ENodeTitleType::MenuTitle))
		{
			Args.Add(TEXT("Delim"), FText::FromString(TEXT(" - ")));
		}
		else
		{
			Args.Add(TEXT("Delim"), FText::FromString(TEXT("\n")));
		}

		Args.Add(TEXT("SourceExpression"), FinalSourceExpression);
		return FText::Format(LOCTEXT("AnimGraphNode_BoneDrivenController_Title", "{TargetBone}.{TargetComponents} = {SourceExpression}{Delim}{ControllerDesc}"), Args);
	}	
}
FText UAnimGraphNode_SkeletalControlBase::GetTooltipText() const
{
	return GetControllerDescription();
}
FText UAnimGraphNode_Fabrik::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	return GetControllerDescription();
}
FText UAnimGraphNode_ModifyBone::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if ((TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle) && (Node.BoneToModify.BoneName == NAME_None))
	{
		return GetControllerDescription();
	}
	// @TODO: the bone can be altered in the property editor, so we have to 
	//        choose to mark this dirty when that happens for this to properly work
	else //if (!CachedNodeTitles.IsTitleCached(TitleType, this))
	{
		FFormatNamedArguments Args;
		Args.Add(TEXT("ControllerDescription"), GetControllerDescription());
		Args.Add(TEXT("BoneName"), FText::FromName(Node.BoneToModify.BoneName));

		// FText::Format() is slow, so we cache this to save on performance
		if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
		{
			CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("AnimGraphNode_ModifyBone_ListTitle", "{ControllerDescription} - Bone: {BoneName}"), Args), this);
		}
		else
		{
			CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("AnimGraphNode_ModifyBone_Title", "{ControllerDescription}\nBone: {BoneName}"), Args), this);
		}
	}
	return CachedNodeTitles[TitleType];
}