using System; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEditor.Experimental; using UnityEngine; namespace UnityEditor.Tilemaps { internal class GridPaintTargetsDropdown : PopupWindowContent { private class Styles { public class IconState { public GUIContent visible; public GUIContent hidden; public GUIContent ping; } public GUIStyle menuItem = "MenuItem"; public GUIContent backIcon = EditorGUIUtility.TrIconContent("tab_next"); public static readonly Color backgroundColor = EditorResources.GetStyle("game-object-tree-view-scene-visibility") .GetColor("background-color"); public static readonly Color hoveredBackgroundColor = EditorResources.GetStyle("game-object-tree-view-scene-visibility") .GetColor("-unity-object-hovered-color"); public static readonly Color selectedBackgroundColor = EditorResources.GetStyle("game-object-tree-view-scene-visibility") .GetColor("-unity-object-selected-color"); public static readonly Color selectedNoFocusBackgroundColor = EditorResources.GetStyle("game-object-tree-view-scene-visibility") .GetColor("-unity-object-selected-no-focus-color"); public static readonly GUIStyle sceneVisibilityStyle = "SceneVisibility"; public static readonly IconState iconNormal = new() { visible = EditorGUIUtility.TrIconContent("scenevis_visible", "Click to hide Target in Scene View"), hidden = EditorGUIUtility.TrIconContent("scenevis_hidden", "Click to show Target in Scene View"), ping = EditorGUIUtility.TrIconContent("Packages/com.unity.2d.tilemap/Editor/Icons/EditorUI.Target.png", "Click to ping Target in Hierarchy"), }; public static readonly IconState iconHovered = new() { visible = EditorGUIUtility.TrIconContent("scenevis_visible_hover", "Click to hide Target in Scene View"), hidden = EditorGUIUtility.TrIconContent("scenevis_hidden_hover", "Click to show Target in Scene View"), ping = EditorGUIUtility.TrIconContent("Packages/com.unity.2d.tilemap/Editor/Icons/EditorUI.TargetHover.png", "Click to ping Target in Hierarchy"), }; } internal static string k_CreateNewPaintTargetName = L10n.Tr("Create New Tilemap"); private static Styles s_Styles; private IFlexibleMenuItemProvider m_ItemProvider; private FlexibleMenuModifyItemUI m_ModifyItemUI; private readonly Action m_ItemClickedCallback; private readonly Action m_ItemHoveredCallback; private Vector2 m_ScrollPosition = Vector2.zero; private bool m_ShowAddNewPresetItem; private int m_HoverIndex; private int[] m_SeperatorIndices; private float m_CachedWidth = -1f; private float m_ParentWidth; private string m_FilterString; private List m_FilteredIndices = new List(); private static readonly string activeTargetFilterTextEditorPref = "TilePaletteActiveTargetFilterText"; private string activeTargetFilterText { get => EditorPrefs.GetString(activeTargetFilterTextEditorPref, String.Empty); set => EditorPrefs.SetString(activeTargetFilterTextEditorPref, value); } private Dictionary m_BrushTargets = new Dictionary(); private const float LineHeight = 18f; private const float SeperatorHeight = 8f; private int maxIndex { get { return m_ShowAddNewPresetItem ? m_ItemProvider.Count() : m_ItemProvider.Count() - 1; } } public int selectedIndex { get; set; } protected float parentWidth { get { return m_ParentWidth; } set { m_ParentWidth = value; ClearCachedWidth(); } } public enum VisibilityState { IsVisible, NotVisible, InvalidForVisible } internal class MenuItemProvider : IFlexibleMenuItemProvider { public int Count() { return GridPaintingState.validTargets != null ? GridPaintingState.validTargets.Length + 1 : 1; } public object GetItem(int index) { if (GridPaintingState.validTargets != null && index < GridPaintingState.validTargets.Length) return GridPaintingState.validTargets[index]; return GridPaintingState.scenePaintTarget; } public int Add(object obj) { throw new NotImplementedException(); } public void Replace(int index, object newPresetObject) { throw new NotImplementedException(); } public void Remove(int index) { throw new NotImplementedException(); } public object Create() { throw new NotImplementedException(); } public void Move(int index, int destIndex, bool insertAfterDestIndex) { throw new NotImplementedException(); } public string GetName(int index) { if (GridPaintingState.validTargets != null && index < GridPaintingState.validTargets.Length) { return GridPaintingState.validTargets[index].name; } return "Create New Tilemap"; } public bool IsModificationAllowed(int index) { return false; } public int[] GetSeperatorIndices() { if (GridPaintingState.validTargets != null) return new int[] { GridPaintingState.validTargets.Length - 1 }; return new int[] { -1 }; } } // itemClickedCallback arguments is clicked index, clicked item object public GridPaintTargetsDropdown(IFlexibleMenuItemProvider itemProvider , int selectionIndex , FlexibleMenuModifyItemUI modifyItemUi , Action itemClickedCallback , Action itemHoveredCallback , float minWidth) { m_ItemProvider = itemProvider; m_ModifyItemUI = modifyItemUi; m_ItemClickedCallback = itemClickedCallback; m_ItemHoveredCallback = itemHoveredCallback; m_SeperatorIndices = m_ItemProvider.GetSeperatorIndices(); selectedIndex = selectionIndex; m_ShowAddNewPresetItem = m_ModifyItemUI != null; m_ParentWidth = minWidth; m_FilterString = activeTargetFilterText; UpdateFilter(); } public override Vector2 GetWindowSize() { return CalcSize(); } private void UpdateFilter() { m_FilteredIndices.Clear(); m_FilteredIndices.Capacity = maxIndex; Regex regex = null; var hasFilter = !String.IsNullOrWhiteSpace(m_FilterString); if (hasFilter) { try { regex = new Regex(m_FilterString, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(50)); } catch (ArgumentException) { hasFilter = false; } } for (var i = 0; i < maxIndex; i++) { if (hasFilter && regex != null && i != selectedIndex) { var name = m_ItemProvider.GetName(i); bool match; try { match = regex.IsMatch(name); } catch (RegexMatchTimeoutException) { match = false; } if (!match) continue; } m_FilteredIndices.Add(i); } } public override void OnGUI(Rect rect) { if (s_Styles == null) s_Styles = new Styles(); Event evt = Event.current; EditorGUI.BeginChangeCheck(); m_FilterString = EditorGUI.ToolbarSearchField(new Rect(0, 0, rect.width, LineHeight), m_FilterString, false); if (EditorGUI.EndChangeCheck() && !m_FilterString.Equals(activeTargetFilterText)) { activeTargetFilterText = m_FilterString; UpdateFilter(); } Rect scrollRect = new Rect(0, LineHeight, rect.width, rect.height - LineHeight); Rect contentRect = new Rect(0, 0, 1, CalcSize().y - LineHeight); m_ScrollPosition = GUI.BeginScrollView(scrollRect, m_ScrollPosition, contentRect); { int itemMaxIndex = m_FilteredIndices.Count; float curY = 0; for (int i = 0; i <= maxIndex; ++i) { var validItemIndex = i < itemMaxIndex; var j = validItemIndex ? m_FilteredIndices[i] : i < maxIndex ? -1 : maxIndex; var itemControlID = j + 1000000; var fullRect = new Rect(0, curY, rect.width, LineHeight); var visRect = new Rect(0, curY, 16, LineHeight); var pingRect = new Rect(16, curY, 16, LineHeight); var arrowRect = new Rect(rect.width - 16 - 1, curY, 16, LineHeight); var itemRect = new Rect(16 + 16, curY, rect.width - 16 - 16, LineHeight); var addSeparator = i == (itemMaxIndex - 1); var isCreate = i == maxIndex; var isSelected = j == selectedIndex; // Handle event switch (evt.type) { case EventType.Repaint: bool hover = false; if (validItemIndex || isCreate) { if (m_HoverIndex == j) { if (fullRect.Contains(evt.mousePosition)) hover = true; else m_HoverIndex = -1; } var visibilityState = GetVisibilityState(j); var showIcon = visibilityState != VisibilityState.InvalidForVisible; var isItemVisible = visibilityState != VisibilityState.NotVisible || isCreate; if (showIcon && (hover || isSelected || !isItemVisible) && !isCreate) { var isVisHover = visRect.Contains(evt.mousePosition); var visIconState = isVisHover ? Styles.iconHovered : Styles.iconNormal; var visIcon = isItemVisible ? visIconState.visible : visIconState.hidden; GUI.Button(visRect, visIcon, Styles.sceneVisibilityStyle); } if (showIcon && (hover || isSelected) && !isCreate) { var isPingHover = pingRect.Contains(evt.mousePosition); var pingIconState = isPingHover ? Styles.iconHovered : Styles.iconNormal; GUI.Button(pingRect, pingIconState.ping, Styles.sceneVisibilityStyle); } using (new EditorGUI.DisabledScope(!isItemVisible)) { var width = (isCreate ? fullRect : itemRect).size.x; var name = m_ItemProvider.GetName(j); var tname = TextUtility.TruncateWithEllipsis(s_Styles.menuItem, name, width); s_Styles.menuItem.Draw(isCreate ? fullRect : itemRect, GUIContent.Temp(tname), hover, false, isSelected, false); } } if (isCreate) { GUI.Button(arrowRect, s_Styles.backIcon, Styles.sceneVisibilityStyle); } if (addSeparator) { const float margin = 4f; Rect seperatorRect = new Rect(fullRect.x + margin, fullRect.y + fullRect.height + SeperatorHeight * 0.5f, fullRect.width - 2 * margin, 1); DrawRect(seperatorRect, (EditorGUIUtility.isProSkin) ? new Color(0.32f, 0.32f, 0.32f, 1.333f) : new Color(0.6f, 0.6f, 0.6f, 1.333f)); // dark : light } break; case EventType.MouseDown: if (evt.button == 0 && visRect.Contains(evt.mousePosition) && j >= 0) { GUIUtility.hotControl = itemControlID; if (evt.clickCount == 1) { GUIUtility.hotControl = 0; ToggleVisibility(j, !evt.alt); evt.Use(); } } if (evt.button == 0 && pingRect.Contains(evt.mousePosition) && j >= 0) { GUIUtility.hotControl = itemControlID; if (evt.clickCount == 1) { GUIUtility.hotControl = 0; PingItem(j); evt.Use(); } } if (evt.button == 0 && itemRect.Contains(evt.mousePosition) && j >= 0 && (GetVisibilityState(j) != VisibilityState.NotVisible || (j == 0 && maxIndex == 0))) { GUIUtility.hotControl = itemControlID; if (evt.clickCount == 1) { GUIUtility.hotControl = 0; SelectItem(j); editorWindow.Close(); evt.Use(); } } break; case EventType.MouseUp: if (GUIUtility.hotControl == itemControlID) { GUIUtility.hotControl = 0; } break; case EventType.MouseMove: if (fullRect.Contains(evt.mousePosition) && j >= 0) { m_HoverIndex = j; HoverItem(itemRect, m_HoverIndex); } else if (m_HoverIndex == j) { m_HoverIndex = -1; } Repaint(); break; } if (validItemIndex) curY += LineHeight; if (addSeparator) curY += SeperatorHeight; } // end foreach item } GUI.EndScrollView(); } private void SelectItem(int index) { selectedIndex = index; if (m_ItemClickedCallback != null && index >= 0) m_ItemClickedCallback(index, m_ItemProvider.GetItem(index)); } private void HoverItem(Rect rect, int index) { if (m_ItemHoveredCallback != null && index >= 0) m_ItemHoveredCallback(index, rect); } private VisibilityState GetVisibilityState(int index) { var obj = m_ItemProvider.GetItem(index) as GameObject; if (obj != null) { if ((obj.hideFlags & HideFlags.HideInHierarchy) > 0) return VisibilityState.InvalidForVisible; return SceneVisibilityManager.instance.IsHidden(obj) ? VisibilityState.NotVisible : VisibilityState.IsVisible; } return VisibilityState.InvalidForVisible; } private void ToggleVisibility(int index, bool includeDescendants) { var obj = m_ItemProvider.GetItem(index) as GameObject; if (obj != null) SceneVisibilityManager.instance.ToggleVisibility(obj, includeDescendants); } private void PingItem(int index) { var obj = m_ItemProvider.GetItem(index) as UnityEngine.Object; if (obj != null) EditorGUIUtility.PingObject(obj); } protected Vector2 CalcSize() { var validSeperators = 0; foreach (var seperator in m_SeperatorIndices) { if (seperator >= 0) validSeperators++; } float height = (m_FilteredIndices.Count + 2) * LineHeight + validSeperators * SeperatorHeight; if (m_CachedWidth < 0) m_CachedWidth = Math.Max(m_ParentWidth, CalcWidth()); return new Vector2(m_CachedWidth, height); } private void ClearCachedWidth() { m_CachedWidth = -1f; } private float CalcWidth() { if (s_Styles == null) s_Styles = new Styles(); var count = m_ItemProvider.Count(); float maxWidth = 0; for (int i = 0; i < count - 1; ++i) { float w = s_Styles.menuItem.CalcSize(GUIContent.Temp(m_ItemProvider.GetName(i))).x; maxWidth = Mathf.Max(w, maxWidth); } if (count > 0) { float limitWidth = s_Styles.menuItem.CalcSize(GUIContent.Temp(m_ItemProvider.GetName(count - 1))).x; if (maxWidth > limitWidth) maxWidth = limitWidth; } const float rightMargin = 6f; return maxWidth + rightMargin; } private void DrawRect(Rect rect, Color color) { if (Event.current.type != EventType.Repaint) return; Color orgColor = GUI.color; GUI.color = GUI.color * color; GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture); GUI.color = orgColor; } private void Repaint() { HandleUtility.Repaint(); // repaints current guiview (needs rename) } } }