diff --git a/Content/Shaders/AtmospherePreCompute.flax b/Content/Shaders/AtmospherePreCompute.flax
index 527275f61..20491857f 100644
--- a/Content/Shaders/AtmospherePreCompute.flax
+++ b/Content/Shaders/AtmospherePreCompute.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f06c5ac062366b3ef86c355c6cc8b3a984b8eb9bc027ffc0874bc51dbaef4b3e
-size 11720
+oid sha256:68e1592216496fd6749ac4538dd2d549c39813d17ec3114514889100782007a6
+size 11418
diff --git a/Content/Shaders/BakeLightmap.flax b/Content/Shaders/BakeLightmap.flax
index ef3e02163..03af565fc 100644
--- a/Content/Shaders/BakeLightmap.flax
+++ b/Content/Shaders/BakeLightmap.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:18eb0ec2642a8f87ee3d4dbe7af8771b0f79f983637aee1a9e5baebce435976b
-size 16284
+oid sha256:0cf36b83e0bdbbf47b2d88b39d18ae3e1ac85246d46a05f1e6c58f89f4abc105
+size 15794
diff --git a/Content/Shaders/BitonicSort.flax b/Content/Shaders/BitonicSort.flax
index 980b664f3..c49c2b73f 100644
--- a/Content/Shaders/BitonicSort.flax
+++ b/Content/Shaders/BitonicSort.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e8d1a797f813d3af8a98bcb8110981ea8c033324e94fb6413615c811b5c5bfd7
-size 6808
+oid sha256:fbecce17a3a0dba01aa1e8d537041c27339a5f171c04cdeb83c68765fd6ac652
+size 6548
diff --git a/Content/Shaders/ColorGrading.flax b/Content/Shaders/ColorGrading.flax
index a7a466d5e..d36626d2c 100644
--- a/Content/Shaders/ColorGrading.flax
+++ b/Content/Shaders/ColorGrading.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2bda196600fbc9d6f362e85471399331941272cdd92f9888bdcf7a1b8b04ada7
-size 10925
+oid sha256:1841743cd96e3efba196cba05678658463c1c1fcd6b06ac7703e87bb2850d641
+size 10618
diff --git a/Content/Shaders/DebugDraw.flax b/Content/Shaders/DebugDraw.flax
index ea3807153..4e824c30c 100644
--- a/Content/Shaders/DebugDraw.flax
+++ b/Content/Shaders/DebugDraw.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b25f3b058f2bca838a661e46e90931f347b7e9984626e2b0112270e0f3e4fa47
-size 2115
+oid sha256:9fc85c05a9203d53f03db74f4d04a39e651e34d310de119f06279a1ce0299bcc
+size 2053
diff --git a/Content/Shaders/Editor/LightmapUVsDensity.flax b/Content/Shaders/Editor/LightmapUVsDensity.flax
index c817058f7..145040bbe 100644
--- a/Content/Shaders/Editor/LightmapUVsDensity.flax
+++ b/Content/Shaders/Editor/LightmapUVsDensity.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f23c12db6332866a9ac91d576575448af02e1a4e0fff20529f32911970de72e5
-size 4509
+oid sha256:7df3b4a9464524bb0354ab9f4e7dc6256af56d3f13f3b347a946742ecbf4c1d1
+size 4391
diff --git a/Content/Shaders/Editor/MaterialComplexity.flax b/Content/Shaders/Editor/MaterialComplexity.flax
index 3c8770e50..490f72a38 100644
--- a/Content/Shaders/Editor/MaterialComplexity.flax
+++ b/Content/Shaders/Editor/MaterialComplexity.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a891d1a83c1c99f17f43f75fbe09ffe5df99bd52f150875771f70ee67f2f52e0
-size 1336
+oid sha256:446446a3e8e3e72793cfdf3cf76d705917a61814fd1f40cb60827ad3c002b21d
+size 1303
diff --git a/Content/Shaders/Editor/QuadOverdraw.flax b/Content/Shaders/Editor/QuadOverdraw.flax
index 7eba0013e..7e6bdfb9a 100644
--- a/Content/Shaders/Editor/QuadOverdraw.flax
+++ b/Content/Shaders/Editor/QuadOverdraw.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6a25d8d4db381507c4c2a33d794b7eec8994ae8d281478e057232adf788224db
-size 1448
+oid sha256:50ab3a3734b0cd349aadd61c404739b48146cc1f326412dc5e4604da3fe03dc6
+size 1414
diff --git a/Content/Shaders/Editor/VertexColors.flax b/Content/Shaders/Editor/VertexColors.flax
index 2d862a36d..92b00ddac 100644
--- a/Content/Shaders/Editor/VertexColors.flax
+++ b/Content/Shaders/Editor/VertexColors.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bf47e691ee17a3bf17948cc365a327685e5298568a993fec47a920934d1a86fb
-size 2102
+oid sha256:66e418d872f95996806ff866e4786d8d22e504c5209f4b7b8e140c29a77ce93f
+size 2053
diff --git a/Content/Shaders/EyeAdaptation.flax b/Content/Shaders/EyeAdaptation.flax
index a81177004..1f52e67da 100644
--- a/Content/Shaders/EyeAdaptation.flax
+++ b/Content/Shaders/EyeAdaptation.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:00e1a4e4fc4153cb83b090169b9d600686e9bf6a6565914de46d55b1b945025e
-size 4610
+oid sha256:0f1a19e5a918049d093d4536e540d4ffb779c2b0b4524a277b1503afbecea79b
+size 4453
diff --git a/Content/Shaders/FXAA.flax b/Content/Shaders/FXAA.flax
index 7f8e18d8a..4b6bdfe25 100644
--- a/Content/Shaders/FXAA.flax
+++ b/Content/Shaders/FXAA.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a8a6d89039e93acb1ac658b4b63ab9baa21c7777d948f14732571529a8cfe519
-size 25139
+oid sha256:142d7ede22ed99b095dd07fcce722b7668554911e57a1b41e17842e0383a8d24
+size 24484
diff --git a/Content/Shaders/Fog.flax b/Content/Shaders/Fog.flax
index 37410e1af..f19396bd8 100644
--- a/Content/Shaders/Fog.flax
+++ b/Content/Shaders/Fog.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:58dab7514164b3cf941636dbead945da12b13dc485dbec997227711c2e67e58f
-size 2878
+oid sha256:4703c3f843dec8248938344d04778d4705eb61247610906af9ec22b4d42f1eae
+size 2795
diff --git a/Content/Shaders/Forward.flax b/Content/Shaders/Forward.flax
index b922a3b16..393603913 100644
--- a/Content/Shaders/Forward.flax
+++ b/Content/Shaders/Forward.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8d18f21b456efe8c9f5c38c35aa0ce6e13552966aa48445cb33826837bea2819
-size 1221
+oid sha256:0c76097d4a231be004658cdbbeae05058ff5408cb003d38ac5ef6ca927c04484
+size 1197
diff --git a/Content/Shaders/GBuffer.flax b/Content/Shaders/GBuffer.flax
index 78308a4bb..a22a76fb1 100644
--- a/Content/Shaders/GBuffer.flax
+++ b/Content/Shaders/GBuffer.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:180768a9304d7bc12c8fb487a39a9991575d55cfaf5fe3135a4985098340d042
-size 2845
+oid sha256:255a153284978cff6e0285f40355ba8695679f97215c2efe39082ae7422f7d8f
+size 2774
diff --git a/Content/Shaders/GPUParticlesSorting.flax b/Content/Shaders/GPUParticlesSorting.flax
index 6b2a31931..08b6a4d78 100644
--- a/Content/Shaders/GPUParticlesSorting.flax
+++ b/Content/Shaders/GPUParticlesSorting.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f5d131298c4f138278a5339e7d1a2dd8263c4afa4e27f3fa345ebda21989ea15
-size 2726
+oid sha256:fcfaa7567daaeac0ba0e3d135db52c674b7f17f8bace31b8a4f75ef8a24b21a7
+size 2639
diff --git a/Content/Shaders/GUI.flax b/Content/Shaders/GUI.flax
index a6e231bd4..18f16aa34 100644
--- a/Content/Shaders/GUI.flax
+++ b/Content/Shaders/GUI.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5512b7584d43b571a9a30cec4b5c160cebd8924f4305c8de0ae439a664d143a7
-size 5111
+oid sha256:b4b204acc4b60f93a490e3178a853a7ae059ac5e6e1e4ee4dc7ebc0b8771413c
+size 4943
diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax
index ec1d39d8f..50ca8c78d 100644
--- a/Content/Shaders/GlobalSignDistanceField.flax
+++ b/Content/Shaders/GlobalSignDistanceField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e14a67948d93abb8ef6124b39fdb7b1bc682e1fdf5341ce6255eefe2606a2dfc
-size 11787
+oid sha256:dd3a18c7831eba6b531d82528733e2f7ab58a02b076441a974e2b999b26da616
+size 11498
diff --git a/Content/Shaders/Histogram.flax b/Content/Shaders/Histogram.flax
index 4c5967874..9df23aad7 100644
--- a/Content/Shaders/Histogram.flax
+++ b/Content/Shaders/Histogram.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:92a990f5286c449d3274bd90766d022d03aeaa43ecb93c0589d391828842b16b
-size 2609
+oid sha256:761d2f9eea984a5cb46ba7a83f3f49c9cb7452a05420faecc70c55c3b56cfa45
+size 2532
diff --git a/Content/Shaders/Lights.flax b/Content/Shaders/Lights.flax
index 612a634ab..c76a501fe 100644
--- a/Content/Shaders/Lights.flax
+++ b/Content/Shaders/Lights.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:86919e780799e02ebae7ce13f876925219e5b03234c7fdae3b610623d8bc81b8
-size 5299
+oid sha256:37b0d62eff4ed0ea2cd48c351e0f8e0ed98f1aac3b272fd0849227eda6cb6856
+size 5122
diff --git a/Content/Shaders/MotionBlur.flax b/Content/Shaders/MotionBlur.flax
index 5dac7c3eb..a2c201bc6 100644
--- a/Content/Shaders/MotionBlur.flax
+++ b/Content/Shaders/MotionBlur.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:82109c08b00acfcd46818e35b05732ccdfd7f453bf6b3cea5b450603b2d1baa8
-size 9685
+oid sha256:7b468b522e29e3464a2e310334e011d793a10ebc20092e5bedd76d33ed608054
+size 9428
diff --git a/Content/Shaders/MultiScaler.flax b/Content/Shaders/MultiScaler.flax
index 3b71c27aa..8e5790610 100644
--- a/Content/Shaders/MultiScaler.flax
+++ b/Content/Shaders/MultiScaler.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:737c91950c010adb13cee6669f77d95737aa671f17d71fb6bee8ca5c3c627e27
-size 7206
+oid sha256:a0412a83a79884e77e09f5debfedf45b5856756b2e2298edef196c9434d4bf19
+size 7006
diff --git a/Content/Shaders/PostProcessing.flax b/Content/Shaders/PostProcessing.flax
index a771d9882..bba56658c 100644
--- a/Content/Shaders/PostProcessing.flax
+++ b/Content/Shaders/PostProcessing.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f72955c7285fb135c80ba73ac0a1e62463394c86979bdad1e0a789158ad7728c
+oid sha256:5f3c7923418f5872dc4bbfb08c39327f46d357c62827711da5e4e50332f26bc0
size 16522
diff --git a/Content/Shaders/ProbesFilter.flax b/Content/Shaders/ProbesFilter.flax
index 760a91040..59ec47ef3 100644
--- a/Content/Shaders/ProbesFilter.flax
+++ b/Content/Shaders/ProbesFilter.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3765117ee658c768184f9af0c7aa7799c575cfc68f5ce0d824e211570d887135
-size 2108
+oid sha256:bfc735d6646e35594c1441298a4718ea8cffe4517762f65982dabed50af8215e
+size 2030
diff --git a/Content/Shaders/Quad.flax b/Content/Shaders/Quad.flax
index 485a11fbf..6a6897dfb 100644
--- a/Content/Shaders/Quad.flax
+++ b/Content/Shaders/Quad.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:04c502c31eaceba9738444cd3a9419474ebfbfd42e7efa39b7cc5778a19e267c
-size 2310
+oid sha256:f9225e31ac76e66a2f4c09f70af326229c563155dc0c34eccb9f243f80c07908
+size 2242
diff --git a/Content/Shaders/Reflections.flax b/Content/Shaders/Reflections.flax
index 9cf210fec..e594c5727 100644
--- a/Content/Shaders/Reflections.flax
+++ b/Content/Shaders/Reflections.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b651d798d3e2a36d3afe960e087f061cf53cac826ac48fff17eebba7293ed46f
-size 3288
+oid sha256:772f6b54b8ab6f6f054935bd9593616896c9e7b56249f0b084d85858a50f4f00
+size 3193
diff --git a/Content/Shaders/SMAA.flax b/Content/Shaders/SMAA.flax
index e315c9ef5..605524a5d 100644
--- a/Content/Shaders/SMAA.flax
+++ b/Content/Shaders/SMAA.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c53b7a4929736b7f603f47475e2d668100e52f53c6ba176397b06b2ebc0f050d
-size 47673
+oid sha256:8071432958f2e295bc22f7058970c18135c001a4f705f20c4c99385d8acf1daa
+size 46449
diff --git a/Content/Shaders/SSAO.flax b/Content/Shaders/SSAO.flax
index e4a48b1f8..232e72a8c 100644
--- a/Content/Shaders/SSAO.flax
+++ b/Content/Shaders/SSAO.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b8ed05e81b6c7822c725976551f75960e770f3a32fdb23ab49f60e32e366ae15
-size 37614
+oid sha256:b6747405d3ef0698c9b84ceebfc1ba2fb3d255ffcec10bd5e5fb86b1ff632187
+size 36841
diff --git a/Content/Shaders/Shadows.flax b/Content/Shaders/Shadows.flax
index 8c45d669a..9c5e5b142 100644
--- a/Content/Shaders/Shadows.flax
+++ b/Content/Shaders/Shadows.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:61ea612b8ee409c1de4bb4ff4455f31bf32ed756a6612d9c5a9a152ff6b034d0
-size 7847
+oid sha256:b823730a83d35980f63d3adb7d23c8ee4585826c6e6dd09555dd97aa77dad269
+size 7654
diff --git a/Content/Shaders/Sky.flax b/Content/Shaders/Sky.flax
index 2ff583cfc..e7bc71130 100644
--- a/Content/Shaders/Sky.flax
+++ b/Content/Shaders/Sky.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0ad54e191498e2c0067b32b041cfb351fa18f928d36372ae259a8b64a9dfb311
-size 3572
+oid sha256:0791b4d9504a0284ecb7f45b8339abab12a00396c2b4063ee4320ac15d92bc9b
+size 3473
diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs
index 1d70a8252..27e7cf76c 100644
--- a/Source/Editor/Content/Tree/ContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/ContentTreeNode.cs
@@ -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); };
}
///
diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs
index 4df247786..d03dc6168 100644
--- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs
+++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs
@@ -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
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index 47147381e..68dca9883 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -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));
}
///
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 602c6d0ee..1050139be 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -407,6 +407,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
public class UIControlControlEditor : GenericEditor
{
private Type _cachedType;
+ private bool _anchorDropDownClosed = true;
///
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));
}
diff --git a/Source/Editor/CustomEditors/Editors/ActorTagEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTagEditor.cs
deleted file mode 100644
index d274e96ee..000000000
--- a/Source/Editor/CustomEditors/Editors/ActorTagEditor.cs
+++ /dev/null
@@ -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
-{
- ///
- /// 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.
- ///
- public sealed class ActorTagEditor : CustomEditor
- {
- private ComboBoxElement element;
- private const string NoTagText = "Untagged";
-
- ///
- public override DisplayStyle Style => DisplayStyle.Inline;
-
- ///
- 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);
- }
-
- ///
- 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;
- }
- }
- }
-}
diff --git a/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs b/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs
index 157c3af17..6b6fa943a 100644
--- a/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs
@@ -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();
};
diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs
new file mode 100644
index 000000000..c5c1ee6b0
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs
@@ -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
+{
+ ///
+ /// Custom editor for .
+ ///
+ [CustomEditor(typeof(Tag)), DefaultEditor]
+ public sealed class TagEditor : CustomEditor
+ {
+ private ClickableLabel _label;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ 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;
+ }
+
+ ///
+ 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 SetValue;
+ public Action SetValues;
+ public List 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();
+ 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();
+ 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();
+ var style = FlaxEngine.GUI.Style.Current;
+ var nodeBackgroundColorHighlighted = style.BackgroundHighlighted * 0.5f;
+ var nodeIndent = 16.0f;
+ var root = tree.AddChild();
+ 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;
+ }
+ }
+
+ ///
+ /// Custom editor for array of .
+ ///
+ [CustomEditor(typeof(Tag[])), DefaultEditor]
+ public sealed class TagsEditor : CustomEditor
+ {
+ private ClickableLabel _label;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ 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;
+ }
+
+ ///
+ 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 asList)
+ return asList.ToArray();
+ return Utils.GetEmptyArray();
+ }
+ set
+ {
+ if (Values[0] is Tag[])
+ SetValue(value);
+ if (Values[0] is List)
+ SetValue(new List(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));
+ }
+ }
+}
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
index 1093b50b7..0bd87082d 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
@@ -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;
}
+ ///
+ public override bool OnKeyDown(KeyboardKeys key)
+ {
+ if (base.OnKeyDown(key))
+ return true;
+
+ switch (key)
+ {
+ case KeyboardKeys.Escape:
+ Hide();
+ return true;
+ }
+ return false;
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Editor/GUI/Input/SearchBox.cs b/Source/Editor/GUI/Input/SearchBox.cs
new file mode 100644
index 000000000..8ffd0874a
--- /dev/null
+++ b/Source/Editor/GUI/Input/SearchBox.cs
@@ -0,0 +1,56 @@
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.GUI.Input
+{
+ ///
+ /// Search box control which can gather text search input from the user.
+ ///
+ public class SearchBox : TextBox
+ {
+ ///
+ /// A button that clears the search bar.
+ ///
+ public Button ClearSearchButton { get; }
+
+ ///
+ /// Init search box
+ ///
+ public SearchBox()
+ : this(false, 0, 0)
+ {
+ }
+
+ ///
+ /// Init search box
+ ///
+ 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);
+ }
+ }
+}
diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs
index 7f4fb51e0..ac253b79d 100644
--- a/Source/Editor/GUI/Input/ValueBox.cs
+++ b/Source/Editor/GUI/Input/ValueBox.cs
@@ -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;
diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs
index 77ee9af76..b761fa989 100644
--- a/Source/Editor/GUI/ItemsListContextMenu.cs
+++ b/Source/Editor/GUI/ItemsListContextMenu.cs
@@ -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;
diff --git a/Source/Editor/GUI/Popups/RenamePopup.cs b/Source/Editor/GUI/Popups/RenamePopup.cs
index 01fbdc03f..03689a114 100644
--- a/Source/Editor/GUI/Popups/RenamePopup.cs
+++ b/Source/Editor/GUI/Popups/RenamePopup.cs
@@ -57,6 +57,11 @@ namespace FlaxEditor.GUI
set => _inputField.Text = value;
}
+ ///
+ /// Gets the text input field control.
+ ///
+ public TextBox InputField => _inputField;
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs
index 8f2cc1c13..e5d5c4355 100644
--- a/Source/Editor/GUI/Tree/TreeNode.cs
+++ b/Source/Editor/GUI/Tree/TreeNode.cs
@@ -217,7 +217,7 @@ namespace FlaxEditor.GUI.Tree
///
/// Gets the arrow rectangle.
///
- 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);
///
/// Gets the header rectangle.
@@ -248,6 +248,12 @@ namespace FlaxEditor.GUI.Tree
}
}
+ ///
+ /// Custom arrow rectangle within node.
+ ///
+ [HideInEditor, NoSerialize]
+ public Rectangle? CustomArrowRect;
+
///
/// Gets the drag over action type.
///
@@ -277,7 +283,7 @@ namespace FlaxEditor.GUI.Tree
/// Initializes a new instance of the class.
///
public TreeNode()
- : this(false)
+ : this(false, SpriteHandle.Invalid, SpriteHandle.Invalid)
{
}
@@ -486,11 +492,14 @@ namespace FlaxEditor.GUI.Tree
/// True if event has been handled.
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)
diff --git a/Source/Editor/GUI/Tree/TreeNodeWithAddons.cs b/Source/Editor/GUI/Tree/TreeNodeWithAddons.cs
new file mode 100644
index 000000000..40e81ff40
--- /dev/null
+++ b/Source/Editor/GUI/Tree/TreeNodeWithAddons.cs
@@ -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
+{
+ ///
+ /// Tree node control with in-built checkbox.
+ ///
+ [HideInEditor]
+ public class TreeNodeWithAddons : TreeNode
+ {
+ ///
+ /// The additional controls (eg. added to the header).
+ ///
+ public List Addons = new List();
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+
+ foreach (var child in Addons)
+ {
+ Render2D.PushTransform(ref child._cachedTransform);
+ child.Draw();
+ Render2D.PopTransform();
+ }
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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();
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index 144698b3f..1ffc9b745 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -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);
diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs
index 7b28367ff..8fa7001a1 100644
--- a/Source/Editor/Modules/SceneEditingModule.cs
+++ b/Source/Editor/Modules/SceneEditingModule.cs
@@ -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;
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 829ad5e00..ff1179ae0 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -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;
}
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 0e341581f..5a07356a7 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -193,6 +193,8 @@ namespace FlaxEditor.Modules
/// Editor window or null if cannot find any window.
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);
}
}
diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
index 1d10ef687..3ecd5d903 100644
--- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
+++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
@@ -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 =>
{
diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
index 984e3e520..5057e4445 100644
--- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs
+++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
@@ -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;
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index 7e02055bf..670ee1131 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -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
///
/// The search box.
/// The tree control.
+ /// Amount of additional space above the search box to put custom UI.
/// The created menu to setup and show.
- 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;
}
+ ///
+ /// Updates (recursivly) search popup tree structures based on the filter text.
+ ///
+ 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;
+ }
+
///
/// Gets the asset name relative to the project root folder (without asset file extension)
///
diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs
index 6c02c4efe..ff41b81ea 100644
--- a/Source/Editor/Windows/AboutDialog.cs
+++ b/Source/Editor/Windows/AboutDialog.cs
@@ -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)
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 90cf9e3d3..20d61c420 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -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),
};
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index 0a507d0e2..1cc0f049d 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -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;
diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index a62205bc8..65f36db41 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -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;
diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs
index 3d1a4f7de..ad02772df 100644
--- a/Source/Editor/Windows/Profiler/MemoryGPU.cs
+++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs
@@ -133,7 +133,9 @@ namespace FlaxEditor.Windows.Profiler
{
resource = new Resource
{
+#if !BUILD_RELEASE
Name = gpuResource.Name,
+#endif
Type = gpuResource.ResourceType,
};
diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs
index 8fd6e33ee..28ab83ff5 100644
--- a/Source/Editor/Windows/SceneTreeWindow.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.cs
@@ -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),
};
diff --git a/Source/Editor/Windows/Search/ContentSearchWindow.cs b/Source/Editor/Windows/Search/ContentSearchWindow.cs
index cb539bf47..7128b7db9 100644
--- a/Source/Editor/Windows/Search/ContentSearchWindow.cs
+++ b/Source/Editor/Windows/Search/ContentSearchWindow.cs
@@ -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),
};
diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs
index 99490ec07..0bd01f293 100644
--- a/Source/Editor/Windows/ToolboxWindow.cs
+++ b/Source/Editor/Windows/ToolboxWindow.cs
@@ -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),
};
diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h
index b8f4a9b50..b6b5d13e7 100644
--- a/Source/Engine/Content/JsonAsset.h
+++ b/Source/Engine/Content/JsonAsset.h
@@ -3,7 +3,7 @@
#pragma once
#include "Asset.h"
-#include "Engine/Serialization/ISerializable.h"
+#include "Engine/Core/ISerializable.h"
#include "Engine/Serialization/Json.h"
///
diff --git a/Source/Engine/ContentImporters/ImportAudio.h b/Source/Engine/ContentImporters/ImportAudio.h
index d269e6851..b7b96254b 100644
--- a/Source/Engine/ContentImporters/ImportAudio.h
+++ b/Source/Engine/ContentImporters/ImportAudio.h
@@ -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
diff --git a/Source/Engine/ContentImporters/ImportShader.cpp b/Source/Engine/ContentImporters/ImportShader.cpp
index fe1253644..c6394c91b 100644
--- a/Source/Engine/ContentImporters/ImportShader.cpp
+++ b/Source/Engine/ContentImporters/ImportShader.cpp
@@ -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;
diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h
index b89acc44f..d2cd50afd 100644
--- a/Source/Engine/Core/Collections/Array.h
+++ b/Source/Engine/Core/Collections/Array.h
@@ -872,8 +872,9 @@ public:
if (!(data[i] == otherData[i]))
return false;
}
+ return true;
}
- return true;
+ return false;
}
template
diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h
index 660b1e0fe..8d7530e42 100644
--- a/Source/Engine/Core/Collections/BitArray.h
+++ b/Source/Engine/Core/Collections/BitArray.h
@@ -343,8 +343,9 @@ public:
if (!(Get(i) == other.Get(i)))
return false;
}
+ return true;
}
- return true;
+ return false;
}
template
diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs
index a6003595c..f5738647a 100644
--- a/Source/Engine/Core/Config/LayersAndTagsSettings.cs
+++ b/Source/Engine/Core/Config/LayersAndTagsSettings.cs
@@ -22,15 +22,6 @@ namespace FlaxEditor.Content.Settings
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)]
public string[] Layers = new string[32];
- ///
- /// Gets the current tags collection.
- ///
- /// The tags collection.
- internal static string[] GetCurrentTags()
- {
- return GetCurrentTags(out int _);
- }
-
///
/// Gets the current layer names (max 32 items but trims last empty items).
///
@@ -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);
diff --git a/Source/Engine/Core/Config/Settings.h b/Source/Engine/Core/Config/Settings.h
index a9fef85f5..17be17fd1 100644
--- a/Source/Engine/Core/Config/Settings.h
+++ b/Source/Engine/Core/Config/Settings.h
@@ -2,7 +2,7 @@
#pragma once
-#include "Engine/Serialization/ISerializable.h"
+#include "Engine/Core/ISerializable.h"
///
/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game.
diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h
index fbaa8c15f..3d3186e66 100644
--- a/Source/Engine/Foliage/FoliageType.h
+++ b/Source/Engine/Foliage/FoliageType.h
@@ -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"
///
/// The foliage instances scaling modes.
diff --git a/Source/Engine/Graphics/Models/ModelInstanceEntry.h b/Source/Engine/Graphics/Models/ModelInstanceEntry.h
index 6e28c2a7e..26d70fe7e 100644
--- a/Source/Engine/Graphics/Models/ModelInstanceEntry.h
+++ b/Source/Engine/Graphics/Models/ModelInstanceEntry.h
@@ -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"
///
diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h
index 7a8645721..a6ab24964 100644
--- a/Source/Engine/Graphics/PostProcessSettings.h
+++ b/Source/Engine/Graphics/PostProcessSettings.h
@@ -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"
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 91bde0625..51f1831f0 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -55,8 +55,8 @@ struct AxisData
namespace InputImpl
{
- Dictionary Actions;
- Dictionary Axes;
+ Dictionary Actions;
+ Dictionary Axes;
bool GamepadsChanged = true;
Array AxesValues;
InputDevice::EventQueue InputEvents;
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index 1c4f4a2e1..d96bd005d 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -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()));
+ }
}
}
diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs
index 936d38565..cd774fca6 100644
--- a/Source/Engine/Level/Actor.cs
+++ b/Source/Engine/Level/Actor.cs
@@ -123,6 +123,9 @@ namespace FlaxEngine
/// The child actor.
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
/// The child actor.
public T AddChild() where T : Actor
{
+ if (typeof(T).IsAbstract)
+ return null;
+
var result = New();
result.SetParent(this, false, false);
return result;
@@ -172,6 +178,9 @@ namespace FlaxEngine
var result = GetChild();
if (result == null)
{
+ if (typeof(T).IsAbstract)
+ return null;
+
result = New();
result.SetParent(this, false, false);
}
@@ -185,6 +194,9 @@ namespace FlaxEngine
/// The created script instance, null otherwise.
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
/// The created script instance, null otherwise.
public T AddScript() where T : Script
{
+ if (typeof(T).IsAbstract)
+ return null;
+
var script = New();
script.Parent = this;
return script;
diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h
index 266feda70..00b88072a 100644
--- a/Source/Engine/Level/Actor.h
+++ b/Source/Engine/Level/Actor.h
@@ -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
-
///
/// Base class for all actor objects on the scene.
///
@@ -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;
+ ///
+ /// Actor tags collection.
+ ///
+ API_FIELD(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68)") Array Tags;
+
public:
///
/// Gets the object layer (index). Can be used for selective rendering or ignoring raycasts.
@@ -96,26 +97,10 @@ public:
}
///
- /// Gets the actor tag (index). Can be used to identify the objects.
+ /// Sets the layer.
///
- FORCE_INLINE int32 GetTagIndex() const
- {
- return _tag;
- }
-
- ///
- /// Determines whether this actor has tag assigned.
- ///
- API_FUNCTION() FORCE_INLINE bool HasTag() const
- {
- return _tag != ACTOR_TAG_INVALID;
- }
-
- ///
- /// Determines whether this actor has given tag assigned.
- ///
- /// The tag to check.
- API_FUNCTION() bool HasTag(const StringView& tag) const;
+ /// The index of the layer.
+ API_PROPERTY() void SetLayer(int32 layerIndex);
///
/// Gets the name of the layer.
@@ -123,28 +108,21 @@ public:
API_PROPERTY() const String& GetLayerName() const;
///
- /// Gets the name of the tag.
+ /// Determines whether this actor has any tag assigned.
///
- API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTagEditor\")")
- const String& GetTag() const;
+ API_FUNCTION() bool HasTag() const;
///
- /// Sets the layer.
+ /// Determines whether this actor has given tag assigned (exact match).
///
- /// The index of the layer.
- API_PROPERTY() void SetLayer(int32 layerIndex);
+ /// The tag to check.
+ API_FUNCTION() bool HasTag(const Tag& tag) const;
///
- /// Sets the tag.
+ /// Determines whether this actor has given tag assigned (exact match).
///
- /// The index of the tag.
- void SetTagIndex(int32 tagIndex);
-
- ///
- /// Sets the tag.
- ///
- /// Name of the tag.
- API_PROPERTY() void SetTag(const StringView& tagName);
+ /// The tag to check.
+ API_FUNCTION() bool HasTag(const StringView& tag) const;
///
/// 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();
result->SetParent(this, false, false);
}
@@ -955,13 +936,6 @@ public:
{
}
- ///
- /// Called when tag gets changed.
- ///
- virtual void OnTagChanged()
- {
- }
-
///
/// Called when adding object to the game.
///
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index 7f871df9d..317973426 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -166,7 +166,6 @@ Action Level::ScriptsReload;
Action Level::ScriptsReloaded;
Action Level::ScriptsReloadEnd;
#endif
-Array 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())
{
// 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& result)
+{
+ if (node->HasTag(tag))
+ result.Add(node);
+ for (Actor* child : node->Children)
+ FindActorRecursive(child, tag, result);
+}
+
+Array Level::FindActors(const Tag& tag, Actor* root)
+{
+ PROFILE_CPU();
+ Array 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();
diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h
index 96202c830..134d5d8b1 100644
--- a/Source/Engine/Level/Level.h
+++ b/Source/Engine/Level/Level.h
@@ -16,6 +16,7 @@ class Engine;
struct RenderView;
struct RenderContext;
struct RenderContextBatch;
+struct Tag;
///
/// The scene manager that contains the loaded scenes collection and spawns/deleted actors.
@@ -445,23 +446,11 @@ public:
static void ConstructParentActorsTreeList(const Array& input, Array& output);
public:
- ///
- /// The tags names.
- ///
- static Array Tags;
-
///
/// The layers names.
///
static String Layers[32];
- ///
- /// Gets or adds the tag (returns the tag index).
- ///
- /// The tag.
- /// The tag index.
- static int32 GetOrAddTag(const StringView& tag);
-
///
/// Gets the amount of non empty layer names (from the beginning, trims the last ones).
///
@@ -473,6 +462,23 @@ public:
///
API_FUNCTION() static int32 GetLayerIndex(const StringView& layer);
+public:
+ ///
+ /// Tries to find the actor with the given tag (returns the first one found).
+ ///
+ /// The tag of the actor to search for.
+ /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.
+ /// Found actor or null.
+ API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr);
+
+ ///
+ /// Tries to find the actors with the given tag (returns all found).
+ ///
+ /// The tag of the actor to search for.
+ /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.
+ /// Found actors or empty if none.
+ API_FUNCTION() static Array FindActors(const Tag& tag, Actor* root = nullptr);
+
private:
// Actor API
enum class ActorEventType
diff --git a/Source/Engine/Level/Scene/SceneCSGData.h b/Source/Engine/Level/Scene/SceneCSGData.h
index 457f4a840..153217892 100644
--- a/Source/Engine/Level/Scene/SceneCSGData.h
+++ b/Source/Engine/Level/Scene/SceneCSGData.h
@@ -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"
diff --git a/Source/Engine/Level/SceneInfo.h b/Source/Engine/Level/SceneInfo.h
index a0c802e02..ce05e047c 100644
--- a/Source/Engine/Level/SceneInfo.h
+++ b/Source/Engine/Level/SceneInfo.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"
///
diff --git a/Source/Engine/Level/SceneObject.h b/Source/Engine/Level/SceneObject.h
index db58a71b8..727939341 100644
--- a/Source/Engine/Level/SceneObject.h
+++ b/Source/Engine/Level/SceneObject.h
@@ -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;
diff --git a/Source/Engine/Level/Tags.cpp b/Source/Engine/Level/Tags.cpp
new file mode 100644
index 000000000..67633df9e
--- /dev/null
+++ b/Source/Engine/Level/Tags.cpp
@@ -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 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& 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& list, const Tag& tag)
+{
+ if (tag.Index == -1)
+ return false;
+ return list.Contains(tag);
+}
+
+bool Tags::HasAny(const Array& list, const Array& tags)
+{
+ for (const Tag& tag : tags)
+ {
+ if (HasTag(list, tag))
+ return true;
+ }
+ return false;
+}
+
+bool Tags::HasAnyExact(const Array& list, const Array& tags)
+{
+ for (const Tag& tag : tags)
+ {
+ if (HasTagExact(list, tag))
+ return true;
+ }
+ return false;
+}
+
+bool Tags::HasAll(const Array& list, const Array& tags)
+{
+ if (tags.IsEmpty())
+ return true;
+ for (const Tag& tag : tags)
+ {
+ if (!HasTag(list, tag))
+ return false;
+ }
+ return true;
+}
+
+bool Tags::HasAllExact(const Array& list, const Array& 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();
+}
diff --git a/Source/Engine/Level/Tags.cs b/Source/Engine/Level/Tags.cs
new file mode 100644
index 000000000..b4aa3468c
--- /dev/null
+++ b/Source/Engine/Level/Tags.cs
@@ -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, IEquatable, IComparable, IComparable, IComparable
+ {
+ ///
+ /// The default .
+ ///
+ public static Tag Default => new Tag(-1);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The tag index.
+ 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;
+ }
+
+ ///
+ /// Compares two tags.
+ ///
+ /// The lft tag.
+ /// The right tag.
+ /// True if both values are equal, otherwise false.
+ public static bool operator ==(Tag left, Tag right)
+ {
+ return left.Index == right.Index;
+ }
+
+ ///
+ /// Compares two tags.
+ ///
+ /// The lft tag.
+ /// The right tag.
+ /// True if both values are not equal, otherwise false.
+ public static bool operator !=(Tag left, Tag right)
+ {
+ return left.Index == right.Index;
+ }
+
+ ///
+ /// Checks if tag is valid.
+ ///
+ /// The tag to check.
+ /// True if tag is valid, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator bool(Tag tag)
+ {
+ return tag.Index != -1;
+ }
+
+ ///
+ public int CompareTo(object obj)
+ {
+ if (obj is string asString)
+ return CompareTo(asString);
+ if (obj is Tag asTag)
+ return CompareTo(asTag);
+ return 0;
+ }
+
+ ///
+ public bool Equals(Tag other)
+ {
+ return Index == other.Index;
+ }
+
+ ///
+ public bool Equals(string other)
+ {
+ return string.Equals(ToString(), other, StringComparison.Ordinal);
+ }
+
+ ///
+ public int CompareTo(Tag other)
+ {
+ return string.Compare(ToString(), ToString(), StringComparison.Ordinal);
+ }
+
+ ///
+ public int CompareTo(string other)
+ {
+ return string.Compare(ToString(), other, StringComparison.Ordinal);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is Tag other && Index == other.Index;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return Index;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Tags.Internal_GetTagName(Index);
+ }
+ }
+
+ partial class Tags
+ {
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tag to check.
+ /// True if given tag is contained by the list of tags. Returns false for empty list.
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tag to check.
+ /// True if given tag is contained by the list of tags. Returns false for empty list.
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if any of of the given tags is contained by the list of tags.
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if any of the given tags is contained by the list of tags.
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if all of the given tags are contained by the list of tags. Returns true for empty list.
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if all of the given tags are contained by the list of tags. Returns true for empty list.
+ 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;
+ }
+ }
+}
diff --git a/Source/Engine/Level/Tags.h b/Source/Engine/Level/Tags.h
new file mode 100644
index 000000000..eda44e224
--- /dev/null
+++ b/Source/Engine/Level/Tags.h
@@ -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"
+
+///
+/// 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.
+///
+API_STRUCT(NoDefault) struct FLAXENGINE_API Tag
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(Tag);
+
+ ///
+ /// Index of the tag (in global Level.Tags list).
+ ///
+ API_FIELD() int32 Index = -1;
+
+ ///
+ /// Gets the tag name.
+ ///
+ 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
+{
+ 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
+
+///
+/// Gameplay tags utilities.
+///
+API_CLASS(Static) class FLAXENGINE_API Tags
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(Tags);
+
+ ///
+ /// List of all tags.
+ ///
+ API_FIELD(ReadOnly) static Array List;
+
+ ///
+ /// Gets or adds the tag.
+ ///
+ /// The tag name.
+ /// The tag.
+ API_FUNCTION() static Tag Get(const StringView& tagName);
+
+public:
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tag to check.
+ /// True if given tag is contained by the list of tags. Returns false for empty list.
+ static bool HasTag(const Array& list, const Tag& tag);
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tag to check.
+ /// True if given tag is contained by the list of tags. Returns false for empty list.
+ static bool HasTagExact(const Array& list, const Tag& tag);
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if any of of the given tags is contained by the list of tags.
+ static bool HasAny(const Array& list, const Array& tags);
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if any of the given tags is contained by the list of tags.
+ static bool HasAnyExact(const Array& list, const Array& tags);
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if all of the given tags are contained by the list of tags. Returns true for empty list.
+ static bool HasAll(const Array& list, const Array& tags);
+
+ ///
+ /// 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.
+ ///
+ /// The tags list to use.
+ /// The tags to check.
+ /// True if all of the given tags are contained by the list of tags. Returns true for empty list.
+ static bool HasAllExact(const Array& list, const Array& tags);
+
+private:
+ API_FUNCTION(NoProxy) static const String& GetTagName(int32 tag);
+};
+
+#if !BUILD_RELEASE
+extern FLAXENGINE_API String* TagsListDebug; // Used by flax.natvis
+#endif
diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp
index 0e2c62100..6ce8c1ae1 100644
--- a/Source/Engine/Networking/NetworkManager.cpp
+++ b/Source/Engine/Networking/NetworkManager.cpp
@@ -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;
}
diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h
index b46c1a294..b87cb5035 100644
--- a/Source/Engine/Physics/PhysicalMaterial.h
+++ b/Source/Engine/Physics/PhysicalMaterial.h
@@ -3,7 +3,7 @@
#pragma once
#include "Types.h"
-#include "Engine/Serialization/ISerializable.h"
+#include "Engine/Core/ISerializable.h"
///
/// Physical materials are used to define the response of a physical object when interacting dynamically with the world.
diff --git a/Source/Engine/Renderer/Lightmaps.h b/Source/Engine/Renderer/Lightmaps.h
index 25c0e5262..0be6fbdd8 100644
--- a/Source/Engine/Renderer/Lightmaps.h
+++ b/Source/Engine/Renderer/Lightmaps.h
@@ -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
diff --git a/Source/Engine/Scripting/BinaryModule.h b/Source/Engine/Scripting/BinaryModule.h
index 7c69a2dc6..6a8e87314 100644
--- a/Source/Engine/Scripting/BinaryModule.h
+++ b/Source/Engine/Scripting/BinaryModule.h
@@ -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"
///
diff --git a/Source/Engine/Scripting/InternalCalls.h b/Source/Engine/Scripting/InternalCalls.h
index 2e8a09aa5..184ebb335 100644
--- a/Source/Engine/Scripting/InternalCalls.h
+++ b/Source/Engine/Scripting/InternalCalls.h
@@ -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);
diff --git a/Source/Engine/Scripting/ManagedSerialization.h b/Source/Engine/Scripting/ManagedSerialization.h
index 10495dd36..874c5ff77 100644
--- a/Source/Engine/Scripting/ManagedSerialization.h
+++ b/Source/Engine/Scripting/ManagedSerialization.h
@@ -2,7 +2,7 @@
#pragma once
-#include "Engine/Serialization/ISerializable.h"
+#include "Engine/Core/ISerializable.h"
#include "ManagedCLR/MTypes.h"
///
diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h
index d8eb5a51f..4615a2c17 100644
--- a/Source/Engine/Terrain/TerrainChunk.h
+++ b/Source/Engine/Terrain/TerrainChunk.h
@@ -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"
diff --git a/Source/Engine/Tests/TestLevel.cpp b/Source/Engine/Tests/TestLevel.cpp
index 830868a1d..0ed90ce45 100644
--- a/Source/Engine/Tests/TestLevel.cpp
+++ b/Source/Engine/Tests/TestLevel.cpp
@@ -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
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({ 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({ 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 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;
+ }
+}
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h
index 84c763a27..237f41826 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.h
+++ b/Source/Engine/Tools/ModelTool/ModelTool.h
@@ -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"
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h
index 2119fa9f1..159efd58c 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.h
+++ b/Source/Engine/Tools/TextureTool/TextureTool.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;
diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs
index f9bc54655..30ff25aa1 100644
--- a/Source/Engine/UI/GUI/Common/Button.cs
+++ b/Source/Engine/UI/GUI/Common/Button.cs
@@ -71,6 +71,16 @@ namespace FlaxEngine.GUI
///
public event Action
public event Action KeyUp;
+ ///
+ /// Gets or sets a value indicating whether the text box can end edit via left click outside of the control
+ ///
+ [HideInEditor]
+ public bool EndEditOnClick { get; set; } = true;
+
///
/// Gets or sets a value indicating whether this is a multiline text box control.
///
@@ -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);
}
+ ///
+ /// Restores the Text from the start.
+ ///
+ public void RestoreTextFromStart()
+ {
+ // Restore text from start
+ SetSelection(-1);
+ _text = _onStartEditValue;
+ OnTextChanged();
+ }
+
///
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;
}
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index bc49c284f..8636e0bab 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -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("");
diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs
index 00c43876e..f437993ce 100644
--- a/Source/Tools/Flax.Build/Build/Builder.cs
+++ b/Source/Tools/Flax.Build/Build/Builder.cs
@@ -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();
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/IncludesCache.cs b/Source/Tools/Flax.Build/Build/NativeCpp/IncludesCache.cs
index fa4001158..16d690e48 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/IncludesCache.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/IncludesCache.cs
@@ -13,9 +13,153 @@ namespace Flax.Build.NativeCpp
///
public static class IncludesCache
{
- private static readonly Dictionary DirectIncludesCache = new Dictionary();
- private static readonly Dictionary AllIncludesCache = new Dictionary();
+ private static Dictionary DirectIncludesCache = new Dictionary();
+ private static Dictionary AllIncludesCache = new Dictionary();
+ private static Dictionary DirectIncludesTimestampCache = new Dictionary();
+ private static Dictionary AllIncludesTimestampCache = new Dictionary();
+ private static Dictionary FileExistsCache = new Dictionary();
+ private static Dictionary FileTimestampCache = new Dictionary();
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 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 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 kvp in DirectIncludesTimestampCache)
+ {
+ writer.Write(kvp.Key);
+ writer.Write(kvp.Value.Ticks);
+ }
+ }
+
+ // AllIncludesTimestampCache
+ {
+ writer.Write(AllIncludesTimestampCache.Count);
+ foreach (KeyValuePair 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;
+ }
///
/// Finds all included files by the source file (including dependencies).
@@ -24,12 +168,24 @@ namespace Flax.Build.NativeCpp
/// The list of included files by this file. Not null nut may be empty.
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();
@@ -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;
}
}
diff --git a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs
index 603b66c9f..1fa8ed4f0 100644
--- a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs
+++ b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs
@@ -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}]);");
diff --git a/Source/flax.natvis b/Source/flax.natvis
index 193efe0cc..70f9b15e3 100644
--- a/Source/flax.natvis
+++ b/Source/flax.natvis
@@ -215,4 +215,10 @@
+
+
+ None
+ Tag={TagsListDebug[Index]}
+
+