123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEditor;
- using UnityEditor.Callbacks;
-
- namespace UnityEngine.InputSystem.Editor
- {
- internal class AdvancedDropdownWindow : EditorWindow
- {
- private static readonly float kBorderThickness = 1f;
- private static readonly float kRightMargin = 13f;
-
- private AdvancedDropdownGUI m_Gui;
- private AdvancedDropdownDataSource m_DataSource;
- private AdvancedDropdownState m_State;
-
- private AdvancedDropdownItem m_CurrentlyRenderedTree;
-
- protected AdvancedDropdownItem renderedTreeItem => m_CurrentlyRenderedTree;
-
- private AdvancedDropdownItem m_AnimationTree;
- private float m_NewAnimTarget;
- private long m_LastTime;
- private bool m_ScrollToSelected = true;
- private float m_InitialSelectionPosition;
- ////FIXME: looks like a bug?
- #pragma warning disable CS0649
- private Rect m_ButtonRectScreenPos;
- private Stack<AdvancedDropdownItem> m_ViewsStack = new Stack<AdvancedDropdownItem>();
- private bool m_DirtyList = true;
-
- private string m_Search = "";
- private bool hasSearch => !string.IsNullOrEmpty(m_Search);
-
- protected internal string searchString
- {
- get => m_Search;
- set
- {
- var isNewSearch = string.IsNullOrEmpty(m_Search) && !string.IsNullOrEmpty(value);
- m_Search = value;
- m_DataSource.RebuildSearch(m_Search);
- m_CurrentlyRenderedTree = m_DataSource.mainTree;
- if (hasSearch)
- {
- m_CurrentlyRenderedTree = m_DataSource.searchTree;
- if (isNewSearch || state.GetSelectedIndex(m_CurrentlyRenderedTree) < 0)
- state.SetSelectedIndex(m_CurrentlyRenderedTree, 0);
- m_ViewsStack.Clear();
- }
- }
- }
-
- internal bool m_ShowHeader = true;
- internal bool showHeader
- {
- get => m_ShowHeader;
- set => m_ShowHeader = value;
- }
- internal bool m_Searchable = true;
- internal bool searchable
- {
- get => m_Searchable;
- set => m_Searchable = value;
- }
- internal bool m_closeOnSelection = true;
- internal bool closeOnSelection
- {
- get => m_closeOnSelection;
- set => m_closeOnSelection = value;
- }
-
- protected virtual bool isSearchFieldDisabled { get; set; }
-
- protected bool m_SetInitialSelectionPosition = true;
-
- public AdvancedDropdownWindow()
- {
- m_InitialSelectionPosition = 0f;
- }
-
- protected virtual bool setInitialSelectionPosition => m_SetInitialSelectionPosition;
-
- protected internal AdvancedDropdownState state
- {
- get => m_State;
- set => m_State = value;
- }
-
- protected internal AdvancedDropdownGUI gui
- {
- get => m_Gui;
- set => m_Gui = value;
- }
-
- protected internal AdvancedDropdownDataSource dataSource
- {
- get => m_DataSource;
- set => m_DataSource = value;
- }
-
- public event Action<AdvancedDropdownWindow> windowClosed;
- public event Action windowDestroyed;
- public event Action<AdvancedDropdownItem> selectionChanged;
-
- protected virtual void OnEnable()
- {
- m_DirtyList = true;
- }
-
- protected virtual void OnDestroy()
- {
- // This window sets 'editingTextField = true' continuously, through EditorGUI.FocusTextInControl(),
- // for the searchfield in its AdvancedDropdownGUI so here we ensure to clean up. This fixes the issue that
- // EditorGUI.IsEditingTextField() was returning true after e.g the Add Component Menu closes
- EditorGUIUtility.editingTextField = false;
- GUIUtility.keyboardControl = 0;
- windowDestroyed?.Invoke();
- }
-
- public static T CreateAndInit<T>(Rect rect, AdvancedDropdownState state) where T : AdvancedDropdownWindow
- {
- var instance = CreateInstance<T>();
- instance.m_State = state;
- instance.Init(rect);
- return instance;
- }
-
- public void Init(Rect buttonRect)
- {
- var screenPoint = GUIUtility.GUIToScreenPoint(new Vector2(buttonRect.x, buttonRect.y));
- m_ButtonRectScreenPos.x = screenPoint.x;
- m_ButtonRectScreenPos.y = screenPoint.y;
-
- if (m_State == null)
- m_State = new AdvancedDropdownState();
- if (m_DataSource == null)
- m_DataSource = new MultiLevelDataSource();
- if (m_Gui == null)
- m_Gui = new AdvancedDropdownGUI();
- m_Gui.state = m_State;
- m_Gui.Init();
-
- // Has to be done before calling Show / ShowWithMode
- screenPoint = GUIUtility.GUIToScreenPoint(new Vector2(buttonRect.x, buttonRect.y));
- buttonRect.x = screenPoint.x;
- buttonRect.y = screenPoint.y;
-
- OnDirtyList();
- m_CurrentlyRenderedTree = hasSearch ? m_DataSource.searchTree : m_DataSource.mainTree;
- ShowAsDropDown(buttonRect, CalculateWindowSize(m_ButtonRectScreenPos, out var requiredDropdownSize));
-
- // If the dropdown is as height as the screen height, give it some margin
- if (position.height < requiredDropdownSize.y)
- {
- var pos = position;
- pos.y += 5;
- pos.height -= 10;
- position = pos;
- }
-
- if (setInitialSelectionPosition)
- {
- m_InitialSelectionPosition = m_Gui.GetSelectionHeight(m_DataSource, buttonRect);
- }
-
- wantsMouseMove = true;
- SetSelectionFromState();
- }
-
- void SetSelectionFromState()
- {
- var selectedIndex = m_State.GetSelectedIndex(m_CurrentlyRenderedTree);
- while (selectedIndex >= 0)
- {
- var child = m_State.GetSelectedChild(m_CurrentlyRenderedTree);
- if (child == null)
- break;
- selectedIndex = m_State.GetSelectedIndex(child);
- if (selectedIndex < 0)
- break;
- m_ViewsStack.Push(m_CurrentlyRenderedTree);
- m_CurrentlyRenderedTree = child;
- }
- }
-
- protected virtual Vector2 CalculateWindowSize(Rect buttonRect, out Vector2 requiredDropdownSize)
- {
- requiredDropdownSize = m_Gui.CalculateContentSize(m_DataSource);
- // Add 1 pixel for each border
- requiredDropdownSize.x += kBorderThickness * 2;
- requiredDropdownSize.y += kBorderThickness * 2;
- requiredDropdownSize.x += kRightMargin;
-
- requiredDropdownSize.y += m_Gui.searchHeight;
-
- if (showHeader)
- {
- requiredDropdownSize.y += m_Gui.headerHeight;
- }
-
- requiredDropdownSize.y = Mathf.Clamp(requiredDropdownSize.y, minSize.y, maxSize.y);
-
- var adjustedButtonRect = buttonRect;
- adjustedButtonRect.y = 0;
- adjustedButtonRect.height = requiredDropdownSize.y;
-
- // Stretch to the width of the button
- if (requiredDropdownSize.x < buttonRect.width)
- {
- requiredDropdownSize.x = buttonRect.width;
- }
- // Apply minimum size
- if (requiredDropdownSize.x < minSize.x)
- {
- requiredDropdownSize.x = minSize.x;
- }
- if (requiredDropdownSize.y < minSize.y)
- {
- requiredDropdownSize.y = minSize.y;
- }
-
- return requiredDropdownSize;
- }
-
- internal void OnGUI()
- {
- m_Gui.BeginDraw(this);
-
- GUI.Label(new Rect(0, 0, position.width, position.height), GUIContent.none, Styles.background);
-
- if (m_DirtyList)
- {
- OnDirtyList();
- }
-
- HandleKeyboard();
- if (searchable)
- OnGUISearch();
-
- if (m_NewAnimTarget != 0 && Event.current.type == EventType.Layout)
- {
- var now = DateTime.Now.Ticks;
- var deltaTime = (now - m_LastTime) / (float)TimeSpan.TicksPerSecond;
- m_LastTime = now;
-
- m_NewAnimTarget = Mathf.MoveTowards(m_NewAnimTarget, 0, deltaTime * 4);
-
- if (m_NewAnimTarget == 0)
- {
- m_AnimationTree = null;
- }
- Repaint();
- }
-
- var anim = m_NewAnimTarget;
- // Smooth the animation
- anim = Mathf.Floor(anim) + Mathf.SmoothStep(0, 1, Mathf.Repeat(anim, 1));
-
- if (anim == 0)
- {
- DrawDropdown(0, m_CurrentlyRenderedTree);
- }
- else if (anim < 0)
- {
- // Go to parent
- // m_NewAnimTarget goes -1 -> 0
- DrawDropdown(anim, m_CurrentlyRenderedTree);
- DrawDropdown(anim + 1, m_AnimationTree);
- }
- else // > 0
- {
- // Go to child
- // m_NewAnimTarget 1 -> 0
- DrawDropdown(anim - 1, m_AnimationTree);
- DrawDropdown(anim, m_CurrentlyRenderedTree);
- }
-
- m_Gui.EndDraw(this);
- }
-
- public void ReloadData()
- {
- OnDirtyList();
- }
-
- private void OnDirtyList()
- {
- m_DirtyList = false;
- m_DataSource.ReloadData();
- if (hasSearch)
- {
- m_DataSource.RebuildSearch(searchString);
- if (state.GetSelectedIndex(m_CurrentlyRenderedTree) < 0)
- {
- state.SetSelectedIndex(m_CurrentlyRenderedTree, 0);
- }
- }
- }
-
- private void OnGUISearch()
- {
- m_Gui.DrawSearchField(isSearchFieldDisabled, m_Search, (newSearch) =>
- {
- searchString = newSearch;
- });
- }
-
- private void HandleKeyboard()
- {
- var evt = Event.current;
- if (evt.type == EventType.KeyDown)
- {
- // Special handling when in new script panel
- if (SpecialKeyboardHandling(evt))
- {
- return;
- }
-
- // Always do these
- if (evt.keyCode == KeyCode.DownArrow)
- {
- m_State.MoveDownSelection(m_CurrentlyRenderedTree);
- m_ScrollToSelected = true;
- evt.Use();
- }
- if (evt.keyCode == KeyCode.UpArrow)
- {
- m_State.MoveUpSelection(m_CurrentlyRenderedTree);
- m_ScrollToSelected = true;
- evt.Use();
- }
- if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
- {
- var selected = m_State.GetSelectedChild(m_CurrentlyRenderedTree);
- if (selected != null)
- {
- if (selected.children.Any())
- {
- GoToChild();
- }
- else
- {
- if (selectionChanged != null)
- {
- selectionChanged(m_State.GetSelectedChild(m_CurrentlyRenderedTree));
- }
- if (closeOnSelection)
- {
- CloseWindow();
- }
- }
- }
- evt.Use();
- }
-
- // Do these if we're not in search mode
- if (!hasSearch)
- {
- if (evt.keyCode == KeyCode.LeftArrow || evt.keyCode == KeyCode.Backspace)
- {
- GoToParent();
- evt.Use();
- }
- if (evt.keyCode == KeyCode.RightArrow)
- {
- var idx = m_State.GetSelectedIndex(m_CurrentlyRenderedTree);
- if (idx > -1 && m_CurrentlyRenderedTree.children.ElementAt(idx).children.Any())
- {
- GoToChild();
- }
- evt.Use();
- }
- if (evt.keyCode == KeyCode.Escape)
- {
- Close();
- evt.Use();
- }
- }
- }
- }
-
- private void CloseWindow()
- {
- windowClosed?.Invoke(this);
- Close();
- }
-
- internal AdvancedDropdownItem GetSelectedItem()
- {
- return m_State.GetSelectedChild(m_CurrentlyRenderedTree);
- }
-
- protected virtual bool SpecialKeyboardHandling(Event evt)
- {
- return false;
- }
-
- private void DrawDropdown(float anim, AdvancedDropdownItem group)
- {
- // Start of animated area (the part that moves left and right)
- var areaPosition = new Rect(0, 0, position.width, position.height);
- // Adjust to the frame
- areaPosition.x += kBorderThickness;
- areaPosition.y += kBorderThickness;
- areaPosition.height -= kBorderThickness * 2;
- areaPosition.width -= kBorderThickness * 2;
-
- GUILayout.BeginArea(m_Gui.GetAnimRect(areaPosition, anim));
- // Header
- if (showHeader)
- m_Gui.DrawHeader(group, GoToParent, m_ViewsStack.Count > 0);
-
- DrawList(group);
- GUILayout.EndArea();
- }
-
- private void DrawList(AdvancedDropdownItem item)
- {
- // Start of scroll view list
- m_State.SetScrollState(item, GUILayout.BeginScrollView(m_State.GetScrollState(item), GUIStyle.none, GUI.skin.verticalScrollbar));
- EditorGUIUtility.SetIconSize(m_Gui.iconSize);
- Rect selectedRect = new Rect();
- for (var i = 0; i < item.children.Count(); i++)
- {
- var child = item.children.ElementAt(i);
- var selected = m_State.GetSelectedIndex(item) == i;
-
- if (child.IsSeparator())
- {
- GUIHelpers.DrawLineSeparator(child.name);
- }
- else
- {
- m_Gui.DrawItem(child, child.name, child.icon, child.enabled, child.children.Any(), selected, hasSearch);
- }
-
- var r = GUILayoutUtility.GetLastRect();
- if (selected)
- selectedRect = r;
-
- // Skip input handling for the tree used for animation
- if (item != m_CurrentlyRenderedTree)
- continue;
-
- // Select the element the mouse cursor is over.
- // Only do it on mouse move - keyboard controls are allowed to overwrite this until the next time the mouse moves.
- if ((Event.current.type == EventType.MouseMove || Event.current.type == EventType.MouseDrag) && child.enabled)
- {
- if (!selected && r.Contains(Event.current.mousePosition))
- {
- m_State.SetSelectedIndex(item, i);
- Event.current.Use();
- }
- }
- if (Event.current.type == EventType.MouseUp && r.Contains(Event.current.mousePosition) && child.enabled)
- {
- m_State.SetSelectedIndex(item, i);
- var selectedChild = m_State.GetSelectedChild(item);
- if (selectedChild.children.Any())
- {
- GoToChild();
- }
- else if (!selectedChild.IsSeparator())
- {
- selectionChanged?.Invoke(selectedChild);
- if (closeOnSelection)
- {
- CloseWindow();
- GUIUtility.ExitGUI();
- }
- }
- Event.current.Use();
- }
- }
- EditorGUIUtility.SetIconSize(Vector2.zero);
- GUILayout.EndScrollView();
-
- // Scroll to selected on windows creation
- if (m_ScrollToSelected && m_InitialSelectionPosition != 0)
- {
- var diffOfPopupAboveTheButton = m_ButtonRectScreenPos.y - position.y;
- diffOfPopupAboveTheButton -= m_Gui.searchHeight + m_Gui.headerHeight;
- m_State.SetScrollState(item, new Vector2(0, m_InitialSelectionPosition - diffOfPopupAboveTheButton));
- m_ScrollToSelected = false;
- m_InitialSelectionPosition = 0;
- }
- // Scroll to show selected
- else if (m_ScrollToSelected && Event.current.type == EventType.Repaint)
- {
- m_ScrollToSelected = false;
- Rect scrollRect = GUILayoutUtility.GetLastRect();
- if (selectedRect.yMax - scrollRect.height > m_State.GetScrollState(item).y)
- {
- m_State.SetScrollState(item, new Vector2(0, selectedRect.yMax - scrollRect.height));
- Repaint();
- }
- if (selectedRect.y < m_State.GetScrollState(item).y)
- {
- m_State.SetScrollState(item, new Vector2(0, selectedRect.y));
- Repaint();
- }
- }
- }
-
- protected void GoToParent()
- {
- if (m_ViewsStack.Count == 0)
- return;
- m_LastTime = DateTime.Now.Ticks;
- if (m_NewAnimTarget > 0)
- m_NewAnimTarget = -1 + m_NewAnimTarget;
- else
- m_NewAnimTarget = -1;
- m_AnimationTree = m_CurrentlyRenderedTree;
- var parentItem = m_ViewsStack.Pop();
-
- m_State.ClearSelectionOnItem(m_CurrentlyRenderedTree);
-
- if (parentItem != null)
- {
- var suggestedIndex = parentItem.GetIndexOfChild(m_CurrentlyRenderedTree);
- m_State.SetSelectionOnItem(parentItem, suggestedIndex);
- }
-
- m_CurrentlyRenderedTree = parentItem;
- }
-
- private void GoToChild()
- {
- m_ViewsStack.Push(m_CurrentlyRenderedTree);
- m_LastTime = DateTime.Now.Ticks;
- if (m_NewAnimTarget < 0)
- m_NewAnimTarget = 1 + m_NewAnimTarget;
- else
- m_NewAnimTarget = 1;
- m_AnimationTree = m_CurrentlyRenderedTree;
- m_CurrentlyRenderedTree = m_State.GetSelectedChild(m_CurrentlyRenderedTree);
- }
-
- [DidReloadScripts]
- private static void OnScriptReload()
- {
- CloseAllOpenWindows<AdvancedDropdownWindow>();
- }
-
- protected static void CloseAllOpenWindows<T>()
- {
- var windows = Resources.FindObjectsOfTypeAll(typeof(T));
- foreach (var window in windows)
- {
- try
- {
- ((EditorWindow)window).Close();
- }
- catch
- {
- DestroyImmediate(window);
- }
- }
- }
-
- private static class Styles
- {
- public static readonly GUIStyle background = "grey_border";
- public static readonly GUIStyle previewHeader = new GUIStyle(EditorStyles.label).WithPadding(new RectOffset(5, 5, 1, 2));
- public static readonly GUIStyle previewText = new GUIStyle(EditorStyles.wordWrappedLabel).WithPadding(new RectOffset(3, 5, 4, 4));
- }
- }
- }
-
- #endif // UNITY_EDITOR
|