{"id":1769,"date":"2017-08-11T03:44:59","date_gmt":"2017-08-10T19:44:59","guid":{"rendered":"http:\/\/www.clonefactor.com\/wordpress\/?p=1769"},"modified":"2019-05-09T16:21:32","modified_gmt":"2019-05-09T08:21:32","slug":"unity3d-editor-textfield-autocompelete","status":"publish","type":"post","link":"https:\/\/www.clonefactor.com\/wordpress\/public\/1769\/","title":{"rendered":"Unity3D Editor TextField AutoCompelete"},"content":{"rendered":"\n<figure class=\"wp-block-embed-wordpress wp-block-embed is-type-wp-embed is-provider-clonefactor\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"XSugqItSF7\"><a href=\"https:\/\/www.clonefactor.com\/wordpress\/program\/c\/1809\/\">Unity3D Editor TextField AutoCompelete (Version 2)<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; clip: rect(1px, 1px, 1px, 1px);\" title=\"Unity3D Editor TextField AutoCompelete (Version 2) &#8212; Clonefactor\" src=\"https:\/\/www.clonefactor.com\/wordpress\/program\/c\/1809\/embed\/#?secret=wvxUtjlnFS#?secret=XSugqItSF7\" data-secret=\"XSugqItSF7\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><figcaption>New solution can be find here.<\/figcaption><\/figure>\n\n\n<div>\u65e5\u524d\u5728\u8655\u7406 170 \u591a\u500b BlendShape \u6642\u8655\u7406\u8a2d\u5b9a\u7684 Mapping,<\/div>\n<div>\u90a3\u5806 1080&#215;1920 \u87a2\u5e55\u4e5f\u4e0d\u80fd\u5b8c\u5168\u986f\u793a\u7684 Popup \u7576\u4e2d\u7528\u6ed1\u9f20\u9ede\u53d6\u81ea\u5df1\u9700\u8981\u7684\u9805\u76ee\u771f\u7684\u975e\u5e38\u6709\u96e3\u5ea6.<\/div>\n<div><\/div>\n<p>\u70ba\u4e86\u53ef\u4ee5\u5728\u8d85\u904e 100\u591a\u500b\u7684\u9078\u9805\u7576\u4e2d\u627e\u51fa\u81ea\u5df1\u9700\u8981\u7684\u5b57\u4e32, (\u662f\u773c\u529b\u8a13\u7df4\u7684\u4e00\u7a2e)<br>\n\u6240\u4ee5\u82b1\u4e86\u9ede\u6642\u9593\u628a\u4e00\u76f4\u60f3\u7528\u7684 Auto Complete \u529f\u80fd\u505a\u51fa\u4f86&#8230;.<br>\n<span style=\"font-size: 1.4rem;\">\u55ef&#8230; \u4e00\u534a\u7f77.<\/span><\/p>\n<p>\u7d50\u679c\u5982\u4e0b:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1790\" src=\"http:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2017\/08\/Unity3d_Editor_TextField_AutoComplete.gif\" alt=\"\" width=\"412\" height=\"480\"><\/p>\n<h3>Source Code<\/h3>\n<pre class=\"brush: actionscript3; gutter: true; first-line: 1\">using UnityEngine;\nusing UnityEditor;\nusing System.Collections.Generic;\npublic sealed class EditorExtend\n{\n#region Text AutoComplete\n\tprivate const string m_AutoCompleteField = \"AutoCompleteField\";\n\tprivate static List&lt;string&gt; m_CacheCheckList = null;\n\tprivate static string m_AutoCompleteLastInput;\n\tprivate static string m_EditorFocusAutoComplete;\n\t\/\/\/ &lt;summary&gt;A textField to popup a matching popup, based on developers input values.&lt;\/summary&gt;\n\t\/\/\/ &lt;param name=\"input\"&gt;string input.&lt;\/param&gt;\n\t\/\/\/ &lt;param name=\"source\"&gt;the data of all possible values (string).&lt;\/param&gt;\n\t\/\/\/ &lt;param name=\"maxShownCount\"&gt;the amount to display result.&lt;\/param&gt;\n\t\/\/\/ &lt;param name=\"levenshteinDistance\"&gt;\n\t\/\/\/ value between 0f ~ 1f,\n\t\/\/\/ - more then 0f will enable the fuzzy matching\n\t\/\/\/ - 1f = anything thing is okay.\n\t\/\/\/ - 0f = require full match to the reference\n\t\/\/\/ - recommend 0.4f ~ 0.7f\n\t\/\/\/ &lt;\/param&gt;\n\t\/\/\/ &lt;returns&gt;output string.&lt;\/returns&gt;\n\tpublic static string TextFieldAutoComplete(Rect position, string input, string[] source, int maxShownCount = 5, float levenshteinDistance = 0.5f)\n\t{\n\t\tstring tag = m_AutoCompleteField + GUIUtility.GetControlID(FocusType.Passive);\n\t\tint uiDepth = GUI.depth;\n\t\tGUI.SetNextControlName(tag);\n\t\tstring rst = EditorGUI.TextField(position, input);\n\t\tif (input.Length &gt; 0 &amp;&amp; GUI.GetNameOfFocusedControl() == tag)\n\t\t{\n\t\t\tif (m_AutoCompleteLastInput != input || \/\/ input changed\n\t\t\t\tm_EditorFocusAutoComplete != tag) \/\/ another field.\n\t\t\t{\n\t\t\t\t\/\/ Update cache\n\t\t\t\tm_EditorFocusAutoComplete = tag;\n\t\t\t\tm_AutoCompleteLastInput = input;\n\n\t\t\t\tList&lt;string&gt; uniqueSrc = new List&lt;string&gt;(new HashSet&lt;string&gt;(source)); \/\/ remove duplicate\n\t\t\t\tint srcCnt = uniqueSrc.Count;\n\t\t\t\tm_CacheCheckList = new List&lt;string&gt;(System.Math.Min(maxShownCount, srcCnt)); \/\/ optimize memory alloc\n\n\t\t\t\t\/\/ Start with - slow\n\t\t\t\tfor (int i = 0; i &lt; srcCnt &amp;&amp; m_CacheCheckList.Count &lt; maxShownCount; i++)\n\t\t\t\t{\n\t\t\t\t\tif (uniqueSrc[i].ToLower().StartsWith(input.ToLower()))\n\t\t\t\t\t{\n\t\t\t\t\t\tm_CacheCheckList.Add(uniqueSrc[i]);\n\t\t\t\t\t\tuniqueSrc.RemoveAt(i);\n\t\t\t\t\t\tsrcCnt--;\n\t\t\t\t\t\ti--;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t\/\/ Contains - very slow\n\t\t\t\tif (m_CacheCheckList.Count == 0)\n\t\t\t\t{\n\t\t\t\t\tfor (int i = 0; i &lt; srcCnt &amp;&amp; m_CacheCheckList.Count &lt; maxShownCount; i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (uniqueSrc[i].ToLower().Contains(input.ToLower()))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tm_CacheCheckList.Add(uniqueSrc[i]);\n\t\t\t\t\t\t\tuniqueSrc.RemoveAt(i);\n\t\t\t\t\t\t\tsrcCnt--;\n\t\t\t\t\t\t\ti--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t\/\/ Levenshtein Distance - very very slow.\n\t\t\t\tif (levenshteinDistance &gt; 0f &amp;&amp; \/\/ only developer request\n\t\t\t\t\tinput.Length &gt; 3 &amp;&amp; \/\/ 3 characters on input, hidden value to avoid doing too early.\n\t\t\t\t\tm_CacheCheckList.Count &lt; maxShownCount) \/\/ have some empty space for matching.\n\t\t\t\t{\n\t\t\t\t\tlevenshteinDistance = Mathf.Clamp01(levenshteinDistance);\n\t\t\t\t\tstring keywords = input.ToLower();\n\t\t\t\t\tfor (int i = 0; i &lt; srcCnt &amp;&amp; m_CacheCheckList.Count &lt; maxShownCount; i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tint distance = Kit.Extend.StringExtend.LevenshteinDistance(uniqueSrc[i], keywords, caseSensitive: false);\n\t\t\t\t\t\tbool closeEnough = (int)(levenshteinDistance * uniqueSrc[i].Length) &gt; distance;\n\t\t\t\t\t\tif (closeEnough)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tm_CacheCheckList.Add(uniqueSrc[i]);\n\t\t\t\t\t\t\tuniqueSrc.RemoveAt(i);\n\t\t\t\t\t\t\tsrcCnt--;\n\t\t\t\t\t\t\ti--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t\/\/ Draw recommend keyward(s)\n\t\t\tif (m_CacheCheckList.Count &gt; 0)\n\t\t\t{\n\t\t\t\tint cnt = m_CacheCheckList.Count;\n\t\t\t\tfloat height = cnt * EditorGUIUtility.singleLineHeight;\n\t\t\t\tRect area = position;\n\t\t\t\tarea = new Rect(area.x, area.y - height, area.width, height);\n\t\t\t\tGUI.depth-=10;\n\t\t\t\t\/\/ GUI.BeginGroup(area);\n\t\t\t\t\/\/ area.position = Vector2.zero;\n\t\t\t\tGUI.BeginClip(area);\n\t\t\t\tRect line = new Rect(0, 0, area.width, EditorGUIUtility.singleLineHeight);\n\t\t\t\t\t\n\t\t\t\tfor (int i = 0; i &lt; cnt; i++)\n\t\t\t\t{\n\t\t\t\t\tif (GUI.Button(line, m_CacheCheckList[i]))\/\/, EditorStyles.toolbarDropDown))\n\t\t\t\t\t{\n\t\t\t\t\t\trst = m_CacheCheckList[i];\n\t\t\t\t\t\tGUI.changed = true;\n\t\t\t\t\t\tGUI.FocusControl(\"\"); \/\/ force update\n\t\t\t\t\t}\n\t\t\t\t\tline.y += line.height;\n\t\t\t\t}\n\t\t\t\tGUI.EndClip();\n\t\t\t\t\/\/GUI.EndGroup();\n\t\t\t\tGUI.depth+=10;\n\t\t\t}\n\t\t}\n\t\treturn rst;\n\t}\n\n\tpublic static string TextFieldAutoComplete(string input, string[] source, int maxShownCount = 5, float levenshteinDistance = 0.5f)\n\t{\n\t\tRect rect = EditorGUILayout.GetControlRect();\n\t\treturn TextFieldAutoComplete(rect, input, source, maxShownCount, levenshteinDistance);\n\t}\n\t#endregion\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>\u4f7f\u7528\u5f88\u7c21\u55ae\u5728 Editor \u7684\u7bc4\u570d\u5167\u76f4\u63a5\u547c\u53eb\u5373\u53ef.<\/p>\n<h3>Usage Demo :<\/h3>\n<pre class=\"brush: csharp; gutter: true; first-line: 1\">\/\/ string[] someNames = new[] { \"Apple\", \"Banana\", \"Circle\", \"Rect\" }\n\/\/ string temp;\npublic override void OnInspectorGUI()\n{\n\ttemp = EditorExtend.TextFieldAutoComplete(temp, someNames, maxShownCount: 10, levenshteinDistance: 0.5f);\n}\n<\/pre>\n<p>&nbsp;<\/p>\n<h3>Bugs:<\/h3>\n<ol>\n<li>\u67d0\u4e9b\u6642\u5019\u597d\u50cf\u5f88\u96e3\u9ede\u5230.<\/li>\n<li><del datetime=\"2017-08-11T13:45:27+00:00\">\u4e0d\u652f\u63f4\u6478\u7cca\u5c0d\u6bd4<\/del>, \u66f4\u65b0\u5f8c\u652f\u63f4<\/li>\n<li><del datetime=\"2017-08-11T13:51:36+00:00\">\u5305\u542b\u7a7a\u683c<\/del><\/li>\n<\/ol>\n<h2>\/\/ Updated : Fuzzy matching\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/h2>\n<p><iframe loading=\"lazy\" title=\"Unity3D Editor TextField AutoCompelete\" width=\"1260\" height=\"945\" src=\"https:\/\/www.youtube.com\/embed\/loQOWVn5Oq4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><\/p>\n<p>\u66f4\u65b0\u5f8c\u652f\u63f4\u6709\u9650\u5ea6\u7684\u6a21\u7cca\u5c0d\u6bd4, \u53c3\u8003\u7684\u662f \u840a\u6587\u65af\u5766\u8ddd\u96e2(Levenshtein Distance) \u7684\u505a\u6cd5.<br>\n\u539f\u59cb\u7248\u672c:&nbsp;<a href=\"https:\/\/blogs.msdn.microsoft.com\/toub\/2006\/05\/05\/generic-levenshtein-edit-distance-with-c\/\">https:\/\/blogs.msdn.microsoft.com\/toub\/2006\/05\/05\/generic-levenshtein-edit-distance-with-c\/<\/a><\/p>\n<p>\u9019\u908a\u653e\u4e00\u500b\u5099\u4efd:<\/p>\n<pre class=\"brush: csharp; gutter: true; first-line: 1\">\/\/\/ &lt;summary&gt;Computes the Levenshtein Edit Distance between two enumerables.&lt;\/summary&gt;\n\/\/\/ &lt;typeparam name=\"T\"&gt;The type of the items in the enumerables.&lt;\/typeparam&gt;\n\/\/\/ &lt;param name=\"lhs\"&gt;The first enumerable.&lt;\/param&gt;\n\/\/\/ &lt;param name=\"rhs\"&gt;The second enumerable.&lt;\/param&gt;\n\/\/\/ &lt;returns&gt;The edit distance.&lt;\/returns&gt;\n\/\/\/ &lt;see cref=\"https:\/\/blogs.msdn.microsoft.com\/toub\/2006\/05\/05\/generic-levenshtein-edit-distance-with-c\/\"\/&gt;\npublic static int LevenshteinDistance&lt;T&gt;(IEnumerable&lt;T&gt; lhs, IEnumerable&lt;T&gt; rhs) where T : System.IEquatable&lt;T&gt;\n{\n\t\/\/ Validate parameters\n\tif (lhs == null) throw new System.ArgumentNullException(\"lhs\");\n\tif (rhs == null) throw new System.ArgumentNullException(\"rhs\");\n\n\t\/\/ Convert the parameters into IList instances\n\t\/\/ in order to obtain indexing capabilities\n\tIList&lt;T&gt; first = lhs as IList&lt;T&gt; ?? new List&lt;T&gt;(lhs);\n\tIList&lt;T&gt; second = rhs as IList&lt;T&gt; ?? new List&lt;T&gt;(rhs);\n\n\t\/\/ Get the length of both.  If either is 0, return\n\t\/\/ the length of the other, since that number of insertions\n\t\/\/ would be required.\n\tint n = first.Count, m = second.Count;\n\tif (n == 0) return m;\n\tif (m == 0) return n;\n\n\t\/\/ Rather than maintain an entire matrix (which would require O(n*m) space),\n\t\/\/ just store the current row and the next row, each of which has a length m+1,\n\t\/\/ so just O(m) space. Initialize the current row.\n\tint curRow = 0, nextRow = 1;\n\n\tint[][] rows = new int[][] { new int[m + 1], new int[m + 1] };\n\tfor (int j = 0; j &lt;= m; ++j)\n\t\trows[curRow][j] = j;\n\n\t\/\/ For each virtual row (since we only have physical storage for two)\n\tfor (int i = 1; i &lt;= n; ++i)\n\t{\n\t\t\/\/ Fill in the values in the row\n\t\trows[nextRow][0] = i;\n\n\t\tfor (int j = 1; j &lt;= m; ++j)\n\t\t{\n\t\t\tint dist1 = rows[curRow][j] + 1;\n\t\t\tint dist2 = rows[nextRow][j - 1] + 1;\n\t\t\tint dist3 = rows[curRow][j - 1] +\n\t\t\t\t(first[i - 1].Equals(second[j - 1]) ? 0 : 1);\n\n\t\t\trows[nextRow][j] = System.Math.Min(dist1, System.Math.Min(dist2, dist3));\n\t\t}\n\n\t\t\/\/ Swap the current and next rows\n\t\tif (curRow == 0)\n\t\t{\n\t\t\tcurRow = 1;\n\t\t\tnextRow = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcurRow = 0;\n\t\t\tnextRow = 1;\n\t\t}\n\t}\n\n\t\/\/ Return the computed edit distance\n\treturn rows[curRow][m];\n}<\/pre>\n<p>\u9019\u908a\u518d\u505a\u4e00\u500b overloader \u70ba\u4e86\u7c21\u55ae\u7684 string \u63a5\u53e3.<\/p>\n<pre class=\"brush: csharp; gutter: true; first-line: 1\">\/\/\/ &lt;summary&gt;Computes the Levenshtein Edit Distance between two enumerables.&lt;\/summary&gt;\n\/\/\/ &lt;param name=\"lhs\"&gt;The first enumerable.&lt;\/param&gt;\n\/\/\/ &lt;param name=\"rhs\"&gt;The second enumerable.&lt;\/param&gt;\n\/\/\/ &lt;returns&gt;The edit distance.&lt;\/returns&gt;\n\/\/\/ &lt;see cref=\"https:\/\/en.wikipedia.org\/wiki\/Levenshtein_distance\"\/&gt;\npublic static int LevenshteinDistance(string lhs, string rhs, bool caseSensitive = true)\n{\n\tif (!caseSensitive)\n\t{\n\t\tlhs = lhs.ToLower();\n\t\trhs = rhs.ToLower();\n\t}\n\tchar[] first = lhs.ToCharArray();\n\tchar[] second = rhs.ToCharArray();\n\treturn LevenshteinDistance&lt;char&gt;(first, second);\n}<\/pre>\n<p>&nbsp;<\/p>","protected":false},"excerpt":{"rendered":"<p>\u65e5\u524d\u5728\u8655\u7406 170 \u591a\u500b BlendShape \u6642\u8655\u7406\u8a2d\u5b9a\u7684 Mapping, \u90a3\u5806 1080&#038;#2 &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,3],"tags":[101,20,43],"class_list":["post-1769","post","type-post","status-publish","format-standard","hentry","category-unity3d","category-public","tag-autocomplete","tag-editor","tag-unity3d-2"],"_links":{"self":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1769","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=1769"}],"version-history":[{"count":0,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1769\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1769"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1769"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1769"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}