{"id":1809,"date":"2017-08-15T02:42:32","date_gmt":"2017-08-14T18:42:32","guid":{"rendered":"http:\/\/www.clonefactor.com\/wordpress\/?p=1809"},"modified":"2017-08-15T03:28:00","modified_gmt":"2017-08-14T19:28:00","slug":"unity3d-editor-textfield-autocompelete-version-2","status":"publish","type":"post","link":"https:\/\/www.clonefactor.com\/wordpress\/program\/c\/1809\/","title":{"rendered":"Unity3D Editor TextField AutoCompelete (Version 2)"},"content":{"rendered":"<p>\u7e7c\u524d\u4e00\u7bc7 :\u00a0<a href=\"http:\/\/www.clonefactor.com\/wordpress\/public\/1769\/\" target=\"_blank\" rel=\"noopener\">Unity3D Editor TextField AutoCompelete<\/a><\/p>\n<p>\u7d93\u904e\u5e7e\u5929\u7684\u8abf\u8a66\u8ddf\u5b78\u7fd2, \u5b8c\u5168\u4f7f\u7528 Event.current \u4f86\u6aa2\u6e2c\u7576\u524d\u4f4d\u7f6e, \u770b\u4f86\u5c31\u662f\u6bd4\u8f03\u6b63\u78ba\u5730\u7e5e\u904e\u9019\u500b (<a href=\"http:\/\/answers.unity3d.com\/questions\/1392897\/inspector-issue-about-draw-buttons-on-top-of-edito.html\">Unity inspector bug<\/a>) \u7684\u65b9\u6cd5<br \/>\n\u4e5f\u56e0\u70ba\u4f7f\u7528\u4e0a Event.current \u5206\u96e2 Mouse Click \u7684\u95dc\u4fc2, \u73fe\u5728\u52a0\u5165\u4e86\u7c21\u55ae\u7684 Keyboard hotkey \u529f\u80fd.<\/p>\n<p>\u5230\u76ee\u524d\u70ba\u6b62, \u66ab\u6642\u6c92\u6709\u627e\u5230\u660e\u986f\u7684 Bugs, \u5982\u679c\u6709\u7684\u8a71\u6b61\u8fce\u4f5c\u51fa\u5efa\u8b70\u53ca\u8a0e\u8ad6.<\/p>\n<figure id=\"attachment_1815\" aria-describedby=\"caption-attachment-1815\" style=\"width: 514px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1815\" src=\"http:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2017\/08\/EditorAutoCompleteV2.gif\" alt=\"\" width=\"514\" height=\"324\" \/><figcaption id=\"caption-attachment-1815\" class=\"wp-caption-text\">Unity3d Editor TextField AutoComplete<\/figcaption><\/figure>\n<p>Feature :<\/p>\n<ul>\n<li>[New] Up\/Down Arrow to select the option<\/li>\n<li>[New] Enter to confirm and replace current text to selected option.<\/li>\n<li>[New] when string are perfect matching the option, the option will display as selected.<\/li>\n<li>Giving recommend result based on giving string[] in editor<\/li>\n<li>support Fuzzy matching &#8211;\u00a0Levenshtein \u00a0Distance (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Levenshtein_distance\">Wiki<\/a>) (code ref: <a href=\"https:\/\/blogs.msdn.microsoft.com\/toub\/2006\/05\/05\/generic-levenshtein-edit-distance-with-c\/\">C#<\/a>)<\/li>\n<\/ul>\n<p><iframe loading=\"lazy\" title=\"Unity3D Editor TextField AutoCompelete (V2)\" width=\"1260\" height=\"945\" src=\"https:\/\/www.youtube.com\/embed\/bOKqdcIs3Mc?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><\/p>\n<p>EditorExtend.cs<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">using UnityEngine;\r\nusing UnityEditor;\r\nusing System.Collections.Generic;\r\n\r\npublic sealed class EditorExtend\r\n{\r\n\t#region Text AutoComplete\r\n\t\/\/\/ &lt;summary&gt;The internal struct used for AutoComplete (Editor)&lt;\/summary&gt;\r\n\tprivate struct EditorAutoCompleteParams\r\n\t{\r\n\t\tpublic const string FieldTag = \"AutoCompleteField\";\r\n\t\tpublic static readonly Color FancyColor = new Color(.6f, .6f, .7f);\r\n\t\tpublic static readonly float optionHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;\r\n\t\tpublic const int fuzzyMatchBias = 3; \/\/ input length smaller then this letter, will not trigger fuzzy checking.\r\n\t\tpublic static List&lt;string&gt; CacheCheckList = null;\r\n\t\tpublic static string lastInput;\r\n\t\tpublic static string focusTag = \"\";\r\n\t\tpublic static string lastTag = \"\"; \/\/ Never null, optimize for length check.\r\n\t\tpublic static int selectedOption = -1; \/\/ record current selected option.\r\n\t\tpublic static Vector2 mouseDown;\r\n\r\n\t\tpublic static void CleanUpAndBlur()\r\n\t\t{\r\n\t\t\tselectedOption = -1;\r\n\t\t\tGUI.FocusControl(\"\");\r\n\t\t}\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;A textField to popup a matching popup, based on developers input values.&lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"input\"&gt;string input.&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"source\"&gt;the data of all possible values (string).&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"maxShownCount\"&gt;the amount to display result.&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"levenshteinDistance\"&gt;\r\n\t\/\/\/ value between 0f ~ 1f, (percent)\r\n\t\/\/\/ - more then 0f will enable the fuzzy matching\r\n\t\/\/\/ - 1f = 100% error threshold = anything thing is okay.\r\n\t\/\/\/ - 0f = 000% error threshold = require full match to the reference\r\n\t\/\/\/ - recommend 0.4f ~ 0.7f\r\n\t\/\/\/ &lt;\/param&gt;\r\n\t\/\/\/ &lt;returns&gt;output string.&lt;\/returns&gt;\r\n\tpublic static string TextFieldAutoComplete(string input, string[] source, int maxShownCount = 5, float levenshteinDistance = 0.5f)\r\n\t{\r\n\t\treturn TextFieldAutoComplete(EditorGUILayout.GetControlRect(), input, source, maxShownCount, levenshteinDistance);\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;A textField to popup a matching popup, based on developers input values.&lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"position\"&gt;EditorGUI position&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"input\"&gt;string input.&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"source\"&gt;the data of all possible values (string).&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"maxShownCount\"&gt;the amount to display result.&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"levenshteinDistance\"&gt;\r\n\t\/\/\/ value between 0f ~ 1f, (percent)\r\n\t\/\/\/ - more then 0f will enable the fuzzy matching\r\n\t\/\/\/ - 1f = 100% error threshold = everything is okay.\r\n\t\/\/\/ - 0f = 000% error threshold = require full match to the reference\r\n\t\/\/\/ - recommend 0.4f ~ 0.7f\r\n\t\/\/\/ &lt;\/param&gt;\r\n\t\/\/\/ &lt;returns&gt;output string.&lt;\/returns&gt;\r\n\tpublic static string TextFieldAutoComplete(Rect position, string input, string[] source, int maxShownCount = 5, float levenshteinDistance = 0.5f)\r\n\t{\r\n\t\t\/\/ Text field\r\n\t\tint controlId = GUIUtility.GetControlID(FocusType.Passive);\r\n\t\tstring tag = EditorAutoCompleteParams.FieldTag + controlId;\r\n\t\tGUI.SetNextControlName(tag);\r\n\t\tstring rst = EditorGUI.TextField(position, input, EditorStyles.popup);\r\n\r\n\t\t\/\/ Matching with giving source\r\n\t\tif (input.Length &gt; 0 &amp;&amp; \/\/ have input\r\n\t\t\t(EditorAutoCompleteParams.lastTag.Length == 0 || EditorAutoCompleteParams.lastTag == tag) &amp;&amp; \/\/ one frame delay for process click event.\r\n\t\t\tGUI.GetNameOfFocusedControl() == tag) \/\/ focus this control\r\n\t\t{\r\n\t\t\t\/\/ Matching\r\n\t\t\tif (EditorAutoCompleteParams.lastInput != input || \/\/ input changed\r\n\t\t\t\tEditorAutoCompleteParams.focusTag != tag) \/\/ switch focus from another field.\r\n\t\t\t{\r\n\t\t\t\t\/\/ Update cache\r\n\t\t\t\tEditorAutoCompleteParams.focusTag = tag;\r\n\t\t\t\tEditorAutoCompleteParams.lastInput = input;\r\n\r\n\t\t\t\tList&lt;string&gt; uniqueSrc = new List&lt;string&gt;(new HashSet&lt;string&gt;(source)); \/\/ remove duplicate\r\n\t\t\t\tint srcCnt = uniqueSrc.Count;\r\n\t\t\t\tEditorAutoCompleteParams.CacheCheckList = new List&lt;string&gt;(System.Math.Min(maxShownCount, srcCnt)); \/\/ optimize memory alloc\r\n\t\t\t\t\/\/ Start with - slow\r\n\t\t\t\tfor (int i = 0; i &lt; srcCnt &amp;&amp; EditorAutoCompleteParams.CacheCheckList.Count &lt; maxShownCount; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (uniqueSrc[i].ToLower().StartsWith(input.ToLower()))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tEditorAutoCompleteParams.CacheCheckList.Add(uniqueSrc[i]);\r\n\t\t\t\t\t\tuniqueSrc.RemoveAt(i);\r\n\t\t\t\t\t\tsrcCnt--;\r\n\t\t\t\t\t\ti--;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t\/\/ Contains - very slow\r\n\t\t\t\tif (EditorAutoCompleteParams.CacheCheckList.Count == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (int i = 0; i &lt; srcCnt &amp;&amp; EditorAutoCompleteParams.CacheCheckList.Count &lt; maxShownCount; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (uniqueSrc[i].ToLower().Contains(input.ToLower()))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.CacheCheckList.Add(uniqueSrc[i]);\r\n\t\t\t\t\t\t\tuniqueSrc.RemoveAt(i);\r\n\t\t\t\t\t\t\tsrcCnt--;\r\n\t\t\t\t\t\t\ti--;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t\/\/ Levenshtein Distance - very very slow.\r\n\t\t\t\tif (levenshteinDistance &gt; 0f &amp;&amp; \/\/ only developer request\r\n\t\t\t\t\tinput.Length &gt; EditorAutoCompleteParams.fuzzyMatchBias &amp;&amp; \/\/ bias on input, hidden value to avoid doing it too early.\r\n\t\t\t\t\tEditorAutoCompleteParams.CacheCheckList.Count &lt; maxShownCount) \/\/ have some empty space for matching.\r\n\t\t\t\t{\r\n\t\t\t\t\tlevenshteinDistance = Mathf.Clamp01(levenshteinDistance);\r\n\t\t\t\t\tstring keywords = input.ToLower();\r\n\t\t\t\t\tfor (int i = 0; i &lt; srcCnt &amp;&amp; EditorAutoCompleteParams.CacheCheckList.Count &lt; maxShownCount; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tint distance = Kit.Extend.StringExtend.LevenshteinDistance(uniqueSrc[i], keywords, caseSensitive: false);\r\n\t\t\t\t\t\tbool closeEnough = (int)(levenshteinDistance * uniqueSrc[i].Length) &gt; distance;\r\n\t\t\t\t\t\tif (closeEnough)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.CacheCheckList.Add(uniqueSrc[i]);\r\n\t\t\t\t\t\t\tuniqueSrc.RemoveAt(i);\r\n\t\t\t\t\t\t\tsrcCnt--;\r\n\t\t\t\t\t\t\ti--;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ Draw recommend keyward(s)\r\n\t\t\tif (EditorAutoCompleteParams.CacheCheckList.Count &gt; 0)\r\n\t\t\t{\r\n\t\t\t\tEvent evt = Event.current;\r\n\t\t\t\tint cnt = EditorAutoCompleteParams.CacheCheckList.Count;\r\n\t\t\t\tfloat height = cnt * EditorAutoCompleteParams.optionHeight;\r\n\t\t\t\tRect area = new Rect(position.x, position.y - height, position.width, height);\r\n\t\t\t\t\r\n\t\t\t\t\/\/ Fancy color UI\r\n\t\t\t\tEditorGUI.BeginDisabledGroup(true);\r\n\t\t\t\tEditorGUI.DrawRect(area, EditorAutoCompleteParams.FancyColor);\r\n\t\t\t\tGUI.Label(area, GUIContent.none, GUI.skin.button);\r\n\t\t\t\tEditorGUI.EndDisabledGroup();\r\n\r\n\t\t\t\t\/\/ Click event hack - part 1\r\n\t\t\t\t\/\/ cached data for click event hack.\r\n\t\t\t\tif (evt.type == EventType.Repaint)\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ Draw option(s), if we have one.\r\n\t\t\t\t\t\/\/ in repaint cycle, we only handle display.\r\n\t\t\t\t\tRect line = new Rect(area.x, area.y, area.width, EditorAutoCompleteParams.optionHeight);\r\n\t\t\t\t\tfor (int i = 0; i &lt; cnt; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tEditorGUI.ToggleLeft(line, GUIContent.none, (input == EditorAutoCompleteParams.CacheCheckList[i]));\r\n\t\t\t\t\t\tRect option = EditorGUI.IndentedRect(line);\r\n\t\t\t\t\t\tif (line.Contains(evt.mousePosition))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\/\/ hover style\r\n\t\t\t\t\t\t\tEditorGUI.LabelField(option, EditorAutoCompleteParams.CacheCheckList[i], GUI.skin.textArea);\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.selectedOption = i;\r\n\r\n\t\t\t\t\t\t\tGUIUtility.hotControl = controlId; \/\/ required for Cursor skin. (AddCursorRect)\r\n\t\t\t\t\t\t\tEditorGUIUtility.AddCursorRect(area, MouseCursor.ArrowPlus);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (EditorAutoCompleteParams.selectedOption == i)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\/\/ hover style\r\n\t\t\t\t\t\t\tEditorGUI.LabelField(option, EditorAutoCompleteParams.CacheCheckList[i], GUI.skin.textArea);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tEditorGUI.LabelField(option, EditorAutoCompleteParams.CacheCheckList[i], EditorStyles.label);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tline.y += line.height;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\/\/ when hover popup, record this as the last usein tag.\r\n\t\t\t\t\tif (area.Contains(evt.mousePosition) &amp;&amp; EditorAutoCompleteParams.lastTag != tag)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\/\/ Debug.Log(\"-&gt;\" + tag + \" Enter popup: \" + area);\r\n\t\t\t\t\t\t\/\/ used to trigger the clicked checking later.\r\n\t\t\t\t\t\tEditorAutoCompleteParams.lastTag = tag;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (evt.type == EventType.MouseDown)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (area.Contains(evt.mousePosition) || position.Contains(evt.mousePosition))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tEditorAutoCompleteParams.mouseDown = evt.mousePosition;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\/\/ click outside popup area, deselected - blur.\r\n\t\t\t\t\t\tEditorAutoCompleteParams.CleanUpAndBlur();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (evt.type == EventType.MouseUp)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (position.Contains(evt.mousePosition))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\/\/ common case click on textfield.\r\n\t\t\t\t\t\treturn rst;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (area.Contains(evt.mousePosition))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (Vector2.Distance(EditorAutoCompleteParams.mouseDown, evt.mousePosition) &gt;= 3f)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\/\/ Debug.Log(\"Click and drag out the area.\");\r\n\t\t\t\t\t\t\treturn rst;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\/\/ Click event hack - part 3\r\n\t\t\t\t\t\t\t\/\/ for some reason, this session only run when popup display on inspector empty space.\r\n\t\t\t\t\t\t\t\/\/ when any selectable field behind of the popup list, Unity3D can't reaching this session.\r\n\t\t\t\t\t\t\t_AutoCompleteClickhandle(position, ref rst);\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.focusTag = string.Empty; \/\/ Clean up\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.lastTag = string.Empty; \/\/ Clean up\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\/\/ click outside popup area, deselected - blur.\r\n\t\t\t\t\t\tEditorAutoCompleteParams.CleanUpAndBlur();\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn rst;\r\n\t\t\t\t}\r\n\t\t\t\telse if (evt.isKey &amp;&amp; evt.type == EventType.KeyUp)\r\n\t\t\t\t{\r\n\t\t\t\t\tswitch (evt.keyCode)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcase KeyCode.PageUp:\r\n\t\t\t\t\t\tcase KeyCode.UpArrow:\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.selectedOption--;\r\n\t\t\t\t\t\t\tif (EditorAutoCompleteParams.selectedOption &lt; 0)\r\n\t\t\t\t\t\t\t\tEditorAutoCompleteParams.selectedOption = EditorAutoCompleteParams.CacheCheckList.Count - 1;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase KeyCode.PageDown:\r\n\t\t\t\t\t\tcase KeyCode.DownArrow:\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.selectedOption++;\r\n\t\t\t\t\t\t\tif (EditorAutoCompleteParams.selectedOption &gt;= EditorAutoCompleteParams.CacheCheckList.Count)\r\n\t\t\t\t\t\t\t\tEditorAutoCompleteParams.selectedOption = 0;\r\n\t\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\t\tcase KeyCode.KeypadEnter:\r\n\t\t\t\t\t\tcase KeyCode.Return:\r\n\t\t\t\t\t\t\tif (EditorAutoCompleteParams.selectedOption != -1)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t_AutoCompleteClickhandle(position, ref rst);\r\n\t\t\t\t\t\t\t\tEditorAutoCompleteParams.focusTag = string.Empty; \/\/ Clean up\r\n\t\t\t\t\t\t\t\tEditorAutoCompleteParams.lastTag = string.Empty; \/\/ Clean up\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tEditorAutoCompleteParams.CleanUpAndBlur();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\t\tcase KeyCode.Escape:\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.CleanUpAndBlur();\r\n\t\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\t\tdefault:\r\n\t\t\t\t\t\t\t\/\/ hit any other key(s), assume typing, avoid override by Enter;\r\n\t\t\t\t\t\t\tEditorAutoCompleteParams.selectedOption = -1;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (EditorAutoCompleteParams.lastTag == tag &amp;&amp;\r\n\t\t\tGUI.GetNameOfFocusedControl() != tag)\r\n\t\t{\r\n\t\t\t\/\/ Click event hack - part 2\r\n\t\t\t\/\/ catching mouse click on blur\r\n\t\t\t_AutoCompleteClickhandle(position, ref rst);\r\n\t\t\tEditorAutoCompleteParams.lastTag = string.Empty; \/\/ reset\r\n\t\t}\r\n\r\n\t\treturn rst;\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;calculate auto complete select option location, and select it.\r\n\t\/\/\/ within area, and we display option in \"Vertical\" style.\r\n\t\/\/\/ which line is what we care.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"rst\"&gt;input string, may overrided&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"cnt\"&gt;&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"area\"&gt;&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=\"mouseY\"&gt;&lt;\/param&gt;\r\n\tprivate static void _AutoCompleteClickhandle(Rect position, ref string rst)\r\n\t{\r\n\t\tint index = EditorAutoCompleteParams.selectedOption;\r\n\t\tVector2 pos = EditorAutoCompleteParams.mouseDown; \/\/ hack: assume mouse are stay in click position (1 frame behind).\r\n\r\n\t\tif (0 &lt;= index &amp;&amp; index &lt; EditorAutoCompleteParams.CacheCheckList.Count)\r\n\t\t{\r\n\t\t\trst = EditorAutoCompleteParams.CacheCheckList[index];\r\n\t\t\tGUI.changed = true;\r\n\t\t\t\/\/ Debug.Log(\"Selecting index (\" + EditorAutoCompleteParams.selectedOption + \") \"+ rst);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t\/\/ Fail safe, when selectedOption failure\r\n\t\t\t\r\n\t\t\tint cnt = EditorAutoCompleteParams.CacheCheckList.Count;\r\n\t\t\tfloat height = cnt * EditorAutoCompleteParams.optionHeight;\r\n\t\t\tRect area = new Rect(position.x, position.y - height, position.width, height);\r\n\t\t\tif (!area.Contains(pos))\r\n\t\t\t\treturn; \/\/ return early.\r\n\r\n\t\t\tfloat lineY = area.y;\r\n\t\t\tfor (int i = 0; i &lt; cnt; i++)\r\n\t\t\t{\r\n\t\t\t\tif (lineY &lt; pos.y &amp;&amp; pos.y &lt; lineY + EditorAutoCompleteParams.optionHeight)\r\n\t\t\t\t{\r\n\t\t\t\t\trst = EditorAutoCompleteParams.CacheCheckList[i];\r\n\t\t\t\t\tDebug.LogError(\"Fail to select on \\\"\" + EditorAutoCompleteParams.lastTag + \"\\\" selected = \" + rst + \"\\ncalculate by mouse position.\");\r\n\t\t\t\t\tGUI.changed = true;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t\tlineY += EditorAutoCompleteParams.optionHeight;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tEditorAutoCompleteParams.CleanUpAndBlur();\r\n\t}\r\n\t#endregion\r\n}<\/pre>\n<p>StringExtend.cs<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">using UnityEngine;\r\nusing System.Collections.Generic;\r\n\r\nnamespace Kit.Extend\r\n{\r\n\tpublic static class StringExtend\r\n\t{\r\n\t\t\/\/\/ &lt;summary&gt;Computes the Levenshtein Edit Distance between two enumerables.&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"lhs\"&gt;The first enumerable.&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"rhs\"&gt;The second enumerable.&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;returns&gt;The edit distance.&lt;\/returns&gt;\r\n\t\t\/\/\/ &lt;see cref=\"https:\/\/en.wikipedia.org\/wiki\/Levenshtein_distance\"\/&gt;\r\n\t\tpublic static int LevenshteinDistance(string lhs, string rhs, bool caseSensitive = true)\r\n\t\t{\r\n\t\t\tif (!caseSensitive)\r\n\t\t\t{\r\n\t\t\t\tlhs = lhs.ToLower();\r\n\t\t\t\trhs = rhs.ToLower();\r\n\t\t\t}\r\n\t\t\tchar[] first = lhs.ToCharArray();\r\n\t\t\tchar[] second = rhs.ToCharArray();\r\n\t\t\treturn LevenshteinDistance&lt;char&gt;(first, second);\r\n\t\t}\r\n\t\t\r\n\t\t\/\/\/ &lt;summary&gt;Computes the Levenshtein Edit Distance between two enumerables.&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;typeparam name=\"T\"&gt;The type of the items in the enumerables.&lt;\/typeparam&gt;\r\n\t\t\/\/\/ &lt;param name=\"lhs\"&gt;The first enumerable.&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"rhs\"&gt;The second enumerable.&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;returns&gt;The edit distance.&lt;\/returns&gt;\r\n\t\t\/\/\/ &lt;see cref=\"https:\/\/blogs.msdn.microsoft.com\/toub\/2006\/05\/05\/generic-levenshtein-edit-distance-with-c\/\"\/&gt;\r\n\t\tpublic static int LevenshteinDistance&lt;T&gt;(IEnumerable&lt;T&gt; lhs, IEnumerable&lt;T&gt; rhs) where T : System.IEquatable&lt;T&gt;\r\n\t\t{\r\n\t\t\t\/\/ Validate parameters\r\n\t\t\tif (lhs == null) throw new System.ArgumentNullException(\"lhs\");\r\n\t\t\tif (rhs == null) throw new System.ArgumentNullException(\"rhs\");\r\n\r\n\t\t\t\/\/ Convert the parameters into IList instances\r\n\t\t\t\/\/ in order to obtain indexing capabilities\r\n\t\t\tIList&lt;T&gt; first = lhs as IList&lt;T&gt; ?? new List&lt;T&gt;(lhs);\r\n\t\t\tIList&lt;T&gt; second = rhs as IList&lt;T&gt; ?? new List&lt;T&gt;(rhs);\r\n\r\n\t\t\t\/\/ Get the length of both.  If either is 0, return\r\n\t\t\t\/\/ the length of the other, since that number of insertions\r\n\t\t\t\/\/ would be required.\r\n\t\t\tint n = first.Count, m = second.Count;\r\n\t\t\tif (n == 0) return m;\r\n\t\t\tif (m == 0) return n;\r\n\r\n\t\t\t\/\/ Rather than maintain an entire matrix (which would require O(n*m) space),\r\n\t\t\t\/\/ just store the current row and the next row, each of which has a length m+1,\r\n\t\t\t\/\/ so just O(m) space. Initialize the current row.\r\n\t\t\tint curRow = 0, nextRow = 1;\r\n\r\n\t\t\tint[][] rows = new int[][] { new int[m + 1], new int[m + 1] };\r\n\t\t\tfor (int j = 0; j &lt;= m; ++j)\r\n\t\t\t\trows[curRow][j] = j;\r\n\r\n\t\t\t\/\/ For each virtual row (since we only have physical storage for two)\r\n\t\t\tfor (int i = 1; i &lt;= n; ++i)\r\n\t\t\t{\r\n\t\t\t\t\/\/ Fill in the values in the row\r\n\t\t\t\trows[nextRow][0] = i;\r\n\r\n\t\t\t\tfor (int j = 1; j &lt;= m; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tint dist1 = rows[curRow][j] + 1;\r\n\t\t\t\t\tint dist2 = rows[nextRow][j - 1] + 1;\r\n\t\t\t\t\tint dist3 = rows[curRow][j - 1] +\r\n\t\t\t\t\t\t(first[i - 1].Equals(second[j - 1]) ? 0 : 1);\r\n\r\n\t\t\t\t\trows[nextRow][j] = System.Math.Min(dist1, System.Math.Min(dist2, dist3));\r\n\t\t\t\t}\r\n\r\n\t\t\t\t\/\/ Swap the current and next rows\r\n\t\t\t\tif (curRow == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tcurRow = 1;\r\n\t\t\t\t\tnextRow = 0;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tcurRow = 0;\r\n\t\t\t\t\tnextRow = 1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ Return the computed edit distance\r\n\t\t\treturn rows[curRow][m];\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u7e7c\u524d\u4e00\u7bc7 :\u00a0Unity3D Editor TextField AutoCompelete \u7d93\u904e\u5e7e\u5929 &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,11],"tags":[101,20,43],"class_list":["post-1809","post","type-post","status-publish","format-standard","hentry","category-c","category-unity3d","tag-autocomplete","tag-editor","tag-unity3d-2"],"_links":{"self":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1809","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/comments?post=1809"}],"version-history":[{"count":0,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1809\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1809"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1809"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1809"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}