123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine;
- using UnityEngine.UIElements;
- using UnityEditor.UIElements;
-
- namespace UnityEditor.Searcher
- {
- class SearcherControl : VisualElement
- {
- // Window constants.
- const string k_WindowTitleLabel = "windowTitleLabel";
- const string k_WindowDetailsPanel = "windowDetailsVisualContainer";
- const string k_WindowResultsScrollViewName = "windowResultsScrollView";
- const string k_WindowSearchTextFieldName = "searchBox";
- const string k_WindowAutoCompleteLabelName = "autoCompleteLabel";
- const string k_WindowSearchIconName = "searchIcon";
- const string k_WindowResizerName = "windowResizer";
- const string kWindowSearcherPanel = "searcherVisualContainer";
- const int k_TabCharacter = 9;
-
- Label m_AutoCompleteLabel;
- IEnumerable<SearcherItem> m_Results;
- List<SearcherItem> m_VisibleResults;
- HashSet<SearcherItem> m_ExpandedResults;
- HashSet<SearcherItem> m_MultiSelectSelection;
- Dictionary<SearcherItem, Toggle> m_SearchItemToVisualToggle;
- Searcher m_Searcher;
- string m_SuggestedTerm;
- string m_Text = string.Empty;
- Action<SearcherItem> m_SelectionCallback;
- Action<Searcher.AnalyticsEvent> m_AnalyticsDataCallback;
- Func<IEnumerable<SearcherItem>, string, SearcherItem> m_SearchResultsFilterCallback;
- ListView m_ListView;
- TextField m_SearchTextField;
- VisualElement m_SearchTextInput;
- VisualElement m_DetailsPanel;
- VisualElement m_SearcherPanel;
- VisualElement m_ContentContainer;
- Button m_ConfirmButton;
-
- internal Label TitleLabel { get; }
- internal VisualElement Resizer { get; }
-
- public SearcherControl()
- {
- // Load window template.
- var windowUxmlTemplate = Resources.Load<VisualTreeAsset>("SearcherWindow");
-
- // Clone Window Template.
- var windowRootVisualElement = windowUxmlTemplate.CloneTree();
- windowRootVisualElement.AddToClassList("content");
-
- windowRootVisualElement.StretchToParentSize();
-
- // Add Window VisualElement to window's RootVisualContainer
- Add(windowRootVisualElement);
-
- m_VisibleResults = new List<SearcherItem>();
- m_ExpandedResults = new HashSet<SearcherItem>();
- m_MultiSelectSelection = new HashSet<SearcherItem>();
- m_SearchItemToVisualToggle = new Dictionary<SearcherItem, Toggle>();
-
- m_ListView = this.Q<ListView>(k_WindowResultsScrollViewName);
-
- if (m_ListView != null)
- {
- m_ListView.bindItem = Bind;
- m_ListView.RegisterCallback<KeyDownEvent>(SetSelectedElementInResultsList);
-
- #if UNITY_2020_1_OR_NEWER
- m_ListView.onItemsChosen += obj => OnListViewSelect((SearcherItem)obj.FirstOrDefault());
- m_ListView.onSelectionChange += selectedItems => m_Searcher.Adapter.OnSelectionChanged(selectedItems.OfType<SearcherItem>().ToList());
- #else
- m_ListView.onItemChosen += obj => OnListViewSelect((SearcherItem)obj);
- m_ListView.onSelectionChanged += selectedItems => m_Searcher.Adapter.OnSelectionChanged(selectedItems.OfType<SearcherItem>());
- #endif
- m_ListView.focusable = true;
- m_ListView.tabIndex = 1;
- }
-
- m_DetailsPanel = this.Q(k_WindowDetailsPanel);
-
- TitleLabel = this.Q<Label>(k_WindowTitleLabel);
-
- m_SearcherPanel = this.Q(kWindowSearcherPanel);
-
- m_SearchTextField = this.Q<TextField>(k_WindowSearchTextFieldName);
- if (m_SearchTextField != null)
- {
- m_SearchTextField.focusable = true;
- m_SearchTextField.RegisterCallback<InputEvent>(OnSearchTextFieldTextChanged, TrickleDown.TrickleDown);
-
- m_SearchTextInput = m_SearchTextField.Q(TextInputBaseField<string>.textInputUssName);
- m_SearchTextInput.RegisterCallback<KeyDownEvent>(OnSearchTextFieldKeyDown, TrickleDown.TrickleDown);
- }
-
- m_AutoCompleteLabel = this.Q<Label>(k_WindowAutoCompleteLabelName);
-
- Resizer = this.Q(k_WindowResizerName);
-
- m_ContentContainer = this.Q("unity-content-container");
-
- m_ConfirmButton = this.Q<Button>("confirmButton");
- #if UNITY_2019_3_OR_NEWER
- m_ConfirmButton.clicked += OnConfirmMultiselect;
- #else
- m_ConfirmButton.clickable.clicked += OnConfirmMultiselect;
- #endif
-
- RegisterCallback<AttachToPanelEvent>(OnEnterPanel);
- RegisterCallback<DetachFromPanelEvent>(OnLeavePanel);
-
- // TODO: HACK - ListView's scroll view steals focus using the scheduler.
- EditorApplication.update += HackDueToListViewScrollViewStealingFocus;
-
- style.flexGrow = 1;
- }
-
- void OnConfirmMultiselect()
- {
- if (m_MultiSelectSelection.Count == 0)
- {
- m_SelectionCallback(null);
- return;
- }
- foreach (SearcherItem item in m_MultiSelectSelection)
- {
- m_SelectionCallback(item);
- }
- }
-
- void HackDueToListViewScrollViewStealingFocus()
- {
- m_SearchTextInput?.Focus();
- // ReSharper disable once DelegateSubtraction
- EditorApplication.update -= HackDueToListViewScrollViewStealingFocus;
- }
-
- void OnEnterPanel(AttachToPanelEvent e)
- {
- RegisterCallback<KeyDownEvent>(OnKeyDown);
- }
-
- void OnLeavePanel(DetachFromPanelEvent e)
- {
- UnregisterCallback<KeyDownEvent>(OnKeyDown);
- }
-
- void OnKeyDown(KeyDownEvent e)
- {
- if (e.keyCode == KeyCode.Escape)
- {
- CancelSearch();
- }
- }
-
- void OnListViewSelect(SearcherItem item)
- {
- if (!m_Searcher.Adapter.MultiSelectEnabled)
- {
- m_SelectionCallback(item);
- }
- else
- {
- ToggleItemForMultiSelect(item, !m_MultiSelectSelection.Contains(item));
- }
- }
-
- void CancelSearch()
- {
- OnSearchTextFieldTextChanged(InputEvent.GetPooled(m_Text, string.Empty));
- OnListViewSelect(null);
- m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Cancelled, m_SearchTextField.value));
- }
-
- public void Setup(Searcher searcher, Action<SearcherItem> selectionCallback, Action<Searcher.AnalyticsEvent> analyticsDataCallback, Func<IEnumerable<SearcherItem>, string, SearcherItem> searchResultsFilterCallback)
- {
- m_Searcher = searcher;
- m_SelectionCallback = selectionCallback;
- m_AnalyticsDataCallback = analyticsDataCallback;
- m_SearchResultsFilterCallback = searchResultsFilterCallback;
-
-
- if (m_Searcher.Adapter.MultiSelectEnabled) {
- AddToClassList("searcher__multiselect");
- }
-
- if (m_Searcher.Adapter.HasDetailsPanel)
- {
- m_Searcher.Adapter.InitDetailsPanel(m_DetailsPanel);
- m_DetailsPanel.RemoveFromClassList("hidden");
- m_DetailsPanel.style.flexGrow = m_Searcher.Adapter.InitialSplitterDetailRatio;
- m_SearcherPanel.style.flexGrow = 1;
- }
- else
- {
- m_DetailsPanel.AddToClassList("hidden");
-
- var splitter = m_DetailsPanel.parent;
-
- splitter.parent.Insert(0,m_SearcherPanel);
- splitter.parent.Insert(1, m_DetailsPanel);
-
- splitter.RemoveFromHierarchy();
- }
-
-
-
- TitleLabel.text = m_Searcher.Adapter.Title;
- if (string.IsNullOrEmpty(TitleLabel.text))
- {
- TitleLabel.parent.style.visibility = Visibility.Hidden;
- TitleLabel.parent.style.position = Position.Absolute;
- }
-
- m_Searcher.BuildIndices();
- Refresh();
- }
-
- void Refresh()
- {
- var query = m_Text;
- m_Results = m_Searcher.Search(query);
- GenerateVisibleResults();
-
- // The first item in the results is always the highest scored item.
- // We want to scroll to and select this item.
- var visibleIndex = -1;
- m_SuggestedTerm = string.Empty;
-
- var results = m_Results.ToList();
- if (results.Any())
- {
- SearcherItem scrollToItem = m_SearchResultsFilterCallback?.Invoke(results, query);
- if(scrollToItem == null)
- scrollToItem = results.First();
- visibleIndex = m_VisibleResults.IndexOf(scrollToItem);
-
- // If we're trying to scroll to a result that is not visible in a single category,
- // we need to add that result and its hierarchy back to the visible results
- // This prevents searcher suggesting a single collapsed category that the user then needs to manually expand regardless
- if (visibleIndex == -1 && m_VisibleResults.Count() == 1)
- {
- SearcherItem currentItemRoot = scrollToItem;
- var idSet = new HashSet<SearcherItem>();
- while (currentItemRoot.Parent != null)
- {
- currentItemRoot = currentItemRoot.Parent;
- }
- idSet.Add(currentItemRoot);
- AddResultChildren(currentItemRoot, idSet);
- visibleIndex = m_VisibleResults.IndexOf(scrollToItem);
- }
-
- var cursorIndex = m_SearchTextField.cursorIndex;
-
- if (query.Length > 0)
- {
- var strings = scrollToItem.Name.Split(' ');
- var wordStartIndex = cursorIndex == 0 ? 0 : query.LastIndexOf(' ', cursorIndex - 1) + 1;
- var word = query.Substring(wordStartIndex, cursorIndex - wordStartIndex);
-
- if (word.Length > 0)
- foreach (var t in strings)
- {
- if (t.StartsWith(word, StringComparison.OrdinalIgnoreCase))
- {
- m_SuggestedTerm = t;
- break;
- }
- }
- }
- }
-
- m_ListView.itemsSource = m_VisibleResults;
- m_ListView.makeItem = MakeItem;
- RefreshListView();
-
- SetSelectedElementInResultsList(visibleIndex);
- }
-
- VisualElement MakeItem()
- {
- VisualElement item = m_Searcher.Adapter.MakeItem();
- if (m_Searcher.Adapter.MultiSelectEnabled)
- {
- var selectionToggle = item.Q<Toggle>("itemToggle");
- if (selectionToggle != null)
- {
- selectionToggle.RegisterValueChangedCallback(changeEvent =>
- {
- SearcherItem searcherItem = item.userData as SearcherItem;
- ToggleItemForMultiSelect(searcherItem, changeEvent.newValue);
- });
- }
- }
- return item;
- }
-
- void GenerateVisibleResults()
- {
- if (string.IsNullOrEmpty(m_Text))
- {
- m_ExpandedResults.Clear();
- RemoveChildrenFromResults();
- return;
- }
-
- RegenerateVisibleResults();
- ExpandAllParents();
- }
-
- void ExpandAllParents()
- {
- m_ExpandedResults.Clear();
- foreach (var item in m_VisibleResults)
- if (item.HasChildren)
- m_ExpandedResults.Add(item);
- }
-
- void RemoveChildrenFromResults()
- {
- m_VisibleResults.Clear();
- var parents = new HashSet<SearcherItem>();
-
- foreach (var item in m_Results.Where(i => !parents.Contains(i)))
- {
- var currentParent = item;
-
- while (true)
- {
- if (currentParent.Parent == null)
- {
- if (parents.Contains(currentParent))
- break;
-
- parents.Add(currentParent);
- m_VisibleResults.Add(currentParent);
- break;
- }
-
- currentParent = currentParent.Parent;
- }
- }
-
- if (m_Searcher.SortComparison != null)
- m_VisibleResults.Sort(m_Searcher.SortComparison);
- }
-
- void RegenerateVisibleResults()
- {
- var idSet = new HashSet<SearcherItem>();
- m_VisibleResults.Clear();
-
- foreach (var item in m_Results.Where(item => !idSet.Contains(item)))
- {
- idSet.Add(item);
- m_VisibleResults.Add(item);
-
- var currentParent = item.Parent;
- while (currentParent != null)
- {
- if (!idSet.Contains(currentParent))
- {
- idSet.Add(currentParent);
- m_VisibleResults.Add(currentParent);
- }
-
- currentParent = currentParent.Parent;
- }
-
- AddResultChildren(item, idSet);
- }
-
- var comparison = m_Searcher.SortComparison ?? ((i1, i2) =>
- {
- var result = i1.Database.Id - i2.Database.Id;
- return result != 0 ? result : i1.Id - i2.Id;
- });
- m_VisibleResults.Sort(comparison);
- }
-
- void AddResultChildren(SearcherItem item, ISet<SearcherItem> idSet)
- {
- if (!item.HasChildren)
- return;
- if (m_Searcher.Adapter.AddAllChildResults)
- {
- //add all children results for current search term
- // eg "Book" will show both "Cook Book" and "Cooking" as children
- foreach (var child in item.Children)
- {
- if (!idSet.Contains(child))
- {
- idSet.Add(child);
- m_VisibleResults.Add(child);
- }
-
- AddResultChildren(child, idSet);
- }
- }
- else
- {
- foreach (var child in item.Children)
- {
- //only add child results if the child matches the search term
- // eg "Book" will show "Cook Book" but not "Cooking" as a child
- if (!m_Results.Contains(child))
- continue;
-
- if (!idSet.Contains(child))
- {
- idSet.Add(child);
- m_VisibleResults.Add(child);
- }
-
- AddResultChildren(child, idSet);
- }
- }
- }
-
- bool HasChildResult(SearcherItem item)
- {
- if (m_Results.Contains(item))
- return true;
-
- foreach (var child in item.Children)
- {
- if (HasChildResult(child))
- return true;
- }
-
- return false;
- }
-
- ItemExpanderState GetExpanderState(int index)
- {
- var item = m_VisibleResults[index];
-
- foreach (var child in item.Children)
- {
- if (!m_VisibleResults.Contains(child) && !HasChildResult(child))
- continue;
-
- return m_ExpandedResults.Contains(item) ? ItemExpanderState.Expanded : ItemExpanderState.Collapsed;
- }
-
- return item.Children.Count != 0 ? ItemExpanderState.Collapsed : ItemExpanderState.Hidden;
- }
-
- void Bind(VisualElement target, int index)
- {
- var item = m_VisibleResults[index];
- var expanderState = GetExpanderState(index);
- var expander = m_Searcher.Adapter.Bind(target, item, expanderState, m_Text);
- var selectionToggle = target.Q<Toggle>("itemToggle");
- if (selectionToggle != null)
- {
- selectionToggle.SetValueWithoutNotify(m_MultiSelectSelection.Contains(item));
- m_SearchItemToVisualToggle[item] = selectionToggle;
- }
- expander.RegisterCallback<MouseDownEvent>(ExpandOrCollapse);
- }
-
- void ToggleItemForMultiSelect(SearcherItem item, bool selected)
- {
- if (selected)
- {
- m_MultiSelectSelection.Add(item);
- } else
- {
- m_MultiSelectSelection.Remove(item);
- }
-
- Toggle toggle;
- if (m_SearchItemToVisualToggle.TryGetValue(item, out toggle))
- {
- toggle.SetValueWithoutNotify(selected);
- }
-
- foreach (var child in item.Children)
- {
- ToggleItemForMultiSelect(child, selected);
- }
- }
-
- static void GetItemsToHide(SearcherItem parent, ref HashSet<SearcherItem> itemsToHide)
- {
- if (!parent.HasChildren)
- {
- itemsToHide.Add(parent);
- return;
- }
-
- foreach (var child in parent.Children)
- {
- itemsToHide.Add(child);
- GetItemsToHide(child, ref itemsToHide);
- }
- }
-
- void HideUnexpandedItems()
- {
- // Hide unexpanded children.
- var itemsToHide = new HashSet<SearcherItem>();
- foreach (var item in m_VisibleResults)
- {
- if (m_ExpandedResults.Contains(item))
- continue;
-
- if (!item.HasChildren)
- continue;
-
- if (itemsToHide.Contains(item))
- continue;
-
- // We need to hide its children.
- GetItemsToHide(item, ref itemsToHide);
- }
-
- foreach (var item in itemsToHide)
- m_VisibleResults.Remove(item);
- }
-
- void RefreshListView()
- {
- m_SearchItemToVisualToggle.Clear();
- #if UNITY_2021_2_OR_NEWER
- m_ListView.Rebuild();
- #else
- m_ListView.Refresh();
- #endif
- }
-
- // ReSharper disable once UnusedMember.Local
- void RefreshListViewOn()
- {
- // TODO: Call ListView.Refresh() when it is fixed.
- // Need this workaround until then.
- // See: https://fogbugz.unity3d.com/f/cases/1027728/
- // And: https://gitlab.internal.unity3d.com/upm-packages/editor/com.unity.searcher/issues/9
-
- var scrollView = m_ListView.Q<ScrollView>();
-
- var scroller = scrollView?.Q<Scroller>("VerticalScroller");
- if (scroller == null)
- return;
-
- var oldValue = scroller.value;
- scroller.value = oldValue + 1.0f;
- scroller.value = oldValue - 1.0f;
- scroller.value = oldValue;
- }
-
- void Expand(SearcherItem item)
- {
- m_ExpandedResults.Add(item);
-
- RegenerateVisibleResults();
- HideUnexpandedItems();
-
- RefreshListView();
- }
-
- void Collapse(SearcherItem item)
- {
- // if it's already collapsed or not collapsed
- if (!m_ExpandedResults.Remove(item))
- {
- // this case applies for a left arrow key press
- if (item.Parent != null)
- SetSelectedElementInResultsList(m_VisibleResults.IndexOf(item.Parent));
-
- // even if it's a root item and has no parents, do nothing more
- return;
- }
-
- RegenerateVisibleResults();
- HideUnexpandedItems();
-
- // TODO: understand what happened
- RefreshListView();
-
- // RefreshListViewOn();
- }
-
- void ExpandOrCollapse(MouseDownEvent evt)
- {
- if (!(evt.target is VisualElement expanderLabel))
- return;
-
- VisualElement itemElement = expanderLabel.GetFirstAncestorOfType<TemplateContainer>();
-
- if (!(itemElement?.userData is SearcherItem item)
- || !item.HasChildren
- || !expanderLabel.ClassListContains("Expanded") && !expanderLabel.ClassListContains("Collapsed"))
- return;
-
- if (!m_ExpandedResults.Contains(item))
- Expand(item);
- else
- Collapse(item);
-
- evt.StopImmediatePropagation();
- }
-
- void OnSearchTextFieldTextChanged(InputEvent inputEvent)
- {
- var text = inputEvent.newData;
-
- if (string.Equals(text, m_Text))
- return;
-
- // This is necessary due to OnTextChanged(...) being called after user inputs that have no impact on the text.
- // Ex: Moving the caret.
- m_Text = text;
-
- // If backspace is pressed and no text remain, clear the suggestion label.
- if (string.IsNullOrEmpty(text))
- {
- this.Q(k_WindowSearchIconName).RemoveFromClassList("Active");
-
- // Display the unfiltered results list.
- Refresh();
-
- m_AutoCompleteLabel.text = String.Empty;
- m_SuggestedTerm = String.Empty;
-
- SetSelectedElementInResultsList(0);
-
- return;
- }
-
- if (!this.Q(k_WindowSearchIconName).ClassListContains("Active"))
- this.Q(k_WindowSearchIconName).AddToClassList("Active");
-
- Refresh();
-
- // Calculate the start and end indexes of the word being modified (if any).
- var cursorIndex = m_SearchTextField.cursorIndex;
-
- // search toward the beginning of the string starting at the character before the cursor
- // +1 because we want the char after a space, or 0 if the search fails
- var wordStartIndex = cursorIndex == 0 ? 0 : (text.LastIndexOf(' ', cursorIndex - 1) + 1);
-
- // search toward the end of the string from the cursor index
- var wordEndIndex = text.IndexOf(' ', cursorIndex);
- if (wordEndIndex == -1) // no space found, assume end of string
- wordEndIndex = text.Length;
-
- // Clear the suggestion term if the caret is not within a word (both start and end indexes are equal, ex: (space)caret(space))
- // or the user didn't append characters to a word at the end of the query.
- if (wordStartIndex == wordEndIndex || wordEndIndex < text.Length)
- {
- m_AutoCompleteLabel.text = string.Empty;
- m_SuggestedTerm = string.Empty;
- return;
- }
-
- var word = text.Substring(wordStartIndex, wordEndIndex - wordStartIndex);
-
- if (!string.IsNullOrEmpty(m_SuggestedTerm))
- {
- var wordSuggestion =
- word + m_SuggestedTerm.Substring(word.Length, m_SuggestedTerm.Length - word.Length);
- text = text.Remove(wordStartIndex, word.Length);
- text = text.Insert(wordStartIndex, wordSuggestion);
- m_AutoCompleteLabel.text = text;
- }
- else
- {
- m_AutoCompleteLabel.text = String.Empty;
- }
- }
-
- void OnSearchTextFieldKeyDown(KeyDownEvent keyDownEvent)
- {
- // First, check if we cancelled the search.
- if (keyDownEvent.keyCode == KeyCode.Escape)
- {
- CancelSearch();
- return;
- }
-
- // For some reason the KeyDown event is raised twice when entering a character.
- // As such, we ignore one of the duplicate event.
- // This workaround was recommended by the Editor team. The cause of the issue relates to how IMGUI works
- // and a fix was not in the works at the moment of this writing.
- if (keyDownEvent.character == k_TabCharacter)
- {
- // Prevent switching focus to another visual element.
- keyDownEvent.PreventDefault();
-
- return;
- }
-
- // If Tab is pressed, complete the query with the suggested term.
- if (keyDownEvent.keyCode == KeyCode.Tab)
- {
- // Used to prevent the TAB input from executing it's default behavior. We're hijacking it for auto-completion.
- keyDownEvent.PreventDefault();
-
- if (!string.IsNullOrEmpty(m_SuggestedTerm))
- {
- SelectAndReplaceCurrentWord();
- m_AutoCompleteLabel.text = string.Empty;
-
- // TODO: Revisit, we shouldn't need to do this here.
- m_Text = m_SearchTextField.text;
-
- Refresh();
-
- m_SuggestedTerm = string.Empty;
- }
- }
- else
- {
- SetSelectedElementInResultsList(keyDownEvent);
- }
- }
-
- void SelectAndReplaceCurrentWord()
- {
- var s = m_SearchTextField.value;
- var lastWordIndex = s.LastIndexOf(' ');
- lastWordIndex++;
-
- var newText = s.Substring(0, lastWordIndex) + m_SuggestedTerm;
-
- // Wait for SelectRange api to reach trunk
- //#if UNITY_2018_3_OR_NEWER
- // m_SearchTextField.value = newText;
- // m_SearchTextField.SelectRange(m_SearchTextField.value.Length, m_SearchTextField.value.Length);
- //#else
- // HACK - relies on the textfield moving the caret when being assigned a value and skipping
- // all low surrogate characters
- var magicMoveCursorToEndString = new string('\uDC00', newText.Length);
- m_SearchTextField.value = magicMoveCursorToEndString;
- m_SearchTextField.value = newText;
-
- //#endif
- }
-
- void SetSelectedElementInResultsList(KeyDownEvent keyDownEvent)
- {
- int index;
- switch (keyDownEvent.keyCode)
- {
- case KeyCode.Escape:
- OnListViewSelect(null);
- m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Cancelled, m_SearchTextField.value));
- break;
- case KeyCode.Return:
- case KeyCode.KeypadEnter:
- if (m_ListView.selectedIndex != -1)
- {
- OnListViewSelect((SearcherItem)m_ListView.selectedItem);
- m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Picked, m_SearchTextField.value));
- }
- else
- {
- OnListViewSelect(null);
- m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Cancelled, m_SearchTextField.value));
- }
- break;
- case KeyCode.LeftArrow:
- index = m_ListView.selectedIndex;
- if (index >= 0 && index < m_ListView.itemsSource.Count)
- Collapse(m_ListView.selectedItem as SearcherItem);
- break;
- case KeyCode.RightArrow:
- index = m_ListView.selectedIndex;
- if (index >= 0 && index < m_ListView.itemsSource.Count)
- Expand(m_ListView.selectedItem as SearcherItem);
- break;
-
- // Fixes bug: https://fogbugz.unity3d.com/f/cases/1358016/
- case KeyCode.UpArrow:
- case KeyCode.PageUp:
- if (m_ListView.selectedIndex > 0)
- SetSelectedElementInResultsList(m_ListView.selectedIndex - 1);
- break;
-
- case KeyCode.DownArrow:
- case KeyCode.PageDown:
- if (m_ListView.selectedIndex < 0)
- SetSelectedElementInResultsList(0);
- else
- SetSelectedElementInResultsList(m_ListView.selectedIndex + 1);
- break;
- }
- }
-
- void SetSelectedElementInResultsList(int selectedIndex)
- {
- var newIndex = selectedIndex >= 0 && selectedIndex < m_VisibleResults.Count ? selectedIndex : -1;
- if (newIndex < 0)
- return;
-
- m_ListView.selectedIndex = newIndex;
- m_ListView.ScrollToItem(m_ListView.selectedIndex);
- }
- }
- }
|