From 85df496657fde22462a32f36a9267cabc755e338 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 20 Jan 2023 00:51:46 -0600 Subject: [PATCH 1/6] Better tag picker --- .../Editor/CustomEditors/Editors/TagEditor.cs | 366 +++++++++++++----- 1 file changed, 271 insertions(+), 95 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index b95f9e722..3f425d845 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -156,88 +156,119 @@ namespace FlaxEditor.CustomEditors.Editors pickerData.IsEditing = false; } - - private static void OnAddButtonClicked(Tree tree, TreeNode parentNode, PickerData pickerData) + private static void OnAddTagButtonClicked(string input, Tree tree, TextBox textBox, DropPanel dropPanel, PickerData pickerData) { - // Create new node - var nodeIndent = 16.0f; - var indentation = 0; - var parentTag = string.Empty; - if (parentNode.CustomArrowRect.HasValue) - { - indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1; - var parentTagValue = (Tag)parentNode.Tag; - parentTag = parentTagValue.ToString(); - } - var node = new TreeNodeWithAddons - { - ChildrenIndent = nodeIndent, - CullChildren = false, - ClipChildren = false, - TextMargin = new Margin(22.0f, 2.0f, 2.0f, 2.0f), - CustomArrowRect = new Rectangle(18 + indentation * nodeIndent, 2, 12, 12), - BackgroundColorSelected = Color.Transparent, - BackgroundColorHighlighted = parentNode.BackgroundColorHighlighted, - }; - var checkbox = new CheckBox(32.0f + indentation * nodeIndent, 0) - { - Height = 16.0f, - IsScrollable = false, - Parent = node, - }; - node.Addons.Add(checkbox); - checkbox.StateChanged += c => OnCheckboxEdited(node, c, pickerData); - var addButton = new Button(tree.Width - 16, 0, 14, 14) - { - Text = "+", - TooltipText = "Add subtag within this tag namespace", - IsScrollable = false, - BorderColor = Color.Transparent, - BackgroundColor = Color.Transparent, - Parent = node, - AnchorPreset = AnchorPresets.TopRight, - }; - node.Addons.Add(addButton); - addButton.ButtonClicked += button => OnAddButtonClicked(tree, node, pickerData); + if (string.IsNullOrEmpty(input)) + return; + + // Ensure that name is unique + if (Tags.List.Contains(input)) + return; - // Link - node.Parent = parentNode; - node.IndexInParent = 0; - parentNode.Expand(true); - ((Panel)tree.Parent.Parent).ScrollViewTo(node); + var subInputs = input.Split('.'); + string tagString = string.Empty; + string lastTagString = string.Empty; - // Start renaming the tag - var prefix = parentTag.Length != 0 ? parentTag + '.' : string.Empty; - var renameArea = node.HeaderRect; - if (renameArea.Location.X < nodeIndent) + TreeNode parentNode = tree.GetChild(); // Start at root + int parentCount = 0; + for (int i = 0; i < subInputs.Length; i++) { - // Fix root node's child renaming - renameArea.Location.X += nodeIndent; - renameArea.Size.X -= nodeIndent; - } - var dialog = RenamePopup.Show(node, renameArea, prefix, false); - var cursor = dialog.InputField.TextLength; - dialog.InputField.SelectionRange = new TextRange(cursor, cursor); - dialog.Validate = (popup, value) => - { - // Ensure that name is unique - if (Tags.List.Contains(value)) - return false; - - // Ensure user entered direct subtag of the parent node - if (value.StartsWith(popup.InitialValue)) + if (string.IsNullOrEmpty(subInputs[i])) { - var name = value.Substring(popup.InitialValue.Length); - if (name.Length > 0 && name.IndexOf('.') == -1) - return true; + lastTagString = tagString; + tagString = string.Empty; + continue; } - return false; - }; - dialog.Renamed += popup => - { + + // Check all entered subtags and create any that dont exist + for (int j = 0; j <= i; j++) + { + if (j == 0) + { + tagString += subInputs[j]; + } + else + { + tagString += "." + subInputs[j]; + } + } + + if (string.IsNullOrEmpty(tagString)) + { + tagString = string.Empty; + continue; + } + + if (Tags.List.Contains(tagString)) + { + // Find next parent node + foreach (var child in parentNode.Children) + { + if (!(child is TreeNode childNode)) + continue; + + var tagValue = (Tag)childNode.Tag; + if (!string.Equals(tagValue.ToString(), tagString)) + continue; + + parentNode = childNode; + parentCount += 1; + } + lastTagString = tagString; + tagString = string.Empty; + continue; + } + + // Create new node + var nodeIndent = 16.0f; + var indentation = 0; + var parentTag = lastTagString; + if (parentNode.CustomArrowRect.HasValue) + { + indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1; + var parentTagValue = (Tag)parentNode.Tag; + parentTag = parentTagValue.ToString(); + } + var node = new TreeNodeWithAddons + { + ChildrenIndent = nodeIndent, + CullChildren = false, + ClipChildren = false, + TextMargin = new Margin(22.0f, 2.0f, 2.0f, 2.0f), + CustomArrowRect = new Rectangle(18 + indentation * nodeIndent, 2, 12, 12), + BackgroundColorSelected = Color.Transparent, + BackgroundColorHighlighted = parentNode.BackgroundColorHighlighted, + }; + var checkbox = new CheckBox(32.0f + indentation * nodeIndent, 0) + { + Height = 16.0f, + IsScrollable = false, + Parent = node, + }; + node.Addons.Add(checkbox); + checkbox.StateChanged += c => OnCheckboxEdited(node, c, pickerData); + var addButton = new Button(tree.Width - 16, 0, 14, 14) + { + Text = "+", + TooltipText = "Add subtag within this tag namespace", + IsScrollable = false, + BorderColor = Color.Transparent, + BackgroundColor = Color.Transparent, + Parent = node, + AnchorPreset = AnchorPresets.TopRight, + }; + node.Addons.Add(addButton); + addButton.ButtonClicked += button => OnAddSubTagButtonClicked(((Tag)node.Tag).ToString(), textBox, dropPanel); + + // Link + node.Parent = parentNode; + node.IndexInParent = 0; + parentNode.Expand(true); + ((Panel)tree.Parent.Parent).ScrollViewTo(node); + // Get tag name - var tagName = popup.Text; - var tagShortName = tagName.Substring(popup.InitialValue.Length); + var tagName = tagString; + var tagShortName = tagName.Substring(parentCount == 0 ? lastTagString.Length : lastTagString.Length + 1); if (tagShortName.Length == 0) return; @@ -261,19 +292,129 @@ namespace FlaxEditor.CustomEditors.Editors // Reload editor window to reflect new tag assetWindow?.RefreshAsset(); } - }; - dialog.Closed += popup => + + textBox.Text = string.Empty; + lastTagString = tagString; + tagString = string.Empty; + parentNode = tree.GetChild(); // return to root + parentCount = 0; + } + } + + private static void OnAddSubTagButtonClicked(string parentTag, TextBox textBox, DropPanel dropPanel) + { + if (textBox == null || dropPanel == null || string.IsNullOrEmpty(parentTag)) { - // Remove temporary node if renaming was canceled - if (popup.InitialValue == popup.Text || popup.Text.Length == 0) - node.Dispose(); - }; + return; + } + dropPanel.Open(); + textBox.Text = string.Empty; + textBox.Text += parentTag + "."; + textBox.Focus(); + textBox.SelectionRange = new TextRange(textBox.Text.Length, textBox.Text.Length); } internal static ContextMenuBase CreatePicker(Tag value, Tag[] values, PickerData pickerData) { // Initialize search popup - var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 20.0f); + var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 40.0f); + + // Add tag drop panel + var addTagDropPanel = new DropPanel + { + HeaderText = "Add Tag", + EnableDropDownIcon = true, + ArrowImageOpened = new SpriteBrush(FlaxEngine.GUI.Style.Current.ArrowDown), + ArrowImageClosed = new SpriteBrush(FlaxEngine.GUI.Style.Current.ArrowRight), + Parent = menu, + HeaderTextMargin = new Margin(2.0f), + AnchorPreset = AnchorPresets.TopLeft, + Bounds = new Rectangle(2, 2, menu.Width - 4, 30), + IsClosed = true, + ItemsMargin = new Margin(2.0f), + CloseAnimationTime = 0, + }; + var tagNamePanel = new HorizontalPanel + { + Parent = addTagDropPanel, + AutoSize = false, + Bounds = new Rectangle(0, 0, addTagDropPanel.Width, 20.0f), + }; + var nameLabel = new Label + { + Size = new Float2(addTagDropPanel.Width / 4, 0), + Parent = tagNamePanel, + Text = "Tag Name:", + }; + var nameTextBox = new TextBox + { + IsMultiline = false, + WatermarkText = "X.Y.Z", + Size = new Float2(addTagDropPanel.Width * 0.7f, 0), + Parent = tagNamePanel, + }; + + bool uniqueText = true; + nameTextBox.TextChanged += () => + { + // Ensure that name is unique + if (Tags.List.Contains(nameTextBox.Text)) + uniqueText = false; + else + uniqueText = true; + + var currentStyle = FlaxEngine.GUI.Style.Current; + if (uniqueText) + { + nameTextBox.BorderColor = Color.Transparent; + nameTextBox.BorderSelectedColor = currentStyle.BackgroundSelected; + } + else + { + var color = new Color(1.0f, 0.0f, 0.02745f, 1.0f); + nameTextBox.BorderColor = Color.Lerp(color, currentStyle.TextBoxBackground, 0.6f); + nameTextBox.BorderSelectedColor = color; + } + }; + + nameTextBox.EditEnd += () => + { + if (!uniqueText) + return; + + if (!nameTextBox.IsFocused) + { + nameTextBox.BorderColor = Color.Transparent; + nameTextBox.BorderSelectedColor = FlaxEngine.GUI.Style.Current.BackgroundSelected; + return; + } + + OnAddTagButtonClicked(nameTextBox.Text, tree, nameTextBox, addTagDropPanel, pickerData); + }; + + var addButtonPanel = new HorizontalPanel + { + Parent = addTagDropPanel, + AutoSize = false, + Bounds = new Rectangle(0, 0, addTagDropPanel.Width, 30.0f), + }; + var buttonAddTag = new Button + { + Parent = addButtonPanel, + Size = new Float2(100, 20), + Text = "Add Tag", + AnchorPreset = AnchorPresets.MiddleCenter, + }; + buttonAddTag.Clicked += () => + { + if (!uniqueText) + return; + + OnAddTagButtonClicked(nameTextBox.Text, tree, nameTextBox, addTagDropPanel, pickerData); + }; + + // Used for how far everything should drop when the drop down panel is opened + var dropPanelOpenHeight = tagNamePanel.Height + addButtonPanel.Height + 4; // Create tree with tags hierarchy tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node @@ -339,7 +480,7 @@ namespace FlaxEditor.CustomEditors.Editors AnchorPreset = AnchorPresets.TopRight, }; node.Addons.Add(addButton); - addButton.ButtonClicked += button => OnAddButtonClicked(tree, node, pickerData); + addButton.ButtonClicked += button => OnAddSubTagButtonClicked(((Tag)node.Tag).ToString(), nameTextBox, addTagDropPanel); // Link to parent { @@ -360,10 +501,13 @@ namespace FlaxEditor.CustomEditors.Editors { Margin = new Margin(1.0f), AutoSize = false, - Bounds = new Rectangle(0, 0, menu.Width, 20.0f), + Bounds = new Rectangle(0, 0, menu.Width - 4, 20.0f), Parent = menu, }; - var buttonsSize = new Float2((menu.Width - buttonsPanel.Margin.Width) / 4.0f - buttonsPanel.Spacing, 18.0f); + buttonsPanel.Y += addTagDropPanel.HeaderHeight + 4; + buttonsPanel.X += 2; + + var buttonsSize = new Float2((menu.Width - 2 - buttonsPanel.Margin.Width) / 3.0f - buttonsPanel.Spacing, 18.0f); var buttonExpandAll = new Button { Size = buttonsSize, @@ -382,20 +526,13 @@ namespace FlaxEditor.CustomEditors.Editors root.CollapseAll(true); root.Expand(true); }; - var buttonAddTag = new Button + var buttonClearAll = new Button { Size = buttonsSize, Parent = buttonsPanel, - Text = "Add Tag", + Text = "Clear All", }; - buttonAddTag.Clicked += () => OnAddButtonClicked(tree, root, pickerData); - var buttonReset = new Button - { - Size = buttonsSize, - Parent = buttonsPanel, - Text = "Reset", - }; - buttonReset.Clicked += () => + buttonClearAll.Clicked += () => { pickerData.IsEditing = true; UncheckAll(root); @@ -404,6 +541,45 @@ namespace FlaxEditor.CustomEditors.Editors pickerData.SetValues?.Invoke(null); }; + // Move menu children to location when drop panel is opened and closed + addTagDropPanel.IsClosedChanged += panel => + { + if (panel.IsClosed) + { + foreach (var child in menu.Children) + { + if (child == panel) + continue; + + // Expand panels so scrollbars work + if (child is Panel) + { + child.Bounds = new Rectangle(child.Bounds.X, child.Bounds.Y - dropPanelOpenHeight, child.Width, child.Height + dropPanelOpenHeight); + continue; + } + child.Y -= dropPanelOpenHeight; + nameTextBox.Text = String.Empty; + } + } + else + { + foreach (var child in menu.Children) + { + if (child == panel) + continue; + + // Shrink panels so scrollbars work + if (child is Panel) + { + child.Bounds = new Rectangle(child.Bounds.X, child.Bounds.Y + dropPanelOpenHeight, child.Width, child.Height - dropPanelOpenHeight); + continue; + } + child.Y += dropPanelOpenHeight; + nameTextBox.Text = String.Empty; + } + } + }; + // Setup search filter searchBox.TextChanged += delegate { From efe11d0105b57d66211ab79fe9c1a26028862b8b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 20 Jan 2023 00:55:54 -0600 Subject: [PATCH 2/6] Added selecting text box when tag add drop panel open --- Source/Editor/CustomEditors/Editors/TagEditor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 3f425d845..56ffdbaba 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -546,6 +546,7 @@ namespace FlaxEditor.CustomEditors.Editors { if (panel.IsClosed) { + // Resize/ Move UI to fit space with drop down foreach (var child in menu.Children) { if (child == panel) @@ -563,6 +564,7 @@ namespace FlaxEditor.CustomEditors.Editors } else { + // Resize/ Move UI to fit space with drop down foreach (var child in menu.Children) { if (child == panel) @@ -577,6 +579,9 @@ namespace FlaxEditor.CustomEditors.Editors child.Y += dropPanelOpenHeight; nameTextBox.Text = String.Empty; } + // Select tag name box on open + nameTextBox.Focus(); + nameTextBox.SelectionRange = new TextRange(nameTextBox.Text.Length, nameTextBox.Text.Length); } }; From 6c1ce82e5cb73eb00e1d1087fe16ac4057f6fe8b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 20 Jan 2023 00:56:46 -0600 Subject: [PATCH 3/6] Small cleanup --- Source/Editor/CustomEditors/Editors/TagEditor.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 56ffdbaba..d1cdf8cfc 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -183,14 +183,7 @@ namespace FlaxEditor.CustomEditors.Editors // Check all entered subtags and create any that dont exist for (int j = 0; j <= i; j++) { - if (j == 0) - { - tagString += subInputs[j]; - } - else - { - tagString += "." + subInputs[j]; - } + tagString += j == 0 ? subInputs[j] : "." + subInputs[j]; } if (string.IsNullOrEmpty(tagString)) From 64dcfa21b8b2187167f6f1123103d3324a81ca01 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 20 Jan 2023 01:22:36 -0600 Subject: [PATCH 4/6] small fixes --- .../Editor/CustomEditors/Editors/TagEditor.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index d1cdf8cfc..9d538cbce 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -194,6 +194,8 @@ namespace FlaxEditor.CustomEditors.Editors if (Tags.List.Contains(tagString)) { + parentNode = tree.GetChild(); // return to root + parentCount = 0; // Find next parent node foreach (var child in parentNode.Children) { @@ -284,13 +286,13 @@ namespace FlaxEditor.CustomEditors.Editors // Reload editor window to reflect new tag assetWindow?.RefreshAsset(); + assetWindow?.MarkAsEdited(); + assetWindow?.Save(); } textBox.Text = string.Empty; lastTagString = tagString; tagString = string.Empty; - parentNode = tree.GetChild(); // return to root - parentCount = 0; } } @@ -301,8 +303,7 @@ namespace FlaxEditor.CustomEditors.Editors return; } dropPanel.Open(); - textBox.Text = string.Empty; - textBox.Text += parentTag + "."; + textBox.Text = parentTag + "."; textBox.Focus(); textBox.SelectionRange = new TextRange(textBox.Text.Length, textBox.Text.Length); } @@ -345,6 +346,7 @@ namespace FlaxEditor.CustomEditors.Editors WatermarkText = "X.Y.Z", Size = new Float2(addTagDropPanel.Width * 0.7f, 0), Parent = tagNamePanel, + EndEditOnClick = false, }; bool uniqueText = true; @@ -375,8 +377,9 @@ namespace FlaxEditor.CustomEditors.Editors if (!uniqueText) return; - if (!nameTextBox.IsFocused) + if (addTagDropPanel.IsClosed) { + Debug.Log("Hit"); nameTextBox.BorderColor = Color.Transparent; nameTextBox.BorderSelectedColor = FlaxEngine.GUI.Style.Current.BackgroundSelected; return; @@ -552,7 +555,8 @@ namespace FlaxEditor.CustomEditors.Editors continue; } child.Y -= dropPanelOpenHeight; - nameTextBox.Text = String.Empty; + nameTextBox.Text = string.Empty; + nameTextBox.Defocus(); } } else @@ -570,11 +574,8 @@ namespace FlaxEditor.CustomEditors.Editors continue; } child.Y += dropPanelOpenHeight; - nameTextBox.Text = String.Empty; + nameTextBox.Text = string.Empty; } - // Select tag name box on open - nameTextBox.Focus(); - nameTextBox.SelectionRange = new TextRange(nameTextBox.Text.Length, nameTextBox.Text.Length); } }; From 56c9be6f8cd8724c28edec51d6ead44c1d0eb741 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 20 Jan 2023 01:55:09 -0600 Subject: [PATCH 5/6] fixes --- .../Editor/CustomEditors/Editors/TagEditor.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 9d538cbce..c6fd4a9a4 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -175,8 +175,6 @@ namespace FlaxEditor.CustomEditors.Editors { if (string.IsNullOrEmpty(subInputs[i])) { - lastTagString = tagString; - tagString = string.Empty; continue; } @@ -194,8 +192,6 @@ namespace FlaxEditor.CustomEditors.Editors if (Tags.List.Contains(tagString)) { - parentNode = tree.GetChild(); // return to root - parentCount = 0; // Find next parent node foreach (var child in parentNode.Children) { @@ -213,11 +209,27 @@ namespace FlaxEditor.CustomEditors.Editors tagString = string.Empty; continue; } + else if (subInputs.Length > 1) + { + // Find next parent node + foreach (var child in parentNode.Children) + { + if (!(child is TreeNode childNode)) + continue; + + var tagValue = (Tag)childNode.Tag; + if (!string.Equals(tagValue.ToString(), lastTagString)) + continue; + + parentNode = childNode; + parentCount += 1; + } + } // Create new node var nodeIndent = 16.0f; var indentation = 0; - var parentTag = lastTagString; + var parentTag = string.Empty; if (parentNode.CustomArrowRect.HasValue) { indentation = (int)((parentNode.CustomArrowRect.Value.Location.X - 18) / nodeIndent) + 1; @@ -282,18 +294,17 @@ namespace FlaxEditor.CustomEditors.Editors // Update asset var settingsObj = (LayersAndTagsSettings)settingsAsset.Instance; settingsObj.Tags.Add(tagName); + settingsObj.Tags.Sort(); settingsAsset.SetInstance(settingsObj); // Reload editor window to reflect new tag assetWindow?.RefreshAsset(); - assetWindow?.MarkAsEdited(); - assetWindow?.Save(); } - textBox.Text = string.Empty; lastTagString = tagString; tagString = string.Empty; } + textBox.Text = string.Empty; } private static void OnAddSubTagButtonClicked(string parentTag, TextBox textBox, DropPanel dropPanel) From 5d601621cf4ea3d9c0a55ed5f718bc83bd542144 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 20 Jan 2023 02:14:05 -0600 Subject: [PATCH 6/6] small fix --- Source/Editor/CustomEditors/Editors/TagEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index c6fd4a9a4..8f330da29 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -537,7 +537,7 @@ namespace FlaxEditor.CustomEditors.Editors { Size = buttonsSize, Parent = buttonsPanel, - Text = "Clear All", + Text = "Clear all", }; buttonClearAll.Clicked += () => {