public static OdinMenuItem AddThumbnailIcon(this OdinMenuItem item, bool preferAssetPreviewAsIcon) { var unityObject = item.ObjectInstance as UnityEngine.Object; var assetPath = item.ObjectInstance as string; var type = item.ObjectInstance as Type; if (unityObject) { item.Icon = GUIHelper.GetAssetThumbnail(unityObject, item.GetType(), preferAssetPreviewAsIcon); } else if (item.ObjectInstance == null) { item.Icon = EditorIcons.UnityFolderIcon; } else if (type != null) { item.Icon = GUIHelper.GetAssetThumbnail(null, type, preferAssetPreviewAsIcon); } else if (assetPath != null) { if (assetPath != null) { item.Icon = InternalEditorUtility.GetIconForFile(assetPath); } } return(item); }
/// <summary> /// Initializes a new instance of the <see cref="OdinMenuTree"/> class. /// </summary> public OdinMenuTree(bool supportsMultiSelect, OdinMenuTreeDrawingConfig config) { this.Config = config; this.selection = new OdinMenuTreeSelection(supportsMultiSelect); this.root = new OdinMenuItem(this, "root", null); this.SetupAutoScroll(); }
/// <summary> /// Scrolls to the specified menu item. /// </summary> public void ScrollToMenuItem(OdinMenuItem menuItem, bool centerMenuItem = false) { if (menuItem != null) { var config = this.Config; var rect = menuItem.Rect; float a, b; if (centerMenuItem) { var r = this.outerScrollViewRect.AlignCenterY(rect.height); a = rect.yMin - (this.innerScrollViewYTop + config.ScrollPos.y - r.y); b = (rect.yMax - r.height + this.innerScrollViewYTop) - (config.ScrollPos.y + r.y); } else { a = rect.yMin - (this.innerScrollViewYTop + config.ScrollPos.y - this.outerScrollViewRect.y); b = (rect.yMax - this.outerScrollViewRect.height + this.innerScrollViewYTop) - (config.ScrollPos.y + this.outerScrollViewRect.y); } if (a < 0) { config.ScrollPos.y += a; } if (b > 0) { config.ScrollPos.y += b; } } }
/// <summary> /// Initializes a new instance of the <see cref="OdinMenuTree"/> class. /// </summary> /// <param name="supportsMultiSelect">if set to <c>true</c> [supports multi select].</param> /// <param name="defaultMenuStyle">The default menu item style.</param> public OdinMenuTree(bool supportsMultiSelect, OdinMenuStyle defaultMenuStyle) { this.DefaultMenuStyle = defaultMenuStyle; this.selection = new OdinMenuTreeSelection(supportsMultiSelect); this.root = new OdinMenuItem(this, "root", null); this.SetupAutoScroll(); }
private void DrawEnumInfo(OdinMenuItem obj) { var member = obj.Value as EnumMember; if (member == null) { return; } var hasMessage = !string.IsNullOrEmpty(member.message); if (member.isObsolete) { var rect = obj.Rect.Padding(5, 3).AlignRight(16).AlignCenterY(16); GUI.DrawTexture(rect, EditorIcons.TestInconclusive); } else if (hasMessage) { var rect = obj.Rect.Padding(5, 3).AlignRight(16).AlignCenterY(16); GUI.DrawTexture(rect, EditorIcons.ConsoleInfoIcon); } if (hasMessage) { GUI.Label(obj.Rect, new GUIContent("", member.message)); } }
private void DrawEnumItem(OdinMenuItem obj) { if (Event.current.type == EventType.MouseDown && obj.Rect.Contains(Event.current.mousePosition)) { obj.Select(); Event.current.Use(); wasMouseDown = true; } if (wasMouseDown) { GUIHelper.RequestRepaint(); } if (wasMouseDown == true && Event.current.type == EventType.MouseDrag && obj.Rect.Contains(Event.current.mousePosition)) { obj.Select(); } if (Event.current.type == EventType.MouseUp) { wasMouseDown = false; if (obj.IsSelected && obj.Rect.Contains(Event.current.mousePosition)) { obj.MenuTree.Selection.ConfirmSelection(); } } }
private void EnableSingleClickToSelect(OdinMenuItem obj) { var t = Event.current.type; if (t == EventType.Layout) { return; } if (obj.Rect.Contains(Event.current.mousePosition) == false) { return; } GUIHelper.RequestRepaint(); if (Event.current.type == EventType.MouseDrag) { if (obj is T) { if (this.IsValidSelection(Enumerable.Repeat((T)obj.Value, 1))) { obj.Select(); } } } bool confirm = t == EventType.MouseUp && (obj.ChildMenuItems.Count == 0); if (confirm) { obj.MenuTree.Selection.ConfirmSelection(); Event.current.Use(); } }
internal void UpdateMenuTreeRecursive(bool isRoot = false) { this.isInitialized = true; OdinMenuItem prev = null; foreach (var child in this.ChildMenuItems) { child.parentMenuItem = null; child.nextMenuItem = null; child.previousMenuItemFlat = null; child.nextMenuItemFlat = null; child.previousMenuItem = null; if (!isRoot) { child.parentMenuItem = this; } if (prev != null) { prev.nextMenuItem = child; child.previousMenuItem = prev; } prev = child; child.UpdateMenuTreeRecursive(); } }
/// <summary> /// Initializes a new instance of the <see cref="OdinMenuTree"/> class. /// </summary> /// <param name="supportsMultiSelect">if set to <c>true</c> [supports multi select].</param> /// <param name="defaultMenuStyle">The default menu item style.</param> public OdinMenuTree(bool supportsMultiSelect, OdinMenuStyle defaultMenuStyle) { this.DefaultMenuStyle = defaultMenuStyle; this.selection = new OdinMenuTreeSelection(supportsMultiSelect); this.root = new OdinMenuItem(this, "root", null); this.SetupAutoScroll(); this.searchFieldControlName = Guid.NewGuid().ToString(); }
private static object GetMenuItemEnumValue(OdinMenuItem item) { var memmebr = item.Value as EnumMember; if (memmebr != null && memmebr.value != null) { return(memmebr.value); } return(0); }
/// <summary> /// Not yet documented. /// </summary> /// <param name="toolbarStyle">Not yet documented.</param> public void DrawSearchToolbar(GUIStyle toolbarStyle = null) { var config = this.Config; var searchFieldRect = GUILayoutUtility.GetRect(0, config.SearchToolbarHeight, GUILayoutOptions.ExpandWidth(true)); if (Event.current.type == EventType.Repaint) { (toolbarStyle ?? SirenixGUIStyles.ToolbarBackground).Draw(searchFieldRect, GUIContent.none, 0); } searchFieldRect = searchFieldRect.HorizontalPadding(5).AlignMiddle(16); searchFieldRect.xMin += 3; searchFieldRect.y += 1; EditorGUI.BeginChangeCheck(); config.SearchTerm = this.DrawSearchField(searchFieldRect, config.SearchTerm, config.AutoFocusSearchBar); var changed = EditorGUI.EndChangeCheck(); if (changed && this.hasRepaintedCurrentSearchResult) { // We want fast visual search feedback. If the user is typing faster than the window can repaint, // then no results will be visible while he's typing. this.hasRepaintedCurrentSearchResult fixes that. this.hasRepaintedCurrentSearchResult = false; bool doSearch = !string.IsNullOrEmpty(config.SearchTerm); if (doSearch) { this.DrawInSearchMode = true; this.EnumerateTree().ForEach(x => x.IsVisible = config.SearchFunction(x)); } else { if (this.DrawInSearchMode) { this.DrawInSearchMode = false; var last = this.selection.LastOrDefault(); UnityEditorEventUtility.DelayAction(() => this.scrollToAndCenter = last); } this.EnumerateTree() .ForEach(x => x.IsVisible = true); this.Selection .SelectMany(x => x.GetParentMenuItemsRecursive(false)) .ForEach(x => x.Toggled = true); } } if (Event.current.type == EventType.Repaint) { this.hasRepaintedCurrentSearchResult = true; } }
/// <summary> /// Assigns the asset mini thumbnail as an icon to all menu items in the collection. If the menu items object is null then a Unity folder icon is assigned. /// </summary> public static OdinMenuItem AddThumbnailIcon(this OdinMenuItem item, bool preferAssetPreviewAsIcon) { var instance = item.Value; var unityObject = instance as UnityEngine.Object; if (unityObject) { if (preferAssetPreviewAsIcon) { item.IconGetter = () => GUIHelper.GetAssetThumbnail(unityObject, unityObject.GetType(), preferAssetPreviewAsIcon); } else { item.Icon = GUIHelper.GetAssetThumbnail(unityObject, unityObject.GetType(), preferAssetPreviewAsIcon); } return(item); } var type = instance as Type; if (type != null) { if (preferAssetPreviewAsIcon) { item.IconGetter = () => GUIHelper.GetAssetThumbnail(null, type, preferAssetPreviewAsIcon); } else { item.Icon = GUIHelper.GetAssetThumbnail(null, type, preferAssetPreviewAsIcon); } return(item); } var assetPath = instance as string; if (assetPath != null) { if (assetPath != null) { if (File.Exists(assetPath)) { item.Icon = InternalEditorUtility.GetIconForFile(assetPath); } else if (Directory.Exists(assetPath)) { item.Icon = EditorIcons.UnityFolderIcon; } } } return(item); }
void OnRightClickEvent(OdinMenuItem item) { if (MenuTree.Selection.Count == 0) { return; } List <string> paths = new List <string>(); for (int i = 0; i < MenuTree.Selection.Count; i++) { paths.Add(MenuTree.Selection[i].Value as string); } SVNTool.DrawGenericMenu(ref _popMenu, paths); _popMenu.DropDown(item.LabelRect); }
private static void AddRecursive(OdinMenuTree tree, List <OdinMenuItem> source, List <OdinMenuItem> destination) { destination.Capacity = source.Count; for (int i = 0; i < source.Count; i++) { var item = source[i]; var clone = new OdinMenuItem(tree, item.Name, item.ObjectInstance) .AddThumbnailIcon(false); destination.Add(clone); if (item.ChildMenuItems.Count > 0) { AddRecursive(tree, item.ChildMenuItems, clone.ChildMenuItems); } } }
private void ToggleEnumFlag(OdinMenuItem obj) { var val = (ulong)Convert.ToInt64(GetMenuItemEnumValue(obj)); if ((val & this.curentValue) == val) { this.curentValue = val == 0 ? 0 : (this.curentValue & ~val); } else { this.curentValue = this.curentValue | val; } if (Event.current.clickCount >= 2) { Event.current.Use(); } }
private void SetupAutoScroll() { this.selection.SelectionChanged += (x) => { if (this.Config.AutoScrollOnSelectionChanged) { if (this.isFirstFrame) { this.scrollToAndCenter = this.selection.LastOrDefault(); } else { this.requestRepaint = true; GUIHelper.RequestRepaint(); ScrollToMenuItem(this.selection.LastOrDefault()); } } }; }
/// <summary> /// Adds the specified object at the specified menu item path and returns all menu items created in order to add in order to end up at the specified menu path. /// </summary> /// <param name="tree">The tree.</param> /// <param name="menuPath">The menu path.</param> /// <param name="instance">The object instance.</param> /// <param name="forceShowOdinSerializedMembers">Set this to true if you want ODIN serialzied members such as dictionaries and generics to be shown as well.</param> /// <returns>Returns all menu items created in order to add the menu item at the specified menu item path.</returns> public static IEnumerable <OdinMenuItem> AddObjectAtPath(this OdinMenuTree tree, string menuPath, object instance, bool forceShowOdinSerializedMembers = false, MyEnumColor textColor = MyEnumColor.Blue) { string name; SplitMenuPath(menuPath, out menuPath, out name); if (forceShowOdinSerializedMembers && !(instance as UnityEngine.Object)) { OdinMenuItem item = new OdinMenuItem(tree, name, new SerializedValueWrapper(instance)); item.TextColor = textColor; return(tree.AddMenuItemAtPath(menuPath, item)); } else { OdinMenuItem item = new OdinMenuItem(tree, name, instance); item.TextColor = textColor; return(tree.AddMenuItemAtPath(menuPath, item)); } }
/// <summary> /// Adds the menu item at the specified menu item path and populates the result list with all menu items created in order to add the menuItem at the specified path. /// </summary> /// <param name="tree">The tree instance.</param> /// <param name="result">The result list.</param> /// <param name="path">The menu item path.</param> /// <param name="menuItem">The menu item.</param> public static void AddMenuItemAtPath(this OdinMenuTree tree, List <OdinMenuItem> result, string path, OdinMenuItem menuItem) { var curr = tree.Root; if (!string.IsNullOrEmpty(path)) { path = path.Trim('/') + "/"; var iFrom = 0; var iTo = 0; do { iTo = path.IndexOf('/', iFrom); var name = path.Substring(iFrom, iTo - iFrom); var child = curr.ChildMenuItems.FirstOrDefault(x => x.Name == name); if (child == null) { child = new OdinMenuItem(tree, name, null); curr.ChildMenuItems.Add(child); } result.Add(child); curr = child; iFrom = iTo + 1; } while (iTo != path.Length - 1); } var oldItem = curr.ChildMenuItems.FirstOrDefault(x => x.Name == menuItem.Name); if (oldItem != null) { curr.ChildMenuItems.Remove(oldItem); menuItem.ChildMenuItems.AddRange(oldItem.ChildMenuItems); } curr.ChildMenuItems.Add(menuItem); result.Add(menuItem); }
internal void UpdateFlatMenuItemNavigation() { int i = 0; OdinMenuItem prev = null; IEnumerable <OdinMenuItem> query = this.menuTree.DrawInSearchMode ? this.menuTree.FlatMenuTree : this.menuTree.EnumerateTree(); foreach (var item in query) { item.flatTreeIndex = i++; item.nextMenuItemFlat = null; item.previousMenuItemFlat = null; if (prev != null) { item.previousMenuItemFlat = prev; prev.nextMenuItemFlat = item; } prev = item; } }
internal void UpdateMenuTreeRecursive(bool isRoot = false) { this.isInitialized = true; OdinMenuItem prev = null; foreach (var child in this.ChildMenuItems) { if (!isRoot) { child.parentMenuItem = this; } if (prev != null) { prev.nextMenuItem = child; child.previousMenuItem = prev; } prev = child; child.UpdateMenuTreeRecursive(); } if (isRoot) { int i = 0; prev = null; foreach (var item in this.menuTree.EnumerateTree()) { item.flatTreeIndex = i++; if (prev != null) { item.previousMenuItemFlat = prev; prev.nextMenuItemFlat = item; } prev = item; } } }
/// <summary> /// Adds the menu item at specified menu item path, and returns all menu items created in order to add the menuItem at the specified path. /// </summary> /// <param name="tree">The tree.</param> /// <param name="path">The menu item path.</param> /// <param name="menuItem">The menu item.</param> /// <returns>Returns all menu items created in order to add the menu item at the specified menu item path.</returns> public static IEnumerable <OdinMenuItem> AddMenuItemAtPath(this OdinMenuTree tree, string path, OdinMenuItem menuItem) { cache.Clear(); AddMenuItemAtPath(tree, cache, path, menuItem); return(cache); }
private void DrawEnumFlagItem(OdinMenuItem obj) { if ((Event.current.type == EventType.MouseDown || Event.current.type == EventType.MouseUp) && obj.Rect.Contains(Event.current.mousePosition)) { if (Event.current.type == EventType.MouseDown) { ToggleEnumFlag(obj); this.TriggerSelectionChanged(); } Event.current.Use(); } if (Event.current.type == EventType.Repaint) { var val = (ulong)Convert.ToInt64(GetMenuItemEnumValue(obj)); var isPowerOfTwo = (val & (val - 1)) == 0; if (val != 0 && !isPowerOfTwo) { var isMouseOver = obj.Rect.Contains(Event.current.mousePosition); if (isMouseOver) { curentMouseOverValue = val; } else if (val == curentMouseOverValue) { curentMouseOverValue = 0; } } var chked = (val & this.curentValue) == val && !((val == 0 && this.curentValue != 0)); var highlight = val != 0 && isPowerOfTwo && (val & this.curentMouseOverValue) == val && !((val == 0 && this.curentMouseOverValue != 0)); if (highlight) { EditorGUI.DrawRect(obj.Rect.AlignLeft(6).Padding(2), highlightLineColor); } if (chked || isPowerOfTwo) { var rect = obj.Rect.AlignLeft(30).AlignCenter(EditorIcons.TestPassed.width, EditorIcons.TestPassed.height); if (chked) { if (isPowerOfTwo) { if (!EditorGUIUtility.isProSkin) { var tmp = GUI.color; GUI.color = new Color(1, 0.7f, 1, 1); GUI.DrawTexture(rect, EditorIcons.TestPassed); GUI.color = tmp; } else { GUI.DrawTexture(rect, EditorIcons.TestPassed); } } else { EditorGUI.DrawRect(obj.Rect.AlignTop(obj.Rect.height - (EditorGUIUtility.isProSkin ? 1 : 0)), selectedMaskBgColor); } } else { GUI.DrawTexture(rect, EditorIcons.TestNormal); } } } }
/// <summary> /// Handles the keybaord menu navigation. Call this at the end of your GUI scope, to prevent the menu tree from stealing input events from other text fields. /// </summary> /// <returns>Returns true, if anything was changed via the keyboard.</returns> public bool HandleKeybaordMenuNavigation() { if (Event.current.type != EventType.KeyDown) { return(false); } if (OdinMenuTree.ActiveMenuTree != this) { return(false); } GUIHelper.RequestRepaint(); var keycode = Event.current.keyCode; // Select first or last if no visisble items is slected. if (this.Selection.Count == 0 || !this.Selection.Any(x => x._IsVisible())) { var query = this.DrawInSearchMode ? this.FlatMenuTree : this.EnumerateTree().Where(x => x._IsVisible()); OdinMenuItem next = null; if (keycode == KeyCode.DownArrow) { next = query.FirstOrDefault(); } else if (keycode == KeyCode.UpArrow) { next = query.LastOrDefault(); } else if (keycode == KeyCode.LeftAlt) { next = query.FirstOrDefault(); } else if (keycode == KeyCode.RightAlt) { next = query.FirstOrDefault(); } if (next != null) { next.Select(); Event.current.Use(); return(true); } } else { if (keycode == KeyCode.LeftArrow && !this.DrawInSearchMode) { bool goUp = true; foreach (var curr in this.Selection.ToList()) { if (curr.Toggled == true && curr.ChildMenuItems.Any()) { goUp = false; curr.Toggled = false; } if ((Event.current.modifiers & EventModifiers.Alt) != 0) { goUp = false; foreach (var item in curr.GetChildMenuItemsRecursive(false)) { item.Toggled = curr.Toggled; } } } if (goUp) { keycode = KeyCode.UpArrow; } Event.current.Use(); } if (keycode == KeyCode.RightArrow && !this.DrawInSearchMode) { bool goDown = true; foreach (var curr in this.Selection.ToList()) { if (curr.Toggled == false && curr.ChildMenuItems.Any()) { curr.Toggled = true; goDown = false; } if ((Event.current.modifiers & EventModifiers.Alt) != 0) { goDown = false; foreach (var item in curr.GetChildMenuItemsRecursive(false)) { item.Toggled = curr.Toggled; } } } if (goDown) { keycode = KeyCode.DownArrow; } Event.current.Use(); } if (keycode == KeyCode.UpArrow) { if ((Event.current.modifiers & EventModifiers.Shift) != 0) { var last = this.Selection.Last(); var prev = last.PrevVisualMenuItem; if (prev != null) { if (prev.IsSelected) { last.Deselect(); } else { prev.Select(true); } Event.current.Use(); return(true); } } else { var prev = this.Selection.Last().PrevVisualMenuItem; if (prev != null) { prev.Select(); Event.current.Use(); return(true); } } } if (keycode == KeyCode.DownArrow) { if ((Event.current.modifiers & EventModifiers.Shift) != 0) { var last = this.Selection.Last(); var next = last.NextVisualMenuItem; if (next != null) { if (next.IsSelected) { last.Deselect(); } else { next.Select(true); } Event.current.Use(); return(true); } } else { var next = this.Selection.Last().NextVisualMenuItem; if (next != null) { next.Select(); Event.current.Use(); return(true); } } } if (keycode == KeyCode.Return) { this.Selection.ConfirmSelection(); Event.current.Use(); return(true); } } return(false); }
/// <summary> /// Scrolls to the specified menu item. /// </summary> public void ScrollToMenuItem(OdinMenuItem menuItem, bool centerMenuItem = false) { if (menuItem != null) { this.scollToCenter = centerMenuItem; this.scrollToWhenReady = menuItem; if (!menuItem._IsVisible()) { foreach (var item in menuItem.GetParentMenuItemsRecursive(false)) { item.Toggled = true; } return; } foreach (var item in menuItem.GetParentMenuItemsRecursive(false)) { item.Toggled = true; } if (this.outerScrollViewRect.height == 0 || menuItem.Rect.height <= 0.01f) { return; } if (Event.current.type == EventType.Layout) { return; } var config = this.Config; var rect = menuItem.Rect; float a, b; if (centerMenuItem) { var r = this.outerScrollViewRect.AlignCenterY(rect.height); a = rect.yMin - (this.innerScrollViewRect.y + config.ScrollPos.y - r.y); b = (rect.yMax - r.height + this.innerScrollViewRect.y) - (config.ScrollPos.y + r.y); } else { var viewRect = this.outerScrollViewRect; viewRect.y = 0; a = rect.yMin - (this.innerScrollViewRect.y + config.ScrollPos.y) - 1; b = (rect.yMax - this.outerScrollViewRect.height + this.innerScrollViewRect.y) - config.ScrollPos.y; a -= rect.height; b += rect.height; } if (a < 0) { config.ScrollPos.y += a; } if (b > 0) { config.ScrollPos.y += b; } // Some windows takes a while to adjust themselves, where the inner and outer range are subject to change. if (this.frameCounter.FrameCount > 6) { this.scrollToWhenReady = null; } else { GUIHelper.RequestRepaint(); } } }
/// <summary> /// Handles the mouse events. /// </summary> /// <param name="rect">The rect.</param> /// <param name="triangleRect">The triangle rect.</param> protected void HandleMouseEvents(Rect rect, Rect triangleRect) { var e = Event.current.type; if (e == EventType.Used && this.wasMouseDownEvent) { this.wasMouseDownEvent = false; handleClickEventOnMouseUp = this; } bool isMouseClick = (e == EventType.MouseDown) || (e == EventType.MouseUp && handleClickEventOnMouseUp == this); if (isMouseClick) { handleClickEventOnMouseUp = null; this.wasMouseDownEvent = false; if (!rect.Contains(Event.current.mousePosition)) { return; } var hasChildren = this.ChildMenuItems.Any(); var instance = this.ObjectInstance; var selected = this.IsSelected; var selectable = !triangleRect.Contains(Event.current.mousePosition); bool isUnityObjectInstance = instance as UnityEngine.Object; if (selected && isUnityObjectInstance) { var unityObject = instance as UnityEngine.Object; var behaviour = unityObject as Behaviour; if (behaviour) { unityObject = behaviour.gameObject; } EditorGUIUtility.PingObject(unityObject); } if (Event.current.button == 1) { if (this.OnRightClick != null) { this.OnRightClick(this); } } if (Event.current.button == 0) { if (hasChildren && selected && Event.current.modifiers == EventModifiers.None || !selectable) { this.Toggled = !this.Toggled; } if (!selectable && Event.current.modifiers == EventModifiers.Alt) { selectable = true; } if (selectable) { if (Event.current.modifiers == EventModifiers.Alt) { foreach (var item in this.GetChildMenuItemsRecursive(false)) { item.Toggled = this.Toggled; } } bool shiftSelect = this.menuTree.Selection.SupportsMultiSelect && Event.current.modifiers == EventModifiers.Shift && this.menuTree.Selection.Count > 0; if (shiftSelect) { var curr = this.menuTree.Selection.First(); var maxIterations = Mathf.Abs(curr.FlatTreeIndex - this.FlatTreeIndex) + 1; var down = curr.FlatTreeIndex < this.FlatTreeIndex; this.menuTree.Selection.Clear(); for (int i = 0; i < maxIterations; i++) { if (curr == null) { break; } curr.Select(true); if (curr == this) { break; } curr = down ? curr.NextVisualMenuItem : curr.PrevVisualMenuItem; } } else { var ctrl = Event.current.modifiers == EventModifiers.Control; if (ctrl && selected) { this.Deselect(); } else { this.Select(ctrl); } if (Event.current.clickCount == 2) { this.MenuTree.Selection.ConfirmSelection(); } } } } GUIHelper.RemoveFocusControl(); Event.current.Use(); } }
/// <summary> /// Draws the menu tree recursively. /// </summary> public void DrawMenuTree() { var config = this.Config; if (this.requestRepaint) { GUIHelper.RequestRepaint(); this.requestRepaint = false; } if (config.DrawSearchToolbar) { DrawSearchToolbar(); } if (config.DrawScrollView) { var r = EditorGUILayout.BeginVertical(); if (Event.current.type == EventType.Repaint) { this.outerScrollViewRect = r; } // GUIScrollViews doesn't play well with ExpandHeight(false). The scroll wheel will flicker when it expanding or contracting in size. // But for popup windows and other stuff, it is important that the ExpandHeight is false during the first couple of frames // so we can calculate a good height for the window. if (frameCounter.Update().FrameCount < 4) { config.ScrollPos = EditorGUILayout.BeginScrollView(config.ScrollPos, GUILayoutOptions.ExpandHeight(false)); } else { config.ScrollPos = EditorGUILayout.BeginScrollView(config.ScrollPos); } if (Event.current.type == EventType.Repaint) { this.innerScrollViewYTop = GUIHelper.GetCurrentLayoutRect().y; } GUILayout.Space(-1); } foreach (var item in this.MenuItems) { item.DrawMenuItems(0); } if (config.DrawScrollView) { EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); if (!this.isFirstFrame) { if (this.scrollToAndCenter != null && Event.current.type == EventType.Layout) { this.ScrollToMenuItem(this.Selection.LastOrDefault(), true); this.scrollToAndCenter = null; } if (this.updateScrollView) { this.ScrollToMenuItem(this.Selection.LastOrDefault()); this.updateScrollView = false; } } } if (config.AutoHandleKeyboardNavigation) { this.HandleKeybaordMenuNavigation(); } if (Event.current.type == EventType.Repaint) { this.isFirstFrame = false; } }
/// <summary> /// Adds the menu item at specified menu item path, and returns all menu items created in order to add the menuItem at the specified path. /// </summary> /// <param name="tree">The tree.</param> /// <param name="path">The menu item path.</param> /// <param name="menuItem">The menu item.</param> /// <returns>Returns all menu items created in order to add the menu item at the specified menu item path.</returns> public static IEnumerable <OdinMenuItem> AddMenuItemAtPath(this OdinMenuTree tree, string path, OdinMenuItem menuItem) { List <OdinMenuItem> result = new List <OdinMenuItem>(5); AddMenuItemAtPath(tree, result, path, menuItem); return(result); }
/// <summary> /// Adds the menu item at the specified menu item path and populates the result list with all menu items created in order to add the menuItem at the specified path. /// </summary> /// <param name="tree">The tree instance.</param> /// <param name="result">The result list.</param> /// <param name="path">The menu item path.</param> /// <param name="menuItem">The menu item.</param> public static void AddMenuItemAtPath(this OdinMenuTree tree, ICollection <OdinMenuItem> result, string path, OdinMenuItem menuItem) { var curr = tree.Root; if (!string.IsNullOrEmpty(path)) { if (path[0] == '/' || path[path.Length - 1] == '/') { path = path.Trim(); } var iFrom = 0; var iTo = 0; do { iTo = path.IndexOf('/', iFrom); string name; if (iTo < 0) { iTo = path.Length - 1; name = path.Substring(iFrom, iTo - iFrom + 1); } else { name = path.Substring(iFrom, iTo - iFrom); } var childs = curr.ChildMenuItems; // OdinMenuItem child = curr.ChildMenuItems.FirstOrDefault(x => x.Name == name); // If we assume people add menu items in a local order, then starting our search from then end of the list, should be faster. OdinMenuItem child = null; for (int i = childs.Count - 1; i >= 0; i--) { if (childs[i].Name == name) { child = childs[i]; break; } } if (child == null) { child = new OdinMenuItem(tree, name, null); curr.ChildMenuItems.Add(child); } result.Add(child); curr = child; iFrom = iTo + 1; } while (iTo != path.Length - 1); } // var oldItem = curr.ChildMenuItems.FirstOrDefault(x => x.Name == menuItem.Name); // If we assume people add menu items in a local order, then starting our search from then end of the list, should be faster. var currChilds = curr.ChildMenuItems; OdinMenuItem oldItem = null; for (int i = currChilds.Count - 1; i >= 0; i--) { if (currChilds[i].Name == menuItem.Name) { oldItem = currChilds[i]; break; } } if (oldItem != null) { curr.ChildMenuItems.Remove(oldItem); menuItem.ChildMenuItems.AddRange(oldItem.ChildMenuItems); } curr.ChildMenuItems.Add(menuItem); result.Add(menuItem); }
private void DrawCheckboxMenuItems(OdinMenuItem xx) { var allChilds = xx.GetChildMenuItemsRecursive(true) .Select(x => x.Value) .OfType <T>().ToList(); bool isEmpty = allChilds.Count == 0; bool isSelected = false; bool isMixed = false; int prevUpdateId = -1; Action validate = () => { if (isEmpty) { return; } isSelected = this.selection.Contains(allChilds[0]); //var a = this.selection.ToList(); var b = allChilds[0]; isMixed = false; for (int i = 1; i < allChilds.Count; i++) { var sel = this.selection.Contains(allChilds[i]); if (sel != isSelected) { isMixed = true; break; } } }; xx.OnDrawItem += (menuItem) => { if (isEmpty) { return; } var checkboxRect = xx.LabelRect; checkboxRect = checkboxRect.AlignMiddle(18).AlignLeft(16); checkboxRect.x -= 16; if (xx.IconGetter()) { checkboxRect.x -= 16; } if (Event.current.type != EventType.Repaint && xx.ChildMenuItems.Count == 0) { checkboxRect = xx.Rect; } //else if (menuItem.ChildMenuItems.Count > 0) //{ // rect = xx.Rect; //} if (prevUpdateId != this.checkboxUpdateId) { validate(); prevUpdateId = this.checkboxUpdateId; } EditorGUI.showMixedValue = isMixed; EditorGUI.BeginChangeCheck(); var newSelected = EditorGUI.Toggle(checkboxRect, isSelected); if (EditorGUI.EndChangeCheck()) { for (int i = 0; i < allChilds.Count; i++) { if (newSelected) { this.selection.Add(allChilds[i]); } else { this.selection.Remove(allChilds[i]); } } xx.Select(); validate(); this.requestCheckboxUpdate = true; GUIHelper.RemoveFocusControl(); } EditorGUI.showMixedValue = false; }; }
/// <summary> /// Handles the keybaord menu navigation. Call this at the end of your GUI scope, to prevent the menu tree from stealing input events from text fields and such. /// </summary> /// <returns>Returns true, if anything was changed via the keyboard.</returns> public bool HandleKeybaordMenuNavigation() { if (Event.current.type != EventType.KeyDown) { return(false); } GUIHelper.RequestRepaint(); if (this.Selection.Count == 0) { OdinMenuItem next = null; if (Event.current.keyCode == KeyCode.DownArrow) { next = this.MenuItems.FirstOrDefault(); } else if (Event.current.keyCode == KeyCode.UpArrow) { next = this.MenuItems.LastOrDefault(); } else if (Event.current.keyCode == KeyCode.LeftAlt) { next = this.MenuItems.FirstOrDefault(); } else if (Event.current.keyCode == KeyCode.RightAlt) { next = this.MenuItems.FirstOrDefault(); } if (next != null) { Event.current.Use(); next.Select(); return(true); } } else { if (Event.current.keyCode == KeyCode.RightArrow) { foreach (var curr in this.Selection.ToList()) { curr.Toggled = true; if ((Event.current.modifiers & EventModifiers.Alt) != 0) { foreach (var item in curr.GetChildMenuItemsRecursive(false)) { item.Toggled = curr.Toggled; } } } Event.current.Use(); } else if (Event.current.keyCode == KeyCode.LeftArrow) { foreach (var curr in this.Selection.ToList()) { if (curr.Parent != null && (!curr.ChildMenuItems.Any() || curr.Parent.Toggled == false)) { curr.Parent.Select(); return(true); } else { curr.Toggled = false; if ((Event.current.modifiers & EventModifiers.Alt) != 0) { foreach (var item in curr.GetChildMenuItemsRecursive(false)) { item.Toggled = curr.Toggled; } } } } Event.current.Use(); } else if (Event.current.keyCode == KeyCode.UpArrow) { if ((Event.current.modifiers & EventModifiers.Shift) != 0) { var last = this.Selection.Last(); var prev = last.PrevVisualMenuItem; if (prev != null) { if (prev.IsSelected) { last.Deselect(); return(true); } else { prev.Select(true); return(true); } } } else { var prev = this.Selection.Last().PrevVisualMenuItem; if (prev != null) { prev.Select(); return(true); } } Event.current.Use(); } else if (Event.current.keyCode == KeyCode.DownArrow) { if ((Event.current.modifiers & EventModifiers.Shift) != 0) { var last = this.Selection.Last(); var next = last.NextVisualMenuItem; if (next != null) { if (next.IsSelected) { last.Deselect(); return(true); } else { next.Select(true); return(true); } } } else { var next = this.Selection.Last().NextVisualMenuItem; if (next != null) { next.Select(); return(true); } } Event.current.Use(); } } return(false); }