Пример #1
0
        /// <summary>calculate auto complete select option location, and select it.
        /// within area, and we display option in "Vertical" style.
        /// which line is what we care.
        /// </summary>
        /// <param name="rst">input string, may overrided</param>
        /// <param name="cnt"></param>
        /// <param name="area"></param>
        /// <param name="mouseY"></param>
        private static void _AutoCompleteClickhandle(Rect position, ref string rst)
        {
            int     index = EditorAutoCompleteParams.selectedOption;
            Vector2 pos   = EditorAutoCompleteParams.mouseDown;           // hack: assume mouse are stay in click position (1 frame behind).

            if (0 <= index && index < EditorAutoCompleteParams.CacheCheckList.Count)
            {
                rst         = EditorAutoCompleteParams.CacheCheckList[index];
                GUI.changed = true;
                // Debug.Log("Selecting index (" + EditorAutoCompleteParams.selectedOption + ") "+ rst);
            }
            else
            {
                // Fail safe, when selectedOption failure

                int   cnt    = EditorAutoCompleteParams.CacheCheckList.Count;
                float height = cnt * EditorAutoCompleteParams.optionHeight;
                Rect  area   = new Rect(position.x, position.y - height, position.width, height);
                if (!area.Contains(pos))
                {
                    return;                     // return early.
                }
                float lineY = area.y;
                for (int i = 0; i < cnt; i++)
                {
                    if (lineY < pos.y && pos.y < lineY + EditorAutoCompleteParams.optionHeight)
                    {
                        rst = EditorAutoCompleteParams.CacheCheckList[i];
                        Debug.LogError("Fail to select on \"" + EditorAutoCompleteParams.lastTag + "\" selected = " + rst + "\ncalculate by mouse position.");
                        GUI.changed = true;
                        break;
                    }
                    lineY += EditorAutoCompleteParams.optionHeight;
                }
            }

            EditorAutoCompleteParams.CleanUpAndBlur();
        }
Пример #2
0
        /// <summary>A textField to popup a matching popup, based on developers input values.</summary>
        /// <param name="position">EditorGUI position</param>
        /// <param name="input">string input.</param>
        /// <param name="source">the data of all possible values (string).</param>
        /// <param name="maxShownCount">the amount to display result.</param>
        /// <param name="levenshteinDistance">
        /// value between 0f ~ 1f, (percent)
        /// - more then 0f will enable the fuzzy matching
        /// - 1f = 100% error threshold = everything is okay.
        /// - 0f = 000% error threshold = require full match to the reference
        /// - recommend 0.4f ~ 0.7f
        /// </param>
        /// <returns>output string.</returns>
        public static string TextFieldAutoComplete(Rect position, string input, string[] source, int maxShownCount = 5, float levenshteinDistance = 0.5f)
        {
            // Text field
            int    controlId = GUIUtility.GetControlID(FocusType.Passive);
            string tag       = EditorAutoCompleteParams.FieldTag + controlId;

            GUI.SetNextControlName(tag);
            string rst = EditorGUI.TextField(position, input, EditorStyles.popup);

            // Matching with giving source
            if (input.Length > 0 &&                                                                          // have input
                (EditorAutoCompleteParams.lastTag.Length == 0 || EditorAutoCompleteParams.lastTag == tag) && // one frame delay for process click event.
                GUI.GetNameOfFocusedControl() == tag)                                                        // focus this control
            {
                // Matching
                if (EditorAutoCompleteParams.lastInput != input ||                // input changed
                    EditorAutoCompleteParams.focusTag != tag)                     // switch focus from another field.
                {
                    // Update cache
                    EditorAutoCompleteParams.focusTag  = tag;
                    EditorAutoCompleteParams.lastInput = input;

                    List <string> uniqueSrc = new List <string>(new HashSet <string>(source));                           // remove duplicate
                    int           srcCnt    = uniqueSrc.Count;
                    EditorAutoCompleteParams.CacheCheckList = new List <string>(System.Math.Min(maxShownCount, srcCnt)); // optimize memory alloc
                    // Start with - slow
                    for (int i = 0; i < srcCnt && EditorAutoCompleteParams.CacheCheckList.Count < maxShownCount; i++)
                    {
                        if (uniqueSrc[i].ToLower().StartsWith(input.ToLower()))
                        {
                            EditorAutoCompleteParams.CacheCheckList.Add(uniqueSrc[i]);
                            uniqueSrc.RemoveAt(i);
                            srcCnt--;
                            i--;
                        }
                    }

                    // Contains - very slow
                    if (EditorAutoCompleteParams.CacheCheckList.Count == 0)
                    {
                        for (int i = 0; i < srcCnt && EditorAutoCompleteParams.CacheCheckList.Count < maxShownCount; i++)
                        {
                            if (uniqueSrc[i].ToLower().Contains(input.ToLower()))
                            {
                                EditorAutoCompleteParams.CacheCheckList.Add(uniqueSrc[i]);
                                uniqueSrc.RemoveAt(i);
                                srcCnt--;
                                i--;
                            }
                        }
                    }

                    // Levenshtein Distance - very very slow.
                    if (levenshteinDistance > 0f &&                                    // only developer request
                        input.Length > EditorAutoCompleteParams.fuzzyMatchBias &&      // bias on input, hidden value to avoid doing it too early.
                        EditorAutoCompleteParams.CacheCheckList.Count < maxShownCount) // have some empty space for matching.
                    {
                        levenshteinDistance = Mathf.Clamp01(levenshteinDistance);
                        string keywords = input.ToLower();
                        for (int i = 0; i < srcCnt && EditorAutoCompleteParams.CacheCheckList.Count < maxShownCount; i++)
                        {
                            int  distance    = Kit.StringExtend.LevenshteinDistance(uniqueSrc[i], keywords, caseSensitive: false);
                            bool closeEnough = (int)(levenshteinDistance * uniqueSrc[i].Length) > distance;
                            if (closeEnough)
                            {
                                EditorAutoCompleteParams.CacheCheckList.Add(uniqueSrc[i]);
                                uniqueSrc.RemoveAt(i);
                                srcCnt--;
                                i--;
                            }
                        }
                    }
                }

                // Draw recommend keyward(s)
                if (EditorAutoCompleteParams.CacheCheckList.Count > 0)
                {
                    Event evt    = Event.current;
                    int   cnt    = EditorAutoCompleteParams.CacheCheckList.Count;
                    float height = cnt * EditorAutoCompleteParams.optionHeight;
                    Rect  area   = new Rect(position.x, position.y - height, position.width, height);

                    // Fancy color UI
                    EditorGUI.BeginDisabledGroup(true);
                    EditorGUI.DrawRect(area, EditorAutoCompleteParams.FancyColor);
                    GUI.Label(area, GUIContent.none, GUI.skin.button);
                    EditorGUI.EndDisabledGroup();

                    // Click event hack - part 1
                    // cached data for click event hack.
                    if (evt.type == EventType.Repaint)
                    {
                        // Draw option(s), if we have one.
                        // in repaint cycle, we only handle display.
                        Rect line = new Rect(area.x, area.y, area.width, EditorAutoCompleteParams.optionHeight);
                        EditorGUI.indentLevel++;
                        for (int i = 0; i < cnt; i++)
                        {
                            EditorGUI.ToggleLeft(line, GUIContent.none, (input == EditorAutoCompleteParams.CacheCheckList[i]));
                            Rect indented = EditorGUI.IndentedRect(line);
                            if (line.Contains(evt.mousePosition))
                            {
                                // hover style
                                EditorGUI.LabelField(indented, EditorAutoCompleteParams.CacheCheckList[i], GUI.skin.textArea);
                                EditorAutoCompleteParams.selectedOption = i;

                                GUIUtility.hotControl = controlId;                                 // required for Cursor skin. (AddCursorRect)
                                EditorGUIUtility.AddCursorRect(area, MouseCursor.ArrowPlus);
                            }
                            else if (EditorAutoCompleteParams.selectedOption == i)
                            {
                                // hover style
                                EditorGUI.LabelField(indented, EditorAutoCompleteParams.CacheCheckList[i], GUI.skin.textArea);
                            }
                            else
                            {
                                EditorGUI.LabelField(indented, EditorAutoCompleteParams.CacheCheckList[i], EditorStyles.label);
                            }
                            line.y += line.height;
                        }
                        EditorGUI.indentLevel--;

                        // when hover popup, record this as the last usein tag.
                        if (area.Contains(evt.mousePosition) && EditorAutoCompleteParams.lastTag != tag)
                        {
                            // Debug.Log("->" + tag + " Enter popup: " + area);
                            // used to trigger the clicked checking later.
                            EditorAutoCompleteParams.lastTag = tag;
                        }
                    }
                    else if (evt.type == EventType.MouseDown)
                    {
                        if (area.Contains(evt.mousePosition) || position.Contains(evt.mousePosition))
                        {
                            EditorAutoCompleteParams.mouseDown = evt.mousePosition;
                        }
                        else
                        {
                            // click outside popup area, deselected - blur.
                            EditorAutoCompleteParams.CleanUpAndBlur();
                        }
                    }
                    else if (evt.type == EventType.MouseUp)
                    {
                        if (position.Contains(evt.mousePosition))
                        {
                            // common case click on textfield.
                            return(rst);
                        }
                        else if (area.Contains(evt.mousePosition))
                        {
                            if (Vector2.Distance(EditorAutoCompleteParams.mouseDown, evt.mousePosition) >= 3f)
                            {
                                // Debug.Log("Click and drag out the area.");
                                return(rst);
                            }
                            else
                            {
                                // Click event hack - part 3
                                // for some reason, this session only run when popup display on inspector empty space.
                                // when any selectable field behind of the popup list, Unity3D can't reaching this session.
                                _AutoCompleteClickhandle(position, ref rst);
                                EditorAutoCompleteParams.focusTag = string.Empty;                                // Clean up
                                EditorAutoCompleteParams.lastTag  = string.Empty;                                // Clean up
                            }
                        }
                        else
                        {
                            // click outside popup area, deselected - blur.
                            EditorAutoCompleteParams.CleanUpAndBlur();
                        }
                        return(rst);
                    }
                    else if (evt.isKey && evt.type == EventType.KeyUp)
                    {
                        switch (evt.keyCode)
                        {
                        case KeyCode.PageUp:
                        case KeyCode.UpArrow:
                            EditorAutoCompleteParams.selectedOption--;
                            if (EditorAutoCompleteParams.selectedOption < 0)
                            {
                                EditorAutoCompleteParams.selectedOption = EditorAutoCompleteParams.CacheCheckList.Count - 1;
                            }
                            break;

                        case KeyCode.PageDown:
                        case KeyCode.DownArrow:
                            EditorAutoCompleteParams.selectedOption++;
                            if (EditorAutoCompleteParams.selectedOption >= EditorAutoCompleteParams.CacheCheckList.Count)
                            {
                                EditorAutoCompleteParams.selectedOption = 0;
                            }
                            break;

                        case KeyCode.KeypadEnter:
                        case KeyCode.Return:
                            if (EditorAutoCompleteParams.selectedOption != -1)
                            {
                                _AutoCompleteClickhandle(position, ref rst);
                                EditorAutoCompleteParams.focusTag = string.Empty;                                        // Clean up
                                EditorAutoCompleteParams.lastTag  = string.Empty;                                        // Clean up
                            }
                            else
                            {
                                EditorAutoCompleteParams.CleanUpAndBlur();
                            }
                            break;

                        case KeyCode.Escape:
                            EditorAutoCompleteParams.CleanUpAndBlur();
                            break;

                        default:
                            // hit any other key(s), assume typing, avoid override by Enter;
                            EditorAutoCompleteParams.selectedOption = -1;
                            break;
                        }
                    }
                }
            }
            else if (EditorAutoCompleteParams.lastTag == tag &&
                     GUI.GetNameOfFocusedControl() != tag)
            {
                // Click event hack - part 2
                // catching mouse click on blur
                _AutoCompleteClickhandle(position, ref rst);
                EditorAutoCompleteParams.lastTag = string.Empty;                 // reset
            }

            return(rst);
        }