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 {
///
/// 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.
///
[AddComponentMenu("UI/UIWidgets/Autocomplete")]
public class Autocomplete : MonoBehaviour
{
///
/// 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 ListView DisplayListView;
///
/// Selected value will be added to this ListView.
///
[SerializeField]
public ListView TargetListView;
///
/// 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 virtual bool Startswith(string value)
{
if (CaseSensitive)
{
return value.StartsWith(Input);
}
return value.ToLower().StartsWith(Input.ToLower());
}
///
/// 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)
{
if (CaseSensitive)
{
return value.Contains(Input);
}
return value.ToLower().Contains(Input.ToLower());
}
///
/// Convert value to string.
///
/// The string value.
/// Value.
protected virtual string GetStringValue(string value)
{
return value;
}
///
/// Start this instance.
///
protected virtual void Start()
{
InputFieldProxy.onValueChanged.AddListener(ApplyFilter);
InputFieldProxy.onEndEdit.AddListener(CloseOptions);
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 CloseOptions(string input)
{
CloseOptions();
}
///
/// Closes the options.
///
protected virtual void CloseOptions()
{
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)
{
CloseOptions();
return ;
}
DisplayListView.Start();
DisplayListView.Multiple = false;
DisplayListView.DataSource = GetResults();
if (DisplayListView.DataSource.Count > 0)
{
ShowOptions();
DisplayListView.SelectedIndex = 0;
}
else
{
CloseOptions();
}
}
///
/// 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 -= 1;
}
else
{
DisplayListView.SelectedIndex = DisplayListView.DataSource.Count - 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.gameObject.activeInHierarchy)
{
return ;
}
*/
if (DisplayListView.SelectedIndex==-1)
{
return ;
}
if ((TargetListView!=null) && (DisplayListView.SelectedIndex!=-1) && TargetListView.gameObject.activeInHierarchy)
{
TargetListView.Start();
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();
CloseOptions();
}
///
/// This function is called when the MonoBehaviour will be destroyed.
///
protected virtual void OnDestroy()
{
if (InputField!=null)
{
InputFieldProxy.onValueChanged.RemoveListener(ApplyFilter);
InputFieldProxy.onEndEdit.RemoveListener(CloseOptions);
var inputListener = InputField.GetComponent();
if (inputListener!=null)
{
inputListener.OnMoveEvent.RemoveListener(SelectResult);
inputListener.OnSubmitEvent.RemoveListener(SubmitResult);
}
}
}
}
}