using UnityEngine; using UnityEngine.UI; using UnityEngine.Events; using UnityEngine.EventSystems; using System.Collections; using System.Collections.Generic; using System; using System.Linq; namespace UIWidgets { /// /// AutocompleteFilter. /// Startswith - value should beginnig with Input. /// Contains - Input occurs with value. /// public enum AutocompleteFilter { Startswith = 0, Contains = 1, } /// /// AutocompleteInput. /// Word - Use current word in input /// AllInput - use entire input. /// public enum AutocompleteInput { Word = 0, AllInput = 1, } /// /// AutocompleteResult. /// Append - append value to input. /// Result - replace input. /// public enum AutocompleteResult { Append = 0, Replace = 1, } /// /// 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. /// public abstract class AutocompleteCustom : MonoBehaviour where TListView : ListViewCustom where TListViewComponent : ListViewItem { /// /// InputField for autocomplete. /// [SerializeField] protected InputField InputField; IInputFieldProxy inputFieldProxy; /// /// Gets the InputFieldProxy. /// protected virtual IInputFieldProxy InputFieldProxy { get { if (inputFieldProxy==null) { inputFieldProxy = new InputFieldProxy(InputField); } return inputFieldProxy; } } /// /// ListView to display available values. /// [SerializeField] public TListView TargetListView; /// /// Selected value will be added to this ListView. /// [SerializeField] public TListView DisplayListView; /// /// 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[] {' '}; /// /// 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 UnityEvent OnOptionSelected = new UnityEvent(); /// /// Current word in input or whole input for autocomplete. /// [HideInInspector] protected string Input = string.Empty; /// /// InputField.caretPosition. Used to keep caretPosition with Up and Down actions. /// protected int CaretPosition; /// /// Determines whether the beginnig of value matches the Input. /// /// Value. /// true if beginnig of value matches the Input; otherwise, false. public abstract bool Startswith(TValue value); /// /// Returns a value indicating whether Input occurs within specified value. /// /// Value. /// true if the Input occurs within value parameter; otherwise, false. public abstract bool Contains(TValue value); /// /// Convert value to string. /// /// The string value. /// Value. protected abstract string GetStringValue(TValue value); /// /// Start this instance. /// protected virtual void Start() { InputFieldProxy.onValueChanged.AddListener(ApplyFilter); InputFieldProxy.onEndEdit.AddListener(HideOptions); var inputListener = InputField.GetComponent(); if (inputListener==null) { inputListener = InputField.gameObject.AddComponent(); } inputListener.OnMoveEvent.AddListener(SelectResult); inputListener.OnSubmitEvent.AddListener(SubmitResult); DisplayListView.gameObject.SetActive(false); } /// /// Canvas will be used as parent for DisplayListView. /// protected Transform CanvasTransform; /// /// To keep DisplayListView position if InputField inside scrollable area. /// protected Vector2 DisplayListViewAnchoredPosition; /// /// Default parent for DisplayListView. /// protected Transform DisplayListViewParent; /// /// Closes the options. /// /// Input. protected virtual void HideOptions(string input) { HideOptions(); } /// /// Closes the options. /// protected virtual void HideOptions() { if (CanvasTransform!=null) { DisplayListView.transform.SetParent(DisplayListViewParent); (DisplayListView.transform as RectTransform).anchoredPosition = DisplayListViewAnchoredPosition; } DisplayListView.gameObject.SetActive(false); } /// /// Shows the options. /// protected virtual void ShowOptions() { CanvasTransform = Utilites.FindTopmostCanvas(DisplayListView.transform); if (CanvasTransform!=null) { DisplayListViewAnchoredPosition = (DisplayListView.transform as RectTransform).anchoredPosition; DisplayListViewParent = DisplayListView.transform.parent; DisplayListView.transform.SetParent(CanvasTransform); } DisplayListView.gameObject.SetActive(true); } /// /// Gets the results. /// /// Values matches filter. protected virtual ObservableList GetResults() { if (CustomFilter!=null) { return CustomFilter(Input); } else { if (Filter==AutocompleteFilter.Startswith) { return DataSource.Where(Startswith).ToObservableList(); } else { return DataSource.Where(Contains).ToObservableList(); } } } /// /// Sets the input. /// protected virtual void SetInput() { if (InputType==AutocompleteInput.AllInput) { Input = InputFieldProxy.text; } else { int end_position = InputFieldProxy.caretPosition; var text = InputFieldProxy.text.Substring(0, end_position); var start_position = text.LastIndexOfAny(DelimiterChars) + 1; Input = text.Substring(start_position).Trim(); } } /// /// Applies the filter. /// /// Input. protected virtual void ApplyFilter(string input) { SetInput(); if (Input.Length==0) { HideOptions(); return ; } DisplayListView.Start(); DisplayListView.Multiple = false; DisplayListView.DataSource = GetResults(); if (DisplayListView.DataSource.Count > 0) { ShowOptions(); DisplayListView.SelectedIndex = 0; } else { HideOptions(); } } /// /// Update this instance. /// protected virtual void Update() { CaretPosition = InputFieldProxy.caretPosition; } /// /// 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 = DisplayListView.DataSource.Count - 1; } else { DisplayListView.SelectedIndex -= 1; } DisplayListView.ScrollTo(DisplayListView.SelectedIndex); InputFieldProxy.caretPosition = CaretPosition; break; case MoveDirection.Down: if (DisplayListView.SelectedIndex==(DisplayListView.DataSource.Count - 1)) { DisplayListView.SelectedIndex = 0; } else { DisplayListView.SelectedIndex += 1; } DisplayListView.ScrollTo(DisplayListView.SelectedIndex); InputFieldProxy.caretPosition = CaretPosition; break; default: var oldInput = Input; SetInput(); if (oldInput!=Input) { ApplyFilter(""); } break; } } /// /// Submits the result. /// /// Event data. protected virtual void SubmitResult(BaseEventData eventData) { if (DisplayListView.SelectedIndex==-1) { return ; } if ((TargetListView!=null) && (DisplayListView.SelectedIndex!=-1)) { TargetListView.Set(DisplayListView.DataSource[DisplayListView.SelectedIndex]); } int end_position = (DisplayListView.gameObject.activeInHierarchy) ? InputFieldProxy.caretPosition : CaretPosition; var text = InputFieldProxy.text.Substring(0, end_position); var start_position = text.LastIndexOfAny(DelimiterChars) + 1; var value = GetStringValue(DisplayListView.DataSource[DisplayListView.SelectedIndex]); InputFieldProxy.text = InputFieldProxy.text.Substring(0, start_position) + value + InputFieldProxy.text.Substring(end_position); #if UNITY_4_6 || UNITY_4_7 || UNITY_5_0 //InputField.gameObject.SetActive(false); //InputField.gameObject.SetActive(true); InputField.ActivateInputField(); #else InputFieldProxy.caretPosition = start_position + value.Length; #endif OnOptionSelected.Invoke(); HideOptions(); } /// /// This function is called when the MonoBehaviour will be destroyed. /// protected virtual void OnDestroy() { if (InputField!=null) { InputFieldProxy.onValueChanged.RemoveListener(ApplyFilter); InputFieldProxy.onEndEdit.RemoveListener(HideOptions); var inputListener = InputField.GetComponent(); if (inputListener!=null) { inputListener.OnMoveEvent.RemoveListener(SelectResult); inputListener.OnSubmitEvent.RemoveListener(SubmitResult); } } } } }