public static void Open([NotNull] IInspector inspector, [NotNull] List <PopupMenuItem> rootItems, [NotNull] Dictionary <string, PopupMenuItem> groupsByLabel, [NotNull] Dictionary <string, PopupMenuItem> itemsByLabel, [CanBeNull] List <PopupMenuItem> tickedItems, bool canTickMultipleItems, Rect clickedItemRect, [NotNull] Action <PopupMenuItem> onMenuItemClicked, [CanBeNull] Action onClosed, GUIContent label, [CanBeNull] IDrawer subject) { #if DEV_MODE && SAFE_MODE Debug.Assert(rootItems.TrueForAll(item => item.parent == null)); Debug.Assert(groupsByLabel.Select(pair => pair.Value).ToList().TrueForAll(item => item.IsGroup)); Debug.Assert(itemsByLabel.Select(pair => pair.Value).ToList().TrueForAll(item => !item.IsGroup)); Debug.Assert(itemsByLabel.Count > 0); Debug.Assert(groupsByLabel.Count > 0 || rootItems.Count == itemsByLabel.Count); #endif #if DEV_MODE || PROFILE_POWER_INSPECTOR Profiler.BeginSample("PopupMenuManager.Open"); #endif lastmenuOpenedForDrawer = subject; lastmenuOpenedForInspector = inspector; if (OnPopupMenuOpening != null) { OnPopupMenuOpening(rootItems, subject); } var unrollPos = clickedItemRect; // menu should open underneath the clicked item //unrollPos.y += clickedItemRect.height; // add inspector y-axis position to open position so that if this is a split view // the menu is opened with the correct offset unrollPos.y += inspector.State.WindowRect.y; #if DEV_MODE && DEBUG_OPEN Debug.Log(inspector + " WindowRect.y=" + inspector.State.WindowRect.y + ", subject=" + (subject == null?"null":subject.ToString()));; #endif var inspectorDrawer = inspector.InspectorDrawer; // when converting unroll position to screen space, // if menu is opening for Drawer inside the inspector viewport, // then we need to consider things like current scroll amounts // of the view if (subject != null) { // Not sure where why this offset is needed. Perhaps EditorWindows // introduce some padding to the position values? unrollPos.y += 8f; // if menu was opened outside the current view rect of the viewport // (e.g. Add Component menu was opened via a shortcut key) then // adjust the opening position so that it doesn't get opened outside // the bounds of the window (alt solution would be to always scroll // down to the button before opening the menu) if (inspector.IsOutsideViewport(unrollPos)) { #if DEV_MODE Debug.LogWarning(unrollPos + " Outside Viewport. Maybe you want to scroll to show the target (" + subject + ") before opening the menu?"); #endif //unrollPosScreenSpace.y = inspectorDrawer.position.y + inspectorDrawer.position.height - PopupMenu.TotalMaxHeightWithNavigationBar; unrollPos.y = inspectorDrawer.position.height - PopupMenu.TotalMaxHeightWithNavigationBar; } else { #if DEV_MODE Debug.Log("<color=green>" + unrollPos + " Not Outside Viewport. Adding inspectorDrawer.pos.y (" + inspectorDrawer.position.y + ") - scrollPos.y (" + inspector.State.ScrollPos.y + ") + 68f</color>"); #endif unrollPos.y += inspectorDrawer.position.y - inspector.State.ScrollPos.y + 15f; } var screenHeight = Screen.currentResolution.height; // If there's not enough screen estate to draw the menu below target position then draw it above. if (unrollPos.y + PopupMenu.TotalMaxHeightWithNavigationBar > screenHeight) { #if DEV_MODE Debug.Log("<color=red>Not enough space to draw below. screenHeight=" + screenHeight + ", unrollPos.y=" + unrollPos + ", TotalMaxHeightWithNavigationBar=" + PopupMenu.TotalMaxHeightWithNavigationBar + "</color>"); #endif unrollPos.y -= PopupMenu.TotalMaxHeightWithNavigationBar + clickedItemRect.height + 2f; } if (clickedItemRect.width < PopupMenu.MinWidth) { // if menu width is too small, roll it open towards the left // this is so that when controls are clicked, the menu is less likely to expand // outside the bounds of the inspector, which would look ugly and could result // int the menu clipping outside the screen bounds float width = PopupMenu.MinWidth; unrollPos.x = inspectorDrawer.position.x + clickedItemRect.xMax - width; unrollPos.width = width; } else { float width = Mathf.Round(clickedItemRect.width); unrollPos.x = inspectorDrawer.position.x + unrollPos.x; unrollPos.width = width; } } // if menu is being opened from outside the inspector viewport // then converting to screen space is as simple as adding // the screenspace positions of the inspector drawer to it else { //unrollPos.x += inspectorDrawer.position.x; // Not sure where why this offset is needed. Perhaps EditorWindows // introduce some padding to the position values? unrollPos.y += inspectorDrawer.position.y; // + 5f; unrollPos.width = Mathf.Max(unrollPos.width, PopupMenu.MinWidth); } #if !UNITY_2019_3_OR_NEWER // if the inspector window is docked, the positions need to be adjusted somewhat to be accurate var inspectorWindow = inspectorDrawer as UnityEditor.EditorWindow; if (inspectorWindow != null) { if (inspectorWindow.IsDocked()) { unrollPos.x += 2f; unrollPos.y -= 4f; } } #endif openMenu(inspector, rootItems, groupsByLabel, itemsByLabel, tickedItems, canTickMultipleItems, unrollPos, onMenuItemClicked, onClosed, label); #if DEV_MODE || PROFILE_POWER_INSPECTOR Profiler.EndSample(); #endif }
/// <summary> /// Setup the newly created instance. /// </summary> /// <param name="setInspector"> The inspector which contains the target. </param> /// <param name="target"> Target onto which components should be added Target onto which components should be added. </param> /// <param name="unrollPosition">The button position. </param> /// <param name="onClosed"> /// This action is invoked /// when the editor window is /// closed. </param> private void Setup(IInspector setInspector, IGameObjectDrawer target, Rect unrollPosition, Action onClosed) { inspector = setInspector; var inspectorDrawer = inspector.InspectorDrawer; this.onClosed = onClosed; inspector.InspectorDrawer.Manager.IgnoreAllMouseInputs = true; var unrollPosScreenSpace = unrollPosition; //menu should open underneath the button unrollPosScreenSpace.y += unrollPosition.height - 1f; // add inspector y-axis position to open position // so that if this is a split view, the menu is opened // with the correct offset unrollPosScreenSpace.y += inspector.State.WindowRect.y; //TO DO: if it's off-screen, scroll to it! //if it's hidden via filter field, maybe clear the filter? //or maybe never hide it via the filter field? //or maybe disable the keyboard shortcut if it's hidden //even trigger it via the addcomponentmenu item? if (setInspector.IsOutsideViewport(unrollPosScreenSpace)) { unrollPosScreenSpace.y = inspectorDrawer.position.y + inspectorDrawer.position.height - AddComponentMenuDrawer.TotalHeight; } else { unrollPosScreenSpace.y += inspectorDrawer.position.y - setInspector.State.ScrollPos.y + 20f; } var screenHeight = Screen.currentResolution.height; //if there's not enough screen estate to draw the menu below //the add component button then draw it above if (unrollPosScreenSpace.y + AddComponentMenuDrawer.TotalHeight > screenHeight) { unrollPosScreenSpace.y -= AddComponentMenuDrawer.TotalHeight + unrollPosition.height + 2f; } unrollPosScreenSpace.x = Mathf.CeilToInt(inspectorDrawer.position.x + setInspector.State.WindowRect.width * 0.5f - AddComponentMenuDrawer.Width * 0.5f); // if the inspector window is docked, the positions need to be adjusted somewhat to be accurate var inspectorWindow = setInspector.InspectorDrawer as EditorWindow; if (inspectorWindow != null) { if (inspectorWindow.IsDocked()) { unrollPosScreenSpace.x += 2f; unrollPosScreenSpace.y -= 4f; } } if (drawer == null) { drawer = AddComponentMenuDrawer.Create(inspector, target, unrollPosition, Close); } else { drawer.Setup(inspector, target, unrollPosition, Close); } ShowAsDropDown(unrollPosScreenSpace, new Vector2(AddComponentMenuDrawer.Width, AddComponentMenuDrawer.TotalHeight)); }