#if ENABLE_PROPERTY_VARIANTS || PACKAGE_DOCS_GENERATION
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Localization.PropertyVariants.TrackedObjects;
using UnityEngine.Localization.PropertyVariants.TrackedProperties;
using UnityEngine.Localization.Settings;
using UnityEngine.Pool;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace UnityEngine.Localization.PropertyVariants
{
///
/// The GameObject Localizer component is responsible for storing and applying all Localized Property Variants Configurations
/// for the [GameObject](https://docs.unity3d.com/ScriptReference/GameObject.html) it is attached to.
///
///
/// This shows how to configure a to apply changes to a [Text](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.Text.html)
/// component for the Font, Font Size and Text properties.
///
///
///
/// This shows how to configure a to apply changes to a [TextMeshProUGUI](https://docs.unity3d.com/Packages/com.unity.textmeshpro@latest/index.html?subfolder=/api/TMPro.TextMeshProUGUI.html)
/// component for the Font, Font Size and Text properties.
///
///
///
/// This shows how to configure a to apply changes to a [Dropdown](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.Dropdown.html)
/// for the options values.
///
///
///
/// This shows how to configure a to apply changes to a [TMP_Dropdown](https://docs.unity3d.com/Packages/com.unity.textmeshpro@latest/index.html?subfolder=/api/TMPro.TMP_Dropdown.html)
/// for the options values.
///
///
///
/// This shows how to configure a to apply changes to a for the x, y and width properties.
/// This can be useful when you need to make adjustments due to changes in text length for a particular .
///
///
///
/// This shows how to configure a to apply changes to a custom script.
///
///
[ExecuteAlways]
[DisallowMultipleComponent]
public class GameObjectLocalizer : MonoBehaviour
{
[SerializeReference]
List m_TrackedObjects = new List();
Locale m_CurrentLocale;
LocalizedString.ChangeHandler m_LocalizedStringChanged;
bool m_IgnoreChange;
internal AsyncOperationHandle CurrentOperation
{
get;
private set;
}
void OnEnable()
{
LocalizationSettings.SelectedLocaleChanged += SelectedLocaleChanged;
RegisterChanges();
// Update when reenabled (LOC-579)
if (m_CurrentLocale != null)
{
var locale = LocalizationSettings.SelectedLocale;
if (!ReferenceEquals(m_CurrentLocale, locale))
{
SelectedLocaleChanged(locale);
}
}
}
void OnDisable()
{
UnregisterChanges();
AddressablesInterface.SafeRelease(CurrentOperation);
CurrentOperation = default;
LocalizationSettings.SelectedLocaleChanged -= SelectedLocaleChanged;
}
IEnumerator Start()
{
m_CurrentLocale = null;
var localeOp = LocalizationSettings.SelectedLocaleAsync;
if (!localeOp.IsDone)
yield return localeOp;
SelectedLocaleChanged(localeOp.Result);
}
void SelectedLocaleChanged(Locale locale)
{
m_CurrentLocale = locale;
// Ignore null, this will reset the driven properties back to their defaults.
if (locale == null)
return;
ApplyLocaleVariant(locale);
}
///
/// The objects that are being tracked by this Localizer.
///
public List TrackedObjects => m_TrackedObjects;
///
/// Returns the for the target component or creates a new instance if is set to .
///
/// The Type of TrackedObject that should be found or added.
/// The Target Object to track.
/// Creates a new Tracked Object if one can not be found.
///
public T GetTrackedObject(Object target, bool create = true) where T : TrackedObject, new()
{
var trackedObject = GetTrackedObject(target);
if (trackedObject != null)
return (T)trackedObject;
if (!create)
return default;
var component = target as Component;
if (component == null)
return null;
if (component.gameObject != gameObject)
{
throw new Exception("Tracked Objects must share the same GameObject as the GameObjectLocalizer. " +
$"The Component {component} is attached to the GameObject {component.gameObject}" +
$" but the GameObjectLocalizer is attached to {gameObject}.");
}
var newTrackedObject = new T {Target = target};
TrackedObjects.Add(newTrackedObject);
return newTrackedObject;
}
///
/// Returns the for the target component or if one does not exist.
/// See for a version that will create a new TrackedObject if one does not already exist.
///
/// The target object to search for.
/// The or if one could not be found.
public TrackedObject GetTrackedObject(Object target)
{
if (target == null)
throw new ArgumentNullException(nameof(target));
foreach (var t in TrackedObjects)
{
if (t?.Target == target)
return t;
}
return null;
}
///
/// Apply all variants for the selected to this GameObject.
///
/// The to apply to the GameObject.
/// A handle to any loading operations or if the operation was immediate.
public AsyncOperationHandle ApplyLocaleVariant(Locale locale) => ApplyLocaleVariant(locale, LocalizationSettings.ProjectLocale);
///
/// Apply all variants for the selected to this GameObject.
/// When a value cannot be found, the value is used.
///
/// The to apply to the GameObject.
/// The fallback to use when a value does not exist for .
/// A handle to any loading operations or if the operation was immediate.
public AsyncOperationHandle ApplyLocaleVariant(Locale locale, Locale fallback)
{
if (CurrentOperation.IsValid())
{
if (!CurrentOperation.IsDone)
Debug.LogWarning("Attempting to Apply Variant when the previous operation has not yet completed.", this);
AddressablesInterface.Release(CurrentOperation);
CurrentOperation = default;
}
var asyncOperations = ListPool.Get();
foreach (var to in TrackedObjects)
{
if (to == null)
continue;
var operation = to.ApplyLocale(locale, fallback);
if (!operation.IsDone)
asyncOperations.Add(operation);
}
if (asyncOperations.Count == 1)
{
AddressablesInterface.Acquire(asyncOperations[0]);
CurrentOperation = asyncOperations[0];
ListPool.Release(asyncOperations);
return CurrentOperation;
}
if (asyncOperations.Count > 1)
{
CurrentOperation = AddressablesInterface.CreateGroupOperation(asyncOperations);
return CurrentOperation;
}
ListPool.Release(asyncOperations);
return default;
}
void RegisterChanges()
{
if (m_LocalizedStringChanged == null)
m_LocalizedStringChanged = _ => RequestUpdate();
try
{
// Ignore any changes sent whilst we register
m_IgnoreChange = true;
foreach (var trackedObject in m_TrackedObjects)
{
foreach (var property in trackedObject.TrackedProperties)
{
if (property is LocalizedStringProperty localizedStringProperty)
{
localizedStringProperty.LocalizedString.StringChanged += m_LocalizedStringChanged;
}
}
}
}
finally
{
m_IgnoreChange = false;
}
}
void UnregisterChanges()
{
if (m_LocalizedStringChanged == null)
return;
foreach (var trackedObject in m_TrackedObjects)
{
foreach (var property in trackedObject.TrackedProperties)
{
if (property is LocalizedStringProperty localizedStringProperty)
{
localizedStringProperty.LocalizedString.StringChanged -= m_LocalizedStringChanged;
}
}
}
}
void RequestUpdate()
{
// Ignore the change, it will be handled by the selected locale change event.
if (m_IgnoreChange || LocalizationSettings.Instance.IsChangingSelectedLocale || (CurrentOperation.IsValid() && !CurrentOperation.IsDone))
return;
ApplyLocaleVariant(m_CurrentLocale);
}
}
}
#endif