diff --git a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/linking/LinkingExtensions.cs b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/linking/LinkingExtensions.cs index 0a060e56b..ce3f65799 100644 --- a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/linking/LinkingExtensions.cs +++ b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/linking/LinkingExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using TMPro; using UnityEngine; namespace com.csutil { @@ -43,8 +44,24 @@ public static T Get(this Dictionary self, string id) { public static T Get(this Link self) { if (typeof(T) == typeof(GameObject)) { return (T)(object)self.gameObject; } var comp = self.GetComponentV2(); + if (comp == null) { CheckTmpAlternativePossible(self); } return comp == null ? (T)(object)null : comp; } + + /// This method checks if the UI already switched to using TMP while the code still uses the + /// old UI components and throws in improved exception instead of just returning null + private static void CheckTmpAlternativePossible(Link self) { + if (typeof(T) == typeof(UnityEngine.UI.Text)) { + if (self.HasComponent(out var tmpText)) { + throw Log.e("Found TMP_Text instead of UnityEngine.UI.Text for " + self.id, tmpText); + } + } else if (typeof(T) == typeof(UnityEngine.UI.InputField)) { + if (self.HasComponent(out var tmpInputField)) { + throw Log.e("Found TMP_InputField instead of UnityEngine.UI.InputField for " + self.id, tmpInputField); + } + } + } + } } diff --git a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiExtensionsForReduxStore.cs b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiExtensionsForReduxStore.cs index 192312f72..7293a2d3d 100644 --- a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiExtensionsForReduxStore.cs +++ b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiExtensionsForReduxStore.cs @@ -1,5 +1,6 @@ using System; using com.csutil.model.immutable; +using TMPro; using UnityEngine; using UnityEngine.UI; @@ -7,15 +8,33 @@ namespace com.csutil { public static class UiExtensionsForReduxSubState { + public static void SubscribeToStateChanges(this TMP_Text self, SubState subState, Func getSubState) { + SubscribeToStateChanges(self, subState, getSubState, newText => self.text = newText); + } + + [Obsolete("Use TMP_Text instead of Text")] public static void SubscribeToStateChanges(this Text self, SubState subState, Func getSubState) { SubscribeToStateChanges(self, subState, getSubState, newText => self.text = newText); } - public static void SetSubState(this InputField self, SubState subState, Func getSubState, Action onValueChanged) { + public static void SetSubState(this TMP_InputField self, SubState subState, Func getSubState, Action onValueChanged) { + if (self.IsNullOrDestroyed()) { throw new ArgumentNullException("self(InputField) must not be null!"); } + self.SubscribeToStateChanges(subState, getSubState); + self.AddOnValueChangedActionThrottled(onValueChanged); + } + + [Obsolete("Use TMP_InputField instead of InputField")] + public static void SetSubState(this InputField self, SubState subState, Func getSubState, Action onValueChanged) { + if (self.IsNullOrDestroyed()) { throw new ArgumentNullException("self(InputField) must not be null!"); } self.SubscribeToStateChanges(subState, getSubState); self.AddOnValueChangedActionThrottled(onValueChanged); } + + public static void SubscribeToStateChanges(this TMP_InputField self, SubState subState, Func getSubState) { + SubscribeToStateChanges(self, subState, getSubState, newText => self.text = newText); + } + [Obsolete("Use TMP_InputField instead of InputField")] public static void SubscribeToStateChanges(this InputField self, SubState subState, Func getSubState) { SubscribeToStateChanges(self, subState, getSubState, newText => self.text = newText); } @@ -29,6 +48,9 @@ public static void SubscribeToStateChanges(this Slider self, SubState(UnityEngine.Object ui, SubState sub, Func getSubState, Action updateUi) { + if (ui.IsNullOrDestroyed()) { + throw new ArgumentNullException("The Unity UI object must not be null!"); + } var subSub = sub.GetSubStateForUnity(ui, getSubState); subSub.onStateChanged += () => { updateUi(subSub.GetState()); }; } @@ -47,6 +69,9 @@ public static SubState GetSubStateForUnity(this IDataStore st } public static SubState GetSubStateForUnity(this SubState parentSubState, UnityEngine.Object context, Func getSubState, bool eventsAlwaysInMainThread = true) { + if (context.IsNullOrDestroyed()) { + throw new ArgumentNullException("The Unity context object must not be null!"); + } var subState = parentSubState.GetSubState(getSubState); var ownListenerInParent = parentSubState.AddStateChangeListener(getSubState, newSubState => { if (eventsAlwaysInMainThread) { diff --git a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiInputFieldExtensions.cs b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiInputFieldExtensions.cs index 02c0fd519..f7e1a7e82 100644 --- a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiInputFieldExtensions.cs +++ b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/UiInputFieldExtensions.cs @@ -1,11 +1,22 @@ using System; using com.csutil.ui; +using TMPro; using UnityEngine.Events; using UnityEngine.UI; namespace com.csutil { + public static class UiInputFieldExtensions { + public static UnityAction SetOnValueChangedActionThrottled(this TMP_InputField self, Action onValueChanged, double delayInMs = 500) { + if (self.onValueChanged != null && self.onValueChanged.GetPersistentEventCount() > 0) { + Log.w("Overriding old onValueChanged listener for input field " + self, self.gameObject); + } + self.onValueChanged = new TMP_InputField.OnChangeEvent(); // clear previous onValueChanged listeners + return AddOnValueChangedActionThrottled(self, onValueChanged, delayInMs); + } + + [Obsolete("Use TMP_InputField instead of InputField")] public static UnityAction SetOnValueChangedActionThrottled(this InputField self, Action onValueChanged, double delayInMs = 500) { if (self.onValueChanged != null && self.onValueChanged.GetPersistentEventCount() > 0) { Log.w("Overriding old onValueChanged listener for input field " + self, self.gameObject); @@ -14,6 +25,16 @@ public static UnityAction SetOnValueChangedActionThrottled(this InputFie return AddOnValueChangedActionThrottled(self, onValueChanged, delayInMs); } + public static UnityAction AddOnValueChangedActionThrottled(this TMP_InputField self, Action onValueChanged, double delayInMs = 500) { + EventHandler action = (_, newText) => { onValueChanged(newText); }; + var throttledAction = action.AsThrottledDebounce(delayInMs, skipFirstEvent: true); + return self.AddOnValueChangedAction((newText) => { + throttledAction(self, newText); + return true; + }); + } + + [Obsolete("Use TMP_InputField instead of InputField")] public static UnityAction AddOnValueChangedActionThrottled(this InputField self, Action onValueChanged, double delayInMs = 500) { EventHandler action = (_, newText) => { onValueChanged(newText); }; var throttledAction = action.AsThrottledDebounce(delayInMs, skipFirstEvent: true); @@ -23,18 +44,39 @@ public static UnityAction AddOnValueChangedActionThrottled(this InputFie }); } + public static void SelectV2(this TMP_InputField self) { + self.Select(); + self.ActivateInputField(); + } + /// Sets focus on the input field + [Obsolete("Use TMP_InputField instead of InputField")] public static void SelectV2(this InputField self) { self.Select(); self.ActivateInputField(); } + public static void SetTextLocalizedWithNotify(this TMP_InputField self, string text) { + self.SelectV2(); // Without this the change listeners are not triggered + self.textLocalized(text); + } + /// Sets the input text localized which will notify all UI listeners + [Obsolete("Use TMP_InputField instead of InputField")] public static void SetTextLocalizedWithNotify(this InputField self, string text) { self.SelectV2(); // Without this the change listeners are not triggered self.textLocalized(text); } + public static UnityAction SetOnValueChangedAction(this TMP_InputField self, Func onValueChanged) { + if (self.onValueChanged != null && self.onValueChanged.GetPersistentEventCount() > 0) { + Log.w("Overriding old onValueChanged listener for input field " + self, self.gameObject); + } + self.onValueChanged = new TMP_InputField.OnChangeEvent(); // clear previous onValueChanged listeners + return AddOnValueChangedAction(self, onValueChanged); + } + + [Obsolete("Use TMP_InputField instead of InputField")] public static UnityAction SetOnValueChangedAction(this InputField self, Func onValueChanged) { if (self.onValueChanged != null && self.onValueChanged.GetPersistentEventCount() > 0) { Log.w("Overriding old onValueChanged listener for input field " + self, self.gameObject); @@ -43,6 +85,30 @@ public static UnityAction SetOnValueChangedAction(this InputField self, return AddOnValueChangedAction(self, onValueChanged); } + public static UnityAction AddOnValueChangedAction(this TMP_InputField self, Func onValueChanged, bool skipChangesByLogic = true) { + if (self.IsNullOrDestroyed()) { + throw new ArgumentNullException("self (InputField)"); + } + if (onValueChanged != null) { + var oldText = self.text; + UnityAction newListener = (newText) => { + if (newText == oldText) { return; } + // Ignore event event if it was triggered through code, only fire for actual user input: + if (skipChangesByLogic && !self.ChangeWasTriggeredByUserThroughEventSystem()) { return; } + if (!onValueChanged(newText)) { + self.text = oldText; + } else { + oldText = newText; + EventBus.instance.Publish(EventConsts.catUi + UiEvents.INPUTFIELD_CHANGED, self, newText); + } + }; + self.onValueChanged.AddListener(newListener); + return newListener; + } + return null; + } + + [Obsolete("Use TMP_InputField instead of InputField")] public static UnityAction AddOnValueChangedAction(this InputField self, Func onValueChanged, bool skipChangesByLogic = true) { if (self.IsNullOrDestroyed()) { throw new ArgumentNullException("self (InputField)"); @@ -67,4 +133,5 @@ public static UnityAction AddOnValueChangedAction(this InputField self, } } + } \ No newline at end of file diff --git a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/localization/LocalizationExtensions.cs b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/localization/LocalizationExtensions.cs index 38fdec027..045b240f4 100644 --- a/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/localization/LocalizationExtensions.cs +++ b/CsCore/CsCoreUnity/Plugins/CsCoreUnity/com/csutil/ui/localization/LocalizationExtensions.cs @@ -2,12 +2,21 @@ using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; +using TMPro; using UnityEngine.UI; namespace com.csutil { public static class LocalizationExtensions { + public static void textLocalized(this TMP_InputField self, string key, params object[] args) { + I18n i18n = I18n.instance(self); + if (i18n == null) { i18n = SetupDefaultI18nInstance(self).Result; } + var localizedText = i18n.Get(key, args); + if (localizedText != self.text) { self.text = localizedText; } + } + + [Obsolete("Use TMP_InputField instead of InputField")] public static void textLocalized(this InputField self, string key, params object[] args) { I18n i18n = I18n.instance(self); if (i18n == null) { i18n = SetupDefaultI18nInstance(self).Result; } @@ -15,6 +24,14 @@ public static void textLocalized(this InputField self, string key, params object if (localizedText != self.text) { self.text = localizedText; } } + public static void textLocalized(this TMP_Text self, string key, params object[] args) { + I18n i18n = I18n.instance(self); + if (i18n == null) { i18n = SetupDefaultI18nInstance(self).Result; } + var localizedText = i18n.Get(key, args); + if (localizedText != self.text) { self.text = localizedText; } + } + + [Obsolete("Use TMP_Text instead of Text")] public static void textLocalized(this Text self, string key, params object[] args) { I18n i18n = I18n.instance(self); if (i18n == null) { i18n = SetupDefaultI18nInstance(self).Result; }