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]} + +