Merge branch 'better-tag-picker' of https://github.com/Tryibion/FlaxEngine into Tryibion-better-tag-picker

This commit is contained in:
Wojciech Figat
2023-01-20 11:51:50 +01:00

View File

@@ -156,88 +156,126 @@ namespace FlaxEditor.CustomEditors.Editors
pickerData.IsEditing = false; pickerData.IsEditing = false;
} }
private static void OnAddTagButtonClicked(string input, Tree tree, TextBox textBox, DropPanel dropPanel, PickerData pickerData)
private static void OnAddButtonClicked(Tree tree, TreeNode parentNode, PickerData pickerData)
{ {
// Create new node if (string.IsNullOrEmpty(input))
var nodeIndent = 16.0f; return;
var indentation = 0;
var parentTag = string.Empty; // Ensure that name is unique
if (parentNode.CustomArrowRect.HasValue) if (Tags.List.Contains(input))
{ return;
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);
// Link var subInputs = input.Split('.');
node.Parent = parentNode; string tagString = string.Empty;
node.IndexInParent = 0; string lastTagString = string.Empty;
parentNode.Expand(true);
((Panel)tree.Parent.Parent).ScrollViewTo(node);
// Start renaming the tag TreeNode parentNode = tree.GetChild<TreeNode>(); // Start at root
var prefix = parentTag.Length != 0 ? parentTag + '.' : string.Empty; int parentCount = 0;
var renameArea = node.HeaderRect; for (int i = 0; i < subInputs.Length; i++)
if (renameArea.Location.X < nodeIndent)
{ {
// Fix root node's child renaming if (string.IsNullOrEmpty(subInputs[i]))
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))
{ {
var name = value.Substring(popup.InitialValue.Length); continue;
if (name.Length > 0 && name.IndexOf('.') == -1)
return true;
} }
return false;
}; // Check all entered subtags and create any that dont exist
dialog.Renamed += popup => for (int j = 0; j <= i; j++)
{ {
tagString += j == 0 ? subInputs[j] : "." + 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;
}
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 = 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 => 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 // Get tag name
var tagName = popup.Text; var tagName = tagString;
var tagShortName = tagName.Substring(popup.InitialValue.Length); var tagShortName = tagName.Substring(parentCount == 0 ? lastTagString.Length : lastTagString.Length + 1);
if (tagShortName.Length == 0) if (tagShortName.Length == 0)
return; return;
@@ -256,24 +294,134 @@ namespace FlaxEditor.CustomEditors.Editors
// Update asset // Update asset
var settingsObj = (LayersAndTagsSettings)settingsAsset.Instance; var settingsObj = (LayersAndTagsSettings)settingsAsset.Instance;
settingsObj.Tags.Add(tagName); settingsObj.Tags.Add(tagName);
settingsObj.Tags.Sort();
settingsAsset.SetInstance(settingsObj); settingsAsset.SetInstance(settingsObj);
// Reload editor window to reflect new tag // Reload editor window to reflect new tag
assetWindow?.RefreshAsset(); assetWindow?.RefreshAsset();
} }
};
dialog.Closed += popup => lastTagString = tagString;
tagString = string.Empty;
}
textBox.Text = string.Empty;
}
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 return;
if (popup.InitialValue == popup.Text || popup.Text.Length == 0) }
node.Dispose(); dropPanel.Open();
}; 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) internal static ContextMenuBase CreatePicker(Tag value, Tag[] values, PickerData pickerData)
{ {
// Initialize search popup // 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,
EndEditOnClick = false,
};
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 (addTagDropPanel.IsClosed)
{
Debug.Log("Hit");
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 // Create tree with tags hierarchy
tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node
@@ -339,7 +487,7 @@ namespace FlaxEditor.CustomEditors.Editors
AnchorPreset = AnchorPresets.TopRight, AnchorPreset = AnchorPresets.TopRight,
}; };
node.Addons.Add(addButton); node.Addons.Add(addButton);
addButton.ButtonClicked += button => OnAddButtonClicked(tree, node, pickerData); addButton.ButtonClicked += button => OnAddSubTagButtonClicked(((Tag)node.Tag).ToString(), nameTextBox, addTagDropPanel);
// Link to parent // Link to parent
{ {
@@ -360,10 +508,13 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
Margin = new Margin(1.0f), Margin = new Margin(1.0f),
AutoSize = false, AutoSize = false,
Bounds = new Rectangle(0, 0, menu.Width, 20.0f), Bounds = new Rectangle(0, 0, menu.Width - 4, 20.0f),
Parent = menu, 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 var buttonExpandAll = new Button
{ {
Size = buttonsSize, Size = buttonsSize,
@@ -382,20 +533,13 @@ namespace FlaxEditor.CustomEditors.Editors
root.CollapseAll(true); root.CollapseAll(true);
root.Expand(true); root.Expand(true);
}; };
var buttonAddTag = new Button var buttonClearAll = new Button
{ {
Size = buttonsSize, Size = buttonsSize,
Parent = buttonsPanel, Parent = buttonsPanel,
Text = "Add Tag", Text = "Clear all",
}; };
buttonAddTag.Clicked += () => OnAddButtonClicked(tree, root, pickerData); buttonClearAll.Clicked += () =>
var buttonReset = new Button
{
Size = buttonsSize,
Parent = buttonsPanel,
Text = "Reset",
};
buttonReset.Clicked += () =>
{ {
pickerData.IsEditing = true; pickerData.IsEditing = true;
UncheckAll(root); UncheckAll(root);
@@ -404,6 +548,48 @@ namespace FlaxEditor.CustomEditors.Editors
pickerData.SetValues?.Invoke(null); pickerData.SetValues?.Invoke(null);
}; };
// Move menu children to location when drop panel is opened and closed
addTagDropPanel.IsClosedChanged += panel =>
{
if (panel.IsClosed)
{
// Resize/ Move UI to fit space with drop down
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;
nameTextBox.Defocus();
}
}
else
{
// Resize/ Move UI to fit space with drop down
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 // Setup search filter
searchBox.TextChanged += delegate searchBox.TextChanged += delegate
{ {