Refactor **Actor tags into hierarchical reusable Tags** system for better gameplay scripting
This commit is contained in:
@@ -1,57 +0,0 @@
|
|||||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
|
||||||
|
|
||||||
using FlaxEditor.Content.Settings;
|
|
||||||
using FlaxEditor.CustomEditors.Elements;
|
|
||||||
using FlaxEditor.GUI;
|
|
||||||
|
|
||||||
namespace FlaxEditor.CustomEditors.Editors
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Custom editor for picking actor tag. Instead of choosing tag index or entering tag text it shows a combo box with simple tag picking by name.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class ActorTagEditor : CustomEditor
|
|
||||||
{
|
|
||||||
private ComboBoxElement element;
|
|
||||||
private const string NoTagText = "Untagged";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Initialize(LayoutElementsContainer layout)
|
|
||||||
{
|
|
||||||
element = layout.ComboBox();
|
|
||||||
element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
|
||||||
|
|
||||||
// Set tag names
|
|
||||||
element.ComboBox.AddItem(NoTagText);
|
|
||||||
element.ComboBox.AddItems(LayersAndTagsSettings.GetCurrentTags());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSelectedIndexChanged(ComboBox comboBox)
|
|
||||||
{
|
|
||||||
string value = comboBox.SelectedItem;
|
|
||||||
if (value == NoTagText)
|
|
||||||
value = string.Empty;
|
|
||||||
SetValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Refresh()
|
|
||||||
{
|
|
||||||
base.Refresh();
|
|
||||||
|
|
||||||
if (HasDifferentValues)
|
|
||||||
{
|
|
||||||
// TODO: support different values on many actor selected
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string value = (string)Values[0];
|
|
||||||
if (string.IsNullOrEmpty(value))
|
|
||||||
value = NoTagText;
|
|
||||||
element.ComboBox.SelectedItem = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
{
|
{
|
||||||
Width = 16.0f,
|
Width = 16.0f,
|
||||||
Text = "...",
|
Text = "...",
|
||||||
|
TooltipText = "Edit...",
|
||||||
Parent = _label,
|
Parent = _label,
|
||||||
};
|
};
|
||||||
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
||||||
@@ -81,30 +82,6 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateFilter(TreeNode node, string filterText)
|
|
||||||
{
|
|
||||||
// Update children
|
|
||||||
bool isAnyChildVisible = false;
|
|
||||||
for (int i = 0; i < node.Children.Count; i++)
|
|
||||||
{
|
|
||||||
if (node.Children[i] is TreeNode child)
|
|
||||||
{
|
|
||||||
UpdateFilter(child, filterText);
|
|
||||||
isAnyChildVisible |= child.Visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update itself
|
|
||||||
bool noFilter = string.IsNullOrWhiteSpace(filterText);
|
|
||||||
bool isThisVisible = noFilter || QueryFilterHelper.Match(filterText, node.Text);
|
|
||||||
bool isExpanded = isAnyChildVisible;
|
|
||||||
if (isExpanded)
|
|
||||||
node.Expand(true);
|
|
||||||
else
|
|
||||||
node.Collapse(true);
|
|
||||||
node.Visible = isThisVisible | isAnyChildVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowPicker()
|
private void ShowPicker()
|
||||||
{
|
{
|
||||||
var menu = CreatePicker(Culture, value => { Culture = value; });
|
var menu = CreatePicker(Culture, value => { Culture = value; });
|
||||||
@@ -147,8 +124,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
if (tree.IsLayoutLocked)
|
if (tree.IsLayoutLocked)
|
||||||
return;
|
return;
|
||||||
root.LockChildrenRecursive();
|
root.LockChildrenRecursive();
|
||||||
var query = searchBox.Text;
|
Utilities.Utils.UpdateSearchPopupFilter(root, searchBox.Text);
|
||||||
UpdateFilter(root, query);
|
|
||||||
root.UnlockChildrenRecursive();
|
root.UnlockChildrenRecursive();
|
||||||
menu.PerformLayout();
|
menu.PerformLayout();
|
||||||
};
|
};
|
||||||
|
|||||||
497
Source/Editor/CustomEditors/Editors/TagEditor.cs
Normal file
497
Source/Editor/CustomEditors/Editors/TagEditor.cs
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
using FlaxEditor.GUI;
|
||||||
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
|
using FlaxEditor.GUI.Tree;
|
||||||
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.GUI;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FlaxEditor.Content.Settings;
|
||||||
|
|
||||||
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Custom editor for <see cref="Tag"/>.
|
||||||
|
/// </summary>
|
||||||
|
[CustomEditor(typeof(Tag)), DefaultEditor]
|
||||||
|
public sealed class TagEditor : CustomEditor
|
||||||
|
{
|
||||||
|
private ClickableLabel _label;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
|
{
|
||||||
|
_label = layout.ClickableLabel(Tag.ToString()).CustomControl;
|
||||||
|
_label.RightClick += ShowPicker;
|
||||||
|
var button = new Button
|
||||||
|
{
|
||||||
|
Size = new Float2(16.0f),
|
||||||
|
Text = "...",
|
||||||
|
TooltipText = "Edit...",
|
||||||
|
Parent = _label,
|
||||||
|
};
|
||||||
|
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
||||||
|
button.Clicked += ShowPicker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Refresh()
|
||||||
|
{
|
||||||
|
base.Refresh();
|
||||||
|
|
||||||
|
// Update label
|
||||||
|
_label.Text = Tag.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tag Tag
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Values[0] is Tag asTag)
|
||||||
|
return asTag;
|
||||||
|
if (Values[0] is int asInt)
|
||||||
|
return new Tag(asInt);
|
||||||
|
if (Values[0] is string asString)
|
||||||
|
return Tags.Get(asString);
|
||||||
|
return Tag.Default;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Values[0] is Tag)
|
||||||
|
SetValue(value);
|
||||||
|
if (Values[0] is int)
|
||||||
|
SetValue(value.Index);
|
||||||
|
else if (Values[0] is string)
|
||||||
|
SetValue(value.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowPicker()
|
||||||
|
{
|
||||||
|
var menu = CreatePicker(Tag, null, new PickerData
|
||||||
|
{
|
||||||
|
IsSingle = true,
|
||||||
|
SetValue = value => { Tag = value; },
|
||||||
|
});
|
||||||
|
menu.Show(_label, new Float2(0, _label.Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class PickerData
|
||||||
|
{
|
||||||
|
public bool IsEditing;
|
||||||
|
public bool IsSingle;
|
||||||
|
public Action<Tag> SetValue;
|
||||||
|
public Action<Tag[]> SetValues;
|
||||||
|
public List<Tag> CachedTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UncheckAll(TreeNode n, TreeNode except = null)
|
||||||
|
{
|
||||||
|
foreach (var child in n.Children)
|
||||||
|
{
|
||||||
|
if (child is CheckBox checkBox && except != n)
|
||||||
|
checkBox.Checked = false;
|
||||||
|
else if (child is TreeNode treeNode)
|
||||||
|
UncheckAll(treeNode, except);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UncheckAll(Tree n, TreeNode except = null)
|
||||||
|
{
|
||||||
|
foreach (var child in n.Children)
|
||||||
|
{
|
||||||
|
if (child is TreeNode treeNode)
|
||||||
|
UncheckAll(treeNode, except);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetTags(TreeNode n, PickerData pickerData)
|
||||||
|
{
|
||||||
|
if (n is TreeNodeWithAddons a && a.Addons.Count != 0 && a.Addons[0] is CheckBox c && c.Checked)
|
||||||
|
pickerData.CachedTags.Add(new Tag((int)n.Tag));
|
||||||
|
foreach (var child in n.Children)
|
||||||
|
{
|
||||||
|
if (child is TreeNode treeNode)
|
||||||
|
GetTags(treeNode, pickerData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetTags(Tree n, PickerData pickerData)
|
||||||
|
{
|
||||||
|
foreach (var child in n.Children)
|
||||||
|
{
|
||||||
|
if (child is TreeNode treeNode)
|
||||||
|
GetTags(treeNode, pickerData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnCheckboxEdited(TreeNode node, CheckBox c, PickerData pickerData)
|
||||||
|
{
|
||||||
|
if (pickerData.IsEditing)
|
||||||
|
return;
|
||||||
|
pickerData.IsEditing = true;
|
||||||
|
if (pickerData.IsSingle)
|
||||||
|
{
|
||||||
|
UncheckAll(node.ParentTree, node);
|
||||||
|
var value = new Tag(c.Checked ? (int)node.Tag : -1);
|
||||||
|
pickerData.SetValue?.Invoke(value);
|
||||||
|
pickerData.SetValues?.Invoke(new[] { value });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (pickerData.CachedTags == null)
|
||||||
|
pickerData.CachedTags = new List<Tag>();
|
||||||
|
else
|
||||||
|
pickerData.CachedTags.Clear();
|
||||||
|
GetTags(node.ParentTree, pickerData);
|
||||||
|
pickerData.SetValue?.Invoke(pickerData.CachedTags.Count != 0 ? pickerData.CachedTags[0] : Tag.Default);
|
||||||
|
pickerData.SetValues?.Invoke(pickerData.CachedTags.ToArray());
|
||||||
|
}
|
||||||
|
pickerData.IsEditing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void OnAddButtonClicked(Tree tree, TreeNode parentNode, 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 parentIndex = (int)parentNode.Tag;
|
||||||
|
parentTag = Tags.List[parentIndex];
|
||||||
|
}
|
||||||
|
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
|
||||||
|
node.Parent = parentNode;
|
||||||
|
node.IndexInParent = 0;
|
||||||
|
parentNode.Expand(true);
|
||||||
|
((Panel)tree.Parent.Parent).ScrollViewTo(node);
|
||||||
|
|
||||||
|
// Start renaming the tag
|
||||||
|
var prefix = parentTag.Length != 0 ? parentTag + '.' : string.Empty;
|
||||||
|
var renameArea = node.HeaderRect;
|
||||||
|
if (renameArea.Location.X < nodeIndent)
|
||||||
|
{
|
||||||
|
// 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))
|
||||||
|
{
|
||||||
|
var name = value.Substring(popup.InitialValue.Length);
|
||||||
|
if (name.Length > 0 && name.IndexOf('.') == -1)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
dialog.Renamed += popup =>
|
||||||
|
{
|
||||||
|
// Get tag name
|
||||||
|
var tagName = popup.Text;
|
||||||
|
var name = tagName.Substring(popup.InitialValue.Length);
|
||||||
|
if (name.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add tag
|
||||||
|
var tag = Tags.Get(tagName);
|
||||||
|
node.Text = name;
|
||||||
|
node.Tag = tag.Index;
|
||||||
|
var settingsAsset = GameSettings.LoadAsset<LayersAndTagsSettings>();
|
||||||
|
if (settingsAsset && !settingsAsset.WaitForLoaded())
|
||||||
|
{
|
||||||
|
// Save any local changes to the tags asset in Editor
|
||||||
|
var settingsAssetItem = Editor.Instance.ContentDatabase.FindAsset(settingsAsset.ID) as Content.JsonAssetItem;
|
||||||
|
var assetWindow = Editor.Instance.Windows.FindEditor(settingsAssetItem) as Windows.Assets.JsonAssetWindow;
|
||||||
|
assetWindow?.Save();
|
||||||
|
|
||||||
|
// Update asset
|
||||||
|
var settingsObj = (LayersAndTagsSettings)settingsAsset.Instance;
|
||||||
|
settingsObj.Tags.Add(tagName);
|
||||||
|
settingsAsset.SetInstance(settingsObj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dialog.Closed += popup =>
|
||||||
|
{
|
||||||
|
// Remove temporary node if renaming was canceled
|
||||||
|
if (popup.InitialValue == popup.Text || popup.Text.Length == 0)
|
||||||
|
node.Dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Create tree with tags hierarchy
|
||||||
|
tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node
|
||||||
|
var tags = Tags.List;
|
||||||
|
var nameToNode = new Dictionary<string, ContainerControl>();
|
||||||
|
var style = FlaxEngine.GUI.Style.Current;
|
||||||
|
var nodeBackgroundColorHighlighted = style.BackgroundHighlighted * 0.5f;
|
||||||
|
var nodeIndent = 16.0f;
|
||||||
|
var root = tree.AddChild<TreeNode>();
|
||||||
|
for (var i = 0; i < tags.Length; i++)
|
||||||
|
{
|
||||||
|
var tag = tags[i];
|
||||||
|
bool isSelected = pickerData.IsSingle ? value.Index == i : values.Contains(new Tag(i));
|
||||||
|
|
||||||
|
// Count parent tags count
|
||||||
|
int indentation = 0;
|
||||||
|
for (int j = 0; j < tag.Length; j++)
|
||||||
|
{
|
||||||
|
if (tag[j] == '.')
|
||||||
|
indentation++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create node
|
||||||
|
var node = new TreeNodeWithAddons
|
||||||
|
{
|
||||||
|
Tag = i,
|
||||||
|
Text = tag,
|
||||||
|
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 = nodeBackgroundColorHighlighted,
|
||||||
|
};
|
||||||
|
var checkbox = new CheckBox(32.0f + indentation * nodeIndent, 0, isSelected)
|
||||||
|
{
|
||||||
|
Height = 16.0f,
|
||||||
|
IsScrollable = false,
|
||||||
|
Parent = node,
|
||||||
|
};
|
||||||
|
node.Addons.Add(checkbox);
|
||||||
|
checkbox.StateChanged += c => OnCheckboxEdited(node, c, pickerData);
|
||||||
|
var addButton = new Button(menu.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 to parent
|
||||||
|
{
|
||||||
|
var lastDotIndex = tag.LastIndexOf('.');
|
||||||
|
var parentTagName = lastDotIndex != -1 ? tag.Substring(0, lastDotIndex) : string.Empty;
|
||||||
|
if (!nameToNode.TryGetValue(parentTagName, out ContainerControl parent))
|
||||||
|
parent = root;
|
||||||
|
node.Parent = parent;
|
||||||
|
}
|
||||||
|
nameToNode[tag] = node;
|
||||||
|
|
||||||
|
// Expand selected nodes to be visible in hierarchy
|
||||||
|
if (isSelected)
|
||||||
|
node.ExpandAllParents(true);
|
||||||
|
}
|
||||||
|
nameToNode.Clear();
|
||||||
|
|
||||||
|
// Adds top panel with utility buttons
|
||||||
|
var buttonsPanel = new HorizontalPanel
|
||||||
|
{
|
||||||
|
Margin = new Margin(1.0f),
|
||||||
|
AutoSize = false,
|
||||||
|
Bounds = new Rectangle(0, 0, menu.Width, 20.0f),
|
||||||
|
Parent = menu,
|
||||||
|
};
|
||||||
|
var buttonsSize = new Float2((menu.Width - buttonsPanel.Margin.Width) / 4.0f - buttonsPanel.Spacing, 18.0f);
|
||||||
|
var buttonExpandAll = new Button
|
||||||
|
{
|
||||||
|
Size = buttonsSize,
|
||||||
|
Parent = buttonsPanel,
|
||||||
|
Text = "Expand all",
|
||||||
|
};
|
||||||
|
buttonExpandAll.Clicked += () => root.ExpandAll(true);
|
||||||
|
var buttonCollapseAll = new Button
|
||||||
|
{
|
||||||
|
Size = buttonsSize,
|
||||||
|
Parent = buttonsPanel,
|
||||||
|
Text = "Collapse all",
|
||||||
|
};
|
||||||
|
buttonCollapseAll.Clicked += () =>
|
||||||
|
{
|
||||||
|
root.CollapseAll(true);
|
||||||
|
root.Expand(true);
|
||||||
|
};
|
||||||
|
var buttonAddTag = new Button
|
||||||
|
{
|
||||||
|
Size = buttonsSize,
|
||||||
|
Parent = buttonsPanel,
|
||||||
|
Text = "Add Tag",
|
||||||
|
};
|
||||||
|
buttonAddTag.Clicked += () => OnAddButtonClicked(tree, root, pickerData);
|
||||||
|
var buttonReset = new Button
|
||||||
|
{
|
||||||
|
Size = buttonsSize,
|
||||||
|
Parent = buttonsPanel,
|
||||||
|
Text = "Reset",
|
||||||
|
};
|
||||||
|
buttonReset.Clicked += () =>
|
||||||
|
{
|
||||||
|
pickerData.IsEditing = true;
|
||||||
|
UncheckAll(root);
|
||||||
|
pickerData.IsEditing = false;
|
||||||
|
pickerData.SetValue?.Invoke(Tag.Default);
|
||||||
|
pickerData.SetValues?.Invoke(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup search filter
|
||||||
|
searchBox.TextChanged += delegate
|
||||||
|
{
|
||||||
|
if (tree.IsLayoutLocked)
|
||||||
|
return;
|
||||||
|
root.LockChildrenRecursive();
|
||||||
|
Utilities.Utils.UpdateSearchPopupFilter(root, searchBox.Text);
|
||||||
|
root.UnlockChildrenRecursive();
|
||||||
|
menu.PerformLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare for display
|
||||||
|
root.SortChildrenRecursive();
|
||||||
|
root.Expand(true);
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom editor for array of <see cref="Tag"/>.
|
||||||
|
/// </summary>
|
||||||
|
[CustomEditor(typeof(Tag[])), DefaultEditor]
|
||||||
|
public sealed class TagsEditor : CustomEditor
|
||||||
|
{
|
||||||
|
private ClickableLabel _label;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
|
{
|
||||||
|
_label = layout.ClickableLabel(GetText(out _)).CustomControl;
|
||||||
|
_label.RightClick += ShowPicker;
|
||||||
|
var button = new Button
|
||||||
|
{
|
||||||
|
Size = new Float2(16.0f),
|
||||||
|
Text = "...",
|
||||||
|
TooltipText = "Edit...",
|
||||||
|
Parent = _label,
|
||||||
|
};
|
||||||
|
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
||||||
|
button.Clicked += ShowPicker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Refresh()
|
||||||
|
{
|
||||||
|
base.Refresh();
|
||||||
|
|
||||||
|
// Update label
|
||||||
|
_label.Text = GetText(out var tags);
|
||||||
|
_label.Height = Math.Max(tags.Length, 1) * 18.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetText(out Tag[] tags)
|
||||||
|
{
|
||||||
|
tags = Tags;
|
||||||
|
if (tags.Length == 0)
|
||||||
|
return string.Empty;
|
||||||
|
if (tags.Length == 1)
|
||||||
|
return tags[0].ToString();
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (var i = 0; i < tags.Length; i++)
|
||||||
|
{
|
||||||
|
var tag = tags[i];
|
||||||
|
if (i != 0)
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.Append(tag.ToString());
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tag[] Tags
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Values[0] is Tag[] asArray)
|
||||||
|
return asArray;
|
||||||
|
if (Values[0] is List<Tag> asList)
|
||||||
|
return asList.ToArray();
|
||||||
|
return Utils.GetEmptyArray<Tag>();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Values[0] is Tag[])
|
||||||
|
SetValue(value);
|
||||||
|
if (Values[0] is List<Tag>)
|
||||||
|
SetValue(new List<Tag>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowPicker()
|
||||||
|
{
|
||||||
|
var menu = TagEditor.CreatePicker(Tag.Default, Tags, new TagEditor.PickerData
|
||||||
|
{
|
||||||
|
SetValues = value => { Tags = value; },
|
||||||
|
});
|
||||||
|
menu.Show(_label, new Float2(0, _label.Height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,11 @@ namespace FlaxEditor.GUI
|
|||||||
set => _inputField.Text = value;
|
set => _inputField.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text input field control.
|
||||||
|
/// </summary>
|
||||||
|
public TextBox InputField => _inputField;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RenamePopup"/> class.
|
/// Initializes a new instance of the <see cref="RenamePopup"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the arrow rectangle.
|
/// Gets the arrow rectangle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Rectangle ArrowRect => new Rectangle(_xOffset + 2 + _margin.Left, 2, 12, 12);
|
public Rectangle ArrowRect => CustomArrowRect.HasValue ? CustomArrowRect.Value : new Rectangle(_xOffset + 2 + _margin.Left, 2, 12, 12);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the header rectangle.
|
/// Gets the header rectangle.
|
||||||
@@ -248,6 +248,12 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom arrow rectangle within node.
|
||||||
|
/// </summary>
|
||||||
|
[HideInEditor, NoSerialize]
|
||||||
|
public Rectangle? CustomArrowRect;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the drag over action type.
|
/// Gets the drag over action type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -277,7 +283,7 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
/// Initializes a new instance of the <see cref="TreeNode"/> class.
|
/// Initializes a new instance of the <see cref="TreeNode"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TreeNode()
|
public TreeNode()
|
||||||
: this(false)
|
: this(false, SpriteHandle.Invalid, SpriteHandle.Invalid)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,11 +492,14 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
/// <returns>True if event has been handled.</returns>
|
/// <returns>True if event has been handled.</returns>
|
||||||
protected virtual bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
|
protected virtual bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
|
||||||
{
|
{
|
||||||
// Toggle open state
|
if (HasAnyVisibleChild)
|
||||||
if (_opened)
|
{
|
||||||
Collapse();
|
// Toggle open state
|
||||||
else
|
if (_opened)
|
||||||
Expand();
|
Collapse();
|
||||||
|
else
|
||||||
|
Expand();
|
||||||
|
}
|
||||||
|
|
||||||
// Handled
|
// Handled
|
||||||
return true;
|
return true;
|
||||||
@@ -754,7 +763,7 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if mouse hits arrow
|
// Check if mouse hits arrow
|
||||||
if (HasAnyVisibleChild && _mouseOverArrow)
|
if (_mouseOverArrow && HasAnyVisibleChild)
|
||||||
{
|
{
|
||||||
// Toggle open state
|
// Toggle open state
|
||||||
if (_opened)
|
if (_opened)
|
||||||
|
|||||||
121
Source/Editor/GUI/Tree/TreeNodeWithAddons.cs
Normal file
121
Source/Editor/GUI/Tree/TreeNodeWithAddons.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.GUI;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FlaxEditor.GUI.Tree
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tree node control with in-built checkbox.
|
||||||
|
/// </summary>
|
||||||
|
[HideInEditor]
|
||||||
|
public class TreeNodeWithAddons : TreeNode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The additional controls (eg. added to the header).
|
||||||
|
/// </summary>
|
||||||
|
public List<Control> Addons = new List<Control>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
base.Draw();
|
||||||
|
|
||||||
|
foreach (var child in Addons)
|
||||||
|
{
|
||||||
|
Render2D.PushTransform(ref child._cachedTransform);
|
||||||
|
child.Draw();
|
||||||
|
Render2D.PopTransform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||||
|
{
|
||||||
|
foreach (var child in Addons)
|
||||||
|
{
|
||||||
|
if (child.Visible && child.Enabled)
|
||||||
|
{
|
||||||
|
if (IntersectsChildContent(child, location, out var childLocation))
|
||||||
|
{
|
||||||
|
if (child.OnMouseDown(childLocation, button))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnMouseDown(location, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||||
|
{
|
||||||
|
foreach (var child in Addons)
|
||||||
|
{
|
||||||
|
if (child.Visible && child.Enabled)
|
||||||
|
{
|
||||||
|
if (IntersectsChildContent(child, location, out var childLocation))
|
||||||
|
{
|
||||||
|
if (child.OnMouseUp(childLocation, button))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnMouseUp(location, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||||
|
{
|
||||||
|
foreach (var child in Addons)
|
||||||
|
{
|
||||||
|
if (child.Visible && child.Enabled)
|
||||||
|
{
|
||||||
|
if (IntersectsChildContent(child, location, out var childLocation))
|
||||||
|
{
|
||||||
|
if (child.OnMouseDoubleClick(childLocation, button))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnMouseDoubleClick(location, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnMouseMove(Float2 location)
|
||||||
|
{
|
||||||
|
base.OnMouseMove(location);
|
||||||
|
|
||||||
|
if (IsCollapsed)
|
||||||
|
{
|
||||||
|
foreach (var child in Addons)
|
||||||
|
{
|
||||||
|
if (child.Visible && child.Enabled)
|
||||||
|
{
|
||||||
|
if (IntersectsChildContent(child, location, out var childLocation))
|
||||||
|
{
|
||||||
|
if (child.IsMouseOver)
|
||||||
|
{
|
||||||
|
// Move
|
||||||
|
child.OnMouseMove(childLocation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Enter
|
||||||
|
child.OnMouseEnter(childLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (child.IsMouseOver)
|
||||||
|
{
|
||||||
|
// Leave
|
||||||
|
child.OnMouseLeave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -327,13 +327,8 @@ namespace CustomEditorsUtilInternal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace LayersAndTagsSettingsInternal1
|
namespace LayersAndTagsSettingsInternal
|
||||||
{
|
{
|
||||||
MonoArray* GetCurrentTags()
|
|
||||||
{
|
|
||||||
return MUtils::ToArray(Level::Tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
MonoArray* GetCurrentLayers()
|
MonoArray* GetCurrentLayers()
|
||||||
{
|
{
|
||||||
return MUtils::ToArray(Span<String>(Level::Layers, Math::Max(1, Level::GetNonEmptyLayerNamesCount())));
|
return MUtils::ToArray(Span<String>(Level::Layers, Math::Max(1, Level::GetNonEmptyLayerNamesCount())));
|
||||||
@@ -1125,8 +1120,7 @@ public:
|
|||||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", &GetTextureImportOptions);
|
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", &GetTextureImportOptions);
|
||||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", &GetModelImportOptions);
|
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", &GetModelImportOptions);
|
||||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions);
|
ADD_INTERNAL_CALL("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions);
|
||||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal1::GetCurrentTags);
|
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal::GetCurrentLayers);
|
||||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal1::GetCurrentLayers);
|
|
||||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply);
|
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply);
|
||||||
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen);
|
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen);
|
||||||
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset);
|
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset);
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ namespace FlaxEditor.Modules
|
|||||||
actor.StaticFlags = old.StaticFlags;
|
actor.StaticFlags = old.StaticFlags;
|
||||||
actor.HideFlags = old.HideFlags;
|
actor.HideFlags = old.HideFlags;
|
||||||
actor.Layer = old.Layer;
|
actor.Layer = old.Layer;
|
||||||
actor.Tag = old.Tag;
|
actor.Tags = old.Tags;
|
||||||
actor.Name = old.Name;
|
actor.Name = old.Name;
|
||||||
actor.IsActive = old.IsActive;
|
actor.IsActive = old.IsActive;
|
||||||
|
|
||||||
|
|||||||
@@ -949,14 +949,15 @@ namespace FlaxEditor.Utilities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="searchBox">The search box.</param>
|
/// <param name="searchBox">The search box.</param>
|
||||||
/// <param name="tree">The tree control.</param>
|
/// <param name="tree">The tree control.</param>
|
||||||
|
/// <param name="headerHeight">Amount of additional space above the search box to put custom UI.</param>
|
||||||
/// <returns>The created menu to setup and show.</returns>
|
/// <returns>The created menu to setup and show.</returns>
|
||||||
public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree)
|
public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree, float headerHeight = 0)
|
||||||
{
|
{
|
||||||
var menu = new ContextMenuBase
|
var menu = new ContextMenuBase
|
||||||
{
|
{
|
||||||
Size = new Float2(320, 220),
|
Size = new Float2(320, 220 + headerHeight),
|
||||||
};
|
};
|
||||||
searchBox = new TextBox(false, 1, 1)
|
searchBox = new TextBox(false, 1, headerHeight + 1)
|
||||||
{
|
{
|
||||||
Width = menu.Width - 3,
|
Width = menu.Width - 3,
|
||||||
WatermarkText = "Search...",
|
WatermarkText = "Search...",
|
||||||
@@ -980,6 +981,33 @@ namespace FlaxEditor.Utilities
|
|||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates (recursivly) search popup tree structures based on the filter text.
|
||||||
|
/// </summary>
|
||||||
|
public static void UpdateSearchPopupFilter(TreeNode node, string filterText)
|
||||||
|
{
|
||||||
|
// Update children
|
||||||
|
bool isAnyChildVisible = false;
|
||||||
|
for (int i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is TreeNode child)
|
||||||
|
{
|
||||||
|
UpdateSearchPopupFilter(child, filterText);
|
||||||
|
isAnyChildVisible |= child.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update itself
|
||||||
|
bool noFilter = string.IsNullOrWhiteSpace(filterText);
|
||||||
|
bool isThisVisible = noFilter || QueryFilterHelper.Match(filterText, node.Text);
|
||||||
|
bool isExpanded = isAnyChildVisible;
|
||||||
|
if (isExpanded)
|
||||||
|
node.Expand(true);
|
||||||
|
else
|
||||||
|
node.Collapse(true);
|
||||||
|
node.Visible = isThisVisible | isAnyChildVisible;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the asset name relative to the project root folder (without asset file extension)
|
/// Gets the asset name relative to the project root folder (without asset file extension)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -20,13 +20,6 @@ namespace FlaxEditor.Content.Settings
|
|||||||
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)]
|
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)]
|
||||||
public string[] Layers = new string[32];
|
public string[] Layers = new string[32];
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current tags collection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The tags collection.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
|
||||||
internal static extern string[] GetCurrentTags();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current layer names (max 32 items but trims last empty items).
|
/// Gets the current layer names (max 32 items but trims last empty items).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -73,13 +73,12 @@ Actor::Actor(const SpawnParams& params)
|
|||||||
, _isPrefabRoot(false)
|
, _isPrefabRoot(false)
|
||||||
, _isEnabled(false)
|
, _isEnabled(false)
|
||||||
, _layer(0)
|
, _layer(0)
|
||||||
, _tag(ACTOR_TAG_INVALID)
|
|
||||||
, _scene(nullptr)
|
|
||||||
, _staticFlags(StaticFlags::FullyStatic)
|
, _staticFlags(StaticFlags::FullyStatic)
|
||||||
, _localTransform(Transform::Identity)
|
, _localTransform(Transform::Identity)
|
||||||
, _transform(Transform::Identity)
|
, _transform(Transform::Identity)
|
||||||
, _sphere(BoundingSphere::Empty)
|
, _sphere(BoundingSphere::Empty)
|
||||||
, _box(BoundingBox::Zero)
|
, _box(BoundingBox::Zero)
|
||||||
|
, _scene(nullptr)
|
||||||
, _physicsScene(nullptr)
|
, _physicsScene(nullptr)
|
||||||
, HideFlags(HideFlags::None)
|
, HideFlags(HideFlags::None)
|
||||||
{
|
{
|
||||||
@@ -439,23 +438,24 @@ void Actor::DestroyChildren(float timeLeft)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Actor::HasTag(const StringView& tag) const
|
|
||||||
{
|
|
||||||
return HasTag() && tag == Level::Tags[_tag];
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& Actor::GetLayerName() const
|
const String& Actor::GetLayerName() const
|
||||||
{
|
{
|
||||||
return Level::Layers[_layer];
|
return Level::Layers[_layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
const String& Actor::GetTag() const
|
bool Actor::HasTag() const
|
||||||
{
|
{
|
||||||
if (HasTag())
|
return Tags.Count() != 0;
|
||||||
{
|
}
|
||||||
return Level::Tags[_tag];
|
|
||||||
}
|
bool Actor::HasTag(const Tag& tag) const
|
||||||
return String::Empty;
|
{
|
||||||
|
return Tags.Contains(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Actor::HasTag(const StringView& tag) const
|
||||||
|
{
|
||||||
|
return Tags.Contains(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::SetLayer(int32 layerIndex)
|
void Actor::SetLayer(int32 layerIndex)
|
||||||
@@ -463,51 +463,10 @@ void Actor::SetLayer(int32 layerIndex)
|
|||||||
layerIndex = Math::Clamp(layerIndex, 0, 31);
|
layerIndex = Math::Clamp(layerIndex, 0, 31);
|
||||||
if (layerIndex == _layer)
|
if (layerIndex == _layer)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_layer = layerIndex;
|
_layer = layerIndex;
|
||||||
OnLayerChanged();
|
OnLayerChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::SetTagIndex(int32 tagIndex)
|
|
||||||
{
|
|
||||||
if (tagIndex == ACTOR_TAG_INVALID)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else if (Level::Tags.IsEmpty())
|
|
||||||
{
|
|
||||||
tagIndex = ACTOR_TAG_INVALID;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tagIndex = tagIndex < 0 ? ACTOR_TAG_INVALID : Math::Min(tagIndex, Level::Tags.Count() - 1);
|
|
||||||
}
|
|
||||||
if (tagIndex == _tag)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_tag = tagIndex;
|
|
||||||
OnTagChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Actor::SetTag(const StringView& tagName)
|
|
||||||
{
|
|
||||||
int32 tagIndex;
|
|
||||||
if (tagName.IsEmpty())
|
|
||||||
{
|
|
||||||
tagIndex = ACTOR_TAG_INVALID;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tagIndex = Level::Tags.Find(tagName);
|
|
||||||
if (tagIndex == -1)
|
|
||||||
{
|
|
||||||
LOG(Error, "Cannot change actor tag. Given value is invalid.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetTagIndex(tagIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Actor::SetName(const StringView& value)
|
void Actor::SetName(const StringView& value)
|
||||||
{
|
{
|
||||||
if (_name == value)
|
if (_name == value)
|
||||||
@@ -897,10 +856,21 @@ void Actor::Serialize(SerializeStream& stream, const void* otherObj)
|
|||||||
SERIALIZE_MEMBER(StaticFlags, _staticFlags);
|
SERIALIZE_MEMBER(StaticFlags, _staticFlags);
|
||||||
SERIALIZE(HideFlags);
|
SERIALIZE(HideFlags);
|
||||||
SERIALIZE_MEMBER(Layer, _layer);
|
SERIALIZE_MEMBER(Layer, _layer);
|
||||||
if (!other || _tag != other->_tag)
|
if (!other || Tags != other->Tags)
|
||||||
{
|
{
|
||||||
stream.JKEY("Tag");
|
if (Tags.Count() == 1)
|
||||||
stream.String(GetTag());
|
{
|
||||||
|
stream.JKEY("Tag");
|
||||||
|
stream.String(Tags.Get()->ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.JKEY("Tags");
|
||||||
|
stream.StartArray();
|
||||||
|
for (auto& tag : Tags)
|
||||||
|
stream.String(tag.ToString());
|
||||||
|
stream.EndArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPrefabDiff)
|
if (isPrefabDiff)
|
||||||
@@ -996,14 +966,27 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
|||||||
_staticFlags |= StaticFlags::Navigation;
|
_staticFlags |= StaticFlags::Navigation;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve tag (it may be missing in the current configuration
|
|
||||||
const auto tag = stream.FindMember("Tag");
|
const auto tag = stream.FindMember("Tag");
|
||||||
if (tag != stream.MemberEnd())
|
if (tag != stream.MemberEnd())
|
||||||
{
|
{
|
||||||
if (tag->value.IsString() && tag->value.GetStringLength())
|
if (tag->value.IsString() && tag->value.GetStringLength())
|
||||||
{
|
{
|
||||||
const String tagName = tag->value.GetText();
|
Tags.Clear();
|
||||||
_tag = Level::GetOrAddTag(tagName);
|
Tags.Add(Tags::Get(tag->value.GetText()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto tags = stream.FindMember("Tags");
|
||||||
|
if (tags != stream.MemberEnd() && tags->value.IsArray())
|
||||||
|
{
|
||||||
|
Tags.Clear();
|
||||||
|
for (rapidjson::SizeType i = 0; i < tags->value.Size(); i++)
|
||||||
|
{
|
||||||
|
auto& e = tags->value[i];
|
||||||
|
if (e.IsString() && e.GetStringLength())
|
||||||
|
Tags.Add(Tags::Get(e.GetText()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "SceneObject.h"
|
#include "SceneObject.h"
|
||||||
|
#include "Tags.h"
|
||||||
#include "Engine/Core/Types/Span.h"
|
#include "Engine/Core/Types/Span.h"
|
||||||
#include "Engine/Core/Math/Transform.h"
|
#include "Engine/Core/Math/Transform.h"
|
||||||
#include "Engine/Core/Math/BoundingBox.h"
|
#include "Engine/Core/Math/BoundingBox.h"
|
||||||
@@ -34,7 +35,6 @@ API_CLASS(Abstract) class FLAXENGINE_API Actor : public SceneObject
|
|||||||
friend SceneRendering;
|
friend SceneRendering;
|
||||||
friend Prefab;
|
friend Prefab;
|
||||||
friend PrefabInstanceData;
|
friend PrefabInstanceData;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int16 _isActive : 1;
|
int16 _isActive : 1;
|
||||||
int16 _isActiveInHierarchy : 1;
|
int16 _isActiveInHierarchy : 1;
|
||||||
@@ -43,7 +43,6 @@ protected:
|
|||||||
int16 _drawNoCulling : 1;
|
int16 _drawNoCulling : 1;
|
||||||
int16 _drawCategory : 4;
|
int16 _drawCategory : 4;
|
||||||
byte _layer;
|
byte _layer;
|
||||||
byte _tag;
|
|
||||||
StaticFlags _staticFlags;
|
StaticFlags _staticFlags;
|
||||||
Transform _localTransform;
|
Transform _localTransform;
|
||||||
Transform _transform;
|
Transform _transform;
|
||||||
@@ -77,6 +76,11 @@ public:
|
|||||||
API_FIELD(Attributes="HideInEditor, NoSerialize")
|
API_FIELD(Attributes="HideInEditor, NoSerialize")
|
||||||
HideFlags HideFlags;
|
HideFlags HideFlags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actor tags collection.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68)") Array<Tag> Tags;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the object layer (index). Can be used for selective rendering or ignoring raycasts.
|
/// Gets the object layer (index). Can be used for selective rendering or ignoring raycasts.
|
||||||
@@ -96,26 +100,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the actor tag (index). Can be used to identify the objects.
|
/// Sets the layer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetTagIndex() const
|
/// <param name="layerIndex">The index of the layer.</param>
|
||||||
{
|
API_PROPERTY() void SetLayer(int32 layerIndex);
|
||||||
return _tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this actor has tag assigned.
|
|
||||||
/// </summary>
|
|
||||||
API_FUNCTION() FORCE_INLINE bool HasTag() const
|
|
||||||
{
|
|
||||||
return _tag != ACTOR_TAG_INVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this actor has given tag assigned.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tag">The tag to check.</param>
|
|
||||||
API_FUNCTION() bool HasTag(const StringView& tag) const;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the layer.
|
/// Gets the name of the layer.
|
||||||
@@ -123,28 +111,21 @@ public:
|
|||||||
API_PROPERTY() const String& GetLayerName() const;
|
API_PROPERTY() const String& GetLayerName() const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the tag.
|
/// Determines whether this actor has any tag assigned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTagEditor\")")
|
API_FUNCTION() bool HasTag() const;
|
||||||
const String& GetTag() const;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the layer.
|
/// Determines whether this actor has given tag assigned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="layerIndex">The index of the layer.</param>
|
/// <param name="tag">The tag to check.</param>
|
||||||
API_PROPERTY() void SetLayer(int32 layerIndex);
|
API_FUNCTION() bool HasTag(const Tag& tag) const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the tag.
|
/// Determines whether this actor has given tag assigned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tagIndex">The index of the tag.</param>
|
/// <param name="tag">The tag to check.</param>
|
||||||
void SetTagIndex(int32 tagIndex);
|
API_FUNCTION() bool HasTag(const StringView& tag) const;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the tag.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tagName">Name of the tag.</param>
|
|
||||||
API_PROPERTY() void SetTag(const StringView& tagName);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the actor name.
|
/// Gets the actor name.
|
||||||
@@ -955,13 +936,6 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when tag gets changed.
|
|
||||||
/// </summary>
|
|
||||||
virtual void OnTagChanged()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when adding object to the game.
|
/// Called when adding object to the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ Action Level::ScriptsReload;
|
|||||||
Action Level::ScriptsReloaded;
|
Action Level::ScriptsReloaded;
|
||||||
Action Level::ScriptsReloadEnd;
|
Action Level::ScriptsReloadEnd;
|
||||||
#endif
|
#endif
|
||||||
Array<String> Level::Tags;
|
|
||||||
String Level::Layers[32];
|
String Level::Layers[32];
|
||||||
|
|
||||||
bool LevelImpl::spawnActor(Actor* actor, Actor* parent)
|
bool LevelImpl::spawnActor(Actor* actor, Actor* parent)
|
||||||
@@ -226,8 +225,7 @@ void LayersAndTagsSettings::Apply()
|
|||||||
// Tags/Layers are stored as index in actors so collection change would break the linkage
|
// Tags/Layers are stored as index in actors so collection change would break the linkage
|
||||||
for (auto& tag : Tags)
|
for (auto& tag : Tags)
|
||||||
{
|
{
|
||||||
if (!Level::Tags.Contains(tag))
|
Tags::Get(tag);
|
||||||
Level::Tags.Add(tag);
|
|
||||||
}
|
}
|
||||||
for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++)
|
for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++)
|
||||||
{
|
{
|
||||||
@@ -735,17 +733,6 @@ void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid scen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 Level::GetOrAddTag(const StringView& tag)
|
|
||||||
{
|
|
||||||
int32 index = Tags.Find(tag);
|
|
||||||
if (index == INVALID_INDEX)
|
|
||||||
{
|
|
||||||
index = Tags.Count();
|
|
||||||
Tags.AddOne() = tag;
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 Level::GetNonEmptyLayerNamesCount()
|
int32 Level::GetNonEmptyLayerNamesCount()
|
||||||
{
|
{
|
||||||
int32 result = 31;
|
int32 result = 31;
|
||||||
|
|||||||
@@ -445,23 +445,11 @@ public:
|
|||||||
static void ConstructParentActorsTreeList(const Array<Actor*>& input, Array<Actor*>& output);
|
static void ConstructParentActorsTreeList(const Array<Actor*>& input, Array<Actor*>& output);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
|
||||||
/// The tags names.
|
|
||||||
/// </summary>
|
|
||||||
static Array<String> Tags;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The layers names.
|
/// The layers names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static String Layers[32];
|
static String Layers[32];
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or adds the tag (returns the tag index).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tag">The tag.</param>
|
|
||||||
/// <returns>The tag index.</returns>
|
|
||||||
static int32 GetOrAddTag(const StringView& tag);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of non empty layer names (from the beginning, trims the last ones).
|
/// Gets the amount of non empty layer names (from the beginning, trims the last ones).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
44
Source/Engine/Level/Tags.cpp
Normal file
44
Source/Engine/Level/Tags.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#include "Tags.h"
|
||||||
|
#include "Engine/Core/Types/String.h"
|
||||||
|
#include "Engine/Core/Types/StringView.h"
|
||||||
|
|
||||||
|
Array<String> Tags::List;
|
||||||
|
#if !BUILD_RELEASE
|
||||||
|
FLAXENGINE_API String* TagsListDebug = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const String& Tag::ToString() const
|
||||||
|
{
|
||||||
|
return Index >= 0 && Index < Tags::List.Count() ? Tags::List.Get()[Index] : String::Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tag::operator==(const StringView& other) const
|
||||||
|
{
|
||||||
|
return ToString() == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tag::operator!=(const StringView& other) const
|
||||||
|
{
|
||||||
|
return ToString() != other;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag Tags::Get(const StringView& tagName)
|
||||||
|
{
|
||||||
|
Tag tag = List.Find(tagName);
|
||||||
|
if (tag.Index == -1 && tagName.HasChars())
|
||||||
|
{
|
||||||
|
tag.Index = List.Count();
|
||||||
|
List.AddOne() = tagName;
|
||||||
|
#if !BUILD_RELEASE
|
||||||
|
TagsListDebug = List.Get();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& Tags::GetTagName(int32 tag)
|
||||||
|
{
|
||||||
|
return Tag(tag).ToString();
|
||||||
|
}
|
||||||
116
Source/Engine/Level/Tags.cs
Normal file
116
Source/Engine/Level/Tags.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace FlaxEngine
|
||||||
|
{
|
||||||
|
partial struct Tag : IEquatable<Tag>, IEquatable<string>, IComparable, IComparable<Tag>, IComparable<string>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default <see cref="Tag"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static Tag Default => new Tag(-1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Tag" /> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The tag index.</param>
|
||||||
|
public Tag(int index)
|
||||||
|
{
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Runtime.Serialization.OnDeserializing]
|
||||||
|
internal void OnDeserializing(System.Runtime.Serialization.StreamingContext context)
|
||||||
|
{
|
||||||
|
// Initialize structure with default values to replicate C++ deserialization behavior
|
||||||
|
this.Index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two tags.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The lft tag.</param>
|
||||||
|
/// <param name="right">The right tag.</param>
|
||||||
|
/// <returns>True if both values are equal, otherwise false.</returns>
|
||||||
|
public static bool operator ==(Tag left, Tag right)
|
||||||
|
{
|
||||||
|
return left.Index == right.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two tags.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The lft tag.</param>
|
||||||
|
/// <param name="right">The right tag.</param>
|
||||||
|
/// <returns>True if both values are not equal, otherwise false.</returns>
|
||||||
|
public static bool operator !=(Tag left, Tag right)
|
||||||
|
{
|
||||||
|
return left.Index == right.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if tag is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The tag to check.</param>
|
||||||
|
/// <returns>True if tag is valid, otherwise false.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator bool(Tag tag)
|
||||||
|
{
|
||||||
|
return tag.Index != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int CompareTo(object obj)
|
||||||
|
{
|
||||||
|
if (obj is string asString)
|
||||||
|
return CompareTo(asString);
|
||||||
|
if (obj is Tag asTag)
|
||||||
|
return CompareTo(asTag);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(Tag other)
|
||||||
|
{
|
||||||
|
return Index == other.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(string other)
|
||||||
|
{
|
||||||
|
return string.Equals(ToString(), other, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int CompareTo(Tag other)
|
||||||
|
{
|
||||||
|
return string.Compare(ToString(), ToString(), StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int CompareTo(string other)
|
||||||
|
{
|
||||||
|
return string.Compare(ToString(), other, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is Tag other && Index == other.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Tags.Internal_GetTagName(Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
Source/Engine/Level/Tags.h
Normal file
88
Source/Engine/Level/Tags.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/Core/Types/BaseTypes.h"
|
||||||
|
#include "Engine/Core/Collections/Array.h"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gameplay tag that represents a hierarchical name of the form 'X.Y.Z' (namespaces separated with a dot). Tags are defined in project LayersAndTagsSettings asset but can be also created from code.
|
||||||
|
/// </summary>
|
||||||
|
API_STRUCT(NoDefault) struct FLAXENGINE_API Tag
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(Tag);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index of the tag (in global Level.Tags list).
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD() int32 Index = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the tag name.
|
||||||
|
/// </summary>
|
||||||
|
const String& ToString() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Tag() = default;
|
||||||
|
|
||||||
|
FORCE_INLINE Tag(int32 index)
|
||||||
|
: Index(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE operator bool() const
|
||||||
|
{
|
||||||
|
return Index != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE bool operator==(const Tag& other) const
|
||||||
|
{
|
||||||
|
return Index == other.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE bool operator!=(const Tag& other) const
|
||||||
|
{
|
||||||
|
return Index != other.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const StringView& other) const;
|
||||||
|
bool operator!=(const StringView& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct TIsPODType<Tag>
|
||||||
|
{
|
||||||
|
enum { Value = true };
|
||||||
|
};
|
||||||
|
|
||||||
|
inline uint32 GetHash(const Tag& key)
|
||||||
|
{
|
||||||
|
return (uint32)key.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gameplay tags utilities.
|
||||||
|
/// </summary>
|
||||||
|
API_CLASS(Static) class FLAXENGINE_API Tags
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(Tags);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of all tags.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(ReadOnly) static Array<String> List;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or adds the tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagName">The tag name.</param>
|
||||||
|
/// <returns>The tag.</returns>
|
||||||
|
API_FUNCTION() static Tag Get(const StringView& tagName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
API_FUNCTION(NoProxy) static const String& GetTagName(int32 tag);
|
||||||
|
};
|
||||||
|
|
||||||
|
#if !BUILD_RELEASE
|
||||||
|
extern FLAXENGINE_API String* TagsListDebug; // Used by flax.natvis
|
||||||
|
#endif
|
||||||
@@ -215,4 +215,10 @@
|
|||||||
</Expand>
|
</Expand>
|
||||||
</Type>
|
</Type>
|
||||||
|
|
||||||
|
<!-- Tag -->
|
||||||
|
<Type Name="Tag">
|
||||||
|
<DisplayString Condition="Index == -1">None</DisplayString>
|
||||||
|
<DisplayString Condition="Index != -1">Tag={TagsListDebug[Index]}</DisplayString>
|
||||||
|
</Type>
|
||||||
|
|
||||||
</AutoVisualizer>
|
</AutoVisualizer>
|
||||||
|
|||||||
Reference in New Issue
Block a user