Merge branch '1.5' into dotnet7
# Conflicts: # Source/Editor/Managed/ManagedEditor.Internal.cpp # Source/Engine/Core/Config/LayersAndTagsSettings.cs
This commit is contained in:
BIN
Content/Shaders/AtmospherePreCompute.flax
(Stored with Git LFS)
BIN
Content/Shaders/AtmospherePreCompute.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/BakeLightmap.flax
(Stored with Git LFS)
BIN
Content/Shaders/BakeLightmap.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/BitonicSort.flax
(Stored with Git LFS)
BIN
Content/Shaders/BitonicSort.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/ColorGrading.flax
(Stored with Git LFS)
BIN
Content/Shaders/ColorGrading.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/DebugDraw.flax
(Stored with Git LFS)
BIN
Content/Shaders/DebugDraw.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Editor/LightmapUVsDensity.flax
(Stored with Git LFS)
BIN
Content/Shaders/Editor/LightmapUVsDensity.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Editor/MaterialComplexity.flax
(Stored with Git LFS)
BIN
Content/Shaders/Editor/MaterialComplexity.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Editor/QuadOverdraw.flax
(Stored with Git LFS)
BIN
Content/Shaders/Editor/QuadOverdraw.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Editor/VertexColors.flax
(Stored with Git LFS)
BIN
Content/Shaders/Editor/VertexColors.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/EyeAdaptation.flax
(Stored with Git LFS)
BIN
Content/Shaders/EyeAdaptation.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/FXAA.flax
(Stored with Git LFS)
BIN
Content/Shaders/FXAA.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Fog.flax
(Stored with Git LFS)
BIN
Content/Shaders/Fog.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Forward.flax
(Stored with Git LFS)
BIN
Content/Shaders/Forward.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/GBuffer.flax
(Stored with Git LFS)
BIN
Content/Shaders/GBuffer.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/GPUParticlesSorting.flax
(Stored with Git LFS)
BIN
Content/Shaders/GPUParticlesSorting.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/GUI.flax
(Stored with Git LFS)
BIN
Content/Shaders/GUI.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Histogram.flax
(Stored with Git LFS)
BIN
Content/Shaders/Histogram.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Lights.flax
(Stored with Git LFS)
BIN
Content/Shaders/Lights.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/MotionBlur.flax
(Stored with Git LFS)
BIN
Content/Shaders/MotionBlur.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/MultiScaler.flax
(Stored with Git LFS)
BIN
Content/Shaders/MultiScaler.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/PostProcessing.flax
(Stored with Git LFS)
BIN
Content/Shaders/PostProcessing.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/ProbesFilter.flax
(Stored with Git LFS)
BIN
Content/Shaders/ProbesFilter.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Quad.flax
(Stored with Git LFS)
BIN
Content/Shaders/Quad.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Reflections.flax
(Stored with Git LFS)
BIN
Content/Shaders/Reflections.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/SMAA.flax
(Stored with Git LFS)
BIN
Content/Shaders/SMAA.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/SSAO.flax
(Stored with Git LFS)
BIN
Content/Shaders/SSAO.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Shadows.flax
(Stored with Git LFS)
BIN
Content/Shaders/Shadows.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/Sky.flax
(Stored with Git LFS)
BIN
Content/Shaders/Sky.flax
(Stored with Git LFS)
Binary file not shown.
@@ -95,19 +95,17 @@ namespace FlaxEditor.Content
|
||||
{
|
||||
if (!_folder.CanRename)
|
||||
return;
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
|
||||
|
||||
// Start renaming the folder
|
||||
var dialog = RenamePopup.Show(this, HeaderRect, _folder.ShortName, false);
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(false);
|
||||
var dialog = RenamePopup.Show(this, TextRect, _folder.ShortName, false);
|
||||
dialog.Tag = _folder;
|
||||
dialog.Renamed += popup =>
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Rename((ContentFolder)popup.Tag, popup.Text);
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
|
||||
};
|
||||
dialog.Closed += popup =>
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true);
|
||||
};
|
||||
dialog.Closed += popup => { Editor.Instance.Windows.ContentWin.ScrollingOnTreeView(true); };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -67,19 +67,21 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (targetType.Type.GetArrayRank() != 1)
|
||||
return new GenericEditor(); // Not-supported multidimensional array
|
||||
|
||||
// Allow using custom editor for array of custom type
|
||||
var customEditorType = Internal_GetCustomEditor(targetType.Type);
|
||||
if (customEditorType != null)
|
||||
return (CustomEditor)Activator.CreateInstance(customEditorType);
|
||||
|
||||
return new ArrayEditor();
|
||||
}
|
||||
var targetTypeType = TypeUtils.GetType(targetType);
|
||||
if (canUseRefPicker)
|
||||
{
|
||||
if (typeof(Asset).IsAssignableFrom(targetTypeType))
|
||||
{
|
||||
return new AssetRefEditor();
|
||||
}
|
||||
if (typeof(FlaxEngine.Object).IsAssignableFrom(targetTypeType))
|
||||
{
|
||||
return new FlaxObjectRefEditor();
|
||||
}
|
||||
}
|
||||
|
||||
// Use custom editor
|
||||
@@ -87,11 +89,9 @@ namespace FlaxEditor.CustomEditors
|
||||
var checkType = targetTypeType;
|
||||
do
|
||||
{
|
||||
var type = Internal_GetCustomEditor(checkType);
|
||||
if (type != null)
|
||||
{
|
||||
return (CustomEditor)Activator.CreateInstance(type);
|
||||
}
|
||||
var customEditorType = Internal_GetCustomEditor(checkType);
|
||||
if (customEditorType != null)
|
||||
return (CustomEditor)Activator.CreateInstance(customEditorType);
|
||||
checkType = checkType.BaseType;
|
||||
|
||||
// Skip if cannot use ref editors
|
||||
@@ -108,23 +108,17 @@ namespace FlaxEditor.CustomEditors
|
||||
|
||||
// Select default editor (based on type)
|
||||
if (targetType.IsEnum)
|
||||
{
|
||||
return new EnumEditor();
|
||||
}
|
||||
if (targetType.IsGenericType)
|
||||
if (targetType.IsGenericType)
|
||||
{
|
||||
if (targetTypeType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
return new DictionaryEditor();
|
||||
}
|
||||
|
||||
// Use custom editor
|
||||
var genericTypeDefinition = targetType.GetGenericTypeDefinition();
|
||||
var type = Internal_GetCustomEditor(genericTypeDefinition);
|
||||
if (type != null)
|
||||
{
|
||||
return (CustomEditor)Activator.CreateInstance(type);
|
||||
}
|
||||
var customEditorType = Internal_GetCustomEditor(genericTypeDefinition);
|
||||
if (customEditorType != null)
|
||||
return (CustomEditor)Activator.CreateInstance(customEditorType);
|
||||
}
|
||||
|
||||
// The most generic editor
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
}
|
||||
cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
|
||||
cm.SortItems();
|
||||
cm.Show(this, button.BottomLeft);
|
||||
cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -407,6 +407,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
public class UIControlControlEditor : GenericEditor
|
||||
{
|
||||
private Type _cachedType;
|
||||
private bool _anchorDropDownClosed = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
@@ -498,7 +499,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
ItemInfo maxItem = new ItemInfo(maxInfo);
|
||||
|
||||
GroupElement ng = main.Group("Anchors", true);
|
||||
ng.Panel.Close(false);
|
||||
ng.Panel.IsClosed = _anchorDropDownClosed;
|
||||
ng.Panel.IsClosedChanged += panel => _anchorDropDownClosed = panel.IsClosed;
|
||||
ng.Property("Min", minItem.GetValues(Values));
|
||||
ng.Property("Max", maxItem.GetValues(Values));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
Text = "...",
|
||||
TooltipText = "Edit...",
|
||||
Parent = _label,
|
||||
};
|
||||
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()
|
||||
{
|
||||
var menu = CreatePicker(Culture, value => { Culture = value; });
|
||||
@@ -147,8 +124,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (tree.IsLayoutLocked)
|
||||
return;
|
||||
root.LockChildrenRecursive();
|
||||
var query = searchBox.Text;
|
||||
UpdateFilter(root, query);
|
||||
Utilities.Utils.UpdateSearchPopupFilter(root, searchBox.Text);
|
||||
root.UnlockChildrenRecursive();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
desc.HasSizingFrame = false;
|
||||
OnWindowCreating(ref desc);
|
||||
_window = Platform.CreateWindow(ref desc);
|
||||
_window.GotFocus += OnWindowGotFocus;
|
||||
_window.LostFocus += OnWindowLostFocus;
|
||||
|
||||
// Attach to the window
|
||||
@@ -353,6 +354,15 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowGotFocus()
|
||||
{
|
||||
if (_childCM != null && _window && _window.IsForegroundWindow)
|
||||
{
|
||||
// Hide child if user clicked over parent (do it next frame to process other events before - eg. child windows focus loss)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(HideChild);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowLostFocus()
|
||||
{
|
||||
// Skip for parent menus (child should handle lost of focus)
|
||||
@@ -428,6 +438,21 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.Escape:
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
|
||||
56
Source/Editor/GUI/Input/SearchBox.cs
Normal file
56
Source/Editor/GUI/Input/SearchBox.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.GUI.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Search box control which can gather text search input from the user.
|
||||
/// </summary>
|
||||
public class SearchBox : TextBox
|
||||
{
|
||||
/// <summary>
|
||||
/// A button that clears the search bar.
|
||||
/// </summary>
|
||||
public Button ClearSearchButton { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Init search box
|
||||
/// </summary>
|
||||
public SearchBox()
|
||||
: this(false, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Init search box
|
||||
/// </summary>
|
||||
public SearchBox(bool isMultiline, float x, float y, float width = 120)
|
||||
: base(isMultiline, x, y, width)
|
||||
{
|
||||
WatermarkText = "Search...";
|
||||
|
||||
ClearSearchButton = new Button
|
||||
{
|
||||
Parent = this,
|
||||
Width = 14.0f,
|
||||
Height = 14.0f,
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Text = "",
|
||||
TooltipText = "Cancel Search.",
|
||||
BackgroundColor = TextColor,
|
||||
BorderColor = Color.Transparent,
|
||||
BackgroundColorHighlighted = Style.Current.ForegroundGrey,
|
||||
BorderColorHighlighted = Color.Transparent,
|
||||
BackgroundColorSelected = Style.Current.ForegroundGrey,
|
||||
BorderColorSelected = Color.Transparent,
|
||||
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Cross12),
|
||||
Visible = false,
|
||||
};
|
||||
ClearSearchButton.LocalY += 2;
|
||||
ClearSearchButton.LocalX -= 2;
|
||||
ClearSearchButton.Clicked += Clear;
|
||||
|
||||
TextChanged += () => ClearSearchButton.Visible = !string.IsNullOrEmpty(Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,6 +173,7 @@ namespace FlaxEditor.GUI.Input
|
||||
private void EndSliding()
|
||||
{
|
||||
_isSliding = false;
|
||||
EndEditOnClick = true;
|
||||
EndMouseCapture();
|
||||
if (_cursorChanged)
|
||||
{
|
||||
@@ -245,6 +246,7 @@ namespace FlaxEditor.GUI.Input
|
||||
_startSlideLocation = location;
|
||||
_startSlideValue = _value;
|
||||
StartMouseCapture(true);
|
||||
EndEditOnClick = false;
|
||||
|
||||
// Hide cursor and cache location
|
||||
Cursor = CursorType.Hidden;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -204,11 +205,10 @@ namespace FlaxEditor.GUI
|
||||
Size = new Float2(width, height);
|
||||
|
||||
// Search box
|
||||
_searchBox = new TextBox(false, 1, 1)
|
||||
_searchBox = new SearchBox(false, 1, 1)
|
||||
{
|
||||
Parent = this,
|
||||
Width = Width - 3,
|
||||
WatermarkText = "Search...",
|
||||
};
|
||||
_searchBox.TextChanged += OnSearchFilterChanged;
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ namespace FlaxEditor.GUI
|
||||
set => _inputField.Text = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text input field control.
|
||||
/// </summary>
|
||||
public TextBox InputField => _inputField;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RenamePopup"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -217,7 +217,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
/// <summary>
|
||||
/// Gets the arrow rectangle.
|
||||
/// </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>
|
||||
/// 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>
|
||||
/// Gets the drag over action type.
|
||||
/// </summary>
|
||||
@@ -277,7 +283,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
/// Initializes a new instance of the <see cref="TreeNode"/> class.
|
||||
/// </summary>
|
||||
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>
|
||||
protected virtual bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
|
||||
{
|
||||
// Toggle open state
|
||||
if (_opened)
|
||||
Collapse();
|
||||
else
|
||||
Expand();
|
||||
if (HasAnyVisibleChild)
|
||||
{
|
||||
// Toggle open state
|
||||
if (_opened)
|
||||
Collapse();
|
||||
else
|
||||
Expand();
|
||||
}
|
||||
|
||||
// Handled
|
||||
return true;
|
||||
@@ -754,7 +763,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
}
|
||||
|
||||
// Check if mouse hits arrow
|
||||
if (HasAnyVisibleChild && _mouseOverArrow)
|
||||
if (_mouseOverArrow && HasAnyVisibleChild)
|
||||
{
|
||||
// Toggle open state
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,15 +328,8 @@ namespace CustomEditorsUtilInternal
|
||||
}
|
||||
}
|
||||
|
||||
namespace LayersAndTagsSettingsInternal1
|
||||
namespace LayersAndTagsSettingsInternal
|
||||
{
|
||||
MonoArray* GetCurrentTags(int* tagsCount)
|
||||
{
|
||||
SCRIPTING_EXPORT("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags")
|
||||
*tagsCount = Level::Tags.Count();
|
||||
return MUtils::ToArray(Level::Tags);
|
||||
}
|
||||
|
||||
MonoArray* GetCurrentLayers(int* layersCount)
|
||||
{
|
||||
SCRIPTING_EXPORT("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers")
|
||||
@@ -1180,8 +1173,7 @@ public:
|
||||
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.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions);
|
||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal1::GetCurrentTags);
|
||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal1::GetCurrentLayers);
|
||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal::GetCurrentLayers);
|
||||
ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply);
|
||||
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen);
|
||||
ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset);
|
||||
|
||||
@@ -379,7 +379,7 @@ namespace FlaxEditor.Modules
|
||||
actor.StaticFlags = old.StaticFlags;
|
||||
actor.HideFlags = old.HideFlags;
|
||||
actor.Layer = old.Layer;
|
||||
actor.Tag = old.Tag;
|
||||
actor.Tags = old.Tags;
|
||||
actor.Name = old.Name;
|
||||
actor.IsActive = old.IsActive;
|
||||
|
||||
|
||||
@@ -741,7 +741,9 @@ namespace FlaxEditor.Modules
|
||||
return;
|
||||
|
||||
// Find layout to use
|
||||
var searchFolder = Globals.ProjectCacheFolder;
|
||||
var searchFolder = StringUtils.CombinePaths(Editor.LocalCachePath, "LayoutsCache");
|
||||
if (!Directory.Exists(searchFolder))
|
||||
Directory.CreateDirectory(searchFolder);
|
||||
var files = Directory.GetFiles(searchFolder, "Layout_*.xml", SearchOption.TopDirectoryOnly);
|
||||
var layouts = _menuWindowApplyWindowLayout.ContextMenu;
|
||||
layouts.DisposeAllItems();
|
||||
@@ -749,8 +751,11 @@ namespace FlaxEditor.Modules
|
||||
{
|
||||
var file = files[i];
|
||||
var name = file.Substring(searchFolder.Length + 8, file.Length - searchFolder.Length - 12);
|
||||
var button = layouts.AddButton(name, OnApplyLayoutButtonClicked);
|
||||
button.Tag = file;
|
||||
var nameCM = layouts.AddChildMenu(name);
|
||||
var applyButton = nameCM.ContextMenu.AddButton("Apply", OnApplyLayoutButtonClicked);
|
||||
applyButton.TooltipText = "Applies the selected layout.";
|
||||
nameCM.ContextMenu.AddButton("Delete", () => File.Delete(file)).TooltipText = "Permanently deletes the selected layout.";
|
||||
applyButton.Tag = file;
|
||||
}
|
||||
_menuWindowApplyWindowLayout.Enabled = files.Length > 0;
|
||||
}
|
||||
|
||||
@@ -193,6 +193,8 @@ namespace FlaxEditor.Modules
|
||||
/// <returns>Editor window or null if cannot find any window.</returns>
|
||||
public EditorWindow FindEditor(ContentItem item)
|
||||
{
|
||||
if (item == null)
|
||||
return null;
|
||||
for (int i = 0; i < Windows.Count; i++)
|
||||
{
|
||||
var win = Windows[i];
|
||||
@@ -556,7 +558,7 @@ namespace FlaxEditor.Modules
|
||||
|
||||
base.OnSubmit();
|
||||
|
||||
var path = StringUtils.CombinePaths(Globals.ProjectCacheFolder, "Layout_" + name + ".xml");
|
||||
var path = StringUtils.CombinePaths(Editor.LocalCachePath, "LayoutsCache", "Layout_" + name + ".xml");
|
||||
Editor.Instance.Windows.SaveLayout(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
(window as PrefabWindow).ScrollingOnTreeView(false);
|
||||
|
||||
// Start renaming the actor
|
||||
var dialog = RenamePopup.Show(this, HeaderRect, _actorNode.Name, false);
|
||||
var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false);
|
||||
dialog.Renamed += OnRenamed;
|
||||
dialog.Closed += popup =>
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -128,10 +129,9 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
Size = new Float2(320, 220);
|
||||
|
||||
// Search box
|
||||
_searchBox = new TextBox(false, 1, 1)
|
||||
_searchBox = new SearchBox(false, 1, 1)
|
||||
{
|
||||
Width = Width - 3,
|
||||
WatermarkText = "Search...",
|
||||
Parent = this
|
||||
};
|
||||
_searchBox.TextChanged += OnSearchFilterChanged;
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
@@ -949,17 +950,17 @@ namespace FlaxEditor.Utilities
|
||||
/// </summary>
|
||||
/// <param name="searchBox">The search box.</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>
|
||||
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
|
||||
{
|
||||
Size = new Float2(320, 220),
|
||||
Size = new Float2(320, 220 + headerHeight),
|
||||
};
|
||||
searchBox = new TextBox(false, 1, 1)
|
||||
searchBox = new SearchBox(false, 1, headerHeight + 1)
|
||||
{
|
||||
Width = menu.Width - 3,
|
||||
WatermarkText = "Search...",
|
||||
Parent = menu,
|
||||
};
|
||||
var panel1 = new Panel(ScrollBars.Vertical)
|
||||
@@ -980,6 +981,33 @@ namespace FlaxEditor.Utilities
|
||||
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>
|
||||
/// Gets the asset name relative to the project root folder (without asset file extension)
|
||||
/// </summary>
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace FlaxEditor.Windows
|
||||
"Stefan Brandmair",
|
||||
"Lukáš Jech",
|
||||
"Jean-Baptiste Perrier",
|
||||
"Chandler Cox",
|
||||
});
|
||||
authors.Sort();
|
||||
var authorsLabel = new Label(4, topParentControl.Bottom + 20, Width - 8, 70)
|
||||
|
||||
@@ -6,6 +6,7 @@ using FlaxEditor.Content;
|
||||
using FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Viewport;
|
||||
using FlaxEngine;
|
||||
@@ -124,10 +125,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(0, 0, 0, 18 + 6),
|
||||
};
|
||||
_searchBox = new TextBox
|
||||
_searchBox = new SearchBox()
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
WatermarkText = "Search...",
|
||||
Parent = headerPanel,
|
||||
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
|
||||
};
|
||||
|
||||
@@ -114,10 +114,9 @@ namespace FlaxEditor.Windows
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(0, 0, 0, 18 + 6),
|
||||
};
|
||||
_foldersSearchBox = new TextBox
|
||||
_foldersSearchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
WatermarkText = "Search...",
|
||||
Parent = headerPanel,
|
||||
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
|
||||
};
|
||||
@@ -150,10 +149,9 @@ namespace FlaxEditor.Windows
|
||||
Parent = _split.Panel2,
|
||||
};
|
||||
const float viewDropdownWidth = 50.0f;
|
||||
_itemsSearchBox = new TextBox
|
||||
_itemsSearchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
WatermarkText = "Search...",
|
||||
Parent = contentItemsSearchPanel,
|
||||
Bounds = new Rectangle(viewDropdownWidth + 8, 4, contentItemsSearchPanel.Width - 12 - viewDropdownWidth, 18),
|
||||
};
|
||||
@@ -838,13 +836,20 @@ namespace FlaxEditor.Windows
|
||||
};
|
||||
_root.Expand(true);
|
||||
|
||||
// Add game project on top, plugins in the middle and engine at bottom
|
||||
_root.AddChild(Editor.ContentDatabase.Game);
|
||||
foreach (var project in Editor.ContentDatabase.Projects)
|
||||
{
|
||||
project.SortChildrenRecursive();
|
||||
if (project == Editor.ContentDatabase.Game || project == Editor.ContentDatabase.Engine)
|
||||
continue;
|
||||
_root.AddChild(project);
|
||||
}
|
||||
_root.AddChild(Editor.ContentDatabase.Engine);
|
||||
|
||||
Editor.ContentDatabase.Game?.Expand(true);
|
||||
_tree.Margin = new Margin(0.0f, 0.0f, -16.0f, 2.0f); // Hide root node
|
||||
_tree.AddChild(_root);
|
||||
_root.SortChildrenRecursive();
|
||||
|
||||
// Setup navigation
|
||||
_navigationUnlocked = true;
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -158,9 +159,8 @@ namespace FlaxEditor.Windows
|
||||
Parent = this,
|
||||
};
|
||||
_viewDropdown.Clicked += OnViewButtonClicked;
|
||||
_searchBox = new TextBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 2 - _scrollSize)
|
||||
_searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 2 - _scrollSize)
|
||||
{
|
||||
WatermarkText = "Search...",
|
||||
Parent = this,
|
||||
};
|
||||
_searchBox.TextChanged += Refresh;
|
||||
|
||||
@@ -133,7 +133,9 @@ namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
resource = new Resource
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
Name = gpuResource.Name,
|
||||
#endif
|
||||
Type = gpuResource.ResourceType,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.GUI;
|
||||
using FlaxEditor.Scripting;
|
||||
@@ -49,10 +50,9 @@ namespace FlaxEditor.Windows
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(0, 0, 0, 18 + 6),
|
||||
};
|
||||
_searchBox = new TextBox
|
||||
_searchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
WatermarkText = "Search...",
|
||||
Parent = headerPanel,
|
||||
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ using FlaxEditor;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Docking;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface;
|
||||
@@ -224,10 +225,9 @@ namespace FlaxEngine.Windows.Search
|
||||
Parent = topPanel,
|
||||
};
|
||||
optionsButton.ButtonClicked += OnOptionsDropdownClicked;
|
||||
_searchBox = new TextBox
|
||||
_searchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
WatermarkText = "Search...",
|
||||
Parent = topPanel,
|
||||
Bounds = new Rectangle(optionsButton.Right + 2.0f, 2, topPanel.Width - 4.0f - optionsButton.Width, 18.0f),
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Scripting;
|
||||
@@ -130,10 +131,9 @@ namespace FlaxEditor.Windows
|
||||
};
|
||||
|
||||
_groupSearch = CreateGroupWithList(_actorGroups, "Search", 26);
|
||||
_searchBox = new TextBox
|
||||
_searchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
WatermarkText = "Search...",
|
||||
Parent = _groupSearch.Parent.Parent,
|
||||
Bounds = new Rectangle(4, 4, _actorGroups.Width - 8, 18),
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Asset.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Serialization/Json.h"
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Tools/AudioTool/AudioDecoder.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Audio/Config.h"
|
||||
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
|
||||
@@ -32,12 +32,14 @@ CreateAssetResult ImportShader::Import(CreateAssetContext& context)
|
||||
LOG(Warning, "Empty shader source file.");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
context.Data.Header.Chunks[SourceCodeChunk]->Data.Allocate(sourceCodeSize + 1);
|
||||
const auto sourceCode = context.Data.Header.Chunks[SourceCodeChunk]->Get();
|
||||
const auto& sourceCodeChunk = context.Data.Header.Chunks[SourceCodeChunk];
|
||||
sourceCodeChunk->Data.Allocate(sourceCodeSize + 1);
|
||||
const auto sourceCode = sourceCodeChunk->Get();
|
||||
Platform::MemoryCopy(sourceCode, sourceCodeText.Get(), sourceCodeSize);
|
||||
|
||||
// Encrypt source code
|
||||
Encryption::EncryptBytes(sourceCode, sourceCodeSize);
|
||||
sourceCode[sourceCodeSize] = 0;
|
||||
|
||||
// Set Custom Data with Header
|
||||
ShaderStorage::Header20 shaderHeader;
|
||||
|
||||
@@ -872,8 +872,9 @@ public:
|
||||
if (!(data[i] == otherData[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename OtherT = T, typename OtherAllocationType = AllocationType>
|
||||
|
||||
@@ -343,8 +343,9 @@ public:
|
||||
if (!(Get(i) == other.Get(i)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename OtherAllocationType = AllocationType>
|
||||
|
||||
@@ -22,15 +22,6 @@ namespace FlaxEditor.Content.Settings
|
||||
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)]
|
||||
public string[] Layers = new string[32];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tags collection.
|
||||
/// </summary>
|
||||
/// <returns>The tags collection.</returns>
|
||||
internal static string[] GetCurrentTags()
|
||||
{
|
||||
return GetCurrentTags(out int _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current layer names (max 32 items but trims last empty items).
|
||||
/// </summary>
|
||||
@@ -40,10 +31,6 @@ namespace FlaxEditor.Content.Settings
|
||||
return GetCurrentLayers(out int _);
|
||||
}
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))]
|
||||
[return: MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = "tagCount")]
|
||||
internal static partial string[] GetCurrentTags(out int tagCount);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))]
|
||||
[return: MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = "layerCount")]
|
||||
internal static partial string[] GetCurrentLayers(out int layerCount);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Config.h"
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
|
||||
/// <summary>
|
||||
/// The foliage instances scaling modes.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/Assets/MaterialBase.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Types.h"
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Content/Assets/Texture.h"
|
||||
#include "Engine/Content/Assets/MaterialBase.h"
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ struct AxisData
|
||||
|
||||
namespace InputImpl
|
||||
{
|
||||
Dictionary<StringView, ActionData> Actions;
|
||||
Dictionary<StringView, AxisData> Axes;
|
||||
Dictionary<String, ActionData> Actions;
|
||||
Dictionary<String, AxisData> Axes;
|
||||
bool GamepadsChanged = true;
|
||||
Array<AxisEvaluation> AxesValues;
|
||||
InputDevice::EventQueue InputEvents;
|
||||
|
||||
@@ -73,13 +73,12 @@ Actor::Actor(const SpawnParams& params)
|
||||
, _isPrefabRoot(false)
|
||||
, _isEnabled(false)
|
||||
, _layer(0)
|
||||
, _tag(ACTOR_TAG_INVALID)
|
||||
, _scene(nullptr)
|
||||
, _staticFlags(StaticFlags::FullyStatic)
|
||||
, _localTransform(Transform::Identity)
|
||||
, _transform(Transform::Identity)
|
||||
, _sphere(BoundingSphere::Empty)
|
||||
, _box(BoundingBox::Zero)
|
||||
, _scene(nullptr)
|
||||
, _physicsScene(nullptr)
|
||||
, 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
|
||||
{
|
||||
return Level::Layers[_layer];
|
||||
}
|
||||
|
||||
const String& Actor::GetTag() const
|
||||
bool Actor::HasTag() const
|
||||
{
|
||||
if (HasTag())
|
||||
{
|
||||
return Level::Tags[_tag];
|
||||
}
|
||||
return String::Empty;
|
||||
return Tags.Count() != 0;
|
||||
}
|
||||
|
||||
bool Actor::HasTag(const Tag& tag) const
|
||||
{
|
||||
return Tags.Contains(tag);
|
||||
}
|
||||
|
||||
bool Actor::HasTag(const StringView& tag) const
|
||||
{
|
||||
return Tags.Contains(tag);
|
||||
}
|
||||
|
||||
void Actor::SetLayer(int32 layerIndex)
|
||||
@@ -463,51 +463,10 @@ void Actor::SetLayer(int32 layerIndex)
|
||||
layerIndex = Math::Clamp(layerIndex, 0, 31);
|
||||
if (layerIndex == _layer)
|
||||
return;
|
||||
|
||||
_layer = layerIndex;
|
||||
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)
|
||||
{
|
||||
if (_name == value)
|
||||
@@ -897,10 +856,21 @@ void Actor::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
SERIALIZE_MEMBER(StaticFlags, _staticFlags);
|
||||
SERIALIZE(HideFlags);
|
||||
SERIALIZE_MEMBER(Layer, _layer);
|
||||
if (!other || _tag != other->_tag)
|
||||
if (!other || Tags != other->Tags)
|
||||
{
|
||||
stream.JKEY("Tag");
|
||||
stream.String(GetTag());
|
||||
if (Tags.Count() == 1)
|
||||
{
|
||||
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)
|
||||
@@ -996,14 +966,27 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
_staticFlags |= StaticFlags::Navigation;
|
||||
}
|
||||
|
||||
// Resolve tag (it may be missing in the current configuration
|
||||
const auto tag = stream.FindMember("Tag");
|
||||
if (tag != stream.MemberEnd())
|
||||
{
|
||||
if (tag->value.IsString() && tag->value.GetStringLength())
|
||||
{
|
||||
const String tagName = tag->value.GetText();
|
||||
_tag = Level::GetOrAddTag(tagName);
|
||||
Tags.Clear();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,9 @@ namespace FlaxEngine
|
||||
/// <returns>The child actor.</returns>
|
||||
public Actor AddChild(Type type)
|
||||
{
|
||||
if (type.IsAbstract)
|
||||
return null;
|
||||
|
||||
var result = (Actor)New(type);
|
||||
result.SetParent(this, false, false);
|
||||
return result;
|
||||
@@ -135,6 +138,9 @@ namespace FlaxEngine
|
||||
/// <returns>The child actor.</returns>
|
||||
public T AddChild<T>() where T : Actor
|
||||
{
|
||||
if (typeof(T).IsAbstract)
|
||||
return null;
|
||||
|
||||
var result = New<T>();
|
||||
result.SetParent(this, false, false);
|
||||
return result;
|
||||
@@ -172,6 +178,9 @@ namespace FlaxEngine
|
||||
var result = GetChild<T>();
|
||||
if (result == null)
|
||||
{
|
||||
if (typeof(T).IsAbstract)
|
||||
return null;
|
||||
|
||||
result = New<T>();
|
||||
result.SetParent(this, false, false);
|
||||
}
|
||||
@@ -185,6 +194,9 @@ namespace FlaxEngine
|
||||
/// <returns>The created script instance, null otherwise.</returns>
|
||||
public Script AddScript(Type type)
|
||||
{
|
||||
if (type.IsAbstract)
|
||||
return null;
|
||||
|
||||
var script = (Script)New(type);
|
||||
script.Parent = this;
|
||||
return script;
|
||||
@@ -197,6 +209,9 @@ namespace FlaxEngine
|
||||
/// <returns>The created script instance, null otherwise.</returns>
|
||||
public T AddScript<T>() where T : Script
|
||||
{
|
||||
if (typeof(T).IsAbstract)
|
||||
return null;
|
||||
|
||||
var script = New<T>();
|
||||
script.Parent = this;
|
||||
return script;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "SceneObject.h"
|
||||
#include "Tags.h"
|
||||
#include "Engine/Core/Types/Span.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Core/Math/BoundingBox.h"
|
||||
@@ -19,9 +20,6 @@ class PhysicsScene;
|
||||
class SceneRendering;
|
||||
class SceneRenderTask;
|
||||
|
||||
// Maximum tag index is used as an invalid value
|
||||
#define ACTOR_TAG_INVALID 255
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all actor objects on the scene.
|
||||
/// </summary>
|
||||
@@ -34,7 +32,6 @@ API_CLASS(Abstract) class FLAXENGINE_API Actor : public SceneObject
|
||||
friend SceneRendering;
|
||||
friend Prefab;
|
||||
friend PrefabInstanceData;
|
||||
|
||||
protected:
|
||||
int16 _isActive : 1;
|
||||
int16 _isActiveInHierarchy : 1;
|
||||
@@ -43,7 +40,6 @@ protected:
|
||||
int16 _drawNoCulling : 1;
|
||||
int16 _drawCategory : 4;
|
||||
byte _layer;
|
||||
byte _tag;
|
||||
StaticFlags _staticFlags;
|
||||
Transform _localTransform;
|
||||
Transform _transform;
|
||||
@@ -77,6 +73,11 @@ public:
|
||||
API_FIELD(Attributes="HideInEditor, NoSerialize")
|
||||
HideFlags HideFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Actor tags collection.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68)") Array<Tag> Tags;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the object layer (index). Can be used for selective rendering or ignoring raycasts.
|
||||
@@ -96,26 +97,10 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor tag (index). Can be used to identify the objects.
|
||||
/// Sets the layer.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 GetTagIndex() const
|
||||
{
|
||||
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;
|
||||
/// <param name="layerIndex">The index of the layer.</param>
|
||||
API_PROPERTY() void SetLayer(int32 layerIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the layer.
|
||||
@@ -123,28 +108,21 @@ public:
|
||||
API_PROPERTY() const String& GetLayerName() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the tag.
|
||||
/// Determines whether this actor has any tag assigned.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTagEditor\")")
|
||||
const String& GetTag() const;
|
||||
API_FUNCTION() bool HasTag() const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the layer.
|
||||
/// Determines whether this actor has given tag assigned (exact match).
|
||||
/// </summary>
|
||||
/// <param name="layerIndex">The index of the layer.</param>
|
||||
API_PROPERTY() void SetLayer(int32 layerIndex);
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
API_FUNCTION() bool HasTag(const Tag& tag) const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the tag.
|
||||
/// Determines whether this actor has given tag assigned (exact match).
|
||||
/// </summary>
|
||||
/// <param name="tagIndex">The index of the tag.</param>
|
||||
void SetTagIndex(int32 tagIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the tag.
|
||||
/// </summary>
|
||||
/// <param name="tagName">Name of the tag.</param>
|
||||
API_PROPERTY() void SetTag(const StringView& tagName);
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
API_FUNCTION() bool HasTag(const StringView& tag) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor name.
|
||||
@@ -229,6 +207,9 @@ public:
|
||||
T* result = (T*)GetChild(T::GetStaticClass());
|
||||
if (!result)
|
||||
{
|
||||
if (T::GetStaticClass()->IsAbstract())
|
||||
return nullptr;
|
||||
|
||||
result = New<T>();
|
||||
result->SetParent(this, false, false);
|
||||
}
|
||||
@@ -955,13 +936,6 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when tag gets changed.
|
||||
/// </summary>
|
||||
virtual void OnTagChanged()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when adding object to the game.
|
||||
/// </summary>
|
||||
|
||||
@@ -166,7 +166,6 @@ Action Level::ScriptsReload;
|
||||
Action Level::ScriptsReloaded;
|
||||
Action Level::ScriptsReloadEnd;
|
||||
#endif
|
||||
Array<String> Level::Tags;
|
||||
String Level::Layers[32];
|
||||
|
||||
bool LevelImpl::spawnActor(Actor* actor, Actor* parent)
|
||||
@@ -177,6 +176,12 @@ bool LevelImpl::spawnActor(Actor* actor, Actor* parent)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actor->GetType().ManagedClass->IsAbstract())
|
||||
{
|
||||
Log::Exception(TEXT("Cannot spawn abstract actor type."));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actor->Is<Scene>())
|
||||
{
|
||||
// Spawn scene
|
||||
@@ -226,8 +231,7 @@ void LayersAndTagsSettings::Apply()
|
||||
// Tags/Layers are stored as index in actors so collection change would break the linkage
|
||||
for (auto& tag : Tags)
|
||||
{
|
||||
if (!Level::Tags.Contains(tag))
|
||||
Level::Tags.Add(tag);
|
||||
Tags::Get(tag);
|
||||
}
|
||||
for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++)
|
||||
{
|
||||
@@ -735,17 +739,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 result = 31;
|
||||
@@ -768,6 +761,59 @@ int32 Level::GetLayerIndex(const StringView& layer)
|
||||
return result;
|
||||
}
|
||||
|
||||
Actor* FindActorRecursive(Actor* node, const Tag& tag)
|
||||
{
|
||||
if (node->HasTag(tag))
|
||||
return node;
|
||||
Actor* result = nullptr;
|
||||
for (Actor* child : node->Children)
|
||||
{
|
||||
result = FindActorRecursive(child, tag);
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Actor* Level::FindActor(const Tag& tag, Actor* root)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
if (root)
|
||||
return FindActorRecursive(root, tag);
|
||||
Actor* result = nullptr;
|
||||
for (Scene* scene : Scenes)
|
||||
{
|
||||
result = FindActorRecursive(scene, tag);
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
|
||||
{
|
||||
if (node->HasTag(tag))
|
||||
result.Add(node);
|
||||
for (Actor* child : node->Children)
|
||||
FindActorRecursive(child, tag, result);
|
||||
}
|
||||
|
||||
Array<Actor*> Level::FindActors(const Tag& tag, Actor* root)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
Array<Actor*> result;
|
||||
if (root)
|
||||
{
|
||||
FindActorRecursive(root, tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Scene* scene : Scenes)
|
||||
FindActorRecursive(scene, tag);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
@@ -16,6 +16,7 @@ class Engine;
|
||||
struct RenderView;
|
||||
struct RenderContext;
|
||||
struct RenderContextBatch;
|
||||
struct Tag;
|
||||
|
||||
/// <summary>
|
||||
/// The scene manager that contains the loaded scenes collection and spawns/deleted actors.
|
||||
@@ -445,23 +446,11 @@ public:
|
||||
static void ConstructParentActorsTreeList(const Array<Actor*>& input, Array<Actor*>& output);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The tags names.
|
||||
/// </summary>
|
||||
static Array<String> Tags;
|
||||
|
||||
/// <summary>
|
||||
/// The layers names.
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets the amount of non empty layer names (from the beginning, trims the last ones).
|
||||
/// </summary>
|
||||
@@ -473,6 +462,23 @@ public:
|
||||
/// </summary>
|
||||
API_FUNCTION() static int32 GetLayerIndex(const StringView& layer);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Tries to find the actor with the given tag (returns the first one found).
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag of the actor to search for.</param>
|
||||
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
|
||||
/// <returns>Found actor or null.</returns>
|
||||
API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the actors with the given tag (returns all found).
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag of the actor to search for.</param>
|
||||
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
|
||||
/// <returns>Found actors or empty if none.</returns>
|
||||
API_FUNCTION() static Array<Actor*> FindActors(const Tag& tag, Actor* root = nullptr);
|
||||
|
||||
private:
|
||||
// Actor API
|
||||
enum class ActorEventType
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Engine/Core/Math/Triangle.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Physics/CollisionData.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/Assets/RawDataAsset.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Engine/Core/Object.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Renderer/Lightmaps.h"
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
class SceneTicking;
|
||||
|
||||
125
Source/Engine/Level/Tags.cpp
Normal file
125
Source/Engine/Level/Tags.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Tags.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Serialization/SerializationFwd.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;
|
||||
}
|
||||
|
||||
void FLAXENGINE_API Serialization::Serialize(ISerializable::SerializeStream& stream, const Tag& v, const void* otherObj)
|
||||
{
|
||||
if (v.Index != -1)
|
||||
stream.String(v.ToString());
|
||||
else
|
||||
stream.String("", 0);
|
||||
}
|
||||
|
||||
void FLAXENGINE_API Serialization::Deserialize(ISerializable::DeserializeStream& stream, Tag& v, ISerializeModifier* modifier)
|
||||
{
|
||||
v = Tags::Get(stream.GetText());
|
||||
}
|
||||
|
||||
Tag Tags::Get(const StringView& tagName)
|
||||
{
|
||||
if (tagName.IsEmpty())
|
||||
return Tag();
|
||||
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;
|
||||
}
|
||||
|
||||
bool Tags::HasTag(const Array<Tag>& list, const Tag& tag)
|
||||
{
|
||||
if (tag.Index == -1)
|
||||
return false;
|
||||
const String& tagName = tag.ToString();
|
||||
for (const Tag& e : list)
|
||||
{
|
||||
const String& eName = e.ToString();
|
||||
if (e == tag || (eName.Length() > tagName.Length() + 1 && eName.StartsWith(tagName) && eName[tagName.Length()] == '.'))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Tags::HasTagExact(const Array<Tag>& list, const Tag& tag)
|
||||
{
|
||||
if (tag.Index == -1)
|
||||
return false;
|
||||
return list.Contains(tag);
|
||||
}
|
||||
|
||||
bool Tags::HasAny(const Array<Tag>& list, const Array<Tag>& tags)
|
||||
{
|
||||
for (const Tag& tag : tags)
|
||||
{
|
||||
if (HasTag(list, tag))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Tags::HasAnyExact(const Array<Tag>& list, const Array<Tag>& tags)
|
||||
{
|
||||
for (const Tag& tag : tags)
|
||||
{
|
||||
if (HasTagExact(list, tag))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Tags::HasAll(const Array<Tag>& list, const Array<Tag>& tags)
|
||||
{
|
||||
if (tags.IsEmpty())
|
||||
return true;
|
||||
for (const Tag& tag : tags)
|
||||
{
|
||||
if (!HasTag(list, tag))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Tags::HasAllExact(const Array<Tag>& list, const Array<Tag>& tags)
|
||||
{
|
||||
if (tags.IsEmpty())
|
||||
return true;
|
||||
for (const Tag& tag : tags)
|
||||
{
|
||||
if (!HasTagExact(list, tag))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const String& Tags::GetTagName(int32 tag)
|
||||
{
|
||||
return Tag(tag).ToString();
|
||||
}
|
||||
235
Source/Engine/Level/Tags.cs
Normal file
235
Source/Engine/Level/Tags.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
partial class Tags
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains a given tag (including parent tags check). For example, HasTag({"A.B"}, "A") returns true, for exact check use HasTagExact.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
|
||||
public static bool HasTag(this Tag[] list, Tag tag)
|
||||
{
|
||||
if (tag.Index == -1)
|
||||
return false;
|
||||
string tagName = tag.ToString();
|
||||
foreach (Tag e in list)
|
||||
{
|
||||
string eName = e.ToString();
|
||||
if (e == tag || (eName.Length > tagName.Length + 1 && eName.StartsWith(tagName) && eName[tagName.Length] == '.'))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains a given tag (exact match). For example, HasTag({"A.B"}, "A") returns false, for parents check use HasTag.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
|
||||
public static bool HasTagExact(this Tag[] list, Tag tag)
|
||||
{
|
||||
if (tag.Index == -1)
|
||||
return false;
|
||||
if (list == null)
|
||||
return false;
|
||||
foreach (Tag e in list)
|
||||
{
|
||||
if (e == tag)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains any of the given tags (including parent tags check). For example, HasAny({"A.B", "C"}, {"A"}) returns true, for exact check use HasAnyExact.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if any of of the given tags is contained by the list of tags.</returns>
|
||||
public static bool HasAny(this Tag[] list, Tag[] tags)
|
||||
{
|
||||
if (list == null)
|
||||
return false;
|
||||
foreach (Tag tag in tags)
|
||||
{
|
||||
if (HasTag(list, tag))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains any of the given tags (exact match). For example, HasAnyExact({"A.B", "C"}, {"A"}) returns false, for parents check use HasAny.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if any of the given tags is contained by the list of tags.</returns>
|
||||
public static bool HasAnyExact(this Tag[] list, Tag[] tags)
|
||||
{
|
||||
if (list == null)
|
||||
return false;
|
||||
foreach (Tag tag in tags)
|
||||
{
|
||||
if (HasTagExact(list, tag))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains all of the given tags (including parent tags check). For example, HasAll({"A.B", "C"}, {"A", "C"}) returns true, for exact check use HasAllExact.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if all of the given tags are contained by the list of tags. Returns true for empty list.</returns>
|
||||
public static bool HasAll(this Tag[] list, Tag[] tags)
|
||||
{
|
||||
if (tags == null || tags.Length == 0)
|
||||
return true;
|
||||
if (list == null || list.Length == 0)
|
||||
return false;
|
||||
foreach (Tag tag in tags)
|
||||
{
|
||||
if (!HasTag(list, tag))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains all of the given tags (exact match). For example, HasAllExact({"A.B", "C"}, {"A", "C"}) returns false, for parents check use HasAll.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if all of the given tags are contained by the list of tags. Returns true for empty list.</returns>
|
||||
public static bool HasAllExact(this Tag[] list, Tag[] tags)
|
||||
{
|
||||
if (tags == null || tags.Length == 0)
|
||||
return true;
|
||||
if (list == null || list.Length == 0)
|
||||
return false;
|
||||
foreach (Tag tag in tags)
|
||||
{
|
||||
if (!HasTagExact(list, tag))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Source/Engine/Level/Tags.h
Normal file
150
Source/Engine/Level/Tags.h
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/ISerializable.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;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
namespace Serialization
|
||||
{
|
||||
inline bool ShouldSerialize(const Tag& v, const void* otherObj)
|
||||
{
|
||||
return !otherObj || v != *(Tag*)otherObj;
|
||||
}
|
||||
void FLAXENGINE_API Serialize(ISerializable::SerializeStream& stream, const Tag& v, const void* otherObj);
|
||||
void FLAXENGINE_API Deserialize(ISerializable::DeserializeStream& stream, Tag& v, ISerializeModifier* modifier);
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
/// <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);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains a given tag (including parent tags check). For example, HasTag({"A.B"}, "A") returns true, for exact check use HasTagExact.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
|
||||
static bool HasTag(const Array<Tag>& list, const Tag& tag);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains a given tag (exact match). For example, HasTag({"A.B"}, "A") returns false, for parents check use HasTag.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
|
||||
static bool HasTagExact(const Array<Tag>& list, const Tag& tag);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains any of the given tags (including parent tags check). For example, HasAny({"A.B", "C"}, {"A"}) returns true, for exact check use HasAnyExact.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if any of of the given tags is contained by the list of tags.</returns>
|
||||
static bool HasAny(const Array<Tag>& list, const Array<Tag>& tags);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains any of the given tags (exact match). For example, HasAnyExact({"A.B", "C"}, {"A"}) returns false, for parents check use HasAny.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if any of the given tags is contained by the list of tags.</returns>
|
||||
static bool HasAnyExact(const Array<Tag>& list, const Array<Tag>& tags);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains all of the given tags (including parent tags check). For example, HasAll({"A.B", "C"}, {"A", "C"}) returns true, for exact check use HasAllExact.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if all of the given tags are contained by the list of tags. Returns true for empty list.</returns>
|
||||
static bool HasAll(const Array<Tag>& list, const Array<Tag>& tags);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains all of the given tags (exact match). For example, HasAllExact({"A.B", "C"}, {"A", "C"}) returns false, for parents check use HasAll.
|
||||
/// </summary>
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tags">The tags to check.</param>
|
||||
/// <returns>True if all of the given tags are contained by the list of tags. Returns true for empty list.</returns>
|
||||
static bool HasAllExact(const Array<Tag>& list, const Array<Tag>& tags);
|
||||
|
||||
private:
|
||||
API_FUNCTION(NoProxy) static const String& GetTagName(int32 tag);
|
||||
};
|
||||
|
||||
#if !BUILD_RELEASE
|
||||
extern FLAXENGINE_API String* TagsListDebug; // Used by flax.natvis
|
||||
#endif
|
||||
@@ -326,10 +326,14 @@ void NetworkManager::Stop()
|
||||
Delete(client);
|
||||
Clients.RemoveAt(i);
|
||||
}
|
||||
if (Mode == NetworkManagerMode::Host && LocalClient)
|
||||
{
|
||||
ClientDisconnected(LocalClient);
|
||||
LocalClient->State = NetworkConnectionState::Disconnected;
|
||||
}
|
||||
StopPeer();
|
||||
if (LocalClient)
|
||||
{
|
||||
LocalClient->State = NetworkConnectionState::Disconnected;
|
||||
Delete(LocalClient);
|
||||
LocalClient = nullptr;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
|
||||
/// <summary>
|
||||
/// Physical materials are used to define the response of a physical object when interacting dynamically with the world.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "Engine/Core/Types/Guid.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
|
||||
#if USE_EDITOR
|
||||
// Additional options used in editor for lightmaps baking
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "Engine/Core/Types/Variant.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "ManagedCLR/MAssemblyOptions.h"
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,30 @@
|
||||
#include "ScriptingType.h"
|
||||
#include "Types.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
// Helper utility to override vtable entry with automatic restore
|
||||
// See BindingsGenerator.Cpp.cs that generates virtuall method wrappers for scripting to properly call overriden base method
|
||||
struct FLAXENGINE_API VTableFunctionInjector
|
||||
{
|
||||
void** VTableAddr;
|
||||
void* OriginalValue;
|
||||
|
||||
VTableFunctionInjector(void* object, void* originalFunc, void* func)
|
||||
{
|
||||
void** vtable = *(void***)object;
|
||||
const int32 vtableIndex = GetVTableIndex(vtable, 200, originalFunc);
|
||||
VTableAddr = vtable + vtableIndex;
|
||||
OriginalValue = *VTableAddr;
|
||||
*VTableAddr = func;
|
||||
}
|
||||
|
||||
~VTableFunctionInjector()
|
||||
{
|
||||
*VTableAddr = OriginalValue;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if USE_MONO
|
||||
|
||||
extern "C" FLAXENGINE_API void mono_add_internal_call(const char* name, const void* method);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "ManagedCLR/MTypes.h"
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Engine/Core/Math/BoundingBox.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Content/Assets/MaterialBase.h"
|
||||
#include "Engine/Level/Scene/Lightmap.h"
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Level/LargeWorlds.h"
|
||||
#include "Engine/Level/Tags.h"
|
||||
#include <ThirdParty/catch2/catch.hpp>
|
||||
|
||||
TEST_CASE("LargeWorlds")
|
||||
@@ -16,3 +19,52 @@ TEST_CASE("LargeWorlds")
|
||||
CHECK(origin == Vector3(0, 0, LargeWorlds::ChunkSize * 1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Tags")
|
||||
{
|
||||
SECTION("Tag")
|
||||
{
|
||||
auto prevTags = Tags::List;
|
||||
|
||||
Tags::List = Array<String>({ TEXT("A"), TEXT("A.1"), TEXT("B"), TEXT("B.1"), });
|
||||
|
||||
auto a = Tags::Get(TEXT("A"));
|
||||
auto a1 = Tags::Get(TEXT("A.1"));
|
||||
auto b = Tags::Get(TEXT("B"));
|
||||
auto b1 = Tags::Get(TEXT("B.1"));
|
||||
auto c = Tags::Get(TEXT("C"));
|
||||
CHECK(a.Index == 0);
|
||||
CHECK(a1.Index == 1);
|
||||
CHECK(b.Index == 2);
|
||||
CHECK(b1.Index == 3);
|
||||
CHECK(c.Index == 4);
|
||||
|
||||
Tags::List = prevTags;
|
||||
}
|
||||
|
||||
SECTION("Tags")
|
||||
{
|
||||
auto prevTags = Tags::List;
|
||||
|
||||
Tags::List = Array<String>({ TEXT("A"), TEXT("A.1"), TEXT("B"), TEXT("B.1"), });
|
||||
|
||||
auto a = Tags::Get(TEXT("A"));
|
||||
auto a1 = Tags::Get(TEXT("A.1"));
|
||||
auto b = Tags::Get(TEXT("B"));
|
||||
auto b1 = Tags::Get(TEXT("B.1"));
|
||||
auto c = Tags::Get(TEXT("C"));
|
||||
|
||||
Array<Tag> list = { a1, b1 };
|
||||
|
||||
CHECK(Tags::HasTag(list, Tag()) == false);
|
||||
CHECK(Tags::HasTag(list, a1) == true);
|
||||
CHECK(Tags::HasTag(list, a) == true);
|
||||
CHECK(Tags::HasTag(list, c) == false);
|
||||
|
||||
CHECK(Tags::HasTagExact(list, a1) == true);
|
||||
CHECK(Tags::HasTagExact(list, a) == false);
|
||||
CHECK(Tags::HasTagExact(list, c) == false);
|
||||
|
||||
Tags::List = prevTags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "Engine/Core/Config.h"
|
||||
#include "Engine/Content/Assets/ModelBase.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||
#include "Engine/Animations/AnimationData.h"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "Engine/Render2D/SpriteAtlas.h"
|
||||
#include "Engine/Graphics/Textures/Types.h"
|
||||
#include "Engine/Graphics/Textures/GPUTexture.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
|
||||
class JsonWriter;
|
||||
|
||||
|
||||
@@ -71,6 +71,16 @@ namespace FlaxEngine.GUI
|
||||
/// </summary>
|
||||
public event Action<Button> ButtonClicked;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when users mouse enters the control.
|
||||
/// </summary>
|
||||
public event Action HoverBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when users mouse leaves the control.
|
||||
/// </summary>
|
||||
public event Action HoverEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush used for background drawing.
|
||||
/// </summary>
|
||||
@@ -232,6 +242,14 @@ namespace FlaxEngine.GUI
|
||||
Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
base.OnMouseEnter(location);
|
||||
|
||||
HoverBegin?.Invoke();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
@@ -240,6 +258,8 @@ namespace FlaxEngine.GUI
|
||||
OnPressEnd();
|
||||
}
|
||||
|
||||
HoverEnd?.Invoke();
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,12 @@ namespace FlaxEngine.GUI
|
||||
/// </summary>
|
||||
public event Action<KeyboardKeys> KeyUp;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the text box can end edit via left click outside of the control
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public bool EndEditOnClick { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this is a multiline text box control.
|
||||
/// </summary>
|
||||
@@ -1042,9 +1048,37 @@ namespace FlaxEngine.GUI
|
||||
// Animate view offset
|
||||
_viewOffset = isDeltaSlow ? _targetViewOffset : Float2.Lerp(_viewOffset, _targetViewOffset, deltaTime * 20.0f);
|
||||
|
||||
// Clicking outside of the text box will end text editing. Left will keep the value, right will restore original value
|
||||
if (_isEditing && EndEditOnClick)
|
||||
{
|
||||
if (!IsMouseOver && RootWindow.ContainsFocus)
|
||||
{
|
||||
if (Input.GetMouseButtonDown(MouseButton.Left))
|
||||
{
|
||||
RemoveFocus();
|
||||
}
|
||||
else if (Input.GetMouseButtonDown(MouseButton.Right))
|
||||
{
|
||||
RestoreTextFromStart();
|
||||
RemoveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the Text from the start.
|
||||
/// </summary>
|
||||
public void RestoreTextFromStart()
|
||||
{
|
||||
// Restore text from start
|
||||
SetSelection(-1);
|
||||
_text = _onStartEditValue;
|
||||
OnTextChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnGotFocus()
|
||||
{
|
||||
@@ -1300,13 +1334,10 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
case KeyboardKeys.Escape:
|
||||
{
|
||||
// Restore text from start
|
||||
SetSelection(-1);
|
||||
_text = _onStartEditValue;
|
||||
RestoreTextFromStart();
|
||||
|
||||
if (!IsNavFocused)
|
||||
RemoveFocus();
|
||||
OnTextChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -237,6 +237,41 @@ namespace Flax.Build.Bindings
|
||||
return $"({typeInfo}){value}";
|
||||
}
|
||||
|
||||
public static void GenerateCppVirtualWrapperCallBaseMethod(BuildData buildData, StringBuilder contents, VirtualClassInfo classInfo, FunctionInfo functionInfo, string scriptVTableBase, string scriptVTableOffset)
|
||||
{
|
||||
contents.AppendLine(" // Prevent stack overflow by calling native base method");
|
||||
if (buildData.Toolchain is Platforms.UnixToolchain)
|
||||
{
|
||||
// Clang compiler
|
||||
// TODO: secure VTableFunctionInjector with mutex (even at cost of performance)
|
||||
contents.AppendLine($" {functionInfo.UniqueName}_Signature funcPtr = &{classInfo.NativeName}::{functionInfo.Name};");
|
||||
contents.AppendLine($" VTableFunctionInjector vtableInjector(object, *(void**)&funcPtr, {scriptVTableBase}[{scriptVTableOffset} + 2]); // TODO: this is not thread-safe");
|
||||
if (classInfo is InterfaceInfo)
|
||||
{
|
||||
contents.Append($" return (({classInfo.NativeName}*)(void*)object)->{functionInfo.Name}(");
|
||||
}
|
||||
else
|
||||
{
|
||||
contents.Append(" return (object->*funcPtr)(");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// MSVC or other compiler
|
||||
contents.Append($" return (this->**({functionInfo.UniqueName}_Internal_Signature*)&{scriptVTableBase}[{scriptVTableOffset} + 2])(");
|
||||
}
|
||||
bool separator = false;
|
||||
for (var i = 0; i < functionInfo.Parameters.Count; i++)
|
||||
{
|
||||
var parameterInfo = functionInfo.Parameters[i];
|
||||
if (separator)
|
||||
contents.Append(", ");
|
||||
separator = true;
|
||||
contents.Append(parameterInfo.Name);
|
||||
}
|
||||
contents.AppendLine(");");
|
||||
}
|
||||
|
||||
private static string GenerateCppGetNativeClass(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo)
|
||||
{
|
||||
// Optimal path for in-build types
|
||||
@@ -1281,19 +1316,7 @@ namespace Flax.Build.Bindings
|
||||
|
||||
contents.AppendLine(" if (WrapperCallInstance == object)");
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine(" // Prevent stack overflow by calling native base method");
|
||||
contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Script.ScriptVTableBase;");
|
||||
contents.Append($" return (this->**({functionInfo.UniqueName}_Internal_Signature*)&scriptVTableBase[{scriptVTableOffset} + 2])(");
|
||||
separator = false;
|
||||
for (var i = 0; i < functionInfo.Parameters.Count; i++)
|
||||
{
|
||||
var parameterInfo = functionInfo.Parameters[i];
|
||||
if (separator)
|
||||
contents.Append(", ");
|
||||
separator = true;
|
||||
contents.Append(parameterInfo.Name);
|
||||
}
|
||||
contents.AppendLine(");");
|
||||
GenerateCppVirtualWrapperCallBaseMethod(buildData, contents, classInfo, functionInfo, "managedTypePtr->Script.ScriptVTableBase", scriptVTableOffset);
|
||||
contents.AppendLine(" }");
|
||||
contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;");
|
||||
contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableOffset}]);");
|
||||
@@ -1410,7 +1433,11 @@ namespace Flax.Build.Bindings
|
||||
}
|
||||
var t = functionInfo.IsConst ? " const" : string.Empty;
|
||||
contents.AppendLine($" typedef {functionInfo.ReturnType} ({classInfo.NativeName}::*{functionInfo.UniqueName}_Signature)({thunkParams}){t};");
|
||||
contents.AppendLine($" typedef {functionInfo.ReturnType} ({classInfo.NativeName}Internal::*{functionInfo.UniqueName}_Internal_Signature)({thunkParams}){t};");
|
||||
if (!(buildData.Toolchain is Platforms.UnixToolchain))
|
||||
{
|
||||
// MSVC or other compiler
|
||||
contents.AppendLine($" typedef {functionInfo.ReturnType} ({classInfo.NativeName}Internal::*{functionInfo.UniqueName}_Internal_Signature)({thunkParams}){t};");
|
||||
}
|
||||
}
|
||||
contents.AppendLine("");
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Flax.Build.Graph;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
namespace Flax.Build
|
||||
{
|
||||
@@ -256,6 +257,11 @@ namespace Flax.Build
|
||||
if (targets.Length == 0)
|
||||
Log.Warning("No targets to build");
|
||||
|
||||
using (new ProfileEventScope("LoadIncludesCache"))
|
||||
{
|
||||
IncludesCache.LoadCache();
|
||||
}
|
||||
|
||||
// Create task graph for building all targets
|
||||
var graph = new TaskGraph(project.ProjectFolderPath);
|
||||
foreach (var target in targets)
|
||||
@@ -395,6 +401,11 @@ namespace Flax.Build
|
||||
}
|
||||
}
|
||||
|
||||
using (new ProfileEventScope("SaveIncludesCache"))
|
||||
{
|
||||
IncludesCache.SaveCache();
|
||||
}
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
target.PostBuild();
|
||||
|
||||
@@ -13,9 +13,153 @@ namespace Flax.Build.NativeCpp
|
||||
/// </summary>
|
||||
public static class IncludesCache
|
||||
{
|
||||
private static readonly Dictionary<string, string[]> DirectIncludesCache = new Dictionary<string, string[]>();
|
||||
private static readonly Dictionary<string, string[]> AllIncludesCache = new Dictionary<string, string[]>();
|
||||
private static Dictionary<string, string[]> DirectIncludesCache = new Dictionary<string, string[]>();
|
||||
private static Dictionary<string, string[]> AllIncludesCache = new Dictionary<string, string[]>();
|
||||
private static Dictionary<string, DateTime> DirectIncludesTimestampCache = new Dictionary<string, DateTime>();
|
||||
private static Dictionary<string, DateTime> AllIncludesTimestampCache = new Dictionary<string, DateTime>();
|
||||
private static Dictionary<string, bool> FileExistsCache = new Dictionary<string, bool>();
|
||||
private static Dictionary<string, DateTime> FileTimestampCache = new Dictionary<string, DateTime>();
|
||||
private static readonly string IncludeToken = "include";
|
||||
private static string CachePath;
|
||||
|
||||
public static void LoadCache()
|
||||
{
|
||||
CachePath = Path.Combine(Globals.Root, Configuration.IntermediateFolder, "IncludesCache.cache");
|
||||
if (!File.Exists(CachePath))
|
||||
return;
|
||||
|
||||
using (var stream = new FileStream(CachePath, FileMode.Open))
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
int version = reader.ReadInt32();
|
||||
if (version != 1)
|
||||
return;
|
||||
|
||||
// DirectIncludesCache
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string key = reader.ReadString();
|
||||
string[] values = new string[reader.ReadInt32()];
|
||||
for (int j = 0; j < values.Length; j++)
|
||||
values[j] = reader.ReadString();
|
||||
|
||||
DirectIncludesCache.Add(key, values);
|
||||
}
|
||||
}
|
||||
|
||||
// AllIncludesCache
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string key = reader.ReadString();
|
||||
string[] values = new string[reader.ReadInt32()];
|
||||
for (int j = 0; j < values.Length; j++)
|
||||
values[j] = reader.ReadString();
|
||||
|
||||
AllIncludesCache.Add(key, values);
|
||||
}
|
||||
}
|
||||
|
||||
// DirectIncludesTimestampCache
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string key = reader.ReadString();
|
||||
DateTime value = new DateTime(reader.ReadInt64());
|
||||
DirectIncludesTimestampCache.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// AllIncludesTimestampCache
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string key = reader.ReadString();
|
||||
DateTime value = new DateTime(reader.ReadInt64());
|
||||
AllIncludesTimestampCache.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveCache()
|
||||
{
|
||||
using (var stream = new FileStream(CachePath, FileMode.Create))
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
// Version
|
||||
writer.Write(1);
|
||||
|
||||
// DirectIncludesCache
|
||||
{
|
||||
writer.Write(DirectIncludesCache.Count);
|
||||
foreach (KeyValuePair<string, string[]> kvp in DirectIncludesCache)
|
||||
{
|
||||
writer.Write(kvp.Key);
|
||||
writer.Write(kvp.Value.Length);
|
||||
foreach (var value in kvp.Value)
|
||||
writer.Write(value);
|
||||
}
|
||||
}
|
||||
|
||||
// AllIncludesCache
|
||||
{
|
||||
writer.Write(AllIncludesCache.Count);
|
||||
foreach (KeyValuePair<string, string[]> kvp in AllIncludesCache)
|
||||
{
|
||||
writer.Write(kvp.Key);
|
||||
writer.Write(kvp.Value.Length);
|
||||
foreach (var value in kvp.Value)
|
||||
writer.Write(value);
|
||||
}
|
||||
}
|
||||
|
||||
// DirectIncludesTimestampCache
|
||||
{
|
||||
writer.Write(DirectIncludesTimestampCache.Count);
|
||||
foreach (KeyValuePair<string, DateTime> kvp in DirectIncludesTimestampCache)
|
||||
{
|
||||
writer.Write(kvp.Key);
|
||||
writer.Write(kvp.Value.Ticks);
|
||||
}
|
||||
}
|
||||
|
||||
// AllIncludesTimestampCache
|
||||
{
|
||||
writer.Write(AllIncludesTimestampCache.Count);
|
||||
foreach (KeyValuePair<string, DateTime> kvp in AllIncludesTimestampCache)
|
||||
{
|
||||
writer.Write(kvp.Key);
|
||||
writer.Write(kvp.Value.Ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool FileExists(string path)
|
||||
{
|
||||
if (FileExistsCache.TryGetValue(path, out bool result))
|
||||
return result;
|
||||
|
||||
result = File.Exists(path);
|
||||
FileExistsCache.Add(path, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DateTime FileLastWriteTime(string path)
|
||||
{
|
||||
if (FileTimestampCache.TryGetValue(path, out DateTime result))
|
||||
return result;
|
||||
|
||||
result = File.GetLastWriteTime(path);
|
||||
FileTimestampCache.Add(path, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all included files by the source file (including dependencies).
|
||||
@@ -24,12 +168,24 @@ namespace Flax.Build.NativeCpp
|
||||
/// <returns>The list of included files by this file. Not null nut may be empty.</returns>
|
||||
public static string[] FindAllIncludedFiles(string sourceFile)
|
||||
{
|
||||
DateTime? lastModified = null;
|
||||
|
||||
// Try hit the cache
|
||||
string[] result;
|
||||
if (AllIncludesCache.TryGetValue(sourceFile, out result))
|
||||
return result;
|
||||
{
|
||||
if (AllIncludesTimestampCache.TryGetValue(sourceFile, out var cachedTimestamp))
|
||||
{
|
||||
lastModified = FileLastWriteTime(sourceFile);
|
||||
if (lastModified == cachedTimestamp)
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!File.Exists(sourceFile))
|
||||
AllIncludesCache.Remove(sourceFile);
|
||||
AllIncludesTimestampCache.Remove(sourceFile);
|
||||
}
|
||||
|
||||
if (!FileExists(sourceFile))
|
||||
throw new Exception(string.Format("Cannot scan file \"{0}\" for includes because it does not exist.", sourceFile));
|
||||
|
||||
//using (new ProfileEventScope("FindAllIncludedFiles"))
|
||||
@@ -44,6 +200,9 @@ namespace Flax.Build.NativeCpp
|
||||
result = includedFiles.ToArray();
|
||||
AllIncludesCache.Add(sourceFile, result);
|
||||
|
||||
if (!AllIncludesTimestampCache.ContainsKey(sourceFile))
|
||||
AllIncludesTimestampCache.Add(sourceFile, lastModified ?? File.GetLastWriteTime(sourceFile));
|
||||
|
||||
/*Log.Info("File includes for " + sourceFile);
|
||||
foreach (var e in result)
|
||||
{
|
||||
@@ -72,10 +231,22 @@ namespace Flax.Build.NativeCpp
|
||||
|
||||
private static string[] GetDirectIncludes(string sourceFile)
|
||||
{
|
||||
DateTime? lastModified = null;
|
||||
|
||||
// Try hit the cache
|
||||
string[] result;
|
||||
if (DirectIncludesCache.TryGetValue(sourceFile, out result))
|
||||
return result;
|
||||
{
|
||||
if (DirectIncludesTimestampCache.TryGetValue(sourceFile, out var cachedTimestamp))
|
||||
{
|
||||
lastModified = FileLastWriteTime(sourceFile);
|
||||
if (lastModified == cachedTimestamp)
|
||||
return result;
|
||||
}
|
||||
|
||||
DirectIncludesCache.Remove(sourceFile);
|
||||
DirectIncludesTimestampCache.Remove(sourceFile);
|
||||
}
|
||||
|
||||
// Find all files included directly
|
||||
var includedFiles = new HashSet<string>();
|
||||
@@ -152,11 +323,11 @@ namespace Flax.Build.NativeCpp
|
||||
|
||||
// Relative to the workspace root
|
||||
var includedFilePath = Path.Combine(Globals.Root, "Source", includedFile);
|
||||
if (!File.Exists(includedFilePath))
|
||||
if (!FileExists(includedFilePath))
|
||||
{
|
||||
// Relative to the source file
|
||||
includedFilePath = Path.Combine(sourceFileFolder, includedFile);
|
||||
if (!File.Exists(includedFilePath))
|
||||
if (!FileExists(includedFilePath))
|
||||
{
|
||||
// Relative to any of the included project workspaces
|
||||
var project = Globals.Project;
|
||||
@@ -164,7 +335,7 @@ namespace Flax.Build.NativeCpp
|
||||
foreach (var reference in project.References)
|
||||
{
|
||||
includedFilePath = Path.Combine(reference.Project.ProjectFolderPath, "Source", includedFile);
|
||||
if (File.Exists(includedFilePath))
|
||||
if (FileExists(includedFilePath))
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
@@ -191,6 +362,8 @@ namespace Flax.Build.NativeCpp
|
||||
// Process result
|
||||
result = includedFiles.ToArray();
|
||||
DirectIncludesCache.Add(sourceFile, result);
|
||||
if (!DirectIncludesTimestampCache.ContainsKey(sourceFile))
|
||||
DirectIncludesTimestampCache.Add(sourceFile, lastModified ?? FileLastWriteTime(sourceFile));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,19 +67,7 @@ namespace Flax.Build.Plugins
|
||||
contents.AppendLine(" static THREADLOCAL void* WrapperCallInstance = nullptr;");
|
||||
contents.AppendLine(" if (WrapperCallInstance == object)");
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine(" // Prevent stack overflow by calling base method");
|
||||
contents.AppendLine(" const auto scriptVTableBase = object->GetType().Script.ScriptVTableBase;");
|
||||
contents.Append($" return (this->**({functionInfo.UniqueName}_Internal_Signature*)&scriptVTableBase[{scriptVTableOffset} + 2])(");
|
||||
separator = false;
|
||||
for (var i = 0; i < functionInfo.Parameters.Count; i++)
|
||||
{
|
||||
var parameterInfo = functionInfo.Parameters[i];
|
||||
if (separator)
|
||||
contents.Append(", ");
|
||||
separator = true;
|
||||
contents.Append(parameterInfo.Name);
|
||||
}
|
||||
contents.AppendLine(");");
|
||||
BindingsGenerator.GenerateCppVirtualWrapperCallBaseMethod(buildData, contents, classInfo, functionInfo, "object->GetType().Script.ScriptVTableBase", scriptVTableOffset);
|
||||
contents.AppendLine(" }");
|
||||
contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Script.ScriptVTable;");
|
||||
contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableOffset}]);");
|
||||
|
||||
@@ -215,4 +215,10 @@
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Tag -->
|
||||
<Type Name="Tag">
|
||||
<DisplayString Condition="Index == -1">None</DisplayString>
|
||||
<DisplayString Condition="Index != -1">Tag={TagsListDebug[Index]}</DisplayString>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
||||
|
||||
Reference in New Issue
Block a user