namespace UIWidgets { using System; using System.Collections; using System.Collections.Generic; using UIWidgets.Styles; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; /// /// Autocomplete. /// Allow quickly find and select from a list of values as user type. /// DisplayListView - used to display list of values. /// TargetListView - if specified selected value will be added to this list. /// DataSource - list of values. /// [Obsolete("Replaced with AutocompleteString.")] public class Autocomplete : MonoBehaviour, IStylable, IUpgradeable, IUpdatable, ILateUpdatable { /// /// InputField for autocomplete. /// [SerializeField] [HideInInspector] [Obsolete("Replaced with InputFieldAdapter")] protected InputField InputField; /// /// Adapter for InputField. /// Allow to work with default InputField and TMPro InputField. /// [SerializeField] protected InputFieldAdapter inputFieldAdapter; /// /// Adapter for InputField. /// Allow to work with default InputField and TMPro InputField. /// public virtual InputFieldAdapter InputFieldAdapter { get { return inputFieldAdapter; } } /// /// Proxy for InputField. /// [Obsolete("Replaced with InputFieldAdapter")] protected IInputFieldProxy inputFieldProxy; /// /// Proxy for InputField. /// Allow to work with default InputField and TMPro InputField. /// [Obsolete("Replaced with InputFieldAdapter")] public virtual IInputFieldProxy InputFieldProxy { get { return inputFieldAdapter; } } /// /// ListView to display available values. /// [SerializeField] public ListView DisplayListView; /// /// Selected value will be added to this ListView. /// [SerializeField] public ListView TargetListView; /// /// The allow duplicate of TargetListView items. /// [SerializeField] public bool AllowDuplicate = false; /// /// List of values. /// [SerializeField] public List DataSource; /// /// The filter. /// [SerializeField] protected AutocompleteFilter filter; /// /// Gets or sets the filter. /// /// The filter. public AutocompleteFilter Filter { get { return filter; } set { filter = value; CustomFilter = null; } } /// /// Is filter case sensitive? /// [SerializeField] public bool CaseSensitive; /// /// The delimiter chars to find word for autocomplete if InputType == Word. /// [SerializeField] public char[] DelimiterChars = new char[] { ' ', '\n' }; /// /// Custom filter. /// public Func> CustomFilter; /// /// Use entire input or current word in input. /// [SerializeField] protected AutocompleteInput InputType = AutocompleteInput.Word; /// /// Append value to input or replace input. /// [SerializeField] protected AutocompleteResult Result = AutocompleteResult.Append; /// /// OnOptionSelected event. /// public AutocompleteEvent OnOptionSelected = new AutocompleteEvent(); /// /// Current word in input or whole input for autocomplete. /// [HideInInspector] protected string Query = string.Empty; /// /// Current word in input or whole input for autocomplete. /// [HideInInspector] [Obsolete("Use Query instead.")] protected string Input { get { return Query; } set { Query = value; } } /// /// The previous input string. /// protected string PrevQuery; /// /// InputField.caretPosition. Used to keep caretPosition with Up and Down actions. /// protected int CaretPosition; /// /// The minimum number of characters a user must type before a search is performed. /// [SerializeField] public int MinLength = 0; /// /// The delay in seconds between when a keystroke occurs and when a search is performed. /// [SerializeField] public float SearchDelay = 0f; /// /// Use unscaled time. /// [SerializeField] public bool UnscaledTime = true; /// /// Coroutine to performs search. /// protected IEnumerator SearchCoroutine; /// /// Determines whether the beginning of value matches the Input. /// /// Value. /// true if beginning of value matches the Input; otherwise, false. public virtual bool Startswith(string value) { return UtilitiesCompare.StartsWith(value, Query, CaseSensitive); } /// /// Returns a value indicating whether Input occurs within specified value. /// /// Value. /// true if the Input occurs within value parameter; otherwise, false. public virtual bool Contains(string value) { return UtilitiesCompare.Contains(value, Query, CaseSensitive); } /// /// Convert value to string. /// /// The string value. /// Value. protected virtual string GetStringValue(string value) { return value; } bool isInited; /// /// Start this instance. /// protected virtual void Start() { Init(); } /// /// Init this instance. /// protected virtual void Init() { if (isInited) { return; } isInited = true; Upgrade(); InputFieldAdapter.onValueChanged.AddListener(ApplyFilter); var inputListener = Utilities.GetOrAddComponent(InputFieldAdapter.gameObject); inputListener.OnMoveEvent.AddListener(SelectResult); inputListener.OnSubmitEvent.AddListener(SubmitResult); inputListener.onDeselect.AddListener(InputDeselected); DisplayListView.gameObject.SetActive(false); DisplayListView.MultipleSelect = false; DisplayListView.OnSelect.AddListener(ItemSelected); } /// /// Gets the input field text. /// /// The input field text. public virtual string GetInputFieldText() { return InputFieldAdapter.text; } /// /// Allow to handle item selection event. /// protected bool AllowItemSelectionEvent; /// /// Handle input deselected event. /// /// Event data. protected virtual void InputDeselected(BaseEventData eventData) { var ev = eventData as PointerEventData; if ((ev != null) && (ev.pointerCurrentRaycast.gameObject != null) && ev.pointerCurrentRaycast.gameObject.transform.IsChildOf(DisplayListView.transform)) { AllowItemSelectionEvent = true; } else { AllowItemSelectionEvent = false; HideOptions(); } } /// /// Handle item selected event. /// /// Index. /// Component. protected virtual void ItemSelected(int index, ListViewItem component) { if (AllowItemSelectionEvent) { AllowItemSelectionEvent = false; SubmitResult(null); } } /// /// Show all options. /// public void ShowAllOptions() { InputFieldAdapter.Focus(); ApplyFilter(string.Empty, false); } /// /// Canvas will be used as parent for DisplayListView. /// protected Transform CanvasTransform; /// /// Closes the options. /// /// Input. protected virtual void HideOptions(string input) { HideOptions(); } /// /// Closes the options. /// protected virtual void HideOptions() { if (CanvasTransform != null) { Utilities.GetOrAddComponent(DisplayListView).Restore(); } DisplayListView.gameObject.SetActive(false); } /// /// Shows the options. /// protected virtual void ShowOptions() { CanvasTransform = UtilitiesUI.FindTopmostCanvas(DisplayListView.transform); if (CanvasTransform != null) { Utilities.GetOrAddComponent(DisplayListView).SetParent(CanvasTransform); } DisplayListView.gameObject.SetActive(true); } /// /// Gets the results. /// /// Values matches filter. protected virtual ObservableList GetResults() { if (CustomFilter != null) { return CustomFilter(Query); } else { if (Filter == AutocompleteFilter.Startswith) { return UtilitiesCollections.FindAll(DataSource, Startswith); } else { return UtilitiesCollections.FindAll(DataSource, Contains); } } } /// /// Sets the input. /// /// Input string. /// Query string. protected virtual string Input2Query(string input) { if (InputType == AutocompleteInput.AllInput) { return input; } int end_position = InputFieldAdapter.caretPosition; if (input.Length >= end_position) { var text = input.Substring(0, end_position); var start_position = text.LastIndexOfAny(DelimiterChars) + 1; return text.Substring(start_position).Trim(); } return input; } /// /// Applies the filter. /// /// Input. protected virtual void ApplyFilter(string input) { ApplyFilter(input, true); } /// /// Applies the filter. /// /// Input. /// Does not show options, if input not changed. protected virtual void ApplyFilter(string input, bool skipIfSame) { if (SearchCoroutine != null) { StopCoroutine(SearchCoroutine); } if (EventSystem.current.currentSelectedGameObject != InputFieldAdapter.gameObject) { return; } Query = Input2Query(input); if (skipIfSame && (Query == PrevQuery)) { return; } PrevQuery = Query; if (Query.Length < MinLength) { DisplayListView.DataSource.Clear(); HideOptions(); return; } DisplayListView.Init(); DisplayListView.MultipleSelect = false; SearchCoroutine = Search(); StartCoroutine(SearchCoroutine); } /// /// Performs search with delay. /// /// Yield instruction. [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "HAA0401:Possible allocation of reference type enumerator", Justification = "Enumerator is reusable.")] protected virtual IEnumerator Search() { if (SearchDelay > 0) { yield return UtilitiesTime.Wait(SearchDelay, UnscaledTime); } DisplayListView.DataSource = GetResults(); if (DisplayListView.DataSource.Count > 0) { ShowOptions(); DisplayListView.SelectedIndex = -1; } else { HideOptions(); } } /// /// Process the enable event. /// protected virtual void OnEnable() { Updater.Add(this); Updater.AddLateUpdate(this); } /// /// Process the disable event. /// protected virtual void OnDisable() { Updater.Remove(this); Updater.RemoveLateUpdate(this); } /// /// Update this instance. /// public virtual void RunUpdate() { if (!AllowItemSelectionEvent) { CaretPosition = InputFieldAdapter.caretPosition; } } /// /// Caret position after Enter pressed. /// protected int FixCaretPosition = -1; /// /// LateUpdate. /// public virtual void RunLateUpdate() { if (FixCaretPosition != -1) { #if UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_3_OR_NEWER InputFieldAdapter.caretPosition = FixCaretPosition; #else InputFieldAdapter.MoveToEnd(); #endif FixCaretPosition = -1; } } /// /// Selects the result. /// /// Event data. protected virtual void SelectResult(AxisEventData eventData) { if (!DisplayListView.gameObject.activeInHierarchy) { return; } if (DisplayListView.DataSource.Count == 0) { return; } switch (eventData.moveDir) { case MoveDirection.Up: if (DisplayListView.SelectedIndex > 0) { DisplayListView.SelectedIndex -= 1; } else { DisplayListView.SelectedIndex = DisplayListView.DataSource.Count - 1; } DisplayListView.ScrollTo(DisplayListView.SelectedIndex); InputFieldAdapter.caretPosition = CaretPosition; break; case MoveDirection.Down: if (DisplayListView.SelectedIndex == (DisplayListView.DataSource.Count - 1)) { DisplayListView.SelectedIndex = 0; } else { DisplayListView.SelectedIndex += 1; } DisplayListView.ScrollTo(DisplayListView.SelectedIndex); InputFieldAdapter.caretPosition = CaretPosition; break; default: if (Input2Query(InputFieldAdapter.text) != Query) { ApplyFilter(InputFieldAdapter.text); } break; } } /// /// Submits the result. /// /// Event data. protected virtual void SubmitResult(BaseEventData eventData) { SubmitResult(eventData, false); } /// /// Submits the result. /// /// Event data. /// Is Enter pressed. protected virtual void SubmitResult(BaseEventData eventData, bool isEnter) { if (DisplayListView.SelectedIndex == -1) { return; } if (InputFieldAdapter.IsMultiLineNewline()) { if (!DisplayListView.gameObject.activeInHierarchy) { return; } else { isEnter = false; } } var item = DisplayListView.DataSource[DisplayListView.SelectedIndex]; if (TargetListView != null) { TargetListView.Init(); TargetListView.Set(item, AllowDuplicate); } var value = GetStringValue(item); if (Result == AutocompleteResult.Append) { int end_position = (DisplayListView.gameObject.activeInHierarchy && eventData != null && !isEnter) ? InputFieldAdapter.caretPosition : CaretPosition; var text = InputFieldAdapter.text.Substring(0, end_position); var start_position = text.LastIndexOfAny(DelimiterChars) + 1; InputFieldAdapter.text = string.Format("{0}{1}{2}", InputFieldAdapter.text.Substring(0, start_position), value, InputFieldAdapter.text.Substring(end_position)); InputFieldAdapter.caretPosition = start_position + value.Length; #if UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_3_OR_NEWER // InputField.selectionFocusPosition = start_position + value.Length; #else InputFieldAdapter.MoveToEnd(); #endif if (isEnter) { FixCaretPosition = start_position + value.Length; InputFieldAdapter.ActivateInputField(); } } else { InputFieldAdapter.text = value; #if UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_3_OR_NEWER InputFieldAdapter.caretPosition = value.Length; #else InputFieldAdapter.ActivateInputField(); #endif FixCaretPosition = value.Length; } OnOptionSelected.Invoke(item); HideOptions(); } /// /// This function is called when the MonoBehaviour will be destroyed. /// protected virtual void OnDestroy() { if (DisplayListView != null) { DisplayListView.OnSelect.RemoveListener(ItemSelected); } if (InputFieldAdapter != null) { InputFieldAdapter.onValueChanged.RemoveListener(ApplyFilter); var inputListener = InputFieldAdapter.gameObject.GetComponent(); if (inputListener != null) { inputListener.OnMoveEvent.RemoveListener(SelectResult); inputListener.OnSubmitEvent.RemoveListener(SubmitResult); inputListener.onDeselect.RemoveListener(InputDeselected); } } } /// /// Upgrade this instance. /// public virtual void Upgrade() { #pragma warning disable 0618 Utilities.GetOrAddComponent(InputField, ref inputFieldAdapter); #pragma warning restore 0618 } #if UNITY_EDITOR /// /// Validate this instance. /// protected virtual void OnValidate() { Compatibility.Upgrade(this); } #endif #region IStylable implementation /// /// Set InputField style. /// /// Style data. protected virtual void SetStyleInput(Style style) { if (InputFieldAdapter == null) { return; } if (InputFieldAdapter.textComponent != null) { style.Autocomplete.InputField.ApplyTo(InputFieldAdapter.textComponent.gameObject, true); } if (InputFieldAdapter.placeholder != null) { style.Autocomplete.Placeholder.ApplyTo(InputFieldAdapter.placeholder.gameObject); } } /// public virtual bool SetStyle(Style style) { style.Autocomplete.Background.ApplyTo(GetComponent()); SetStyleInput(style); if (DisplayListView != null) { DisplayListView.SetStyle(style); } if (TargetListView != null) { TargetListView.SetStyle(style); } return true; } /// /// Set style options from InputField. /// /// Style data. protected virtual void GetStyleInput(Style style) { if (InputFieldAdapter == null) { return; } if (InputFieldAdapter.textComponent != null) { style.Autocomplete.InputField.GetFrom(InputFieldAdapter.textComponent.gameObject, true); } if (InputFieldAdapter.placeholder != null) { style.Autocomplete.Placeholder.GetFrom(InputFieldAdapter.placeholder.gameObject); } } /// public virtual bool GetStyle(Style style) { style.Autocomplete.Background.GetFrom(GetComponent()); GetStyleInput(style); if (DisplayListView != null) { DisplayListView.GetStyle(style); } if (TargetListView != null) { TargetListView.GetStyle(style); } return true; } #endregion } }