Tanoda
AutoCompleteComboBox.cs
Go to the documentation of this file.
1
3
4using System.Collections.Generic;
5using System.Linq;
6
8{
10 {
12 Linq
13 }
14
15 [RequireComponent(typeof(RectTransform))]
16 [AddComponentMenu("UI/Extensions/AutoComplete ComboBox")]
17 public class AutoCompleteComboBox : MonoBehaviour
18 {
20 public DropDownListItem SelectedItem { get; private set; } //outside world gets to get this, not set it
21
22 public List<string> AvailableOptions;
23
24 //private bool isInitialized = false;
25 private bool _isPanelActive = false;
26 private bool _hasDrawnOnce = false;
27
28 private InputField _mainInput;
29 private RectTransform _inputRT;
30
31 //private Button _arrow_Button;
32
33 private RectTransform _rectTransform;
34
35 private RectTransform _overlayRT;
36 private RectTransform _scrollPanelRT;
37 private RectTransform _scrollBarRT;
38 private RectTransform _slidingAreaRT;
39 // private RectTransform scrollHandleRT;
40 private RectTransform _itemsPanelRT;
41 private Canvas _canvas;
42 private RectTransform _canvasRT;
43
44 private ScrollRect _scrollRect;
45
46 private List<string> _panelItems; //items that will get shown in the drop-down
47 private List<string> _prunedPanelItems; //items that used to show in the drop-down
48
49 private Dictionary<string, GameObject> panelObjects;
50
51 private GameObject itemTemplate;
52
53 public string Text { get; private set; }
54
55 [SerializeField]
56 private float _scrollBarWidth = 20.0f;
57 public float ScrollBarWidth
58 {
59 get { return _scrollBarWidth; }
60 set
61 {
62 _scrollBarWidth = value;
63 RedrawPanel();
64 }
65 }
66
67 // private int scrollOffset; //offset of the selected item
68 // private int _selectedIndex = 0;
69
70 [SerializeField]
71 private int _itemsToDisplay;
72 public int ItemsToDisplay
73 {
74 get { return _itemsToDisplay; }
75 set
76 {
77 _itemsToDisplay = value;
78 RedrawPanel();
79 }
80 }
81
82 public bool SelectFirstItemOnStart = false;
83
84 [SerializeField]
85 [Tooltip("Change input text color based on matching items")]
86 private bool _ChangeInputTextColorBasedOnMatchingItems = false;
87 public bool InputColorMatching{
88 get { return _ChangeInputTextColorBasedOnMatchingItems; }
89 set
90 {
91 _ChangeInputTextColorBasedOnMatchingItems = value;
92 if (_ChangeInputTextColorBasedOnMatchingItems) {
93 SetInputTextColor ();
94 }
95 }
96 }
97
98 //TODO design as foldout for Inspector
102
104
105 private bool _selectionIsValid = false;
106
107 [System.Serializable]
108 public class SelectionChangedEvent : UnityEngine.Events.UnityEvent<string, bool> {
109 }
110
111 [System.Serializable]
112 public class SelectionTextChangedEvent : UnityEngine.Events.UnityEvent<string> {
113 }
114
115 [System.Serializable]
116 public class SelectionValidityChangedEvent : UnityEngine.Events.UnityEvent<bool> {
117 }
118
119 // fires when input text is changed;
121 // fires when an Item gets selected / deselected (including when items are added/removed once this is possible)
123 // fires in both cases
125
126 public void Awake()
127 {
128 Initialize();
129 }
130 public void Start()
131 {
132 if (SelectFirstItemOnStart && AvailableOptions.Count > 0) {
133 ToggleDropdownPanel (false);
134 OnItemClicked (AvailableOptions [0]);
135 }
136 }
137
138 private bool Initialize()
139 {
140 bool success = true;
141 try
142 {
143 _rectTransform = GetComponent<RectTransform>();
144 _inputRT = _rectTransform.Find("InputField").GetComponent<RectTransform>();
145 _mainInput = _inputRT.GetComponent<InputField>();
146
147 //_arrow_Button = _rectTransform.FindChild ("ArrowBtn").GetComponent<Button> ();
148
149 _overlayRT = _rectTransform.Find("Overlay").GetComponent<RectTransform>();
150 _overlayRT.gameObject.SetActive(false);
151
152
153 _scrollPanelRT = _overlayRT.Find("ScrollPanel").GetComponent<RectTransform>();
154 _scrollBarRT = _scrollPanelRT.Find("Scrollbar").GetComponent<RectTransform>();
155 _slidingAreaRT = _scrollBarRT.Find("SlidingArea").GetComponent<RectTransform>();
156 // scrollHandleRT = slidingAreaRT.FindChild("Handle").GetComponent<RectTransform>();
157 _itemsPanelRT = _scrollPanelRT.Find("Items").GetComponent<RectTransform>();
158 //itemPanelLayout = itemsPanelRT.gameObject.GetComponent<LayoutGroup>();
159
160 _canvas = GetComponentInParent<Canvas>();
161 _canvasRT = _canvas.GetComponent<RectTransform>();
162
163 _scrollRect = _scrollPanelRT.GetComponent<ScrollRect>();
164 _scrollRect.scrollSensitivity = _rectTransform.sizeDelta.y / 2;
165 _scrollRect.movementType = ScrollRect.MovementType.Clamped;
166 _scrollRect.content = _itemsPanelRT;
167
168 itemTemplate = _rectTransform.Find("ItemTemplate").gameObject;
169 itemTemplate.SetActive(false);
170 }
171 catch (System.NullReferenceException ex)
172 {
173 Debug.LogException(ex);
174 Debug.LogError("Something is setup incorrectly with the dropdownlist component causing a Null Reference Exception");
175 success = false;
176 }
177 panelObjects = new Dictionary<string, GameObject>();
178
179 _prunedPanelItems = new List<string>();
180 _panelItems = new List<string>();
181
182 RebuildPanel();
183 //RedrawPanel(); - causes an initialisation failure in U5
184 return success;
185 }
186
187 /* currently just using items in the list instead of being able to add to it.
188 public void AddItems(params object[] list)
189 {
190 List<DropDownListItem> ddItems = new List<DropDownListItem>();
191 foreach (var obj in list)
192 {
193 if (obj is DropDownListItem)
194 {
195 ddItems.Add((DropDownListItem)obj);
196 }
197 else if (obj is string)
198 {
199 ddItems.Add(new DropDownListItem(caption: (string)obj));
200 }
201 else if (obj is Sprite)
202 {
203 ddItems.Add(new DropDownListItem(image: (Sprite)obj));
204 }
205 else
206 {
207 throw new System.Exception("Only ComboBoxItems, Strings, and Sprite types are allowed");
208 }
209 }
210 Items.AddRange(ddItems);
211 Items = Items.Distinct().ToList();//remove any duplicates
212 RebuildPanel();
213 }
214 */
215
219 private void RebuildPanel()
220 {
221 //panel starts with all options
222 _panelItems.Clear();
223 _prunedPanelItems.Clear();
224 panelObjects.Clear();
225
226 foreach (string option in AvailableOptions)
227 {
228 _panelItems.Add(option.ToLower());
229 }
230
231 List<GameObject> itemObjs = new List<GameObject>(panelObjects.Values);
232
233 int indx = 0;
234 while (itemObjs.Count < AvailableOptions.Count)
235 {
236 GameObject newItem = Instantiate(itemTemplate) as GameObject;
237 newItem.name = "Item " + indx;
238 newItem.transform.SetParent(_itemsPanelRT, false);
239 itemObjs.Add(newItem);
240 indx++;
241 }
242
243 for (int i = 0; i < itemObjs.Count; i++)
244 {
245 itemObjs[i].SetActive(i <= AvailableOptions.Count);
246 if (i < AvailableOptions.Count)
247 {
248 itemObjs[i].name = "Item " + i + " " + _panelItems[i];
249 itemObjs[i].transform.Find("Text").GetComponent<Text>().text = _panelItems[i]; //set the text value
250
251 Button itemBtn = itemObjs[i].GetComponent<Button>();
252 itemBtn.onClick.RemoveAllListeners();
253 string textOfItem = _panelItems[i]; //has to be copied for anonymous function or it gets garbage collected away
254 itemBtn.onClick.AddListener(() =>
255 {
256 OnItemClicked(textOfItem);
257 });
258 panelObjects[_panelItems[i]] = itemObjs[i];
259 }
260 }
261 SetInputTextColor ();
262 }
263
268 private void OnItemClicked(string item)
269 {
270 //Debug.Log("item " + item + " clicked");
271 Text = item;
272 _mainInput.text = Text;
274 }
275
276 //private void UpdateSelected()
277 //{
278 // SelectedItem = (_selectedIndex > -1 && _selectedIndex < Items.Count) ? Items[_selectedIndex] : null;
279 // if (SelectedItem == null) return;
280
281 // bool hasImage = SelectedItem.Image != null;
282 // if (hasImage)
283 // {
284 // mainButton.img.sprite = SelectedItem.Image;
285 // mainButton.img.color = Color.white;
286
287 // //if (Interactable) mainButton.img.color = Color.white;
288 // //else mainButton.img.color = new Color(1, 1, 1, .5f);
289 // }
290 // else
291 // {
292 // mainButton.img.sprite = null;
293 // }
294
295 // mainButton.txt.text = SelectedItem.Caption;
296
297 // //update selected index color
298 // for (int i = 0; i < itemsPanelRT.childCount; i++)
299 // {
300 // panelItems[i].btnImg.color = (_selectedIndex == i) ? mainButton.btn.colors.highlightedColor : new Color(0, 0, 0, 0);
301 // }
302 //}
303
304
305 private void RedrawPanel()
306 {
307 float scrollbarWidth = _panelItems.Count > ItemsToDisplay ? _scrollBarWidth : 0f;//hide the scrollbar if there's not enough items
308 _scrollBarRT.gameObject.SetActive(_panelItems.Count > ItemsToDisplay);
309 if (!_hasDrawnOnce || _rectTransform.sizeDelta != _inputRT.sizeDelta)
310 {
311 _hasDrawnOnce = true;
312 _inputRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _rectTransform.sizeDelta.x);
313 _inputRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, _rectTransform.sizeDelta.y);
314
315 _scrollPanelRT.SetParent(transform, true);//break the scroll panel from the overlay
316 _scrollPanelRT.anchoredPosition = new Vector2(0, -_rectTransform.sizeDelta.y); //anchor it to the bottom of the button
317
318 //make the overlay fill the screen
319 _overlayRT.SetParent(_canvas.transform, false); //attach it to top level object
320 _overlayRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _canvasRT.sizeDelta.x);
321 _overlayRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, _canvasRT.sizeDelta.y);
322
323 _overlayRT.SetParent(transform, true);//reattach to this object
324 _scrollPanelRT.SetParent(_overlayRT, true); //reattach the scrollpanel to the overlay
325 }
326
327 if (_panelItems.Count < 1) return;
328
329 float dropdownHeight = _rectTransform.sizeDelta.y * Mathf.Min(_itemsToDisplay, _panelItems.Count);
330
331 _scrollPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight);
332 _scrollPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _rectTransform.sizeDelta.x);
333
334 _itemsPanelRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, _scrollPanelRT.sizeDelta.x - scrollbarWidth - 5);
335 _itemsPanelRT.anchoredPosition = new Vector2(5, 0);
336
337 _scrollBarRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, scrollbarWidth);
338 _scrollBarRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight);
339
340 _slidingAreaRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0);
341 _slidingAreaRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, dropdownHeight - _scrollBarRT.sizeDelta.x);
342 }
343
344 public void OnValueChanged(string currText)
345 {
346 Text = currText;
347 PruneItems(currText);
348 RedrawPanel();
349 //Debug.Log("value changed to: " + currText);
350
351 if (_panelItems.Count == 0)
352 {
353 _isPanelActive = true;//this makes it get turned off
354 ToggleDropdownPanel(false);
355 }
356 else if (!_isPanelActive)
357 {
358 ToggleDropdownPanel(false);
359 }
360
361 bool validity_changed = (_panelItems.Contains (Text) != _selectionIsValid);
362 _selectionIsValid = _panelItems.Contains (Text);
363 OnSelectionChanged.Invoke (Text, _selectionIsValid);
365 if(validity_changed){
366 OnSelectionValidityChanged.Invoke (_selectionIsValid);
367 }
368
369 SetInputTextColor ();
370 }
371
372 private void SetInputTextColor(){
373 if (InputColorMatching) {
374 if (_selectionIsValid) {
375 _mainInput.textComponent.color = ValidSelectionTextColor;
376 } else if (_panelItems.Count > 0) {
377 _mainInput.textComponent.color = MatchingItemsRemainingTextColor;
378 } else {
379 _mainInput.textComponent.color = NoItemsRemainingTextColor;
380 }
381 }
382 }
383
384
385
390 public void ToggleDropdownPanel(bool directClick)
391 {
392 _isPanelActive = !_isPanelActive;
393
394 _overlayRT.gameObject.SetActive(_isPanelActive);
395 if (_isPanelActive)
396 {
397 transform.SetAsLastSibling();
398 }
399 else if (directClick)
400 {
401 // scrollOffset = Mathf.RoundToInt(itemsPanelRT.anchoredPosition.y / _rectTransform.sizeDelta.y);
402 }
403 }
404
405 private void PruneItems(string currText)
406 {
408 {
409 PruneItemsLinq(currText);
410 }
411 else
412 {
413 PruneItemsArray(currText);
414 }
415 }
416
417 private void PruneItemsLinq(string currText)
418 {
419 currText = currText.ToLower();
420 var toPrune = _panelItems.Where(x => !x.Contains(currText)).ToArray();
421 foreach (string key in toPrune)
422 {
423 panelObjects[key].SetActive(false);
424 _panelItems.Remove(key);
425 _prunedPanelItems.Add(key);
426 }
427
428 var toAddBack = _prunedPanelItems.Where(x => x.Contains(currText)).ToArray();
429 foreach (string key in toAddBack)
430 {
431 panelObjects[key].SetActive(true);
432 _panelItems.Add(key);
433 _prunedPanelItems.Remove(key);
434 }
435 }
436
437 //Updated to not use Linq
438 private void PruneItemsArray(string currText)
439 {
440 string _currText = currText.ToLower();
441
442 for (int i = _panelItems.Count - 1; i >= 0; i--)
443 {
444 string _item = _panelItems[i];
445 if (!_item.Contains(_currText))
446 {
447 panelObjects[_panelItems[i]].SetActive(false);
448 _panelItems.RemoveAt(i);
449 _prunedPanelItems.Add(_item);
450 }
451 }
452 for (int i = _prunedPanelItems.Count - 1; i >= 0; i--)
453 {
454 string _item = _prunedPanelItems[i];
455 if (_item.Contains(_currText))
456 {
457 panelObjects[_prunedPanelItems[i]].SetActive(true);
458 _prunedPanelItems.RemoveAt(i);
459 _panelItems.Add(_item);
460 }
461 }
462 }
463 }
464}
UnityEngine.UI.Button Button
Definition: Pointer.cs:7
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
UnityEngine.Color Color
Definition: TestScript.cs:32
void ToggleDropdownPanel(bool directClick)
Toggle the drop down list
SelectionValidityChangedEvent OnSelectionValidityChanged
Credit Erdener Gonenc - @PixelEnvision.