diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax
index 13af92065..519aebba2 100644
--- a/Content/Editor/IconsAtlas.flax
+++ b/Content/Editor/IconsAtlas.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:de930eef22208b95882bc9f870063e339672b577d381bcfa6603f0be45c814c5
-size 5610110
+oid sha256:63e9f8409580a700befdfceac9f8db3123efb72eb589b54d86429d330ff36a8f
+size 5611340
diff --git a/Content/Shaders/BakeLightmap.flax b/Content/Shaders/BakeLightmap.flax
index fac992469..37ae66747 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:cc4b141137661d995ff571191150c5997fa6f6576572b5c2281c395a12772d7c
-size 16095
+oid sha256:6f3f83f05ee829a7981dc8b832f5b1160f23cf779aa84a7838c63aa7ff0bb336
+size 16284
diff --git a/Flax.flaxproj b/Flax.flaxproj
index 6826bedfe..e135c2741 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -3,7 +3,7 @@
"Version": {
"Major": 1,
"Minor": 1,
- "Build": 6219
+ "Build": 6221
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.",
diff --git a/README.md b/README.md
index be785107d..1c11dec82 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,16 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de
* Compile Flax project (hit F7 or CTRL+Shift+B)
* Run Flax (hit F5 key)
+---
+
+**Note**
+
+If building on Windows to support Vulkan rendering, first install the Vulkan SDK then set an environment variable to provide the path to the SDK prior to running GenerateProjectFiles.bat:
+
+ set VULKAN_SDK=C:\VulkanSDK\version\
+
+---
+
## Linux
* Install Visual Studio Code
diff --git a/Source/Editor/Content/Create/SettingsCreateEntry.cs b/Source/Editor/Content/Create/SettingsCreateEntry.cs
index 974de76cc..8445425b0 100644
--- a/Source/Editor/Content/Create/SettingsCreateEntry.cs
+++ b/Source/Editor/Content/Create/SettingsCreateEntry.cs
@@ -68,6 +68,11 @@ namespace FlaxEditor.Content.Create
///
InputSettings,
+ ///
+ /// The streaming settings.
+ ///
+ StreamingSettings,
+
///
/// The Windows settings.
///
@@ -116,6 +121,7 @@ namespace FlaxEditor.Content.Create
typeof(LocalizationSettings),
typeof(BuildSettings),
typeof(InputSettings),
+ typeof(StreamingSettings),
typeof(WindowsPlatformSettings),
typeof(UWPPlatformSettings),
typeof(LinuxPlatformSettings),
diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs
index db1e3ae8c..0985f7e5a 100644
--- a/Source/Editor/Content/Import/TextureImportEntry.cs
+++ b/Source/Editor/Content/Import/TextureImportEntry.cs
@@ -283,6 +283,13 @@ namespace FlaxEditor.Content.Import
[EditorOrder(250), VisibleIf("PreserveAlphaCoverage"), DefaultValue(0.5f), Tooltip("The reference value for the alpha coverage preserving.")]
public float PreserveAlphaCoverageReference { get; set; } = 0.5f;
+ ///
+ /// Texture group for streaming (negative if unused). See Streaming Settings.
+ ///
+ [CustomEditor(typeof(CustomEditors.Dedicated.TextureGroupEditor))]
+ [EditorOrder(300), Tooltip("Texture group for streaming (negative if unused). See Streaming Settings.")]
+ public int TextureGroup = -1;
+
///
/// The sprites. Used to keep created sprites on sprite atlas reimport.
///
@@ -305,6 +312,7 @@ namespace FlaxEditor.Content.Import
public float PreserveAlphaCoverageReference;
public float Scale;
public int MaxSize;
+ public int TextureGroup;
public Int2 Size;
public Rectangle[] SpriteAreas;
public string[] SpriteNames;
@@ -327,7 +335,8 @@ namespace FlaxEditor.Content.Import
PreserveAlphaCoverageReference = PreserveAlphaCoverageReference,
Scale = Scale,
Size = Size,
- MaxSize = (int)MaxSize
+ MaxSize = (int)MaxSize,
+ TextureGroup = TextureGroup,
};
if (Sprites != null && Sprites.Count > 0)
{
@@ -362,6 +371,7 @@ namespace FlaxEditor.Content.Import
PreserveAlphaCoverageReference = options.PreserveAlphaCoverageReference;
Scale = options.Scale;
MaxSize = ConvertMaxSize(options.MaxSize);
+ TextureGroup = options.TextureGroup;
Size = options.Size;
if (options.SpriteAreas != null)
{
diff --git a/Source/Editor/Content/Items/AssetItem.cs b/Source/Editor/Content/Items/AssetItem.cs
index afb68d42a..f534e93eb 100644
--- a/Source/Editor/Content/Items/AssetItem.cs
+++ b/Source/Editor/Content/Items/AssetItem.cs
@@ -108,6 +108,26 @@ namespace FlaxEditor.Content
return false;
}
+ ///
+ /// Called when user dags this item into editor viewport or scene tree node.
+ ///
+ /// The editor context (eg. editor viewport or scene tree node).
+ /// True if item can be dropped in, otherwise false.
+ public virtual bool OnEditorDrag(object context)
+ {
+ return false;
+ }
+
+ ///
+ /// Called when user drops the item into editor viewport or scene tree node.
+ ///
+ /// The editor context (eg. editor viewport or scene tree node).
+ /// The spawned object.
+ public virtual Actor OnEditorDrop(object context)
+ {
+ throw new NotSupportedException($"Asset {GetType()} doesn't support dropping into viewport.");
+ }
+
///
protected override bool DrawShadow => true;
diff --git a/Source/Editor/Content/Items/BinaryAssetItem.cs b/Source/Editor/Content/Items/BinaryAssetItem.cs
index 417bd23e4..2503e9a38 100644
--- a/Source/Editor/Content/Items/BinaryAssetItem.cs
+++ b/Source/Editor/Content/Items/BinaryAssetItem.cs
@@ -103,14 +103,26 @@ namespace FlaxEditor.Content
/// Implementation of for assets.
///
///
- public class ModelAssetItem : BinaryAssetItem
+ public class ModelItem : BinaryAssetItem
{
///
- public ModelAssetItem(string path, ref Guid id, string typeName, Type type)
+ public ModelItem(string path, ref Guid id, string typeName, Type type)
: base(path, ref id, typeName, type, ContentItemSearchFilter.Model)
{
}
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return true;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return new StaticModel { Model = FlaxEngine.Content.LoadAsync(ID) };
+ }
+
///
protected override void OnBuildTooltipText(StringBuilder sb)
{
@@ -142,14 +154,26 @@ namespace FlaxEditor.Content
/// Implementation of for assets.
///
///
- public class SkinnedModelAssetItem : BinaryAssetItem
+ public class SkinnedModeItem : BinaryAssetItem
{
///
- public SkinnedModelAssetItem(string path, ref Guid id, string typeName, Type type)
+ public SkinnedModeItem(string path, ref Guid id, string typeName, Type type)
: base(path, ref id, typeName, type, ContentItemSearchFilter.Model)
{
}
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return true;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return new AnimatedModel { SkinnedModel = FlaxEngine.Content.LoadAsync(ID) };
+ }
+
///
protected override void OnBuildTooltipText(StringBuilder sb)
{
diff --git a/Source/Editor/Content/Items/JsonAssetItem.cs b/Source/Editor/Content/Items/JsonAssetItem.cs
index 1c9232c9c..80957f181 100644
--- a/Source/Editor/Content/Items/JsonAssetItem.cs
+++ b/Source/Editor/Content/Items/JsonAssetItem.cs
@@ -22,7 +22,7 @@ namespace FlaxEditor.Content
public JsonAssetItem(string path, Guid id, string typeName)
: base(path, typeName, ref id)
{
- _thumbnail = Editor.Instance.Icons.Document128;
+ _thumbnail = Editor.Instance.Icons.Json128;
}
///
diff --git a/Source/Editor/Content/Items/PrefabItem.cs b/Source/Editor/Content/Items/PrefabItem.cs
index aee264d55..e758595ae 100644
--- a/Source/Editor/Content/Items/PrefabItem.cs
+++ b/Source/Editor/Content/Items/PrefabItem.cs
@@ -21,6 +21,18 @@ namespace FlaxEditor.Content
{
}
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return true;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return PrefabManager.SpawnPrefab(FlaxEngine.Content.LoadAsync(ID), null);
+ }
+
///
public override ContentItemType ItemType => ContentItemType.Asset;
diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs
index 04311644b..c1ccf5d73 100644
--- a/Source/Editor/Content/Items/VisualScriptItem.cs
+++ b/Source/Editor/Content/Items/VisualScriptItem.cs
@@ -538,6 +538,18 @@ namespace FlaxEditor.Content
Editor.Instance.CodeEditing.ClearTypes();
}
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return new ScriptType(typeof(Actor)).IsAssignableFrom(ScriptType) && ScriptType.CanCreateInstance;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return (Actor)ScriptType.CreateInstance();
+ }
+
///
public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.VisualScript128;
diff --git a/Source/Editor/Content/PreviewsCache.cpp b/Source/Editor/Content/PreviewsCache.cpp
index c89b00004..e05f16600 100644
--- a/Source/Editor/Content/PreviewsCache.cpp
+++ b/Source/Editor/Content/PreviewsCache.cpp
@@ -241,16 +241,13 @@ CreateAssetResult PreviewsCache::create(CreateAssetContext& context)
IMPORT_SETUP(PreviewsCache, 4);
// Create texture header (custom data)
- TextureHeader header;
- header.Width = ASSETS_ICONS_ATLAS_SIZE;
- header.Height = ASSETS_ICONS_ATLAS_SIZE;
- header.Format = ASSETS_ICONS_ATLAS_FORMAT;
- header.IsSRGB = false;
- header.IsCubeMap = false;
- header.MipLevels = 1;
- header.NeverStream = true;
- header.Type = TextureFormatType::Unknown;
- context.Data.CustomData.Copy(&header);
+ TextureHeader textureHeader;
+ textureHeader.Width = ASSETS_ICONS_ATLAS_SIZE;
+ textureHeader.Height = ASSETS_ICONS_ATLAS_SIZE;
+ textureHeader.Format = ASSETS_ICONS_ATLAS_FORMAT;
+ textureHeader.MipLevels = 1;
+ textureHeader.NeverStream = true;
+ context.Data.CustomData.Copy(&textureHeader);
// Create blank image (chunk 0)
uint64 imageSize = CalculateTextureMemoryUsage(ASSETS_ICONS_ATLAS_FORMAT, ASSETS_ICONS_ATLAS_SIZE, ASSETS_ICONS_ATLAS_SIZE, 1);
diff --git a/Source/Editor/Content/Proxy/AudioClipProxy.cs b/Source/Editor/Content/Proxy/AudioClipProxy.cs
index ba60d6ef9..ceff22dd1 100644
--- a/Source/Editor/Content/Proxy/AudioClipProxy.cs
+++ b/Source/Editor/Content/Proxy/AudioClipProxy.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Text;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
@@ -11,11 +12,51 @@ using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
+ ///
+ /// Implementation of for assets.
+ ///
+ ///
+ class AudioClipItem : BinaryAssetItem
+ {
+ ///
+ public AudioClipItem(string path, ref Guid id, string typeName, Type type)
+ : base(path, ref id, typeName, type, ContentItemSearchFilter.Audio)
+ {
+ }
+
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return true;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return new AudioSource { Clip = FlaxEngine.Content.LoadAsync(ID) };
+ }
+
+ ///
+ protected override void OnBuildTooltipText(StringBuilder sb)
+ {
+ base.OnBuildTooltipText(sb);
+
+ var asset = FlaxEngine.Content.Load(ID, 100);
+ if (asset)
+ {
+ var info = asset.Info;
+ sb.Append("Duration: ").Append(asset.Length).AppendLine();
+ sb.Append("Channels: ").Append(info.NumChannels).AppendLine();
+ sb.Append("Bit Depth: ").Append(info.BitDepth).AppendLine();
+ }
+ }
+ }
+
///
/// A asset proxy object.
///
///
- public class AudioClipProxy : BinaryAssetProxy
+ class AudioClipProxy : BinaryAssetProxy
{
private List _previews;
@@ -34,6 +75,12 @@ namespace FlaxEditor.Content
return new AudioClipWindow(editor, (AssetItem)item);
}
+ ///
+ public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
+ {
+ return new AudioClipItem(path, ref id, typeName, AssetType);
+ }
+
///
public override Color AccentColor => Color.FromRGB(0xB3452B);
diff --git a/Source/Editor/Content/Proxy/BinaryAssetProxy.cs b/Source/Editor/Content/Proxy/BinaryAssetProxy.cs
index 4d27f325b..d19f8fd63 100644
--- a/Source/Editor/Content/Proxy/BinaryAssetProxy.cs
+++ b/Source/Editor/Content/Proxy/BinaryAssetProxy.cs
@@ -47,9 +47,9 @@ namespace FlaxEditor.Content
if (typeof(TextureBase).IsAssignableFrom(type))
return new TextureAssetItem(path, ref id, typeName, type);
if (typeof(Model).IsAssignableFrom(type))
- return new ModelAssetItem(path, ref id, typeName, type);
+ return new ModelItem(path, ref id, typeName, type);
if (typeof(SkinnedModel).IsAssignableFrom(type))
- return new SkinnedModelAssetItem(path, ref id, typeName, type);
+ return new SkinnedModeItem(path, ref id, typeName, type);
ContentItemSearchFilter searchFilter;
if (typeof(MaterialBase).IsAssignableFrom(type))
@@ -58,11 +58,9 @@ namespace FlaxEditor.Content
searchFilter = ContentItemSearchFilter.Prefab;
else if (typeof(SceneAsset).IsAssignableFrom(type))
searchFilter = ContentItemSearchFilter.Scene;
- else if (typeof(AudioClip).IsAssignableFrom(type))
- searchFilter = ContentItemSearchFilter.Audio;
else if (typeof(Animation).IsAssignableFrom(type))
searchFilter = ContentItemSearchFilter.Animation;
- else if (typeof(ParticleEmitter).IsAssignableFrom(type) || typeof(ParticleSystem).IsAssignableFrom(type))
+ else if (typeof(ParticleEmitter).IsAssignableFrom(type))
searchFilter = ContentItemSearchFilter.Particles;
else
searchFilter = ContentItemSearchFilter.Other;
diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
index c23730375..e7d437adc 100644
--- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs
+++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
@@ -8,11 +8,36 @@ using FlaxEngine;
namespace FlaxEditor.Content
{
+ ///
+ /// Implementation of for assets.
+ ///
+ ///
+ class CollisionDataItem : BinaryAssetItem
+ {
+ ///
+ public CollisionDataItem(string path, ref Guid id, string typeName, Type type)
+ : base(path, ref id, typeName, type, ContentItemSearchFilter.Other)
+ {
+ }
+
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return true;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return new MeshCollider { CollisionData = FlaxEngine.Content.LoadAsync(ID) };
+ }
+ }
+
///
/// A asset proxy object.
///
///
- public class CollisionDataProxy : BinaryAssetProxy
+ class CollisionDataProxy : BinaryAssetProxy
{
///
public override string Name => "Collision Data";
@@ -23,6 +48,12 @@ namespace FlaxEditor.Content
return new CollisionDataWindow(editor, item as AssetItem);
}
+ ///
+ public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
+ {
+ return new CollisionDataItem(path, ref id, typeName, AssetType);
+ }
+
///
public override Color AccentColor => Color.FromRGB(0x2c3e50);
diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs
index 41cc06ee1..5029c2eb4 100644
--- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs
+++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs
@@ -92,6 +92,7 @@ namespace FlaxEditor.Content
!type.IsAbstract &&
!type.IsGenericType &&
type.Type.GetConstructor(Type.EmptyTypes) != null &&
+ !typeof(FlaxEngine.GUI.Control).IsAssignableFrom(type.Type) &&
!typeof(FlaxEngine.Object).IsAssignableFrom(type.Type);
}
}
diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs
index a28a022a8..2baf717e1 100644
--- a/Source/Editor/Content/Proxy/ModelProxy.cs
+++ b/Source/Editor/Content/Proxy/ModelProxy.cs
@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
menu.AddButton("Create collision data", () =>
{
- var model = FlaxEngine.Content.LoadAsync(((ModelAssetItem)item).ID);
+ var model = FlaxEngine.Content.LoadAsync(((ModelItem)item).ID);
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
collisionDataProxy.CreateCollisionDataFromModel(model);
});
diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
index f3b2eb67a..7e206fba9 100644
--- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
+++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs
@@ -10,6 +10,31 @@ using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
+ ///
+ /// Implementation of for assets.
+ ///
+ ///
+ class ParticleSystemItem : BinaryAssetItem
+ {
+ ///
+ public ParticleSystemItem(string path, ref Guid id, string typeName, Type type)
+ : base(path, ref id, typeName, type, ContentItemSearchFilter.Particles)
+ {
+ }
+
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return true;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return new ParticleEffect { ParticleSystem = FlaxEngine.Content.LoadAsync(ID) };
+ }
+ }
+
///
/// A asset proxy object.
///
@@ -28,6 +53,12 @@ namespace FlaxEditor.Content
return new ParticleSystemWindow(editor, item as AssetItem);
}
+ ///
+ public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
+ {
+ return new ParticleSystemItem(path, ref id, typeName, AssetType);
+ }
+
///
public override Color AccentColor => Color.FromRGB(0xFF790200);
diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
index 83d9247d8..66c1bfff2 100644
--- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
+++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs
@@ -7,6 +7,31 @@ using FlaxEngine;
namespace FlaxEditor.Content
{
+ ///
+ /// Implementation of for assets.
+ ///
+ ///
+ class SceneAnimationItem : BinaryAssetItem
+ {
+ ///
+ public SceneAnimationItem(string path, ref Guid id, string typeName, Type type)
+ : base(path, ref id, typeName, type, ContentItemSearchFilter.Other)
+ {
+ }
+
+ ///
+ public override bool OnEditorDrag(object context)
+ {
+ return true;
+ }
+
+ ///
+ public override Actor OnEditorDrop(object context)
+ {
+ return new SceneAnimationPlayer { Animation = FlaxEngine.Content.LoadAsync(ID) };
+ }
+ }
+
///
/// A asset proxy object.
///
@@ -22,6 +47,12 @@ namespace FlaxEditor.Content
return new SceneAnimationWindow(editor, item as AssetItem);
}
+ ///
+ public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
+ {
+ return new SceneAnimationItem(path, ref id, typeName, AssetType);
+ }
+
///
public override Color AccentColor => Color.FromRGB(0xff5c4a87);
diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h
index 20d578d2e..63b018343 100644
--- a/Source/Editor/Cooker/PlatformTools.h
+++ b/Source/Editor/Cooker/PlatformTools.h
@@ -136,6 +136,8 @@ public:
String AssemblerArgs;
String ArchiverPath;
String ArchiverArgs;
+ String AuxToolPath;
+ String AuxToolArgs;
String AotCachePath;
Dictionary EnvVars;
Array AssembliesSearchDirs;
diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
index 5f097f638..703b4179a 100644
--- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
@@ -25,6 +25,7 @@
#include "Engine/Core/Config/PlatformSettings.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Core/Config/BuildSettings.h"
+#include "Engine/Streaming/StreamingSettings.h"
#include "Engine/ShadersCompilation/ShadersCompilation.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
@@ -44,6 +45,35 @@
Dictionary CookAssetsStep::AssetProcessors;
+bool CookAssetsStep::CacheEntry::IsValid(bool withDependencies)
+{
+ AssetInfo assetInfo;
+ if (Content::GetAssetInfo(ID, assetInfo))
+ {
+ if (TypeName == assetInfo.TypeName)
+ {
+ if (FileSystem::GetFileLastEditTime(assetInfo.Path) <= FileModified)
+ {
+ bool isValid = true;
+ if (withDependencies)
+ {
+ for (auto& f : FileDependencies)
+ {
+ if (FileSystem::GetFileLastEditTime(f.First) > f.Second)
+ {
+ isValid = false;
+ break;
+ }
+ }
+ }
+ if (isValid)
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
CookAssetsStep::CacheEntry& CookAssetsStep::CacheData::CreateEntry(const JsonAssetBase* asset, String& cachedFilePath)
{
ASSERT(asset->DataTypeName.HasChars());
@@ -68,7 +98,6 @@ CookAssetsStep::CacheEntry& CookAssetsStep::CacheData::CreateEntry(const Asset*
void CookAssetsStep::CacheData::InvalidateShaders()
{
LOG(Info, "Invalidating cached shader assets.");
-
for (auto e = Entries.Begin(); e.IsNotEnd(); ++e)
{
auto& typeName = e->Value.TypeName;
@@ -83,6 +112,23 @@ void CookAssetsStep::CacheData::InvalidateShaders()
}
}
+void CookAssetsStep::CacheData::InvalidateTextures()
+{
+ LOG(Info, "Invalidating cached texture assets.");
+ for (auto e = Entries.Begin(); e.IsNotEnd(); ++e)
+ {
+ auto& typeName = e->Value.TypeName;
+ if (
+ typeName == Texture::TypeName ||
+ typeName == CubeTexture::TypeName ||
+ typeName == SpriteAtlas::TypeName
+ )
+ {
+ Entries.Remove(e);
+ }
+ }
+}
+
void CookAssetsStep::CacheData::Load(CookingData& data)
{
HeaderFilePath = data.CacheDirectory / String::Format(TEXT("CookedHeader_{0}.bin"), FLAXENGINE_VERSION_BUILD);
@@ -159,17 +205,17 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
Entries.Clear();
}
+ const auto buildSettings = BuildSettings::Get();
+ const auto gameSettings = GameSettings::Get();
+
// Invalidate shaders and assets with shaders if need to rebuild them
bool invalidateShaders = false;
- const auto buildSettings = BuildSettings::Get();
- const bool shadersNoOptimize = buildSettings->ShadersNoOptimize;
- const bool shadersGenerateDebugData = buildSettings->ShadersGenerateDebugData;
- if (shadersNoOptimize != Settings.Global.ShadersNoOptimize)
+ if (buildSettings->ShadersNoOptimize != Settings.Global.ShadersNoOptimize)
{
LOG(Info, "ShadersNoOptimize option has been modified.");
invalidateShaders = true;
}
- if (shadersGenerateDebugData != Settings.Global.ShadersGenerateDebugData)
+ if (buildSettings->ShadersGenerateDebugData != Settings.Global.ShadersGenerateDebugData)
{
LOG(Info, "ShadersGenerateDebugData option has been modified.");
invalidateShaders = true;
@@ -218,6 +264,12 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
#endif
if (invalidateShaders)
InvalidateShaders();
+
+ // Invalidate textures if streaming settings gets modified
+ if (Settings.Global.StreamingSettingsAssetId != gameSettings->Streaming || (Entries.ContainsKey(gameSettings->Streaming) && !Entries[gameSettings->Streaming].IsValid()))
+ {
+ InvalidateTextures();
+ }
}
void CookAssetsStep::CacheData::Save()
@@ -541,91 +593,127 @@ bool ProcessParticleEmitter(CookAssetsStep::AssetCookData& data)
bool ProcessTextureBase(CookAssetsStep::AssetCookData& data)
{
const auto asset = static_cast(data.Asset);
-
- // Check if target platform doesn't support the texture format
+ const auto& assetHeader = asset->StreamingTexture()->GetHeader();
const auto format = asset->Format();
const auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format);
+ const auto streamingSettings = StreamingSettings::Get();
+ int32 mipLevelsMax = GPU_MAX_TEXTURE_MIP_LEVELS;
+ if (assetHeader->TextureGroup >= 0 && assetHeader->TextureGroup < streamingSettings->TextureGroups.Count())
+ {
+ auto& group = streamingSettings->TextureGroups[assetHeader->TextureGroup];
+ mipLevelsMax = group.MipLevelsMax;
+ group.MipLevelsMaxPerPlatform.TryGet(data.Data.Tools->GetPlatform(), mipLevelsMax);
+ }
+
+ // Faster path if don't need to modify texture for the target platform
+ if (format == targetFormat && assetHeader->MipLevels <= mipLevelsMax)
+ {
+ return CookAssetsStep::ProcessDefaultAsset(data);
+ }
+
+ // Extract texture data from the asset
+ TextureData textureDataSrc;
+ auto assetLock = asset->LockData();
+ if (asset->GetTextureData(textureDataSrc, false))
+ {
+ LOG(Error, "Failed to load data from texture {0}", asset->ToString());
+ return true;
+ }
+
+ TextureData* textureData = &textureDataSrc;
+ TextureData textureDataTmp1;
+
if (format != targetFormat)
{
- // Extract texture data from the asset
- TextureData textureData;
- auto assetLock = asset->LockData();
- if (asset->GetTextureData(textureData, false))
- {
- LOG(Error, "Failed to load data from texture {0}", asset->ToString());
- return true;
- }
-
// Convert texture data to the target format
- TextureData targetTextureData;
- if (TextureTool::Convert(targetTextureData, textureData, targetFormat))
+ if (TextureTool::Convert(textureDataTmp1, *textureData, targetFormat))
{
LOG(Error, "Failed to convert texture {0} from format {1} to {2}", asset->ToString(), (int32)format, (int32)targetFormat);
return true;
}
-
- // Adjust texture header
- auto& header = *(TextureHeader*)data.InitData.CustomData.Get();
- header.Width = targetTextureData.Width;
- header.Height = targetTextureData.Height;
- header.Format = targetTextureData.Format;
- header.MipLevels = targetTextureData.GetMipLevels();
-
- // Serialize texture data into the asset chunks
- for (int32 mipIndex = 0; mipIndex < targetTextureData.GetMipLevels(); mipIndex++)
- {
- auto chunk = New();
- data.InitData.Header.Chunks[mipIndex] = chunk;
-
- // Calculate the texture data storage layout
- uint32 rowPitch, slicePitch;
- const int32 mipWidth = Math::Max(1, targetTextureData.Width >> mipIndex);
- const int32 mipHeight = Math::Max(1, targetTextureData.Height >> mipIndex);
- RenderTools::ComputePitch(targetTextureData.Format, mipWidth, mipHeight, rowPitch, slicePitch);
- chunk->Data.Allocate(slicePitch * targetTextureData.GetArraySize());
-
- // Copy array slices into mip data (sequential)
- for (int32 arrayIndex = 0; arrayIndex < targetTextureData.Items.Count(); arrayIndex++)
- {
- auto& mipData = targetTextureData.Items[arrayIndex].Mips[mipIndex];
- byte* src = mipData.Data.Get();
- byte* dst = chunk->Data.Get() + (slicePitch * arrayIndex);
-
- // Faster path if source and destination data layout matches
- if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch)
- {
- Platform::MemoryCopy(dst, src, slicePitch);
- }
- else
- {
- const auto copyRowSize = Math::Min(mipData.RowPitch, rowPitch);
- for (uint32 line = 0; line < mipData.Lines; line++)
- {
- Platform::MemoryCopy(dst, src, copyRowSize);
- src += mipData.RowPitch;
- dst += rowPitch;
- }
- }
- }
- }
-
- // Clone any custom asset chunks (eg. sprite atlas data, mips are in 0-13 chunks)
- for (int32 i = 14; i < ASSET_FILE_DATA_CHUNKS; i++)
- {
- const auto chunk = asset->GetChunk(i);
- if (chunk != nullptr && chunk->IsMissing() && chunk->ExistsInFile())
- {
- if (asset->Storage->LoadAssetChunk(chunk))
- return true;
- data.InitData.Header.Chunks[i] = chunk->Clone();
- }
- }
-
- return false;
+ textureData = &textureDataSrc;
}
- // Fallback to the default asset processing
- return CookAssetsStep::ProcessDefaultAsset(data);
+ if (assetHeader->MipLevels > mipLevelsMax)
+ {
+ // Reduce texture quality
+ const int32 mipLevelsToStrip = assetHeader->MipLevels - mipLevelsMax;
+ textureData->Width = Math::Max(1, textureData->Width >> mipLevelsToStrip);
+ textureData->Height = Math::Max(1, textureData->Height >> mipLevelsToStrip);
+ textureData->Depth = Math::Max(1, textureData->Depth >> mipLevelsToStrip);
+ for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++)
+ {
+ auto& item = textureData->Items[arrayIndex];
+ Array> oldMips(MoveTemp(item.Mips));
+ item.Mips.Resize(mipLevelsMax);
+ for (int32 mipIndex = 0; mipIndex < mipLevelsMax; mipIndex++)
+ {
+ auto& dstMip = item.Mips[mipIndex];
+ auto& srcMip = oldMips[mipIndex + mipLevelsToStrip];
+ dstMip = MoveTemp(srcMip);
+ }
+ }
+ }
+
+ // Adjust texture header
+ auto& header = *(TextureHeader*)data.InitData.CustomData.Get();
+ header.Width = textureData->Width;
+ header.Height = textureData->Height;
+ header.Depth = textureData->Depth;
+ header.Format = textureData->Format;
+ header.MipLevels = textureData->GetMipLevels();
+
+ // Serialize texture data into the asset chunks
+ for (int32 mipIndex = 0; mipIndex < textureData->GetMipLevels(); mipIndex++)
+ {
+ auto chunk = New();
+ data.InitData.Header.Chunks[mipIndex] = chunk;
+
+ // Calculate the texture data storage layout
+ uint32 rowPitch, slicePitch;
+ const int32 mipWidth = Math::Max(1, textureData->Width >> mipIndex);
+ const int32 mipHeight = Math::Max(1, textureData->Height >> mipIndex);
+ RenderTools::ComputePitch(textureData->Format, mipWidth, mipHeight, rowPitch, slicePitch);
+ chunk->Data.Allocate(slicePitch * textureData->GetArraySize());
+
+ // Copy array slices into mip data (sequential)
+ for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++)
+ {
+ auto& mipData = textureData->Items[arrayIndex].Mips[mipIndex];
+ byte* src = mipData.Data.Get();
+ byte* dst = chunk->Data.Get() + (slicePitch * arrayIndex);
+
+ // Faster path if source and destination data layout matches
+ if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch)
+ {
+ Platform::MemoryCopy(dst, src, slicePitch);
+ }
+ else
+ {
+ const auto copyRowSize = Math::Min(mipData.RowPitch, rowPitch);
+ for (uint32 line = 0; line < mipData.Lines; line++)
+ {
+ Platform::MemoryCopy(dst, src, copyRowSize);
+ src += mipData.RowPitch;
+ dst += rowPitch;
+ }
+ }
+ }
+ }
+
+ // Clone any custom asset chunks (eg. sprite atlas data, mips are in 0-13 chunks)
+ for (int32 i = 14; i < ASSET_FILE_DATA_CHUNKS; i++)
+ {
+ const auto chunk = asset->GetChunk(i);
+ if (chunk != nullptr && chunk->IsMissing() && chunk->ExistsInFile())
+ {
+ if (asset->Storage->LoadAssetChunk(chunk))
+ return true;
+ data.InitData.Header.Chunks[i] = chunk->Clone();
+ }
+ }
+
+ return false;
}
CookAssetsStep::CookAssetsStep()
@@ -938,6 +1026,7 @@ bool CookAssetsStep::Perform(CookingData& data)
{
cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize;
cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData;
+ cache.Settings.Global.StreamingSettingsAssetId = gameSettings->Streaming;
}
// Note: this step converts all the assets (even the json) into the binary files (FlaxStorage format).
diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.h b/Source/Editor/Cooker/Steps/CookAssetsStep.h
index 8896e1e99..f134d3dd9 100644
--- a/Source/Editor/Cooker/Steps/CookAssetsStep.h
+++ b/Source/Editor/Cooker/Steps/CookAssetsStep.h
@@ -49,6 +49,8 @@ public:
/// The list of files on which this entry depends on. Cached date is the last edit time used to discard cache result on modification.
///
FileDependenciesList FileDependencies;
+
+ bool IsValid(bool withDependencies = false);
};
///
@@ -93,6 +95,7 @@ public:
{
bool ShadersNoOptimize;
bool ShadersGenerateDebugData;
+ Guid StreamingSettingsAssetId;
} Global;
} Settings;
@@ -134,6 +137,11 @@ public:
///
void InvalidateShaders();
+ ///
+ /// Removes all cached entries for assets that contain a texture. This forces rebuild for them.
+ ///
+ void InvalidateTextures();
+
///
/// Loads the cache for the given cooking data.
///
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index ca45652b9..c1c004d43 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -142,6 +142,10 @@ namespace FlaxEditor.CustomEditors
///
public void RebuildLayout()
{
+ // Skip rebuilding during init
+ if (CurrentCustomEditor == this)
+ return;
+
// Special case for root objects to run normal layout build
if (_presenter.Selection == Values)
{
@@ -789,8 +793,7 @@ namespace FlaxEditor.CustomEditors
/// True if allow to handle this event, otherwise false.
protected virtual bool OnDirty(CustomEditor editor, object value, object token = null)
{
- ParentEditor.OnDirty(editor, value, token);
- return true;
+ return ParentEditor.OnDirty(editor, value, token);
}
///
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index ffe4ce585..8a6ae54a9 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -68,19 +68,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var cm = new ItemsListContextMenu(180);
for (int i = 0; i < scripts.Count; i++)
{
- var scriptType = scripts[i];
- var item = new ItemsListContextMenu.Item(scriptType.Name, scriptType)
- {
- TooltipText = scriptType.TypeName,
- };
- var attributes = scriptType.GetAttributes(false);
- var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
- if (tooltipAttribute != null)
- {
- item.TooltipText += '\n';
- item.TooltipText += tooltipAttribute.Text;
- }
- cm.AddItem(item);
+ cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
}
cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
cm.SortChildren();
diff --git a/Source/Editor/CustomEditors/Dedicated/TextureGroupEditor.cs b/Source/Editor/CustomEditors/Dedicated/TextureGroupEditor.cs
new file mode 100644
index 000000000..1aff3583b
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/TextureGroupEditor.cs
@@ -0,0 +1,51 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Content.Settings;
+using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.GUI;
+using FlaxEngine;
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for index.
+ ///
+ internal class TextureGroupEditor : CustomEditor
+ {
+ private ComboBoxElement _element;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ _element = layout.ComboBox();
+ _element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
+ _element.ComboBox.AddItem("None");
+ var groups = GameSettings.Load();
+ if (groups?.TextureGroups != null)
+ {
+ for (int i = 0; i < groups.TextureGroups.Length; i++)
+ {
+ _element.ComboBox.AddItem(groups.TextureGroups[i].Name);
+ }
+ }
+ }
+
+ private void OnSelectedIndexChanged(ComboBox comboBox)
+ {
+ var value = comboBox.HasSelection ? comboBox.SelectedIndex - 1 : -1;
+ SetValue(value);
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ var value = (int)Values[0];
+ _element.ComboBox.SelectedIndex = value + 1;
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index dcd70a340..d7a6f2617 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -412,6 +412,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
public override void Initialize(LayoutElementsContainer layout)
{
_cachedType = null;
+ if (HasDifferentTypes)
+ {
+ // TODO: support stable editing multiple different control types (via generic way or for transform-only)
+ return;
+ }
// Set control type button
var space = layout.Space(20);
@@ -600,34 +605,26 @@ namespace FlaxEditor.CustomEditors.Dedicated
private bool _cachedXEq;
private bool _cachedYEq;
- ///
- /// Refreshes if equality of anchors does not correspond to the cached equality
- ///
- public void RefreshBaseOnAnchorsEquality()
- {
- if (Values.HasNull)
- return;
-
- GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
- if (xEq != _cachedXEq || yEq != _cachedYEq)
- {
- RebuildLayout();
- return;
- }
- }
-
///
public override void Refresh()
{
- // Automatic layout rebuild if control type gets changed
- var type = Values.HasNull ? null : Values[0].GetType();
- if (type != _cachedType)
+ if (_cachedType != null)
{
- RebuildLayout();
- return;
+ // Automatic layout rebuild if control type gets changed
+ var type = Values.HasNull ? null : Values[0].GetType();
+ if (type != _cachedType)
+ {
+ RebuildLayout();
+ return;
+ }
+
+ // Refresh anchors
+ GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
+ if (xEq != _cachedXEq || yEq != _cachedYEq)
+ {
+ RebuildLayout();
+ }
}
- RefreshBaseOnAnchorsEquality();
- //RefreshValues();
base.Refresh();
}
@@ -642,59 +639,39 @@ namespace FlaxEditor.CustomEditors.Dedicated
var cm = new ItemsListContextMenu(180);
for (int i = 0; i < controlTypes.Count; i++)
{
- var controlType = controlTypes[i];
- var item = new ItemsListContextMenu.Item(controlType.Name, controlType)
- {
- TooltipText = controlType.TypeName,
- };
- var attributes = controlType.GetAttributes(false);
- var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
- if (tooltipAttribute != null)
- {
- item.TooltipText += '\n';
- item.TooltipText += tooltipAttribute.Text;
- }
- cm.AddItem(item);
+ cm.AddItem(new TypeSearchPopup.TypeItemView(controlTypes[i]));
}
-
cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag);
cm.SortChildren();
cm.Show(button.Parent, button.BottomLeft);
}
+ private void SetType(ref ScriptType controlType, UIControl uiControl)
+ {
+ string previousName = uiControl.Control?.GetType().Name ?? nameof(UIControl);
+ uiControl.Control = (Control)controlType.CreateInstance();
+ if (uiControl.Name.StartsWith(previousName))
+ {
+ string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
+ uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null);
+ }
+ }
+
private void SetType(ScriptType controlType)
{
var uiControls = ParentEditor.Values;
- if (Presenter.Undo != null)
+ if (Presenter.Undo?.Enabled ?? false)
{
using (new UndoMultiBlock(Presenter.Undo, uiControls, "Set Control Type"))
{
for (int i = 0; i < uiControls.Count; i++)
- {
- var uiControl = (UIControl)uiControls[i];
- string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name;
- uiControl.Control = (Control)controlType.CreateInstance();
- if (uiControl.Name.StartsWith(previousName))
- {
- string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
- uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null);
- }
- }
+ SetType(ref controlType, (UIControl)uiControls[i]);
}
}
else
{
for (int i = 0; i < uiControls.Count; i++)
- {
- var uiControl = (UIControl)uiControls[i];
- string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name;
- uiControl.Control = (Control)controlType.CreateInstance();
- if (uiControl.Name.StartsWith(previousName))
- {
- string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
- uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null);
- }
- }
+ SetType(ref controlType, (UIControl)uiControls[i]);
}
ParentEditor.RebuildLayout();
diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs
index 41ca6ba86..7f023e8bc 100644
--- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs
@@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Copy old values
Array.Copy(array, 0, newValues, 0, sharedCount);
- if (elementType.IsValueType)
+ if (elementType.IsValueType || NotNullItems)
{
// Fill new entries with the last value
for (int i = oldSize; i < newSize; i++)
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 141330aea..6c3dda04e 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -77,11 +77,16 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
+ ///
+ /// Determines if value of collection can be null.
+ ///
+ protected bool NotNullItems;
+
private IntegerValueElement _size;
+ private Color _background;
private int _elementsCount;
private bool _readOnly;
private bool _canReorderItems;
- private bool _notNullItems;
///
/// Gets the length of the collection.
@@ -103,15 +108,15 @@ namespace FlaxEditor.CustomEditors.Editors
///
public override void Initialize(LayoutElementsContainer layout)
{
- _readOnly = false;
- _canReorderItems = true;
- _notNullItems = false;
-
// No support for different collections for now
if (HasDifferentValues || HasDifferentTypes)
return;
var size = Count;
+ _readOnly = false;
+ _canReorderItems = true;
+ _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
+ NotNullItems = false;
// Try get CollectionAttribute for collection editor meta
var attributes = Values.GetAttributes();
@@ -120,17 +125,17 @@ namespace FlaxEditor.CustomEditors.Editors
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
if (collection != null)
{
- // TODO: handle NotNullItems by filtering child editors SetValue
-
_readOnly = collection.ReadOnly;
_canReorderItems = collection.CanReorderItems;
- _notNullItems = collection.NotNullItems;
+ NotNullItems = collection.NotNullItems;
+ if (collection.BackgroundColor.HasValue)
+ _background = collection.BackgroundColor.Value;
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
spacing = collection.Spacing;
}
// Size
- if (_readOnly)
+ if (_readOnly || (NotNullItems && size == 0))
{
layout.Label("Size", size.ToString());
}
@@ -146,6 +151,8 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements
if (size > 0)
{
+ var panel = layout.VerticalPanel();
+ panel.Panel.BackgroundColor = _background;
var elementType = ElementType;
if (_canReorderItems)
{
@@ -153,7 +160,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (i != 0 && spacing > 0f)
{
- if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
+ if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement)
{
if (propertiesListElement.Labels.Count > 0)
{
@@ -166,12 +173,12 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
- layout.Space(spacing);
+ panel.Space(spacing);
}
}
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
- layout.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor);
+ panel.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor);
}
}
else
@@ -180,14 +187,14 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (i != 0 && spacing > 0f)
{
- if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
+ if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement)
propertiesListElement.Space(spacing);
else
- layout.Space(spacing);
+ panel.Space(spacing);
}
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
- layout.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor);
+ panel.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor);
}
}
}
@@ -202,7 +209,8 @@ namespace FlaxEditor.CustomEditors.Editors
Text = "+",
TooltipText = "Add new item",
AnchorPreset = AnchorPresets.TopRight,
- Parent = area.ContainerControl
+ Parent = area.ContainerControl,
+ Enabled = !NotNullItems || size > 0,
};
addButton.Clicked += () =>
{
@@ -217,7 +225,7 @@ namespace FlaxEditor.CustomEditors.Editors
TooltipText = "Remove last item",
AnchorPreset = AnchorPresets.TopRight,
Parent = area.ContainerControl,
- Enabled = size > 0
+ Enabled = size > 0,
};
removeButton.Clicked += () =>
{
@@ -229,6 +237,24 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
+ ///
+ /// Rebuilds the parent layout if its collection.
+ ///
+ public void RebuildParentCollection()
+ {
+ if (ParentEditor is DictionaryEditor dictionaryEditor)
+ {
+ dictionaryEditor.RebuildParentCollection();
+ dictionaryEditor.RebuildLayout();
+ return;
+ }
+ if (ParentEditor is CollectionEditor collectionEditor)
+ {
+ collectionEditor.RebuildParentCollection();
+ collectionEditor.RebuildLayout();
+ }
+ }
+
private void OnSizeChanged()
{
if (IsSetBlocked)
@@ -312,7 +338,27 @@ namespace FlaxEditor.CustomEditors.Editors
if (Count != _elementsCount)
{
RebuildLayout();
+ RebuildParentCollection();
}
}
+
+ ///
+ protected override bool OnDirty(CustomEditor editor, object value, object token = null)
+ {
+ if (NotNullItems)
+ {
+ if (value == null && editor.ParentEditor == this)
+ return false;
+ if (editor == this && value is IList list)
+ {
+ for (int i = 0; i < list.Count; i++)
+ {
+ if (list[i] == null)
+ return false;
+ }
+ }
+ }
+ return base.OnDirty(editor, value, token);
+ }
}
}
diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
index 4a94136db..51309a2fa 100644
--- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
@@ -135,10 +135,12 @@ namespace FlaxEditor.CustomEditors.Editors
}
private IntegerValueElement _size;
+ private Color _background;
private int _elementsCount;
private bool _readOnly;
private bool _notNullItems;
private bool _canEditKeys;
+ private bool _keyEdited;
///
/// Determines whether this editor[can edit the specified dictionary type.
@@ -164,9 +166,6 @@ namespace FlaxEditor.CustomEditors.Editors
///
public override void Initialize(LayoutElementsContainer layout)
{
- _readOnly = false;
- _notNullItems = false;
-
// No support for different collections for now
if (HasDifferentValues || HasDifferentTypes)
return;
@@ -177,23 +176,23 @@ namespace FlaxEditor.CustomEditors.Editors
var keyType = argTypes[0];
var valueType = argTypes[1];
_canEditKeys = keyType == typeof(string) || keyType.IsPrimitive || keyType.IsEnum;
+ _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
+ _readOnly = false;
+ _notNullItems = false;
// Try get CollectionAttribute for collection editor meta
var attributes = Values.GetAttributes();
Type overrideEditorType = null;
float spacing = 0.0f;
- if (attributes != null)
+ var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
+ if (collection != null)
{
- var collection = (CollectionAttribute)attributes.FirstOrDefault(x => x is CollectionAttribute);
- if (collection != null)
- {
- // TODO: handle ReadOnly and NotNullItems by filtering child editors SetValue
-
- _readOnly = collection.ReadOnly;
- _notNullItems = collection.NotNullItems;
- overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
- spacing = collection.Spacing;
- }
+ _readOnly = collection.ReadOnly;
+ _notNullItems = collection.NotNullItems;
+ if (collection.BackgroundColor.HasValue)
+ _background = collection.BackgroundColor.Value;
+ overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
+ spacing = collection.Spacing;
}
// Size
@@ -205,7 +204,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
_size = layout.IntegerValue("Size");
_size.IntValue.MinValue = 0;
- _size.IntValue.MaxValue = ushort.MaxValue;
+ _size.IntValue.MaxValue = _notNullItems ? size : ushort.MaxValue;
_size.IntValue.Value = size;
_size.IntValue.ValueChanged += OnSizeChanged;
}
@@ -213,13 +212,15 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements
if (size > 0)
{
+ var panel = layout.VerticalPanel();
+ panel.Panel.BackgroundColor = _background;
var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType