Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -646,7 +646,9 @@ namespace FlaxEditor.Content.GUI
|
||||
_rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0);
|
||||
_isRubberBandSpanning = true;
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return AutoFocus && Focus(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,8 @@ namespace FlaxEditor.Content
|
||||
if (_attributes == null)
|
||||
{
|
||||
var data = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.AttributeMetaTypeID);
|
||||
_attributes = Surface.SurfaceMeta.GetAttributes(data);
|
||||
var dataOld = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.OldAttributeMetaTypeID);
|
||||
_attributes = Surface.SurfaceMeta.GetAttributes(data, dataOld);
|
||||
}
|
||||
return _attributes;
|
||||
}
|
||||
@@ -290,13 +291,11 @@ namespace FlaxEditor.Content
|
||||
_methods = Utils.GetEmptyArray<ScriptMemberInfo>();
|
||||
|
||||
// Cache Visual Script attributes
|
||||
var attributesData = _asset.GetMetaData(Surface.SurfaceMeta.AttributeMetaTypeID);
|
||||
if (attributesData != null && attributesData.Length != 0)
|
||||
{
|
||||
_attributes = Surface.SurfaceMeta.GetAttributes(attributesData);
|
||||
var data = _asset.GetMetaData(Surface.SurfaceMeta.AttributeMetaTypeID);
|
||||
var dataOld = _asset.GetMetaData(Surface.SurfaceMeta.OldAttributeMetaTypeID);
|
||||
_attributes = Surface.SurfaceMeta.GetAttributes(data, dataOld);
|
||||
}
|
||||
else
|
||||
_attributes = Utils.GetEmptyArray<object>();
|
||||
}
|
||||
|
||||
private void OnAssetReloading(Asset asset)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using FlaxEditor.Content.Thumbnails;
|
||||
using FlaxEditor.Viewport.Previews;
|
||||
using FlaxEditor.Windows;
|
||||
@@ -194,4 +195,64 @@ namespace FlaxEditor.Content
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Content proxy for quick UI Control prefab creation as widget.
|
||||
/// </summary>
|
||||
[ContentContextMenu("New/Widget")]
|
||||
internal sealed class WidgetProxy : AssetProxy
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "UI Widget";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsProxyFor(ContentItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FileExtension => PrefabProxy.Extension;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override EditorWindow Open(Editor editor, ContentItem item)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Color AccentColor => Color.Transparent;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string TypeName => PrefabProxy.AssetTypename;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanCreate(ContentFolder targetLocation)
|
||||
{
|
||||
return targetLocation.CanHaveAssets;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
// Create prefab with UI Control
|
||||
var actor = new UIControl
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(outputPath),
|
||||
StaticFlags = StaticFlags.None,
|
||||
};
|
||||
actor.Control = new Button
|
||||
{
|
||||
Text = "Button",
|
||||
};
|
||||
PrefabManager.CreatePrefab(actor, outputPath, false);
|
||||
Object.Destroy(actor, 20.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class PlatformTools;
|
||||
|
||||
#if OFFICIAL_BUILD
|
||||
// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
|
||||
#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=7")
|
||||
#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=8")
|
||||
#else
|
||||
#define GAME_BUILD_DOTNET_VER TEXT("")
|
||||
#endif
|
||||
|
||||
@@ -22,6 +22,11 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform);
|
||||
|
||||
namespace
|
||||
{
|
||||
struct AndroidPlatformCache
|
||||
{
|
||||
AndroidPlatformSettings::TextureQuality TexturesQuality;
|
||||
};
|
||||
|
||||
void DeployIcon(const CookingData& data, const TextureData& iconData, const Char* subDir, int32 iconSize, int32 adaptiveIconSize)
|
||||
{
|
||||
const String mipmapPath = data.OriginalOutputPath / TEXT("app/src/main/res") / subDir;
|
||||
@@ -30,6 +35,24 @@ namespace
|
||||
FileSystem::CreateDirectory(mipmapPath);
|
||||
EditorUtilities::ExportApplicationImage(iconData, iconSize, iconSize, PixelFormat::B8G8R8A8_UNorm, iconPath);
|
||||
}
|
||||
|
||||
PixelFormat GetQualityTextureFormat(bool sRGB, PixelFormat format)
|
||||
{
|
||||
const auto platformSettings = AndroidPlatformSettings::Get();
|
||||
switch (platformSettings->TexturesQuality)
|
||||
{
|
||||
case AndroidPlatformSettings::TextureQuality::Uncompressed:
|
||||
return PixelFormatExtensions::FindUncompressedFormat(format);
|
||||
case AndroidPlatformSettings::TextureQuality::ASTC_High:
|
||||
return sRGB ? PixelFormat::ASTC_4x4_UNorm_sRGB : PixelFormat::ASTC_4x4_UNorm;
|
||||
case AndroidPlatformSettings::TextureQuality::ASTC_Medium:
|
||||
return sRGB ? PixelFormat::ASTC_6x6_UNorm_sRGB : PixelFormat::ASTC_6x6_UNorm;
|
||||
case AndroidPlatformSettings::TextureQuality::ASTC_Low:
|
||||
return sRGB ? PixelFormat::ASTC_8x8_UNorm_sRGB : PixelFormat::ASTC_8x8_UNorm;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Char* AndroidPlatformTools::GetDisplayName() const
|
||||
@@ -54,62 +77,67 @@ ArchitectureType AndroidPlatformTools::GetArchitecture() const
|
||||
|
||||
PixelFormat AndroidPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format)
|
||||
{
|
||||
// TODO: add ETC compression support for Android
|
||||
// TODO: add ASTC compression support for Android
|
||||
|
||||
// BC formats are not widely supported on Android
|
||||
if (PixelFormatExtensions::IsCompressedBC(format))
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case PixelFormat::BC1_Typeless:
|
||||
case PixelFormat::BC2_Typeless:
|
||||
case PixelFormat::BC3_Typeless:
|
||||
return PixelFormat::R8G8B8A8_Typeless;
|
||||
case PixelFormat::BC1_UNorm:
|
||||
case PixelFormat::BC2_UNorm:
|
||||
case PixelFormat::BC3_UNorm:
|
||||
return PixelFormat::R8G8B8A8_UNorm;
|
||||
case PixelFormat::BC1_UNorm_sRGB:
|
||||
case PixelFormat::BC2_UNorm_sRGB:
|
||||
case PixelFormat::BC3_UNorm_sRGB:
|
||||
return PixelFormat::R8G8B8A8_UNorm_sRGB;
|
||||
case PixelFormat::BC4_Typeless:
|
||||
return PixelFormat::R8_Typeless;
|
||||
case PixelFormat::BC4_UNorm:
|
||||
return PixelFormat::R8_UNorm;
|
||||
case PixelFormat::BC4_SNorm:
|
||||
return PixelFormat::R8_SNorm;
|
||||
case PixelFormat::BC5_Typeless:
|
||||
return PixelFormat::R16G16_Typeless;
|
||||
case PixelFormat::BC5_UNorm:
|
||||
return PixelFormat::R16G16_UNorm;
|
||||
case PixelFormat::BC5_SNorm:
|
||||
return PixelFormat::R16G16_SNorm;
|
||||
case PixelFormat::BC7_Typeless:
|
||||
case PixelFormat::BC6H_Typeless:
|
||||
return PixelFormat::R16G16B16A16_Typeless;
|
||||
case PixelFormat::BC7_UNorm:
|
||||
case PixelFormat::BC6H_Uf16:
|
||||
case PixelFormat::BC6H_Sf16:
|
||||
return PixelFormat::R16G16B16A16_Float;
|
||||
case PixelFormat::BC7_UNorm_sRGB:
|
||||
return PixelFormat::R16G16B16A16_UNorm;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
switch (format)
|
||||
{
|
||||
// Not all Android devices support R11G11B10 textures (eg. M6 Note)
|
||||
case PixelFormat::R11G11B10_Float:
|
||||
// Not all Android devices support R11G11B10 textures (eg. M6 Note)
|
||||
return PixelFormat::R16G16B16A16_UNorm;
|
||||
case PixelFormat::BC1_Typeless:
|
||||
case PixelFormat::BC2_Typeless:
|
||||
case PixelFormat::BC3_Typeless:
|
||||
case PixelFormat::BC4_Typeless:
|
||||
case PixelFormat::BC5_Typeless:
|
||||
case PixelFormat::BC1_UNorm:
|
||||
case PixelFormat::BC2_UNorm:
|
||||
case PixelFormat::BC3_UNorm:
|
||||
case PixelFormat::BC4_UNorm:
|
||||
case PixelFormat::BC5_UNorm:
|
||||
return GetQualityTextureFormat(false, format);
|
||||
case PixelFormat::BC1_UNorm_sRGB:
|
||||
case PixelFormat::BC2_UNorm_sRGB:
|
||||
case PixelFormat::BC3_UNorm_sRGB:
|
||||
case PixelFormat::BC7_UNorm_sRGB:
|
||||
return GetQualityTextureFormat(true, format);
|
||||
case PixelFormat::BC4_SNorm:
|
||||
return PixelFormat::R8_SNorm;
|
||||
case PixelFormat::BC5_SNorm:
|
||||
return PixelFormat::R16G16_SNorm;
|
||||
case PixelFormat::BC6H_Typeless:
|
||||
case PixelFormat::BC6H_Uf16:
|
||||
case PixelFormat::BC6H_Sf16:
|
||||
case PixelFormat::BC7_Typeless:
|
||||
case PixelFormat::BC7_UNorm:
|
||||
return PixelFormat::R16G16B16A16_Float; // TODO: ASTC HDR
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidPlatformTools::LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes)
|
||||
{
|
||||
const auto platformSettings = AndroidPlatformSettings::Get();
|
||||
bool invalidTextures = true;
|
||||
if (bytes.Length() == sizeof(AndroidPlatformCache))
|
||||
{
|
||||
auto* platformCache = (AndroidPlatformCache*)bytes.Get();
|
||||
invalidTextures = platformCache->TexturesQuality != platformSettings->TexturesQuality;
|
||||
}
|
||||
if (invalidTextures)
|
||||
{
|
||||
LOG(Info, "{0} option has been modified.", TEXT("TexturesQuality"));
|
||||
cache->InvalidateCacheTextures();
|
||||
}
|
||||
}
|
||||
|
||||
Array<byte> AndroidPlatformTools::SaveCache(CookingData& data, IBuildCache* cache)
|
||||
{
|
||||
const auto platformSettings = AndroidPlatformSettings::Get();
|
||||
AndroidPlatformCache platformCache;
|
||||
platformCache.TexturesQuality = platformSettings->TexturesQuality;
|
||||
Array<byte> result;
|
||||
result.Add((const byte*)&platformCache, sizeof(platformCache));
|
||||
return result;
|
||||
}
|
||||
void AndroidPlatformTools::OnBuildStarted(CookingData& data)
|
||||
{
|
||||
// Adjust the cooking output folder to be located inside the Gradle assets directory
|
||||
@@ -328,7 +356,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
|
||||
|
||||
// Copy result package
|
||||
const String apk = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/apk/release/app-release-unsigned.apk") : TEXT("app/build/outputs/apk/debug/app-debug.apk"));
|
||||
const String outputApk = data.OriginalOutputPath / gameSettings->ProductName + TEXT(".apk");
|
||||
const String outputApk = data.OriginalOutputPath / EditorUtilities::GetOutputName() + TEXT(".apk");
|
||||
if (FileSystem::CopyFile(outputApk, apk))
|
||||
{
|
||||
LOG(Error, "Failed to copy package from {0} to {1}", apk, outputApk);
|
||||
|
||||
@@ -30,6 +30,8 @@ public:
|
||||
PlatformType GetPlatform() const override;
|
||||
ArchitectureType GetArchitecture() const override;
|
||||
PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override;
|
||||
void LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes) override;
|
||||
Array<byte> SaveCache(CookingData& data, IBuildCache* cache) override;
|
||||
void OnBuildStarted(CookingData& data) override;
|
||||
bool OnPostProcess(CookingData& data) override;
|
||||
};
|
||||
|
||||
@@ -494,11 +494,11 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
{
|
||||
const auto platformSettings = WindowsPlatformSettings::Get();
|
||||
|
||||
// Apply executable icon
|
||||
Array<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly);
|
||||
if (files.HasItems())
|
||||
{
|
||||
// Apply executable icon
|
||||
TextureData iconData;
|
||||
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
|
||||
{
|
||||
@@ -508,11 +508,31 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Rename app
|
||||
const String newName = EditorUtilities::GetOutputName();
|
||||
if (newName != StringUtils::GetFileNameWithoutExtension(files[0]))
|
||||
{
|
||||
if (FileSystem::MoveFile(data.NativeCodeOutputPath / newName + TEXT(".exe"), files[0], true))
|
||||
{
|
||||
data.Error(TEXT("Failed to change output executable name."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void WindowsPlatformTools::OnBuildStarted(CookingData& data)
|
||||
{
|
||||
// Remove old executable
|
||||
Array<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly);
|
||||
for (auto& file : files)
|
||||
FileSystem::DeleteFile(file);
|
||||
}
|
||||
|
||||
void WindowsPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
|
||||
{
|
||||
// Pick the first executable file
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
ArchitectureType GetArchitecture() const override;
|
||||
bool UseSystemDotnet() const override;
|
||||
bool OnDeployBinaries(CookingData& data) override;
|
||||
void OnBuildStarted(CookingData& data) override;
|
||||
void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ IMPLEMENT_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform);
|
||||
|
||||
namespace
|
||||
{
|
||||
struct iOSPlatformCache
|
||||
{
|
||||
iOSPlatformSettings::TextureQuality TexturesQuality;
|
||||
};
|
||||
|
||||
String GetAppName()
|
||||
{
|
||||
const auto gameSettings = GameSettings::Get();
|
||||
@@ -60,6 +65,24 @@ namespace
|
||||
result = result.TrimTrailing();
|
||||
return result;
|
||||
}
|
||||
|
||||
PixelFormat GetQualityTextureFormat(bool sRGB, PixelFormat format)
|
||||
{
|
||||
const auto platformSettings = iOSPlatformSettings::Get();
|
||||
switch (platformSettings->TexturesQuality)
|
||||
{
|
||||
case iOSPlatformSettings::TextureQuality::Uncompressed:
|
||||
return PixelFormatExtensions::FindUncompressedFormat(format);
|
||||
case iOSPlatformSettings::TextureQuality::ASTC_High:
|
||||
return sRGB ? PixelFormat::ASTC_4x4_UNorm_sRGB : PixelFormat::ASTC_4x4_UNorm;
|
||||
case iOSPlatformSettings::TextureQuality::ASTC_Medium:
|
||||
return sRGB ? PixelFormat::ASTC_6x6_UNorm_sRGB : PixelFormat::ASTC_6x6_UNorm;
|
||||
case iOSPlatformSettings::TextureQuality::ASTC_Low:
|
||||
return sRGB ? PixelFormat::ASTC_8x8_UNorm_sRGB : PixelFormat::ASTC_8x8_UNorm;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Char* iOSPlatformTools::GetDisplayName() const
|
||||
@@ -89,51 +112,37 @@ DotNetAOTModes iOSPlatformTools::UseAOT() const
|
||||
|
||||
PixelFormat iOSPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format)
|
||||
{
|
||||
// TODO: add ETC compression support for iOS
|
||||
// TODO: add ASTC compression support for iOS
|
||||
|
||||
if (PixelFormatExtensions::IsCompressedBC(format))
|
||||
switch (format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case PixelFormat::BC1_Typeless:
|
||||
case PixelFormat::BC2_Typeless:
|
||||
case PixelFormat::BC3_Typeless:
|
||||
return PixelFormat::R8G8B8A8_Typeless;
|
||||
case PixelFormat::BC1_UNorm:
|
||||
case PixelFormat::BC2_UNorm:
|
||||
case PixelFormat::BC3_UNorm:
|
||||
return PixelFormat::R8G8B8A8_UNorm;
|
||||
case PixelFormat::BC1_UNorm_sRGB:
|
||||
case PixelFormat::BC2_UNorm_sRGB:
|
||||
case PixelFormat::BC3_UNorm_sRGB:
|
||||
return PixelFormat::R8G8B8A8_UNorm_sRGB;
|
||||
case PixelFormat::BC4_Typeless:
|
||||
return PixelFormat::R8_Typeless;
|
||||
case PixelFormat::BC4_UNorm:
|
||||
return PixelFormat::R8_UNorm;
|
||||
case PixelFormat::BC4_SNorm:
|
||||
return PixelFormat::R8_SNorm;
|
||||
case PixelFormat::BC5_Typeless:
|
||||
return PixelFormat::R16G16_Typeless;
|
||||
case PixelFormat::BC5_UNorm:
|
||||
return PixelFormat::R16G16_UNorm;
|
||||
case PixelFormat::BC5_SNorm:
|
||||
return PixelFormat::R16G16_SNorm;
|
||||
case PixelFormat::BC7_Typeless:
|
||||
case PixelFormat::BC6H_Typeless:
|
||||
return PixelFormat::R16G16B16A16_Typeless;
|
||||
case PixelFormat::BC7_UNorm:
|
||||
case PixelFormat::BC6H_Uf16:
|
||||
case PixelFormat::BC6H_Sf16:
|
||||
return PixelFormat::R16G16B16A16_Float;
|
||||
case PixelFormat::BC7_UNorm_sRGB:
|
||||
return PixelFormat::R16G16B16A16_UNorm;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
case PixelFormat::BC1_Typeless:
|
||||
case PixelFormat::BC2_Typeless:
|
||||
case PixelFormat::BC3_Typeless:
|
||||
case PixelFormat::BC4_Typeless:
|
||||
case PixelFormat::BC5_Typeless:
|
||||
case PixelFormat::BC1_UNorm:
|
||||
case PixelFormat::BC2_UNorm:
|
||||
case PixelFormat::BC3_UNorm:
|
||||
case PixelFormat::BC4_UNorm:
|
||||
case PixelFormat::BC5_UNorm:
|
||||
return GetQualityTextureFormat(false, format);
|
||||
case PixelFormat::BC1_UNorm_sRGB:
|
||||
case PixelFormat::BC2_UNorm_sRGB:
|
||||
case PixelFormat::BC3_UNorm_sRGB:
|
||||
case PixelFormat::BC7_UNorm_sRGB:
|
||||
return GetQualityTextureFormat(true, format);
|
||||
case PixelFormat::BC4_SNorm:
|
||||
return PixelFormat::R8_SNorm;
|
||||
case PixelFormat::BC5_SNorm:
|
||||
return PixelFormat::R16G16_SNorm;
|
||||
case PixelFormat::BC6H_Typeless:
|
||||
case PixelFormat::BC6H_Uf16:
|
||||
case PixelFormat::BC6H_Sf16:
|
||||
case PixelFormat::BC7_Typeless:
|
||||
case PixelFormat::BC7_UNorm:
|
||||
return PixelFormat::R16G16B16A16_Float; // TODO: ASTC HDR
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
@@ -143,6 +152,32 @@ bool iOSPlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
|
||||
return extension.IsEmpty() || extension == TEXT("dylib");
|
||||
}
|
||||
|
||||
void iOSPlatformTools::LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes)
|
||||
{
|
||||
const auto platformSettings = iOSPlatformSettings::Get();
|
||||
bool invalidTextures = true;
|
||||
if (bytes.Length() == sizeof(iOSPlatformCache))
|
||||
{
|
||||
auto* platformCache = (iOSPlatformCache*)bytes.Get();
|
||||
invalidTextures = platformCache->TexturesQuality != platformSettings->TexturesQuality;
|
||||
}
|
||||
if (invalidTextures)
|
||||
{
|
||||
LOG(Info, "{0} option has been modified.", TEXT("TexturesQuality"));
|
||||
cache->InvalidateCacheTextures();
|
||||
}
|
||||
}
|
||||
|
||||
Array<byte> iOSPlatformTools::SaveCache(CookingData& data, IBuildCache* cache)
|
||||
{
|
||||
const auto platformSettings = iOSPlatformSettings::Get();
|
||||
iOSPlatformCache platformCache;
|
||||
platformCache.TexturesQuality = platformSettings->TexturesQuality;
|
||||
Array<byte> result;
|
||||
result.Add((const byte*)&platformCache, sizeof(platformCache));
|
||||
return result;
|
||||
}
|
||||
|
||||
void iOSPlatformTools::OnBuildStarted(CookingData& data)
|
||||
{
|
||||
// Adjust the cooking output folders for packaging app
|
||||
@@ -167,13 +202,25 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
|
||||
if (EditorUtilities::FormatAppPackageName(appIdentifier))
|
||||
return true;
|
||||
|
||||
// Copy fresh Gradle project template
|
||||
// Copy fresh XCode project template
|
||||
if (FileSystem::CopyDirectory(data.OriginalOutputPath, platformDataPath / TEXT("Project"), true))
|
||||
{
|
||||
LOG(Error, "Failed to deploy XCode project to {0} from {1}", data.OriginalOutputPath, platformDataPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix MoltenVK lib (copied from VulkanSDK xcframework)
|
||||
FileSystem::MoveFile(data.DataOutputPath / TEXT("libMoltenVK.dylib"), data.DataOutputPath / TEXT("MoltenVK"), true);
|
||||
{
|
||||
// Fix rpath to point into dynamic library (rather than framework location)
|
||||
CreateProcessSettings procSettings;
|
||||
procSettings.HiddenWindow = true;
|
||||
procSettings.WorkingDirectory = data.DataOutputPath;
|
||||
procSettings.FileName = TEXT("/usr/bin/install_name_tool");
|
||||
procSettings.Arguments = TEXT("-id \"@rpath/libMoltenVK.dylib\" libMoltenVK.dylib");
|
||||
Platform::CreateProcess(procSettings);
|
||||
}
|
||||
|
||||
// Format project template files
|
||||
Dictionary<String, String> configReplaceMap;
|
||||
configReplaceMap[TEXT("${AppName}")] = appName;
|
||||
|
||||
@@ -19,6 +19,8 @@ public:
|
||||
ArchitectureType GetArchitecture() const override;
|
||||
DotNetAOTModes UseAOT() const override;
|
||||
PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override;
|
||||
void LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes) override;
|
||||
Array<byte> SaveCache(CookingData& data, IBuildCache* cache) override;
|
||||
bool IsNativeCodeFile(CookingData& data, const String& file) override;
|
||||
void OnBuildStarted(CookingData& data) override;
|
||||
bool OnPostProcess(CookingData& data) override;
|
||||
|
||||
@@ -8,6 +8,37 @@
|
||||
|
||||
class TextureBase;
|
||||
|
||||
/// <summary>
|
||||
/// The game cooker cache interface.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API IBuildCache
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Removes all cached entries for assets that contain a given asset type. This forces rebuild for them.
|
||||
/// </summary>
|
||||
virtual void InvalidateCachePerType(const StringView& typeName) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cached entries for assets that contain a given asset type. This forces rebuild for them.
|
||||
/// </summary>
|
||||
template<typename T>
|
||||
FORCE_INLINE void InvalidateCachePerType()
|
||||
{
|
||||
InvalidateCachePerType(T::TypeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cached entries for assets that contain a shader. This forces rebuild for them.
|
||||
/// </summary>
|
||||
void InvalidateCacheShaders();
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cached entries for assets that contain a texture. This forces rebuild for them.
|
||||
/// </summary>
|
||||
void InvalidateCacheTextures();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The platform support tools base interface.
|
||||
/// </summary>
|
||||
@@ -76,6 +107,27 @@ public:
|
||||
virtual bool IsNativeCodeFile(CookingData& data, const String& file);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Loads the build cache. Allows to invalidate any cached asset types based on the build settings for incremental builds (eg. invalidate textures/shaders).
|
||||
/// </summary>
|
||||
/// <param name="data">The cooking data.</param>
|
||||
/// <param name="data">The build cache interface.</param>
|
||||
/// <param name="data">The loaded cache data. Can be empty when starting a fresh build.</param>
|
||||
virtual void LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the build cache. Allows to store any build settings to be used for cache invalidation on incremental builds.
|
||||
/// </summary>
|
||||
/// <param name="data">The cooking data.</param>
|
||||
/// <param name="data">The build cache interface.</param>
|
||||
/// <returns>Data to cache, will be restored during next incremental build.<returns>
|
||||
virtual Array<byte> SaveCache(CookingData& data, IBuildCache* cache)
|
||||
{
|
||||
return Array<byte>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when game building starts.
|
||||
/// </summary>
|
||||
|
||||
@@ -31,10 +31,12 @@
|
||||
#include "Engine/Graphics/Shaders/GPUShader.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Graphics/Materials/MaterialShader.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h"
|
||||
#include "Engine/Engine/Base/GameBase.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/Enums.h"
|
||||
#if PLATFORM_TOOLS_WINDOWS
|
||||
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
|
||||
@@ -49,6 +51,20 @@
|
||||
|
||||
Dictionary<String, CookAssetsStep::ProcessAssetFunc> CookAssetsStep::AssetProcessors;
|
||||
|
||||
void IBuildCache::InvalidateCacheShaders()
|
||||
{
|
||||
InvalidateCachePerType<Shader>();
|
||||
InvalidateCachePerType<Material>();
|
||||
InvalidateCachePerType<ParticleEmitter>();
|
||||
}
|
||||
|
||||
void IBuildCache::InvalidateCacheTextures()
|
||||
{
|
||||
InvalidateCachePerType<Texture>();
|
||||
InvalidateCachePerType<CubeTexture>();
|
||||
InvalidateCachePerType<SpriteAtlas>();
|
||||
}
|
||||
|
||||
bool CookAssetsStep::CacheEntry::IsValid(bool withDependencies)
|
||||
{
|
||||
AssetInfo assetInfo;
|
||||
@@ -113,15 +129,13 @@ void CookAssetsStep::CacheData::InvalidateCachePerType(const StringView& typeNam
|
||||
|
||||
void CookAssetsStep::CacheData::Load(CookingData& data)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
HeaderFilePath = data.CacheDirectory / String::Format(TEXT("CookedHeader_{0}.bin"), FLAXENGINE_VERSION_BUILD);
|
||||
CacheFolder = data.CacheDirectory / TEXT("Cooked");
|
||||
Entries.Clear();
|
||||
|
||||
if (!FileSystem::DirectoryExists(CacheFolder))
|
||||
{
|
||||
FileSystem::CreateDirectory(CacheFolder);
|
||||
}
|
||||
|
||||
if (!FileSystem::FileExists(HeaderFilePath))
|
||||
{
|
||||
LOG(Warning, "Missing incremental build cooking assets cache.");
|
||||
@@ -143,9 +157,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
|
||||
return;
|
||||
|
||||
LOG(Info, "Loading incremental build cooking cache (entries count: {0})", entriesCount);
|
||||
|
||||
file->ReadBytes(&Settings, sizeof(Settings));
|
||||
|
||||
Entries.EnsureCapacity(Math::RoundUpToPowerOf2(static_cast<int32>(entriesCount * 3.0f)));
|
||||
|
||||
Array<Pair<String, DateTime>> fileDependencies;
|
||||
@@ -179,6 +191,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
|
||||
e.FileDependencies = fileDependencies;
|
||||
}
|
||||
|
||||
Array<byte> platformCache;
|
||||
file->Read(platformCache);
|
||||
|
||||
int32 checkChar;
|
||||
file->ReadInt32(&checkChar);
|
||||
if (checkChar != 13)
|
||||
@@ -187,6 +202,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
|
||||
Entries.Clear();
|
||||
}
|
||||
|
||||
// Per-platform custom data loading (eg. to invalidate textures/shaders options)
|
||||
data.Tools->LoadCache(data, this, ToSpan(platformCache));
|
||||
|
||||
const auto buildSettings = BuildSettings::Get();
|
||||
const auto gameSettings = GameSettings::Get();
|
||||
|
||||
@@ -200,12 +218,12 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
|
||||
if (MATERIAL_GRAPH_VERSION != Settings.Global.MaterialGraphVersion)
|
||||
{
|
||||
LOG(Info, "{0} option has been modified.", TEXT("MaterialGraphVersion"));
|
||||
InvalidateCachePerType(Material::TypeName);
|
||||
InvalidateCachePerType<Material>();
|
||||
}
|
||||
if (PARTICLE_GPU_GRAPH_VERSION != Settings.Global.ParticleGraphVersion)
|
||||
{
|
||||
LOG(Info, "{0} option has been modified.", TEXT("ParticleGraphVersion"));
|
||||
InvalidateCachePerType(ParticleEmitter::TypeName);
|
||||
InvalidateCachePerType<ParticleEmitter>();
|
||||
}
|
||||
if (buildSettings->ShadersNoOptimize != Settings.Global.ShadersNoOptimize)
|
||||
{
|
||||
@@ -262,24 +280,24 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
|
||||
#endif
|
||||
if (invalidateShaders)
|
||||
{
|
||||
InvalidateCachePerType(Shader::TypeName);
|
||||
InvalidateCachePerType(Material::TypeName);
|
||||
InvalidateCachePerType(ParticleEmitter::TypeName);
|
||||
InvalidateCachePerType<Shader>();
|
||||
InvalidateCachePerType<Material>();
|
||||
InvalidateCachePerType<ParticleEmitter>();
|
||||
}
|
||||
|
||||
// Invalidate textures if streaming settings gets modified
|
||||
if (Settings.Global.StreamingSettingsAssetId != gameSettings->Streaming || (Entries.ContainsKey(gameSettings->Streaming) && !Entries[gameSettings->Streaming].IsValid()))
|
||||
{
|
||||
InvalidateCachePerType(Texture::TypeName);
|
||||
InvalidateCachePerType(CubeTexture::TypeName);
|
||||
InvalidateCachePerType(SpriteAtlas::TypeName);
|
||||
InvalidateCachePerType<Texture>();
|
||||
InvalidateCachePerType<CubeTexture>();
|
||||
InvalidateCachePerType<SpriteAtlas>();
|
||||
}
|
||||
}
|
||||
|
||||
void CookAssetsStep::CacheData::Save()
|
||||
void CookAssetsStep::CacheData::Save(CookingData& data)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
LOG(Info, "Saving incremental build cooking cache (entries count: {0})", Entries.Count());
|
||||
|
||||
auto file = FileWriteStream::Open(HeaderFilePath);
|
||||
if (file == nullptr)
|
||||
return;
|
||||
@@ -302,6 +320,7 @@ void CookAssetsStep::CacheData::Save()
|
||||
file->Write(f.Second);
|
||||
}
|
||||
}
|
||||
file->Write(data.Tools->SaveCache(data, this));
|
||||
file->WriteInt32(13);
|
||||
}
|
||||
|
||||
@@ -624,7 +643,8 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data)
|
||||
const auto asset = static_cast<TextureBase*>(data.Asset);
|
||||
const auto& assetHeader = asset->StreamingTexture()->GetHeader();
|
||||
const auto format = asset->Format();
|
||||
const auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format);
|
||||
auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format);
|
||||
CHECK_RETURN(!PixelFormatExtensions::IsTypeless(targetFormat), true);
|
||||
const auto streamingSettings = StreamingSettings::Get();
|
||||
int32 mipLevelsMax = GPU_MAX_TEXTURE_MIP_LEVELS;
|
||||
if (assetHeader->TextureGroup >= 0 && assetHeader->TextureGroup < streamingSettings->TextureGroups.Count())
|
||||
@@ -634,6 +654,11 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data)
|
||||
group.MipLevelsMaxPerPlatform.TryGet(data.Data.Tools->GetPlatform(), mipLevelsMax);
|
||||
}
|
||||
|
||||
// If texture is smaller than the block size of the target format (eg. 4x4 texture using ASTC_6x6) then fallback to uncompressed
|
||||
int32 blockSize = PixelFormatExtensions::ComputeBlockSize(targetFormat);
|
||||
if (assetHeader->Width < blockSize || assetHeader->Height < blockSize || (blockSize != 1 && mipLevelsMax < 4))
|
||||
targetFormat = PixelFormatExtensions::FindUncompressedFormat(format);
|
||||
|
||||
// Faster path if don't need to modify texture for the target platform
|
||||
if (format == targetFormat && assetHeader->MipLevels <= mipLevelsMax)
|
||||
{
|
||||
@@ -961,6 +986,7 @@ public:
|
||||
const int32 count = addedEntries.Count();
|
||||
if (count == 0)
|
||||
return false;
|
||||
PROFILE_CPU();
|
||||
|
||||
// Get assets init data and load all chunks
|
||||
Array<AssetInitData> assetsData;
|
||||
@@ -1143,7 +1169,7 @@ bool CookAssetsStep::Perform(CookingData& data)
|
||||
// Cook asset
|
||||
if (Process(data, cache, assetRef.Get()))
|
||||
{
|
||||
cache.Save();
|
||||
cache.Save(data);
|
||||
return true;
|
||||
}
|
||||
data.Stats.CookedAssets++;
|
||||
@@ -1151,12 +1177,12 @@ bool CookAssetsStep::Perform(CookingData& data)
|
||||
// Auto save build cache after every few cooked assets (reduces next build time if cooking fails later)
|
||||
if (data.Stats.CookedAssets % 50 == 0)
|
||||
{
|
||||
cache.Save();
|
||||
cache.Save(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Save build cache header
|
||||
cache.Save();
|
||||
cache.Save(data);
|
||||
|
||||
// Create build game header
|
||||
{
|
||||
@@ -1173,7 +1199,6 @@ bool CookAssetsStep::Perform(CookingData& data)
|
||||
}
|
||||
|
||||
stream->WriteInt32(('x' + 'D') * 131); // think about it as '131 times xD'
|
||||
|
||||
stream->WriteInt32(FLAXENGINE_VERSION_BUILD);
|
||||
|
||||
Array<byte> bytes;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Editor/Cooker/GameCooker.h"
|
||||
#include "Editor/Cooker/PlatformTools.h"
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
@@ -56,7 +57,7 @@ public:
|
||||
/// <summary>
|
||||
/// Assets cooking cache data (incremental building feature).
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API CacheData
|
||||
struct FLAXENGINE_API CacheData : public IBuildCache
|
||||
{
|
||||
/// <summary>
|
||||
/// The cache header file path.
|
||||
@@ -136,16 +137,6 @@ public:
|
||||
/// <returns>The added entry reference.</returns>
|
||||
CacheEntry& CreateEntry(const Asset* asset, String& cachedFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cached entries for assets that contain a shader. This forces rebuild for them.
|
||||
/// </summary>
|
||||
void InvalidateShaders();
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cached entries for assets that contain a texture. This forces rebuild for them.
|
||||
/// </summary>
|
||||
void InvalidateCachePerType(const StringView& typeName);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the cache for the given cooking data.
|
||||
/// </summary>
|
||||
@@ -155,7 +146,11 @@ public:
|
||||
/// <summary>
|
||||
/// Saves this cache (header file).
|
||||
/// </summary>
|
||||
void Save();
|
||||
/// <param name="data">The data.</param>
|
||||
void Save(CookingData& data);
|
||||
|
||||
using IBuildCache::InvalidateCachePerType;
|
||||
void InvalidateCachePerType(const StringView& typeName) override;
|
||||
};
|
||||
|
||||
struct FLAXENGINE_API AssetCookData
|
||||
|
||||
@@ -116,7 +116,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
for (String& version : versions)
|
||||
{
|
||||
version = String(StringUtils::GetFileName(version));
|
||||
if (!version.StartsWith(TEXT("7.")) && !version.StartsWith(TEXT("8."))) // .NET 7 or .NET 8
|
||||
if (!version.StartsWith(TEXT("8."))) // Check for major part of 8.0
|
||||
version.Clear();
|
||||
}
|
||||
Sorting::QuickSort(versions);
|
||||
@@ -204,14 +204,14 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
{
|
||||
// AOT runtime files inside Engine Platform folder
|
||||
packFolder /= TEXT("Dotnet");
|
||||
dstDotnetLibs /= TEXT("lib/net7.0");
|
||||
srcDotnetLibs = packFolder / TEXT("lib/net7.0");
|
||||
dstDotnetLibs /= TEXT("lib/net8.0");
|
||||
srcDotnetLibs = packFolder / TEXT("lib/net8.0");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Runtime files inside Dotnet SDK folder but placed for AOT
|
||||
dstDotnetLibs /= TEXT("lib/net7.0");
|
||||
srcDotnetLibs /= TEXT("../lib/net7.0");
|
||||
dstDotnetLibs /= TEXT("lib/net8.0");
|
||||
srcDotnetLibs /= TEXT("../lib/net8.0");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -219,16 +219,18 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
if (srcDotnetFromEngine)
|
||||
{
|
||||
// Runtime files inside Engine Platform folder
|
||||
dstDotnetLibs /= TEXT("lib/net7.0");
|
||||
srcDotnetLibs /= TEXT("lib/net7.0");
|
||||
dstDotnetLibs /= TEXT("lib/net8.0");
|
||||
srcDotnetLibs /= TEXT("lib/net8.0");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Runtime files inside Dotnet SDK folder
|
||||
dstDotnetLibs /= TEXT("shared/Microsoft.NETCore.App");
|
||||
srcDotnetLibs /= TEXT("../lib/net7.0");
|
||||
srcDotnetLibs /= TEXT("../lib/net8.0");
|
||||
}
|
||||
}
|
||||
LOG(Info, "Copying .NET files from {} to {}", packFolder, dstDotnet);
|
||||
LOG(Info, "Copying .NET files from {} to {}", srcDotnetLibs, dstDotnetLibs);
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.txt"));
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.TXT"));
|
||||
FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("ThirdPartyNotices.txt"));
|
||||
|
||||
@@ -35,7 +35,8 @@ void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
|
||||
if (cachedData != aotModeCacheValue)
|
||||
{
|
||||
LOG(Info, "AOT cache invalidation");
|
||||
FileSystem::DeleteDirectory(data.ManagedCodeOutputPath);
|
||||
FileSystem::DeleteDirectory(data.ManagedCodeOutputPath); // Remove AOT cache
|
||||
FileSystem::DeleteDirectory(data.DataOutputPath / TEXT("Dotnet")); // Remove deployed Dotnet libs (be sure to remove any leftovers from previous build)
|
||||
}
|
||||
}
|
||||
if (!FileSystem::DirectoryExists(data.ManagedCodeOutputPath))
|
||||
|
||||
@@ -385,6 +385,15 @@ namespace FlaxEditor.CustomEditors
|
||||
LinkedLabel = label;
|
||||
}
|
||||
|
||||
internal bool CanEditValue
|
||||
{
|
||||
get
|
||||
{
|
||||
var readOnly = Values.Info.GetAttribute<ReadOnlyAttribute>();
|
||||
return readOnly == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, the value reverting to default/reference will be handled via iteration over children editors, instead of for a whole object at once.
|
||||
/// </summary>
|
||||
@@ -413,7 +422,7 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (!Values.IsDefaultValueModified)
|
||||
return false;
|
||||
return true;
|
||||
return CanEditValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +431,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
public void RevertToDefaultValue()
|
||||
{
|
||||
if (!Values.HasDefaultValue)
|
||||
if (!Values.HasDefaultValue || !CanEditValue)
|
||||
return;
|
||||
RevertDiffToDefault();
|
||||
}
|
||||
@@ -471,7 +480,7 @@ namespace FlaxEditor.CustomEditors
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Values.IsReferenceValueModified)
|
||||
if (CanRevertReferenceValue)
|
||||
SetValueToReference();
|
||||
}
|
||||
}
|
||||
@@ -485,7 +494,7 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (!Values.IsReferenceValueModified)
|
||||
return false;
|
||||
return true;
|
||||
return CanEditValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +503,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
public void RevertToReferenceValue()
|
||||
{
|
||||
if (!Values.HasReferenceValue)
|
||||
if (!Values.HasReferenceValue || !CanEditValue)
|
||||
return;
|
||||
RevertDiffToReference();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Types/Stopwatch.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
@@ -81,7 +82,7 @@ bool CustomEditorsUtilService::Init()
|
||||
|
||||
void OnAssemblyLoaded(MAssembly* assembly)
|
||||
{
|
||||
const auto startTime = DateTime::NowUTC();
|
||||
Stopwatch stopwatch;
|
||||
|
||||
// Prepare FlaxEngine
|
||||
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
|
||||
@@ -162,8 +163,8 @@ void OnAssemblyLoaded(MAssembly* assembly)
|
||||
}
|
||||
}
|
||||
|
||||
const auto endTime = DateTime::NowUTC();
|
||||
LOG(Info, "Assembly \'{0}\' scanned for custom editors in {1} ms", assembly->ToString(), (int32)(endTime - startTime).GetTotalMilliseconds());
|
||||
stopwatch.Stop();
|
||||
LOG(Info, "Assembly \'{0}\' scanned for custom editors in {1} ms", assembly->ToString(), stopwatch.GetMilliseconds());
|
||||
}
|
||||
|
||||
void OnAssemblyUnloading(MAssembly* assembly)
|
||||
|
||||
@@ -589,25 +589,27 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
LayoutElementsContainer yEl;
|
||||
LayoutElementsContainer hEl;
|
||||
LayoutElementsContainer vEl;
|
||||
Color axisColorX = ActorTransformEditor.AxisColorX;
|
||||
Color axisColorY = ActorTransformEditor.AxisColorY;
|
||||
if (xEq)
|
||||
{
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values));
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values));
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX);
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX);
|
||||
}
|
||||
else
|
||||
{
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values));
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values));
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX);
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX);
|
||||
}
|
||||
if (yEq)
|
||||
{
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values));
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values));
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY);
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY);
|
||||
}
|
||||
else
|
||||
{
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values));
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values));
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY);
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY);
|
||||
}
|
||||
xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y);
|
||||
xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y);
|
||||
@@ -624,28 +626,34 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
|
||||
private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont)
|
||||
{
|
||||
var horUp = cont.VerticalPanel();
|
||||
horUp.Panel.Margin = Margin.Zero;
|
||||
return horUp;
|
||||
var panel = cont.VerticalPanel();
|
||||
panel.Panel.Margin = Margin.Zero;
|
||||
return panel;
|
||||
}
|
||||
|
||||
private CustomElementsContainer<UniformGridPanel> UniformGridTwoByOne(LayoutElementsContainer cont)
|
||||
{
|
||||
var horUp = cont.CustomContainer<UniformGridPanel>();
|
||||
horUp.CustomControl.SlotsHorizontally = 2;
|
||||
horUp.CustomControl.SlotsVertically = 1;
|
||||
horUp.CustomControl.SlotPadding = Margin.Zero;
|
||||
horUp.CustomControl.ClipChildren = false;
|
||||
return horUp;
|
||||
var grid = cont.CustomContainer<UniformGridPanel>();
|
||||
grid.CustomControl.SlotsHorizontally = 2;
|
||||
grid.CustomControl.SlotsVertically = 1;
|
||||
grid.CustomControl.SlotPadding = Margin.Zero;
|
||||
grid.CustomControl.ClipChildren = false;
|
||||
return grid;
|
||||
}
|
||||
|
||||
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values)
|
||||
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor)
|
||||
{
|
||||
CustomElementsContainer<UniformGridPanel> hor = UniformGridTwoByOne(el);
|
||||
hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0);
|
||||
LabelElement lab = hor.Label(text);
|
||||
hor.Object(values);
|
||||
return hor;
|
||||
var grid = UniformGridTwoByOne(el);
|
||||
grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1);
|
||||
var label = grid.Label(text);
|
||||
var editor = grid.Object(values);
|
||||
if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement)
|
||||
{
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
|
||||
floatEditorElement.ValueBox.BorderSelectedColor = borderColor;
|
||||
}
|
||||
return grid;
|
||||
}
|
||||
|
||||
private bool _cachedXEq;
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// </summary>
|
||||
public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The axes colors grey out scale when input field is not focused.
|
||||
/// </summary>
|
||||
public static float AxisGreyOutFactor = 0.6f;
|
||||
|
||||
/// <summary>
|
||||
/// Custom editor for actor position property.
|
||||
/// </summary>
|
||||
@@ -39,13 +44,15 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
// Override colors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
|
||||
XElement.ValueBox.BorderSelectedColor = AxisColorX;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
|
||||
XElement.ValueBox.Category = Utils.ValueCategory.Distance;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
|
||||
YElement.ValueBox.BorderSelectedColor = AxisColorY;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
|
||||
YElement.ValueBox.Category = Utils.ValueCategory.Distance;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
|
||||
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
|
||||
ZElement.ValueBox.Category = Utils.ValueCategory.Distance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +69,15 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
// Override colors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
|
||||
XElement.ValueBox.BorderSelectedColor = AxisColorX;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
|
||||
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
|
||||
YElement.ValueBox.BorderSelectedColor = AxisColorY;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
|
||||
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
|
||||
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
|
||||
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +111,17 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
SetLinkStyle();
|
||||
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value);
|
||||
_linkButton.LocalX += textSize.X + 10;
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
if (LinkedLabel != null)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
if (LinkValues)
|
||||
menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling");
|
||||
else
|
||||
menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling");
|
||||
};
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
if (LinkValues)
|
||||
menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling");
|
||||
else
|
||||
menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling");
|
||||
};
|
||||
}
|
||||
|
||||
// Override colors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
|
||||
@@ -21,32 +21,31 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
doubleValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
doubleValue.SetLimits(limit);
|
||||
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
|
||||
if (valueCategory != Utils.ValueCategory.None)
|
||||
{
|
||||
// Use double value editor with limit
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.SetLimits((LimitAttribute)limit);
|
||||
doubleValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
doubleValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
return;
|
||||
doubleValue.SetCategory(valueCategory);
|
||||
if (LinkedLabel != null)
|
||||
{
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt => { doubleValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = doubleValue.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use double value editor
|
||||
var doubleValue = layout.DoubleValue();
|
||||
doubleValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
doubleValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
using FlaxEngine;
|
||||
using Utils = FlaxEngine.Utils;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
@@ -27,41 +28,42 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
_element = null;
|
||||
|
||||
// Try get limit attribute for value min/max range setting and slider speed
|
||||
var attributes = Values.GetAttributes();
|
||||
var range = (RangeAttribute)attributes?.FirstOrDefault(x => x is RangeAttribute);
|
||||
if (range != null)
|
||||
{
|
||||
// Use slider
|
||||
var slider = layout.Slider();
|
||||
slider.Slider.SetLimits(range);
|
||||
slider.Slider.ValueChanged += OnValueChanged;
|
||||
slider.Slider.SlidingEnd += ClearToken;
|
||||
_element = slider;
|
||||
return;
|
||||
}
|
||||
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
floatValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
if (attributes != null)
|
||||
{
|
||||
var range = attributes.FirstOrDefault(x => x is RangeAttribute);
|
||||
if (range != null)
|
||||
var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
floatValue.SetLimits(limit);
|
||||
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
|
||||
if (valueCategory != Utils.ValueCategory.None)
|
||||
{
|
||||
// Use slider
|
||||
var slider = layout.Slider();
|
||||
slider.SetLimits((RangeAttribute)range);
|
||||
slider.Slider.ValueChanged += OnValueChanged;
|
||||
slider.Slider.SlidingEnd += ClearToken;
|
||||
_element = slider;
|
||||
return;
|
||||
floatValue.SetCategory(valueCategory);
|
||||
if (LinkedLabel != null)
|
||||
{
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt => { floatValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = floatValue.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
}
|
||||
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
if (limit != null)
|
||||
{
|
||||
// Use float value editor with limit
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.SetLimits((LimitAttribute)limit);
|
||||
floatValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
floatValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_element == null)
|
||||
{
|
||||
// Use float value editor
|
||||
var floatValue = layout.FloatValue();
|
||||
floatValue.ValueBox.ValueChanged += OnValueChanged;
|
||||
floatValue.ValueBox.SlidingEnd += ClearToken;
|
||||
_element = floatValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
LinkedLabel.SetupContextMenu += OnSetupContextMenu;
|
||||
if (LinkedLabel != null)
|
||||
LinkedLabel.SetupContextMenu += OnSetupContextMenu;
|
||||
var comboBoxElement = layout.ComboBox();
|
||||
_comboBox = comboBoxElement.ComboBox;
|
||||
var names = new List<string>();
|
||||
|
||||
@@ -45,23 +45,29 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
XElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
XElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
YElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
YElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.FloatValue();
|
||||
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
|
||||
ZElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
ZElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
if (LinkedLabel != null)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var value = ((Quaternion)Values[0]).EulerAngles;
|
||||
menu.AddButton("Copy Euler", () => { Clipboard.Text = JsonSerializer.Serialize(value); }).TooltipText = "Copy the Euler Angles in Degrees";
|
||||
};
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var value = ((Quaternion)Values[0]).EulerAngles;
|
||||
menu.AddButton("Copy Euler", () => { Clipboard.Text = JsonSerializer.Serialize(value); }).TooltipText = "Copy the Euler Angles in Degrees";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
|
||||
@@ -70,25 +70,48 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
LimitAttribute limit = null;
|
||||
var attributes = Values.GetAttributes();
|
||||
var category = Utils.ValueCategory.None;
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
|
||||
if (categoryAttribute != null)
|
||||
category = categoryAttribute.Category;
|
||||
}
|
||||
|
||||
XElement = grid.FloatValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.SetCategory(category);
|
||||
XElement.ValueBox.ValueChanged += OnXValueChanged;
|
||||
XElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.FloatValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.SetCategory(category);
|
||||
YElement.ValueBox.ValueChanged += OnYValueChanged;
|
||||
YElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.FloatValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.SetCategory(category);
|
||||
ZElement.ValueBox.ValueChanged += OnZValueChanged;
|
||||
ZElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
if (LinkedLabel != null)
|
||||
{
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt =>
|
||||
{
|
||||
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
});
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnXValueChanged()
|
||||
@@ -248,26 +271,49 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
gridControl.SlotsVertically = 1;
|
||||
|
||||
LimitAttribute limit = null;
|
||||
Utils.ValueCategory category = Utils.ValueCategory.None;
|
||||
var attributes = Values.GetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
|
||||
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
|
||||
if (categoryAttribute != null)
|
||||
category = categoryAttribute.Category;
|
||||
}
|
||||
|
||||
XElement = grid.DoubleValue();
|
||||
XElement.SetLimits(limit);
|
||||
XElement.SetCategory(category);
|
||||
XElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
XElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
YElement = grid.DoubleValue();
|
||||
YElement.SetLimits(limit);
|
||||
YElement.SetCategory(category);
|
||||
YElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
YElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
ZElement = grid.DoubleValue();
|
||||
ZElement.SetLimits(limit);
|
||||
ZElement.SetCategory(category);
|
||||
ZElement.ValueBox.ValueChanged += OnValueChanged;
|
||||
ZElement.ValueBox.SlidingEnd += ClearToken;
|
||||
|
||||
if (LinkedLabel != null)
|
||||
{
|
||||
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
|
||||
{
|
||||
menu.AddSeparator();
|
||||
var mb = menu.AddButton("Show formatted", bt =>
|
||||
{
|
||||
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
|
||||
});
|
||||
mb.AutoCheck = true;
|
||||
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
|
||||
@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor value category.
|
||||
/// </summary>
|
||||
/// <param name="category">The category.</param>
|
||||
public void SetCategory(Utils.ValueCategory category)
|
||||
{
|
||||
ValueBox.Category = category;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor value category.
|
||||
/// </summary>
|
||||
/// <param name="category">The category.</param>
|
||||
public void SetCategory(Utils.ValueCategory category)
|
||||
{
|
||||
ValueBox.Category = category;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the editor limits from member <see cref="LimitAttribute"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -315,9 +315,9 @@ namespace FlaxEditor.CustomEditors
|
||||
internal LabelElement Header(HeaderAttribute header)
|
||||
{
|
||||
var element = Header(header.Text);
|
||||
if (header.FontSize != -1)
|
||||
if (header.FontSize > 0)
|
||||
element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize);
|
||||
if (header.Color != 0)
|
||||
if (header.Color > 0)
|
||||
element.Label.TextColor = Color.FromRGBA(header.Color);
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -407,12 +407,26 @@ int32 Editor::LoadProduct()
|
||||
{
|
||||
Array<String> projectFiles;
|
||||
FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly);
|
||||
if (projectFiles.Count() == 1)
|
||||
if (projectFiles.Count() > 1)
|
||||
{
|
||||
// Skip creating new project if it already exists
|
||||
LOG(Info, "Skip creatinng new project because it already exists");
|
||||
Platform::Fatal(TEXT("Too many project files."));
|
||||
return -2;
|
||||
}
|
||||
else if (projectFiles.Count() == 1)
|
||||
{
|
||||
LOG(Info, "Skip creating new project because it already exists");
|
||||
CommandLine::Options.NewProject.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
Array<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, projectPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
|
||||
if (files.Count() > 0)
|
||||
{
|
||||
Platform::Fatal(String::Format(TEXT("Target project folder '{0}' is not empty."), projectPath));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CommandLine::Options.NewProject)
|
||||
{
|
||||
|
||||
@@ -290,7 +290,6 @@ namespace FlaxEditor
|
||||
|
||||
StateMachine = new EditorStateMachine(this);
|
||||
Undo = new EditorUndo(this);
|
||||
UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet;
|
||||
|
||||
if (newProject)
|
||||
InitProject();
|
||||
@@ -355,27 +354,6 @@ namespace FlaxEditor
|
||||
StateMachine.LoadingState.StartInitEnding(skipCompile);
|
||||
}
|
||||
|
||||
private ContainerControl OnUIControlFallbackParentGet(UIControl control)
|
||||
{
|
||||
// Check if prefab root control is this UIControl
|
||||
var loadingPreview = Viewport.Previews.PrefabPreview.LoadingPreview;
|
||||
var activePreviews = Viewport.Previews.PrefabPreview.ActivePreviews;
|
||||
if (activePreviews != null)
|
||||
{
|
||||
foreach (var preview in activePreviews)
|
||||
{
|
||||
if (preview == loadingPreview ||
|
||||
(preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control))))
|
||||
{
|
||||
// Link it to the prefab preview to see it in the editor
|
||||
preview.customControlLinked = control;
|
||||
return preview;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void RegisterModule(EditorModule module)
|
||||
{
|
||||
Log("Register Editor module " + module);
|
||||
|
||||
@@ -431,7 +431,6 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
protected CurveEditor()
|
||||
{
|
||||
_tickStrengths = new float[TickSteps.Length];
|
||||
Accessor.GetDefaultValue(out DefaultValue);
|
||||
|
||||
var style = Style.Current;
|
||||
@@ -780,75 +779,31 @@ namespace FlaxEditor.GUI
|
||||
return _mainPanel.PointToParent(point);
|
||||
}
|
||||
|
||||
private void DrawAxis(Float2 axis, ref Rectangle viewRect, float min, float max, float pixelRange)
|
||||
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
|
||||
{
|
||||
int minDistanceBetweenTicks = 20;
|
||||
int maxDistanceBetweenTicks = 60;
|
||||
var range = max - min;
|
||||
|
||||
// Find the strength for each modulo number tick marker
|
||||
int smallestTick = 0;
|
||||
int biggestTick = TickSteps.Length - 1;
|
||||
for (int i = TickSteps.Length - 1; i >= 0; i--)
|
||||
Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
|
||||
{
|
||||
// Calculate how far apart these modulo tick steps are spaced
|
||||
float tickSpacing = TickSteps[i] * pixelRange / range;
|
||||
var p = PointFromKeyframes(axis * tick, ref viewRect);
|
||||
|
||||
// Calculate the strength of the tick markers based on the spacing
|
||||
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
|
||||
// Draw line
|
||||
var lineRect = new Rectangle
|
||||
(
|
||||
viewRect.Location + (p - 0.5f) * axis,
|
||||
Float2.Lerp(viewRect.Size, Float2.One, axis)
|
||||
);
|
||||
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
|
||||
|
||||
// Beyond threshold the ticks don't get any bigger or fatter
|
||||
if (_tickStrengths[i] >= 1)
|
||||
biggestTick = i;
|
||||
|
||||
// Do not show small tick markers
|
||||
if (tickSpacing <= minDistanceBetweenTicks)
|
||||
{
|
||||
smallestTick = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw all tick levels
|
||||
int tickLevels = biggestTick - smallestTick + 1;
|
||||
for (int level = 0; level < tickLevels; level++)
|
||||
{
|
||||
float strength = _tickStrengths[smallestTick + level];
|
||||
if (strength <= Mathf.Epsilon)
|
||||
continue;
|
||||
|
||||
// Draw all ticks
|
||||
int l = Mathf.Clamp(smallestTick + level, 0, TickSteps.Length - 1);
|
||||
int startTick = Mathf.FloorToInt(min / TickSteps[l]);
|
||||
int endTick = Mathf.CeilToInt(max / TickSteps[l]);
|
||||
for (int i = startTick; i <= endTick; i++)
|
||||
{
|
||||
if (l < biggestTick && (i % Mathf.RoundToInt(TickSteps[l + 1] / TickSteps[l]) == 0))
|
||||
continue;
|
||||
|
||||
var tick = i * TickSteps[l];
|
||||
var p = PointFromKeyframes(axis * tick, ref viewRect);
|
||||
|
||||
// Draw line
|
||||
var lineRect = new Rectangle
|
||||
(
|
||||
viewRect.Location + (p - 0.5f) * axis,
|
||||
Float2.Lerp(viewRect.Size, Float2.One, axis)
|
||||
);
|
||||
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
|
||||
|
||||
// Draw label
|
||||
string label = tick.ToString(CultureInfo.InvariantCulture);
|
||||
var labelRect = new Rectangle
|
||||
(
|
||||
viewRect.X + 4.0f + (p.X * axis.X),
|
||||
viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
|
||||
50,
|
||||
LabelsSize
|
||||
);
|
||||
Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
|
||||
}
|
||||
}
|
||||
// Draw label
|
||||
string label = tick.ToString(CultureInfo.InvariantCulture);
|
||||
var labelRect = new Rectangle
|
||||
(
|
||||
viewRect.X + 4.0f + (p.X * axis.X),
|
||||
viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
|
||||
50,
|
||||
LabelsSize
|
||||
);
|
||||
Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
|
||||
}, TickSteps, ref _tickStrengths, min, max, pixelRange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -890,9 +845,9 @@ namespace FlaxEditor.GUI
|
||||
Render2D.PushClip(ref viewRect);
|
||||
|
||||
if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical)
|
||||
DrawAxis(Float2.UnitX, ref viewRect, min.X, max.X, pixelRange.X);
|
||||
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
|
||||
if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal)
|
||||
DrawAxis(Float2.UnitY, ref viewRect, min.Y, max.Y, pixelRange.Y);
|
||||
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
|
||||
|
||||
Render2D.PopClip();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using Utils = FlaxEngine.Utils;
|
||||
|
||||
namespace FlaxEditor.GUI.Input
|
||||
{
|
||||
@@ -13,6 +14,8 @@ namespace FlaxEditor.GUI.Input
|
||||
[HideInEditor]
|
||||
public class DoubleValueBox : ValueBox<double>
|
||||
{
|
||||
private Utils.ValueCategory _category = Utils.ValueCategory.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override double Value
|
||||
{
|
||||
@@ -129,10 +132,25 @@ namespace FlaxEditor.GUI.Input
|
||||
Value = Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
|
||||
/// </summary>
|
||||
public Utils.ValueCategory Category
|
||||
{
|
||||
get => _category;
|
||||
set
|
||||
{
|
||||
if (_category == value)
|
||||
return;
|
||||
_category = value;
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected sealed override void UpdateText()
|
||||
{
|
||||
SetText(Utilities.Utils.FormatFloat(_value));
|
||||
SetText(Utilities.Utils.FormatFloat(_value, Category));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using Utils = FlaxEngine.Utils;
|
||||
|
||||
namespace FlaxEditor.GUI.Input
|
||||
{
|
||||
@@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input
|
||||
[HideInEditor]
|
||||
public class FloatValueBox : ValueBox<float>
|
||||
{
|
||||
private Utils.ValueCategory _category = Utils.ValueCategory.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float Value
|
||||
{
|
||||
@@ -137,10 +139,25 @@ namespace FlaxEditor.GUI.Input
|
||||
Value = Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
|
||||
/// </summary>
|
||||
public Utils.ValueCategory Category
|
||||
{
|
||||
get => _category;
|
||||
set
|
||||
{
|
||||
if (_category == value)
|
||||
return;
|
||||
_category = value;
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected sealed override void UpdateText()
|
||||
{
|
||||
SetText(Utilities.Utils.FormatFloat(_value));
|
||||
SetText(Utilities.Utils.FormatFloat(_value, Category));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
{
|
||||
_timeline = timeline;
|
||||
_tickSteps = Utilities.Utils.CurveTickSteps;
|
||||
_tickStrengths = new float[_tickSteps.Length];
|
||||
}
|
||||
|
||||
private void UpdateSelectionRectangle()
|
||||
@@ -173,55 +172,20 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond;
|
||||
var min = leftFrame;
|
||||
var max = rightFrame;
|
||||
int smallestTick = 0;
|
||||
int biggestTick = _tickSteps.Length - 1;
|
||||
for (int i = _tickSteps.Length - 1; i >= 0; i--)
|
||||
{
|
||||
// Calculate how far apart these modulo tick steps are spaced
|
||||
float tickSpacing = _tickSteps[i] * _timeline.Zoom;
|
||||
|
||||
// Calculate the strength of the tick markers based on the spacing
|
||||
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
|
||||
|
||||
// Beyond threshold the ticks don't get any bigger or fatter
|
||||
if (_tickStrengths[i] >= 1)
|
||||
biggestTick = i;
|
||||
|
||||
// Do not show small tick markers
|
||||
if (tickSpacing <= minDistanceBetweenTicks)
|
||||
{
|
||||
smallestTick = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int tickLevels = biggestTick - smallestTick + 1;
|
||||
|
||||
// Draw vertical lines for time axis
|
||||
for (int level = 0; level < tickLevels; level++)
|
||||
var pixelsInRange = _timeline.Zoom;
|
||||
var pixelRange = pixelsInRange * (max - min);
|
||||
var tickRange = Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
|
||||
{
|
||||
float strength = _tickStrengths[smallestTick + level];
|
||||
if (strength <= Mathf.Epsilon)
|
||||
continue;
|
||||
|
||||
// Draw all ticks
|
||||
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1);
|
||||
var lStep = _tickSteps[l];
|
||||
var lNextStep = _tickSteps[l + 1];
|
||||
int startTick = Mathf.FloorToInt(min / lStep);
|
||||
int endTick = Mathf.CeilToInt(max / lStep);
|
||||
Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
|
||||
for (int i = startTick; i <= endTick; i++)
|
||||
{
|
||||
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
|
||||
continue;
|
||||
var tick = i * lStep;
|
||||
var time = tick / _timeline.FramesPerSecond;
|
||||
var x = time * zoom + Timeline.StartOffset;
|
||||
|
||||
// Draw line
|
||||
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
|
||||
}
|
||||
}
|
||||
var time = tick / _timeline.FramesPerSecond;
|
||||
var x = time * zoom + Timeline.StartOffset;
|
||||
var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
|
||||
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
|
||||
}, _tickSteps, ref _tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks);
|
||||
var smallestTick = tickRange.X;
|
||||
var biggestTick = tickRange.Y;
|
||||
var tickLevels = biggestTick - smallestTick + 1;
|
||||
|
||||
// Draw selection rectangle
|
||||
if (_isSelecting)
|
||||
|
||||
@@ -904,7 +904,7 @@ namespace FlaxEditor.GUI
|
||||
var k = new Keyframe
|
||||
{
|
||||
Time = keyframesPos.X,
|
||||
Value = DefaultValue,
|
||||
Value = Utilities.Utils.CloneValue(DefaultValue),
|
||||
};
|
||||
OnEditingStart();
|
||||
AddKeyframe(k);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using FlaxEditor.GUI.Timeline.Undo;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -44,6 +46,25 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
var thickness = 2.0f;
|
||||
var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal);
|
||||
Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor);
|
||||
if (_canEdit && _isMoving)
|
||||
{
|
||||
// TODO: handle start
|
||||
string labelText;
|
||||
switch (_timeline.TimeShowMode)
|
||||
{
|
||||
case Timeline.TimeShowModes.Frames:
|
||||
labelText = _timeline.DurationFrames.ToString("###0", CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case Timeline.TimeShowModes.Seconds:
|
||||
labelText = _timeline.Duration.ToString("###0.##'s'", CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case Timeline.TimeShowModes.Time:
|
||||
labelText = TimeSpan.FromSeconds(_timeline.DurationFrames / _timeline.FramesPerSecond).ToString("g");
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2((Width - thickness) * 0.5f + 4, timeAxisHeaderOffset));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -90,13 +111,26 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
{
|
||||
_timeline.MarkAsEdited();
|
||||
}
|
||||
Cursor = CursorType.SizeWE;
|
||||
}
|
||||
else if (IsMouseOver && _canEdit)
|
||||
{
|
||||
Cursor = CursorType.SizeWE;
|
||||
}
|
||||
else
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
@@ -127,6 +161,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
{
|
||||
EndMoving();
|
||||
}
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
@@ -1141,17 +1141,19 @@ namespace FlaxEditor.GUI.Timeline
|
||||
{
|
||||
foreach (var e in _playbackNavigation)
|
||||
{
|
||||
e.Enabled = false;
|
||||
e.Visible = false;
|
||||
e.Enabled = true;
|
||||
e.Visible = true;
|
||||
}
|
||||
}
|
||||
if (_playbackStop != null)
|
||||
{
|
||||
_playbackStop.Visible = false;
|
||||
_playbackStop.Visible = true;
|
||||
_playbackStop.Enabled = false;
|
||||
}
|
||||
if (_playbackPlay != null)
|
||||
{
|
||||
_playbackPlay.Visible = false;
|
||||
_playbackPlay.Visible = true;
|
||||
_playbackPlay.Enabled = false;
|
||||
}
|
||||
if (_positionHandle != null)
|
||||
{
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor);
|
||||
}
|
||||
}
|
||||
menu.AddButton("Select...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track";
|
||||
menu.AddButton("Retarget...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using FlaxEditor.GUI.Timeline.Undo;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
@@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
throw new Exception("Invalid track data.");
|
||||
|
||||
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
var propertyType = TypeUtils.GetManagedType(e.MemberTypeName);
|
||||
if (propertyType == null)
|
||||
{
|
||||
@@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
return;
|
||||
}
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
|
||||
for (int i = 0; i < keyframesCount; i++)
|
||||
if (e.ValueSize != 0)
|
||||
{
|
||||
var time = stream.ReadSingle();
|
||||
stream.Read(dataBuffer, 0, e.ValueSize);
|
||||
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
|
||||
|
||||
keyframes[i] = new KeyframesEditor.Keyframe
|
||||
// POD value type - use raw memory
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
|
||||
for (int i = 0; i < keyframesCount; i++)
|
||||
{
|
||||
Time = time,
|
||||
Value = value,
|
||||
};
|
||||
var time = stream.ReadSingle();
|
||||
stream.Read(dataBuffer, 0, e.ValueSize);
|
||||
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
|
||||
|
||||
keyframes[i] = new KeyframesEditor.Keyframe
|
||||
{
|
||||
Time = time,
|
||||
Value = value,
|
||||
};
|
||||
}
|
||||
handle.Free();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic value - use Json storage (as UTF-8)
|
||||
for (int i = 0; i < keyframesCount; i++)
|
||||
{
|
||||
var time = stream.ReadSingle();
|
||||
var len = stream.ReadInt32();
|
||||
var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null;
|
||||
keyframes[i] = new KeyframesEditor.Keyframe
|
||||
{
|
||||
Time = time,
|
||||
Value = value,
|
||||
};
|
||||
}
|
||||
}
|
||||
handle.Free();
|
||||
|
||||
e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType);
|
||||
e.Keyframes.SetKeyframes(keyframes);
|
||||
@@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
stream.Write(propertyTypeNameData);
|
||||
stream.Write('\0');
|
||||
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
|
||||
for (int i = 0; i < keyframes.Count; i++)
|
||||
if (e.ValueSize != 0)
|
||||
{
|
||||
var keyframe = keyframes[i];
|
||||
Marshal.StructureToPtr(keyframe.Value, ptr, true);
|
||||
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
|
||||
stream.Write(keyframe.Time);
|
||||
stream.Write(dataBuffer);
|
||||
// POD value type - use raw memory
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
|
||||
for (int i = 0; i < keyframes.Count; i++)
|
||||
{
|
||||
var keyframe = keyframes[i];
|
||||
Marshal.StructureToPtr(keyframe.Value, ptr, true);
|
||||
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
|
||||
stream.Write(keyframe.Time);
|
||||
stream.Write(dataBuffer);
|
||||
}
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic value - use Json storage (as UTF-8)
|
||||
for (int i = 0; i < keyframes.Count; i++)
|
||||
{
|
||||
var keyframe = keyframes[i];
|
||||
stream.Write(keyframe.Time);
|
||||
var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null;
|
||||
var len = json?.Length ?? 0;
|
||||
stream.Write(len);
|
||||
if (len > 0)
|
||||
stream.Write(Encoding.UTF8.GetBytes(json));
|
||||
}
|
||||
}
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
|
||||
private byte[] _keyframesEditingStartData;
|
||||
@@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
private void OnKeyframesEditingEnd()
|
||||
{
|
||||
var after = EditTrackAction.CaptureData(this);
|
||||
if (!Utils.ArraysEqual(_keyframesEditingStartData, after))
|
||||
if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after))
|
||||
Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after));
|
||||
_keyframesEditingStartData = null;
|
||||
}
|
||||
@@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// <returns>The default value.</returns>
|
||||
protected virtual object GetDefaultValue(Type propertyType)
|
||||
{
|
||||
return Activator.CreateInstance(propertyType);
|
||||
var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType));
|
||||
if (value == null)
|
||||
value = Activator.CreateInstance(propertyType);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
{ typeof(Guid), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(DateTime), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(string), StringPropertyTrack.GetArchetype() },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -161,5 +161,20 @@ namespace FlaxEditor.Gizmo
|
||||
}
|
||||
throw new ArgumentException("Not added mode to activate.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gizmo of a given type or returns null if not added.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the gizmo.</typeparam>
|
||||
/// <returns>Found gizmo or null.</returns>
|
||||
public T Get<T>() where T : GizmoBase
|
||||
{
|
||||
foreach (var e in this)
|
||||
{
|
||||
if (e is T asT)
|
||||
return asT;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ namespace FlaxEditor.Gizmo
|
||||
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
|
||||
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
|
||||
|
||||
float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size;
|
||||
var options = Editor.Instance.Options.Options;
|
||||
float space = options.Viewport.ViewportGridScale, size;
|
||||
if (dst <= 500.0f)
|
||||
{
|
||||
size = 8000;
|
||||
@@ -62,8 +63,12 @@ namespace FlaxEditor.Gizmo
|
||||
size = 100000;
|
||||
}
|
||||
|
||||
Color color = Color.Gray * 0.7f;
|
||||
float bigLineIntensity = 0.8f;
|
||||
Color bigColor = Color.Gray * bigLineIntensity;
|
||||
Color color = bigColor * 0.8f;
|
||||
int count = (int)(size / space);
|
||||
int midLine = count / 2;
|
||||
int bigLinesMod = count / 8;
|
||||
|
||||
Vector3 start = new Vector3(0, 0, size * -0.5f);
|
||||
Vector3 end = new Vector3(0, 0, size * 0.5f);
|
||||
@@ -71,7 +76,12 @@ namespace FlaxEditor.Gizmo
|
||||
for (int i = 0; i <= count; i++)
|
||||
{
|
||||
start.X = end.X = i * space + start.Z;
|
||||
DebugDraw.DrawLine(start, end, color);
|
||||
Color lineColor = color;
|
||||
if (i == midLine)
|
||||
lineColor = Color.Blue * bigLineIntensity;
|
||||
else if (i % bigLinesMod == 0)
|
||||
lineColor = bigColor;
|
||||
DebugDraw.DrawLine(start, end, lineColor);
|
||||
}
|
||||
|
||||
start = new Vector3(size * -0.5f, 0, 0);
|
||||
@@ -80,7 +90,12 @@ namespace FlaxEditor.Gizmo
|
||||
for (int i = 0; i <= count; i++)
|
||||
{
|
||||
start.Z = end.Z = i * space + start.X;
|
||||
DebugDraw.DrawLine(start, end, color);
|
||||
Color lineColor = color;
|
||||
if (i == midLine)
|
||||
lineColor = Color.Red * bigLineIntensity;
|
||||
else if (i % bigLinesMod == 0)
|
||||
lineColor = bigColor;
|
||||
DebugDraw.DrawLine(start, end, lineColor);
|
||||
}
|
||||
|
||||
DebugDraw.Draw(ref renderContext, input.View(), null, true);
|
||||
|
||||
@@ -117,5 +117,10 @@ namespace FlaxEditor.Gizmo
|
||||
/// </summary>
|
||||
/// <param name="actor">The new actor to spawn.</param>
|
||||
void Spawn(Actor actor);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the context menu at the current mouse location (using current selection).
|
||||
/// </summary>
|
||||
void OpenContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace FlaxEditor.Gizmo
|
||||
/// </summary>
|
||||
public Action Duplicate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array of selected objects.
|
||||
/// </summary>
|
||||
public List<SceneGraphNode> Selection => _selection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array of selected parent objects (as actors).
|
||||
/// </summary>
|
||||
|
||||
891
Source/Editor/Gizmo/UIEditorGizmo.cs
Normal file
891
Source/Editor/Gizmo/UIEditorGizmo.cs
Normal file
@@ -0,0 +1,891 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// UI editor camera.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
internal sealed class UIEditorCamera : ViewportCamera
|
||||
{
|
||||
public UIEditorRoot UIEditor;
|
||||
|
||||
public void ShowActors(IEnumerable<Actor> actors)
|
||||
{
|
||||
var root = UIEditor.UIRoot;
|
||||
if (root == null)
|
||||
return;
|
||||
|
||||
// Calculate bounds of all selected objects
|
||||
var areaRect = Rectangle.Empty;
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
Rectangle bounds;
|
||||
if (actor is UIControl uiControl && uiControl.HasControl && uiControl.IsActive)
|
||||
{
|
||||
var control = uiControl.Control;
|
||||
bounds = control.EditorBounds;
|
||||
|
||||
var ul = control.PointToParent(root, bounds.UpperLeft);
|
||||
var ur = control.PointToParent(root, bounds.UpperRight);
|
||||
var bl = control.PointToParent(root, bounds.BottomLeft);
|
||||
var br = control.PointToParent(root, bounds.BottomRight);
|
||||
|
||||
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
|
||||
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
}
|
||||
else if (actor is UICanvas uiCanvas && uiCanvas.IsActive && uiCanvas.GUI.Parent == root)
|
||||
{
|
||||
bounds = uiCanvas.GUI.Bounds;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
if (areaRect == Rectangle.Empty)
|
||||
areaRect = bounds;
|
||||
else
|
||||
areaRect = Rectangle.Union(areaRect, bounds);
|
||||
}
|
||||
if (areaRect == Rectangle.Empty)
|
||||
return;
|
||||
|
||||
// Add margin
|
||||
areaRect = areaRect.MakeExpanded(100.0f);
|
||||
|
||||
// Show bounds
|
||||
UIEditor.ViewScale = (UIEditor.Size / areaRect.Size).MinValue * 0.95f;
|
||||
UIEditor.ViewCenterPosition = areaRect.Center;
|
||||
}
|
||||
|
||||
public override void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
|
||||
{
|
||||
ShowActors(gizmos.Get<TransformGizmo>().Selection, ref orientation);
|
||||
}
|
||||
|
||||
public override void ShowActor(Actor actor)
|
||||
{
|
||||
ShowActors(new[] { actor });
|
||||
}
|
||||
|
||||
public override void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
|
||||
{
|
||||
ShowActors(selection.ConvertAll(x => (Actor)x.EditableObject));
|
||||
}
|
||||
|
||||
public override void UpdateView(float dt, ref Vector3 moveDelta, ref Float2 mouseDelta, out bool centerMouse)
|
||||
{
|
||||
centerMouse = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Root control for UI Controls presentation in the game/prefab viewport.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
internal class UIEditorRoot : InputsPassThrough
|
||||
{
|
||||
/// <summary>
|
||||
/// View for the UI structure to be linked in for camera zoom and panning operations.
|
||||
/// </summary>
|
||||
private sealed class View : ContainerControl
|
||||
{
|
||||
public View(UIEditorRoot parent)
|
||||
{
|
||||
AutoFocus = false;
|
||||
ClipChildren = false;
|
||||
CullChildren = false;
|
||||
Pivot = Float2.Zero;
|
||||
Size = new Float2(1920, 1080);
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public override bool RayCast(ref Float2 location, out Control hit)
|
||||
{
|
||||
// Ignore self
|
||||
return RayCastChildren(ref location, out hit);
|
||||
}
|
||||
|
||||
public override bool IntersectsContent(ref Float2 locationParent, out Float2 location)
|
||||
{
|
||||
location = PointFromParent(ref locationParent);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void DrawSelf()
|
||||
{
|
||||
var uiRoot = (UIEditorRoot)Parent;
|
||||
if (!uiRoot.EnableBackground)
|
||||
return;
|
||||
|
||||
// Draw canvas area
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, new Color(0, 0, 0, 0.2f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cached placement of the widget used to size/edit control
|
||||
/// </summary>
|
||||
private struct Widget
|
||||
{
|
||||
public UIControl UIControl;
|
||||
public Rectangle Bounds;
|
||||
public Float2 ResizeAxis;
|
||||
public CursorType Cursor;
|
||||
}
|
||||
|
||||
private bool _mouseMovesControl, _mouseMovesView, _mouseMovesWidget;
|
||||
private Float2 _mouseMovesPos, _moveSnapDelta;
|
||||
private float _mouseMoveSum;
|
||||
private UndoMultiBlock _undoBlock;
|
||||
private View _view;
|
||||
private float[] _gridTickSteps = Utilities.Utils.CurveTickSteps, _gridTickStrengths;
|
||||
private List<Widget> _widgets;
|
||||
private Widget _activeWidget;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable displaying UI editing background and grid elements.
|
||||
/// </summary>
|
||||
public virtual bool EnableBackground => false;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable selecting controls with mouse button.
|
||||
/// </summary>
|
||||
public virtual bool EnableSelecting => false;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable panning and zooming the view.
|
||||
/// </summary>
|
||||
public bool EnableCamera => _view != null && EnableBackground;
|
||||
|
||||
/// <summary>
|
||||
/// Transform gizmo to use sync with (selection, snapping, transformation settings).
|
||||
/// </summary>
|
||||
public virtual TransformGizmo TransformGizmo => null;
|
||||
|
||||
/// <summary>
|
||||
/// The root control for controls to be linked in.
|
||||
/// </summary>
|
||||
public readonly ContainerControl UIRoot;
|
||||
|
||||
internal Float2 ViewPosition
|
||||
{
|
||||
get => _view.Location / -ViewScale;
|
||||
set => _view.Location = value * -ViewScale;
|
||||
}
|
||||
|
||||
internal Float2 ViewCenterPosition
|
||||
{
|
||||
get => (_view.Location - Size * 0.5f) / -ViewScale;
|
||||
set => _view.Location = Size * 0.5f + value * -ViewScale;
|
||||
}
|
||||
|
||||
internal float ViewScale
|
||||
{
|
||||
get => _view?.Scale.X ?? 1;
|
||||
set
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
value = Mathf.Clamp(value, 0.1f, 4.0f);
|
||||
_view.Scale = new Float2(value);
|
||||
}
|
||||
}
|
||||
|
||||
public UIEditorRoot(bool enableCamera = false)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll;
|
||||
Offsets = Margin.Zero;
|
||||
AutoFocus = false;
|
||||
UIRoot = this;
|
||||
CullChildren = false;
|
||||
ClipChildren = true;
|
||||
if (enableCamera)
|
||||
{
|
||||
_view = new View(this);
|
||||
UIRoot = _view;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
var transformGizmo = TransformGizmo;
|
||||
var owner = transformGizmo?.Owner;
|
||||
if (_widgets != null && _widgets.Count != 0 && button == MouseButton.Left)
|
||||
{
|
||||
foreach (var widget in _widgets)
|
||||
{
|
||||
if (widget.Bounds.Contains(ref location))
|
||||
{
|
||||
// Initialize widget movement
|
||||
_activeWidget = widget;
|
||||
_mouseMovesWidget = true;
|
||||
_mouseMovesPos = location;
|
||||
Cursor = widget.Cursor;
|
||||
StartUndo();
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (EnableSelecting && owner != null && !_mouseMovesControl && button == MouseButton.Left)
|
||||
{
|
||||
// Raycast the control under the mouse
|
||||
var mousePos = PointFromWindow(RootWindow.MousePosition);
|
||||
if (RayCastControl(ref mousePos, out var hitControl))
|
||||
{
|
||||
var uiControlNode = FindUIControlNode(hitControl);
|
||||
if (uiControlNode != null)
|
||||
{
|
||||
// Select node (with additive mode)
|
||||
var selection = new List<SceneGraphNode>();
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
// Add/remove from selection
|
||||
selection.AddRange(transformGizmo.Selection);
|
||||
if (transformGizmo.Selection.Contains(uiControlNode))
|
||||
selection.Remove(uiControlNode);
|
||||
else
|
||||
selection.Add(uiControlNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select
|
||||
selection.Add(uiControlNode);
|
||||
}
|
||||
owner.Select(selection);
|
||||
|
||||
// Initialize control movement
|
||||
_mouseMovesControl = true;
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum = 0.0f;
|
||||
_moveSnapDelta = Float2.Zero;
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Allow deselecting if user clicks on nothing
|
||||
else
|
||||
{
|
||||
owner.Select(null);
|
||||
}
|
||||
}
|
||||
if (EnableCamera && (button == MouseButton.Right || button == MouseButton.Middle))
|
||||
{
|
||||
// Initialize surface movement
|
||||
_mouseMovesView = true;
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum = 0.0f;
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return Focus(this);
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
base.OnMouseMove(location);
|
||||
|
||||
// Change cursor if mouse is over active control widget
|
||||
bool cursorChanged = false;
|
||||
if (_widgets != null && _widgets.Count != 0 && !_mouseMovesControl && !_mouseMovesWidget && !_mouseMovesView)
|
||||
{
|
||||
foreach (var widget in _widgets)
|
||||
{
|
||||
if (widget.Bounds.Contains(ref location))
|
||||
{
|
||||
Cursor = widget.Cursor;
|
||||
cursorChanged = true;
|
||||
}
|
||||
else if (Cursor != CursorType.Default && !cursorChanged)
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var transformGizmo = TransformGizmo;
|
||||
if (_mouseMovesControl && transformGizmo != null)
|
||||
{
|
||||
// Calculate transform delta
|
||||
var delta = location - _mouseMovesPos;
|
||||
if (transformGizmo.TranslationSnapEnable || transformGizmo.Owner.UseSnapping)
|
||||
{
|
||||
_moveSnapDelta += delta;
|
||||
delta = Float2.SnapToGrid(_moveSnapDelta, new Float2(transformGizmo.TranslationSnapValue * ViewScale));
|
||||
_moveSnapDelta -= delta;
|
||||
}
|
||||
|
||||
// Move selected controls
|
||||
if (delta.LengthSquared > 0.0f)
|
||||
{
|
||||
StartUndo();
|
||||
var moved = false;
|
||||
var moveLocation = _mouseMovesPos + delta;
|
||||
var selection = transformGizmo.Selection;
|
||||
for (var i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (IsValidControl(selection[i], out var uiControl))
|
||||
{
|
||||
// Move control (handle any control transformations by moving in editor's local-space)
|
||||
var control = uiControl.Control;
|
||||
var localLocation = control.LocalLocation;
|
||||
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
|
||||
control.LocalLocation = localLocation + uiControlDelta;
|
||||
|
||||
// Don't move if layout doesn't allow it
|
||||
if (control.Parent != null)
|
||||
control.Parent.PerformLayout();
|
||||
else
|
||||
control.PerformLayout();
|
||||
|
||||
// Check if control was moved (parent container could block it)
|
||||
if (localLocation != control.LocalLocation)
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum += delta.Length;
|
||||
if (moved)
|
||||
Cursor = CursorType.SizeAll;
|
||||
}
|
||||
}
|
||||
if (_mouseMovesWidget && _activeWidget.UIControl)
|
||||
{
|
||||
// Calculate transform delta
|
||||
var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute;
|
||||
var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
|
||||
var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
|
||||
var delta = location - _mouseMovesPos;
|
||||
// TODO: scale/size snapping?
|
||||
delta *= resizeAxisAbs;
|
||||
|
||||
// Resize control via widget
|
||||
var moveLocation = _mouseMovesPos + delta;
|
||||
var control = _activeWidget.UIControl.Control;
|
||||
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
|
||||
control.LocalLocation += uiControlDelta * resizeAxisNeg;
|
||||
control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
|
||||
|
||||
// Don't move if layout doesn't allow it
|
||||
if (control.Parent != null)
|
||||
control.Parent.PerformLayout();
|
||||
else
|
||||
control.PerformLayout();
|
||||
|
||||
_mouseMovesPos = location;
|
||||
}
|
||||
if (_mouseMovesView)
|
||||
{
|
||||
// Move view
|
||||
var delta = location - _mouseMovesPos;
|
||||
if (delta.LengthSquared > 4.0f)
|
||||
{
|
||||
_mouseMovesPos = location;
|
||||
_mouseMoveSum += delta.Length;
|
||||
_view.Location += delta;
|
||||
Cursor = CursorType.SizeAll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
EndMovingControls();
|
||||
EndMovingWidget();
|
||||
if (_mouseMovesView)
|
||||
{
|
||||
EndMovingView();
|
||||
if (button == MouseButton.Right && _mouseMoveSum < 2.0f)
|
||||
TransformGizmo.Owner.OpenContextMenu();
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
EndMovingControls();
|
||||
EndMovingView();
|
||||
EndMovingWidget();
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
EndMovingControls();
|
||||
EndMovingView();
|
||||
EndMovingWidget();
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
if (base.OnMouseWheel(location, delta))
|
||||
return true;
|
||||
|
||||
if (EnableCamera && !_mouseMovesControl)
|
||||
{
|
||||
// Zoom view
|
||||
var nextViewScale = ViewScale + delta * 0.1f;
|
||||
if (delta > 0 && !_mouseMovesControl)
|
||||
{
|
||||
// Scale towards mouse when zooming in
|
||||
var nextCenterPosition = ViewPosition + location / ViewScale;
|
||||
ViewScale = nextViewScale;
|
||||
ViewPosition = nextCenterPosition - (location / ViewScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scale while keeping center position when zooming out or when dragging view
|
||||
var viewCenter = ViewCenterPosition;
|
||||
ViewScale = nextViewScale;
|
||||
ViewCenterPosition = viewCenter;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (EnableBackground && _view != null)
|
||||
{
|
||||
// Draw background
|
||||
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
|
||||
|
||||
// Draw grid
|
||||
var viewRect = GetClientArea();
|
||||
var upperLeft = _view.PointFromParent(viewRect.Location);
|
||||
var bottomRight = _view.PointFromParent(viewRect.Size);
|
||||
var min = Float2.Min(upperLeft, bottomRight);
|
||||
var max = Float2.Max(upperLeft, bottomRight);
|
||||
var pixelRange = (max - min) * ViewScale;
|
||||
Render2D.PushClip(ref viewRect);
|
||||
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
|
||||
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
|
||||
if (!_mouseMovesWidget)
|
||||
{
|
||||
// Clear widgets to collect them during drawing
|
||||
_widgets?.Clear();
|
||||
}
|
||||
|
||||
bool drawAnySelectedControl = false;
|
||||
var transformGizmo = TransformGizmo;
|
||||
var mousePos = PointFromWindow(RootWindow.MousePosition);
|
||||
if (transformGizmo != null)
|
||||
{
|
||||
// Selected UI controls outline
|
||||
var selection = transformGizmo.Selection;
|
||||
for (var i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (IsValidControl(selection[i], out var controlActor))
|
||||
{
|
||||
DrawControl(controlActor, controlActor.Control, true, ref mousePos, ref drawAnySelectedControl, EnableSelecting);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver)
|
||||
{
|
||||
// Highlight control under mouse for easier selecting (except if already selected)
|
||||
if (RayCastControl(ref mousePos, out var hitControl) &&
|
||||
(transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl)))
|
||||
{
|
||||
DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl);
|
||||
}
|
||||
}
|
||||
if (drawAnySelectedControl)
|
||||
Render2D.PopTransform();
|
||||
|
||||
if (EnableBackground)
|
||||
{
|
||||
// Draw border
|
||||
if (ContainsFocus)
|
||||
{
|
||||
Render2D.DrawRectangle(new Rectangle(1, 1, Width - 2, Height - 2), Editor.IsPlayMode ? Color.OrangeRed : Style.Current.BackgroundSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
EndMovingControls();
|
||||
EndMovingView();
|
||||
EndMovingWidget();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
private Float2 GetControlDelta(Control control, ref Float2 start, ref Float2 end)
|
||||
{
|
||||
var pointOrigin = control.Parent ?? control;
|
||||
var startPos = pointOrigin.PointFromParent(this, start);
|
||||
var endPos = pointOrigin.PointFromParent(this, end);
|
||||
return endPos - startPos;
|
||||
}
|
||||
|
||||
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
|
||||
{
|
||||
var style = Style.Current;
|
||||
var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f);
|
||||
var labelsColor = style.ForegroundDisabled;
|
||||
var labelsSize = 10.0f;
|
||||
Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
|
||||
{
|
||||
var p = _view.PointToParent(axis * tick);
|
||||
|
||||
// Draw line
|
||||
var lineRect = new Rectangle
|
||||
(
|
||||
viewRect.Location + (p - 0.5f) * axis,
|
||||
Float2.Lerp(viewRect.Size, Float2.One, axis)
|
||||
);
|
||||
Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength));
|
||||
|
||||
// Draw label
|
||||
string label = tick.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
var labelRect = new Rectangle
|
||||
(
|
||||
viewRect.X + 4.0f + (p.X * axis.X),
|
||||
viewRect.Y - labelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
|
||||
50,
|
||||
labelsSize
|
||||
);
|
||||
Render2D.DrawText(style.FontSmall, label, labelRect, labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
|
||||
}, _gridTickSteps, ref _gridTickStrengths, min, max, pixelRange);
|
||||
}
|
||||
|
||||
private void DrawControl(UIControl uiControl, Control control, bool selection, ref Float2 mousePos, ref bool drawAnySelectedControl, bool withWidgets = false)
|
||||
{
|
||||
if (!drawAnySelectedControl)
|
||||
{
|
||||
drawAnySelectedControl = true;
|
||||
Render2D.PushTransform(ref _cachedTransform);
|
||||
}
|
||||
var options = Editor.Instance.Options.Options.Visual;
|
||||
|
||||
// Draw bounds
|
||||
var bounds = control.EditorBounds;
|
||||
var ul = control.PointToParent(this, bounds.UpperLeft);
|
||||
var ur = control.PointToParent(this, bounds.UpperRight);
|
||||
var bl = control.PointToParent(this, bounds.BottomLeft);
|
||||
var br = control.PointToParent(this, bounds.BottomRight);
|
||||
var color = selection ? options.SelectionOutlineColor0 : Style.Current.SelectionBorder;
|
||||
#if false
|
||||
// AABB
|
||||
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
|
||||
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
Render2D.DrawRectangle(bounds, color, options.UISelectionOutlineSize);
|
||||
#else
|
||||
// OBB
|
||||
Render2D.DrawLine(ul, ur, color, options.UISelectionOutlineSize);
|
||||
Render2D.DrawLine(ur, br, color, options.UISelectionOutlineSize);
|
||||
Render2D.DrawLine(br, bl, color, options.UISelectionOutlineSize);
|
||||
Render2D.DrawLine(bl, ul, color, options.UISelectionOutlineSize);
|
||||
#endif
|
||||
if (withWidgets)
|
||||
{
|
||||
// Draw sizing widgets
|
||||
if (_widgets == null)
|
||||
_widgets = new List<Widget>();
|
||||
var widgetSize = 8.0f;
|
||||
var viewScale = ViewScale;
|
||||
if (viewScale < 0.7f)
|
||||
widgetSize *= viewScale;
|
||||
var controlSize = control.Size.Absolute.MinValue / 50.0f;
|
||||
if (controlSize < 1.0f)
|
||||
widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f);
|
||||
var cornerSize = new Float2(widgetSize);
|
||||
DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE);
|
||||
DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW);
|
||||
DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW);
|
||||
DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE);
|
||||
var edgeSizeV = new Float2(widgetSize * 2, widgetSize);
|
||||
var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X);
|
||||
Float2.Lerp(ref ul, ref bl, 0.5f, out var el);
|
||||
Float2.Lerp(ref ur, ref br, 0.5f, out var er);
|
||||
Float2.Lerp(ref ul, ref ur, 0.5f, out var eu);
|
||||
Float2.Lerp(ref bl, ref br, 0.5f, out var eb);
|
||||
DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE);
|
||||
DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE);
|
||||
DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS);
|
||||
DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS);
|
||||
|
||||
// TODO: draw anchors
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor)
|
||||
{
|
||||
var style = Style.Current;
|
||||
var rect = new Rectangle(pos - size * 0.5f, size);
|
||||
if (rect.Contains(ref mousePos))
|
||||
{
|
||||
Render2D.FillRectangle(rect, style.Foreground);
|
||||
}
|
||||
else
|
||||
{
|
||||
Render2D.FillRectangle(rect, style.ForegroundGrey);
|
||||
Render2D.DrawRectangle(rect, style.Foreground);
|
||||
}
|
||||
if (!_mouseMovesWidget && uiControl != null)
|
||||
{
|
||||
// Collect widget
|
||||
_widgets.Add(new Widget
|
||||
{
|
||||
UIControl = uiControl,
|
||||
Bounds = rect,
|
||||
ResizeAxis = resizeAxis,
|
||||
Cursor = cursor,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidControl(SceneGraphNode node, out UIControl uiControl)
|
||||
{
|
||||
uiControl = null;
|
||||
if (node.EditableObject is UIControl controlActor)
|
||||
uiControl = controlActor;
|
||||
return uiControl != null &&
|
||||
uiControl.Control != null &&
|
||||
uiControl.Control.VisibleInHierarchy &&
|
||||
uiControl.Control.RootWindow != null;
|
||||
}
|
||||
|
||||
private bool RayCastControl(ref Float2 location, out Control hit)
|
||||
{
|
||||
#if false
|
||||
// Raycast only controls with content (eg. skips transparent panels)
|
||||
return RayCastChildren(ref location, out hit);
|
||||
#else
|
||||
// Find any control under mouse (hierarchical)
|
||||
hit = GetChildAtRecursive(location);
|
||||
if (hit is View || hit is CanvasContainer)
|
||||
hit = null;
|
||||
return hit != null;
|
||||
#endif
|
||||
}
|
||||
|
||||
private UIControlNode FindUIControlNode(Control control)
|
||||
{
|
||||
return FindUIControlNode(TransformGizmo.Owner.SceneGraphRoot, control);
|
||||
}
|
||||
|
||||
private UIControlNode FindUIControlNode(SceneGraphNode node, Control control)
|
||||
{
|
||||
var result = node as UIControlNode;
|
||||
if (result != null && ((UIControl)result.Actor).Control == control)
|
||||
return result;
|
||||
foreach (var e in node.ChildNodes)
|
||||
{
|
||||
result = FindUIControlNode(e, control);
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void StartUndo()
|
||||
{
|
||||
var undo = TransformGizmo?.Owner?.Undo;
|
||||
if (undo == null || _undoBlock != null)
|
||||
return;
|
||||
_undoBlock = new UndoMultiBlock(undo, TransformGizmo.Selection.ConvertAll(x => x.EditableObject), "Edit control");
|
||||
}
|
||||
|
||||
private void EndUndo()
|
||||
{
|
||||
if (_undoBlock == null)
|
||||
return;
|
||||
_undoBlock.Dispose();
|
||||
_undoBlock = null;
|
||||
}
|
||||
|
||||
private void EndMovingControls()
|
||||
{
|
||||
if (!_mouseMovesControl)
|
||||
return;
|
||||
_mouseMovesControl = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
EndUndo();
|
||||
}
|
||||
|
||||
private void EndMovingView()
|
||||
{
|
||||
if (!_mouseMovesView)
|
||||
return;
|
||||
_mouseMovesView = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
|
||||
private void EndMovingWidget()
|
||||
{
|
||||
if (!_mouseMovesWidget)
|
||||
return;
|
||||
_mouseMovesWidget = false;
|
||||
_activeWidget = new Widget();
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
EndUndo();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control that can optionally disable inputs to the children.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
internal class InputsPassThrough : ContainerControl
|
||||
{
|
||||
private bool _isMouseOver;
|
||||
|
||||
/// <summary>
|
||||
/// True if enable input events passing to the UI.
|
||||
/// </summary>
|
||||
public virtual bool EnableInputs => true;
|
||||
|
||||
public override bool RayCast(ref Float2 location, out Control hit)
|
||||
{
|
||||
return RayCastChildren(ref location, out hit);
|
||||
}
|
||||
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise = false)
|
||||
{
|
||||
if (precise)
|
||||
return false;
|
||||
return base.ContainsPoint(ref location, precise);
|
||||
}
|
||||
|
||||
public override bool OnCharInput(char c)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnCharInput(c);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return DragDropEffect.None;
|
||||
return base.OnDragDrop(ref location, data);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return DragDropEffect.None;
|
||||
return base.OnDragEnter(ref location, data);
|
||||
}
|
||||
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return DragDropEffect.None;
|
||||
return base.OnDragMove(ref location, data);
|
||||
}
|
||||
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
public override void OnKeyUp(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnKeyUp(key);
|
||||
}
|
||||
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
public override bool IsMouseOver => _isMouseOver;
|
||||
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
_isMouseOver = true;
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
_isMouseOver = false;
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return;
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
if (!EnableInputs)
|
||||
return false;
|
||||
return base.OnMouseWheel(location, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,22 +263,22 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_GetShaderAssetSourceCode(BinaryAss
|
||||
INTERNAL_CALL_CHECK_RETURN(obj, nullptr);
|
||||
if (obj->WaitForLoaded())
|
||||
DebugLog::ThrowNullReference();
|
||||
|
||||
auto lock = obj->Storage->Lock();
|
||||
|
||||
if (obj->LoadChunk(SHADER_FILE_CHUNK_SOURCE))
|
||||
return nullptr;
|
||||
|
||||
// Decrypt source code
|
||||
BytesContainer data;
|
||||
obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data);
|
||||
auto source = data.Get<char>();
|
||||
auto sourceLength = data.Length();
|
||||
Encryption::DecryptBytes(data.Get(), data.Length());
|
||||
source[sourceLength - 1] = 0;
|
||||
|
||||
Encryption::DecryptBytes((byte*)data.Get(), data.Length());
|
||||
|
||||
// Get source and encrypt it back
|
||||
const StringAnsiView srcData((const char*)data.Get(), data.Length());
|
||||
const String source(srcData);
|
||||
const auto str = MUtils::ToString(source);
|
||||
|
||||
Encryption::EncryptBytes((byte*)data.Get(), data.Length());
|
||||
const auto str = MUtils::ToString(srcData);
|
||||
Encryption::EncryptBytes(data.Get(), data.Length());
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -1106,6 +1106,7 @@ namespace FlaxEditor.Modules
|
||||
Proxy.Add(new VisualScriptProxy());
|
||||
Proxy.Add(new BehaviorTreeProxy());
|
||||
Proxy.Add(new LocalizedStringTableProxy());
|
||||
Proxy.Add(new WidgetProxy());
|
||||
Proxy.Add(new FileProxy());
|
||||
Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>());
|
||||
|
||||
|
||||
@@ -101,7 +101,10 @@ namespace FlaxEditor.Modules
|
||||
public void Select(List<SceneGraphNode> selection, bool additive = false)
|
||||
{
|
||||
if (selection == null)
|
||||
throw new ArgumentNullException();
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent from selecting null nodes
|
||||
selection.RemoveAll(x => x == null);
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.IO;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Modules
|
||||
@@ -454,6 +455,41 @@ namespace FlaxEditor.Modules
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
private Dictionary<ContainerControl, Float2> _uiRootSizes;
|
||||
|
||||
internal void OnSaveStart(ContainerControl uiRoot)
|
||||
{
|
||||
// Force viewport UI to have fixed size during scene/prefabs saving to result in stable data (less mess in version control diffs)
|
||||
if (_uiRootSizes == null)
|
||||
_uiRootSizes = new Dictionary<ContainerControl, Float2>();
|
||||
_uiRootSizes[uiRoot] = uiRoot.Size;
|
||||
uiRoot.Size = new Float2(1920, 1080);
|
||||
}
|
||||
|
||||
internal void OnSaveEnd(ContainerControl uiRoot)
|
||||
{
|
||||
// Restore cached size of the UI root container
|
||||
if (_uiRootSizes != null && _uiRootSizes.Remove(uiRoot, out var size))
|
||||
{
|
||||
uiRoot.Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSceneSaving(Scene scene, Guid sceneId)
|
||||
{
|
||||
OnSaveStart(RootControl.GameRoot);
|
||||
}
|
||||
|
||||
private void OnSceneSaved(Scene scene, Guid sceneId)
|
||||
{
|
||||
OnSaveEnd(RootControl.GameRoot);
|
||||
}
|
||||
|
||||
private void OnSceneSaveError(Scene scene, Guid sceneId)
|
||||
{
|
||||
OnSaveEnd(RootControl.GameRoot);
|
||||
}
|
||||
|
||||
private void OnSceneLoaded(Scene scene, Guid sceneId)
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
@@ -659,6 +695,9 @@ namespace FlaxEditor.Modules
|
||||
Root = new ScenesRootNode();
|
||||
|
||||
// Bind events
|
||||
Level.SceneSaving += OnSceneSaving;
|
||||
Level.SceneSaved += OnSceneSaved;
|
||||
Level.SceneSaveError += OnSceneSaveError;
|
||||
Level.SceneLoaded += OnSceneLoaded;
|
||||
Level.SceneUnloading += OnSceneUnloading;
|
||||
Level.ActorSpawned += OnActorSpawned;
|
||||
@@ -673,6 +712,9 @@ namespace FlaxEditor.Modules
|
||||
public override void OnExit()
|
||||
{
|
||||
// Unbind events
|
||||
Level.SceneSaving -= OnSceneSaving;
|
||||
Level.SceneSaved -= OnSceneSaved;
|
||||
Level.SceneSaveError -= OnSceneSaveError;
|
||||
Level.SceneLoaded -= OnSceneLoaded;
|
||||
Level.SceneUnloading -= OnSceneUnloading;
|
||||
Level.ActorSpawned -= OnActorSpawned;
|
||||
|
||||
@@ -201,8 +201,8 @@ namespace FlaxEditor.Options
|
||||
/// <returns>True if input has been processed, otherwise false.</returns>
|
||||
public bool Process(Control control)
|
||||
{
|
||||
var root = control.Root;
|
||||
return root.GetKey(Key) && ProcessModifiers(control);
|
||||
var root = control?.Root;
|
||||
return root != null && root.GetKey(Key) && ProcessModifiers(control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using FlaxEditor.GUI.Docking;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Options
|
||||
@@ -116,6 +117,27 @@ namespace FlaxEditor.Options
|
||||
BorderlessWindow,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for formatting numerical values.
|
||||
/// </summary>
|
||||
public enum ValueFormattingType
|
||||
{
|
||||
/// <summary>
|
||||
/// No formatting.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Format using the base SI unit.
|
||||
/// </summary>
|
||||
BaseUnit,
|
||||
|
||||
/// <summary>
|
||||
/// Format using a unit that matches the value best.
|
||||
/// </summary>
|
||||
AutoUnit,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
|
||||
/// </summary>
|
||||
@@ -174,6 +196,20 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")]
|
||||
public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the formatting option for numeric values in the editor.
|
||||
/// </summary>
|
||||
[DefaultValue(ValueFormattingType.None)]
|
||||
[EditorDisplay("Interface"), EditorOrder(300)]
|
||||
public ValueFormattingType ValueFormatting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option to put a space between numbers and units for unit formatting.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Interface"), EditorOrder(310)]
|
||||
public bool SeparateValueAndUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamps prefix mode for output log messages.
|
||||
/// </summary>
|
||||
|
||||
@@ -200,6 +200,27 @@ namespace FlaxEditor.Options
|
||||
|
||||
EditorAssets.Cache.OnEditorOptionsChanged(Options);
|
||||
|
||||
// Units formatting options
|
||||
bool useUnitsFormatting = Options.Interface.ValueFormatting != InterfaceOptions.ValueFormattingType.None;
|
||||
bool automaticUnitsFormatting = Options.Interface.ValueFormatting == InterfaceOptions.ValueFormattingType.AutoUnit;
|
||||
bool separateValueAndUnit = Options.Interface.SeparateValueAndUnit;
|
||||
if (useUnitsFormatting != Utilities.Units.UseUnitsFormatting ||
|
||||
automaticUnitsFormatting != Utilities.Units.AutomaticUnitsFormatting ||
|
||||
separateValueAndUnit != Utilities.Units.SeparateValueAndUnit)
|
||||
{
|
||||
Utilities.Units.UseUnitsFormatting = useUnitsFormatting;
|
||||
Utilities.Units.AutomaticUnitsFormatting = automaticUnitsFormatting;
|
||||
Utilities.Units.SeparateValueAndUnit = separateValueAndUnit;
|
||||
|
||||
// Refresh UI in property panels
|
||||
Editor.Windows.PropertiesWin?.Presenter.BuildLayoutOnUpdate();
|
||||
foreach (var window in Editor.Windows.Windows)
|
||||
{
|
||||
if (window is Windows.Assets.PrefabWindow prefabWindow)
|
||||
prefabWindow.Presenter.BuildLayoutOnUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Send event
|
||||
OptionsChanged?.Invoke(Options);
|
||||
}
|
||||
|
||||
@@ -308,11 +308,14 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
var selection = Editor.Instance.SceneEditing.Selection;
|
||||
if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this)
|
||||
{
|
||||
if (Input.Keyboard.GetKey(KeyboardKeys.Shift))
|
||||
var mouse = Input.Mouse;
|
||||
var keyboard = Input.Keyboard;
|
||||
|
||||
if (keyboard.GetKey(KeyboardKeys.Shift))
|
||||
EditSplineWithSnap(selectedPoint);
|
||||
|
||||
var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero;
|
||||
var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right);
|
||||
var canAddSplinePoint = mouse.PositionDelta == Float2.Zero && mouse.Position != Float2.Zero;
|
||||
var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && mouse.GetButtonDown(MouseButton.Right);
|
||||
if (requestAddSplinePoint && canAddSplinePoint)
|
||||
AddSplinePoint(selectedPoint);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
@@ -84,76 +85,109 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
|
||||
contextMenu.AddButton("Add collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null;
|
||||
contextMenu.AddButton("Add collider", () => OnAddMeshCollider(window)).Enabled = ((StaticModel)Actor).Model != null;
|
||||
}
|
||||
|
||||
private void OnAddMeshCollider()
|
||||
private void OnAddMeshCollider(EditorWindow window)
|
||||
{
|
||||
var model = ((StaticModel)Actor).Model;
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
// Special case for in-built Editor models that can use analytical collision
|
||||
var modelPath = model.Path;
|
||||
if (modelPath.EndsWith("/Primitives/Cube.flax", StringComparison.Ordinal))
|
||||
// Allow collider to be added to evey static model selection
|
||||
SceneGraphNode[] selection = Array.Empty<SceneGraphNode>();
|
||||
if (window is SceneTreeWindow)
|
||||
{
|
||||
var actor = new BoxCollider
|
||||
{
|
||||
StaticFlags = Actor.StaticFlags,
|
||||
Transform = Actor.Transform,
|
||||
};
|
||||
Root.Spawn(actor, Actor);
|
||||
return;
|
||||
selection = Editor.Instance.SceneEditing.Selection.ToArray();
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal))
|
||||
else if (window is PrefabWindow prefabWindow)
|
||||
{
|
||||
var actor = new SphereCollider
|
||||
{
|
||||
StaticFlags = Actor.StaticFlags,
|
||||
Transform = Actor.Transform,
|
||||
};
|
||||
Root.Spawn(actor, Actor);
|
||||
return;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new BoxCollider
|
||||
{
|
||||
StaticFlags = Actor.StaticFlags,
|
||||
Transform = Actor.Transform,
|
||||
Size = new Float3(100.0f, 100.0f, 1.0f),
|
||||
};
|
||||
Root.Spawn(actor, Actor);
|
||||
return;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new CapsuleCollider
|
||||
{
|
||||
StaticFlags = Actor.StaticFlags,
|
||||
Transform = Actor.Transform,
|
||||
Radius = 25.0f,
|
||||
Height = 50.0f,
|
||||
};
|
||||
Editor.Instance.SceneEditing.Spawn(actor, Actor);
|
||||
actor.LocalPosition = new Vector3(0, 50.0f, 0);
|
||||
actor.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
|
||||
return;
|
||||
selection = prefabWindow.Selection.ToArray();
|
||||
}
|
||||
|
||||
// Create collision data (or reuse) and add collision actor
|
||||
Action<CollisionData> created = collisionData =>
|
||||
var createdNodes = new List<SceneGraphNode>();
|
||||
foreach (var node in selection)
|
||||
{
|
||||
var actor = new MeshCollider
|
||||
if (node is not StaticModelNode staticModelNode)
|
||||
continue;
|
||||
|
||||
var model = ((StaticModel)staticModelNode.Actor).Model;
|
||||
if (!model)
|
||||
continue;
|
||||
|
||||
// Special case for in-built Editor models that can use analytical collision
|
||||
var modelPath = model.Path;
|
||||
if (modelPath.EndsWith("/Primitives/Cube.flax", StringComparison.Ordinal))
|
||||
{
|
||||
StaticFlags = Actor.StaticFlags,
|
||||
Transform = Actor.Transform,
|
||||
CollisionData = collisionData,
|
||||
var actor = new BoxCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = staticModelNode.Actor.Transform,
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
continue;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new SphereCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = staticModelNode.Actor.Transform,
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
continue;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new BoxCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = staticModelNode.Actor.Transform,
|
||||
Size = new Float3(100.0f, 100.0f, 1.0f),
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
continue;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new CapsuleCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = staticModelNode.Actor.Transform,
|
||||
Radius = 25.0f,
|
||||
Height = 50.0f,
|
||||
};
|
||||
Editor.Instance.SceneEditing.Spawn(actor, staticModelNode.Actor);
|
||||
actor.LocalPosition = new Vector3(0, 50.0f, 0);
|
||||
actor.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create collision data (or reuse) and add collision actor
|
||||
Action<CollisionData> created = collisionData =>
|
||||
{
|
||||
var actor = new MeshCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = staticModelNode.Actor.Transform,
|
||||
CollisionData = collisionData,
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
};
|
||||
Root.Spawn(actor, Actor);
|
||||
};
|
||||
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>();
|
||||
collisionDataProxy.CreateCollisionDataFromModel(model, created);
|
||||
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>();
|
||||
collisionDataProxy.CreateCollisionDataFromModel(model, created, selection.Length == 1);
|
||||
}
|
||||
|
||||
// Select all created nodes
|
||||
if (window is SceneTreeWindow)
|
||||
{
|
||||
Editor.Instance.SceneEditing.Select(createdNodes);
|
||||
}
|
||||
else if (window is PrefabWindow pWindow)
|
||||
{
|
||||
pWindow.Select(createdNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,8 +617,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public override void SetLocation(int index, Float2 location)
|
||||
{
|
||||
var dataA = (Float4)_node.Values[4 + index * 2];
|
||||
var ranges = (Float4)_node.Values[0];
|
||||
|
||||
dataA.X = location.X;
|
||||
dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
|
||||
|
||||
_node.Values[4 + index * 2] = dataA;
|
||||
_node.Surface.MarkAsEdited();
|
||||
@@ -750,9 +751,10 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public override void SetLocation(int index, Float2 location)
|
||||
{
|
||||
var dataA = (Float4)_node.Values[4 + index * 2];
|
||||
var ranges = (Float4)_node.Values[0];
|
||||
|
||||
dataA.X = location.X;
|
||||
dataA.Y = location.Y;
|
||||
dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
|
||||
dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W);
|
||||
|
||||
_node.Values[4 + index * 2] = dataA;
|
||||
_node.Surface.MarkAsEdited();
|
||||
|
||||
@@ -117,17 +117,9 @@ namespace FlaxEditor.Surface
|
||||
editor.Panel.Tag = attributeType;
|
||||
_presenter = editor;
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
|
||||
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
|
||||
// Cache 'previous' state to check if attributes were edited after operation
|
||||
_oldData = SurfaceMeta.GetAttributesData(attributes);
|
||||
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning disable SYSLIB0011
|
||||
formatter.Serialize(stream, attributes);
|
||||
#pragma warning restore SYSLIB0011
|
||||
_oldData = stream.ToArray();
|
||||
}
|
||||
editor.Select(new Proxy
|
||||
{
|
||||
Value = attributes,
|
||||
@@ -145,20 +137,11 @@ namespace FlaxEditor.Surface
|
||||
return;
|
||||
}
|
||||
}
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
|
||||
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
|
||||
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning disable SYSLIB0011
|
||||
formatter.Serialize(stream, newValue);
|
||||
#pragma warning restore SYSLIB0011
|
||||
var newData = stream.ToArray();
|
||||
if (!_oldData.SequenceEqual(newData))
|
||||
{
|
||||
Edited?.Invoke(newValue);
|
||||
}
|
||||
var newData = SurfaceMeta.GetAttributesData(newValue);
|
||||
if (!_oldData.SequenceEqual(newData))
|
||||
{
|
||||
Edited?.Invoke(newValue);
|
||||
}
|
||||
|
||||
Hide();
|
||||
|
||||
@@ -4,9 +4,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
@@ -39,28 +39,48 @@ namespace FlaxEditor.Surface
|
||||
public readonly List<Entry> Entries = new List<Entry>();
|
||||
|
||||
/// <summary>
|
||||
/// The attribute meta type identifier.
|
||||
/// The attribute meta type identifier. Uses byte[] as storage for Attribute[] serialized with BinaryFormatter (deprecated in .NET 5).
|
||||
/// [Deprecated on 8.12.2023, expires on 8.12.2025]
|
||||
/// </summary>
|
||||
public const int AttributeMetaTypeID = 12;
|
||||
public const int OldAttributeMetaTypeID = 12;
|
||||
|
||||
/// <summary>
|
||||
/// The attribute meta type identifier. Uses byte[] as storage for Attribute[] serialized with JsonSerializer.
|
||||
/// </summary>
|
||||
public const int AttributeMetaTypeID = 13;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attributes collection from the data.
|
||||
/// </summary>
|
||||
/// <param name="data">The graph metadata.</param>
|
||||
/// <param name="data">The graph metadata serialized with JsonSerializer.</param>
|
||||
/// <param name="oldData">The graph metadata serialized with BinaryFormatter.</param>
|
||||
/// <returns>The attributes collection.</returns>
|
||||
public static Attribute[] GetAttributes(byte[] data)
|
||||
public static Attribute[] GetAttributes(byte[] data, byte[] oldData)
|
||||
{
|
||||
if (data != null && data.Length != 0)
|
||||
{
|
||||
using (var stream = new MemoryStream(data))
|
||||
try
|
||||
{
|
||||
var json = Encoding.Unicode.GetString(data);
|
||||
return FlaxEngine.Json.JsonSerializer.Deserialize<Attribute[]>(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogError("Failed to deserialize Visject attributes array.");
|
||||
Editor.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
if (oldData != null && oldData.Length != 0)
|
||||
{
|
||||
// [Deprecated on 8.12.2023, expires on 8.12.2025]
|
||||
using (var stream = new MemoryStream(oldData))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
|
||||
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
|
||||
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning disable SYSLIB0011
|
||||
var formatter = new BinaryFormatter();
|
||||
return (Attribute[])formatter.Deserialize(stream);
|
||||
#pragma warning restore SYSLIB0011
|
||||
}
|
||||
@@ -74,6 +94,21 @@ namespace FlaxEditor.Surface
|
||||
return Utils.GetEmptyArray<Attribute>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes surface attributes into byte[] data using the current format.
|
||||
/// </summary>
|
||||
/// <param name="attributes">The input attributes.</param>
|
||||
/// <returns>The result array with bytes. Can be empty but not null.</returns>
|
||||
internal static byte[] GetAttributesData(Attribute[] attributes)
|
||||
{
|
||||
if (attributes != null && attributes.Length != 0)
|
||||
{
|
||||
var json = FlaxEngine.Json.JsonSerializer.Serialize(attributes);
|
||||
return Encoding.Unicode.GetBytes(json);
|
||||
}
|
||||
return Utils.GetEmptyArray<byte>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified attribute was defined for this member.
|
||||
/// </summary>
|
||||
@@ -93,7 +128,8 @@ namespace FlaxEditor.Surface
|
||||
public static Attribute[] GetAttributes(GraphParameter parameter)
|
||||
{
|
||||
var data = parameter.GetMetaData(AttributeMetaTypeID);
|
||||
return GetAttributes(data);
|
||||
var dataOld = parameter.GetMetaData(OldAttributeMetaTypeID);
|
||||
return GetAttributes(data, dataOld);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -102,12 +138,7 @@ namespace FlaxEditor.Surface
|
||||
/// <returns>The attributes collection.</returns>
|
||||
public Attribute[] GetAttributes()
|
||||
{
|
||||
for (int i = 0; i < Entries.Count; i++)
|
||||
{
|
||||
if (Entries[i].TypeID == AttributeMetaTypeID)
|
||||
return GetAttributes(Entries[i].Data);
|
||||
}
|
||||
return Utils.GetEmptyArray<Attribute>();
|
||||
return GetAttributes(GetEntry(AttributeMetaTypeID).Data, GetEntry(OldAttributeMetaTypeID).Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,25 +150,12 @@ namespace FlaxEditor.Surface
|
||||
if (attributes == null || attributes.Length == 0)
|
||||
{
|
||||
RemoveEntry(AttributeMetaTypeID);
|
||||
RemoveEntry(OldAttributeMetaTypeID);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < attributes.Length; i++)
|
||||
{
|
||||
if (attributes[i] == null)
|
||||
throw new NullReferenceException("One of the Visject attributes is null.");
|
||||
}
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
|
||||
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
|
||||
|
||||
var formatter = new BinaryFormatter();
|
||||
#pragma warning disable SYSLIB0011
|
||||
formatter.Serialize(stream, attributes);
|
||||
#pragma warning restore SYSLIB0011
|
||||
AddEntry(AttributeMetaTypeID, stream.ToArray());
|
||||
}
|
||||
AddEntry(AttributeMetaTypeID, GetAttributesData(attributes));
|
||||
RemoveEntry(OldAttributeMetaTypeID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +198,11 @@ namespace FlaxEditor.Surface
|
||||
/// <returns>True if cannot save data</returns>
|
||||
public void Save(BinaryWriter stream)
|
||||
{
|
||||
stream.Write(Entries.Count);
|
||||
|
||||
for (int i = 0; i < Entries.Count; i++)
|
||||
var entries = Entries;
|
||||
stream.Write(entries.Count);
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
Entry e = Entries[i];
|
||||
Entry e = entries[i];
|
||||
|
||||
stream.Write(e.TypeID);
|
||||
stream.Write((long)0);
|
||||
@@ -214,14 +232,12 @@ namespace FlaxEditor.Surface
|
||||
/// <returns>Entry</returns>
|
||||
public Entry GetEntry(int typeID)
|
||||
{
|
||||
for (int i = 0; i < Entries.Count; i++)
|
||||
var entries = Entries;
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
if (Entries[i].TypeID == typeID)
|
||||
{
|
||||
return Entries[i];
|
||||
}
|
||||
if (entries[i].TypeID == typeID)
|
||||
return entries[i];
|
||||
}
|
||||
|
||||
return new Entry();
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
protected virtual void DrawBackground()
|
||||
{
|
||||
var background = Style.Background;
|
||||
DrawBackgroundDefault(Style.Background, Width, Height);
|
||||
}
|
||||
|
||||
internal static void DrawBackgroundDefault(Texture background, float width, float height)
|
||||
{
|
||||
if (background && background.ResidentMipLevels > 0)
|
||||
{
|
||||
var bSize = background.Size;
|
||||
@@ -77,8 +81,8 @@ namespace FlaxEditor.Surface
|
||||
if (pos.Y > 0)
|
||||
pos.Y -= bh;
|
||||
|
||||
int maxI = Mathf.CeilToInt(Width / bw + 1.0f);
|
||||
int maxJ = Mathf.CeilToInt(Height / bh + 1.0f);
|
||||
int maxI = Mathf.CeilToInt(width / bw + 1.0f);
|
||||
int maxJ = Mathf.CeilToInt(height / bh + 1.0f);
|
||||
|
||||
for (int i = 0; i < maxI; i++)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Core/Config/GameSettings.h"
|
||||
#include "Engine/Core/Config/BuildSettings.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/Assets/Texture.h"
|
||||
@@ -17,7 +18,18 @@
|
||||
#if PLATFORM_MAC
|
||||
#include "Engine/Platform/Apple/ApplePlatformSettings.h"
|
||||
#endif
|
||||
#include <fstream>
|
||||
|
||||
String EditorUtilities::GetOutputName()
|
||||
{
|
||||
const auto gameSettings = GameSettings::Get();
|
||||
const auto buildSettings = BuildSettings::Get();
|
||||
String outputName = buildSettings->OutputName;
|
||||
outputName.Replace(TEXT("${PROJECT_NAME}"), *gameSettings->ProductName, StringSearchCase::IgnoreCase);
|
||||
outputName.Replace(TEXT("${COMPANY_NAME}"), *gameSettings->CompanyName, StringSearchCase::IgnoreCase);
|
||||
if (outputName.IsEmpty())
|
||||
outputName = TEXT("FlaxGame");
|
||||
return outputName;
|
||||
}
|
||||
|
||||
bool EditorUtilities::FormatAppPackageName(String& packageName)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
SplashScreen,
|
||||
};
|
||||
|
||||
static String GetOutputName();
|
||||
static bool FormatAppPackageName(String& packageName);
|
||||
static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon);
|
||||
static bool GetTexture(const Guid& textureId, TextureData& textureData);
|
||||
|
||||
@@ -121,6 +121,37 @@ namespace FlaxEditor.Utilities
|
||||
["e"] = Math.E,
|
||||
["infinity"] = double.MaxValue,
|
||||
["-infinity"] = -double.MaxValue,
|
||||
["m"] = Units.Meters2Units,
|
||||
["cm"] = Units.Meters2Units / 100,
|
||||
["km"] = Units.Meters2Units * 1000,
|
||||
["s"] = 1,
|
||||
["ms"] = 0.001,
|
||||
["min"] = 60,
|
||||
["h"] = 3600,
|
||||
["cm²"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
|
||||
["cm³"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
|
||||
["dm²"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
|
||||
["dm³"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
|
||||
["l"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
|
||||
["m²"] = Units.Meters2Units * Units.Meters2Units,
|
||||
["m³"] = Units.Meters2Units * Units.Meters2Units * Units.Meters2Units,
|
||||
["kg"] = 1,
|
||||
["g"] = 0.001,
|
||||
["n"] = Units.Meters2Units
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// List known units which cannot be handled as a variable easily because they contain operator symbols (mostly a forward slash). The value is the factor to calculate game units.
|
||||
/// </summary>
|
||||
private static readonly IDictionary<string, double> UnitSymbols = new Dictionary<string, double>
|
||||
{
|
||||
["cm/s"] = Units.Meters2Units / 100,
|
||||
["cm/s²"] = Units.Meters2Units / 100,
|
||||
["m/s"] = Units.Meters2Units,
|
||||
["m/s²"] = Units.Meters2Units,
|
||||
["km/h"] = 1 / 3.6 * Units.Meters2Units,
|
||||
// Nm is here because these values are compared case-sensitive, and we don't want to confuse nanometers and Newtonmeters
|
||||
["Nm"] = Units.Meters2Units * Units.Meters2Units,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -156,7 +187,7 @@ namespace FlaxEditor.Utilities
|
||||
if (Operators.ContainsKey(str))
|
||||
return TokenType.Operator;
|
||||
|
||||
if (char.IsLetter(c))
|
||||
if (char.IsLetter(c) || c == '²' || c == '³')
|
||||
return TokenType.Variable;
|
||||
|
||||
throw new ParsingException("wrong character");
|
||||
@@ -170,7 +201,24 @@ namespace FlaxEditor.Utilities
|
||||
public static IEnumerable<Token> Tokenize(string text)
|
||||
{
|
||||
// Prepare text
|
||||
text = text.Replace(',', '.');
|
||||
text = text.Replace(',', '.').Replace("°", "");
|
||||
foreach (var kv in UnitSymbols)
|
||||
{
|
||||
int idx;
|
||||
do
|
||||
{
|
||||
idx = text.IndexOf(kv.Key, StringComparison.InvariantCulture);
|
||||
if (idx > 0)
|
||||
{
|
||||
if (DetermineType(text[idx - 1]) != TokenType.Number)
|
||||
throw new ParsingException($"unit found without a number: {kv.Key} at {idx} in {text}");
|
||||
if (Mathf.Abs(kv.Value - 1) < Mathf.Epsilon)
|
||||
text = text.Remove(idx, kv.Key.Length);
|
||||
else
|
||||
text = text.Replace(kv.Key, "*" + kv.Value);
|
||||
}
|
||||
} while (idx > 0);
|
||||
}
|
||||
|
||||
// Necessary to correctly parse negative numbers
|
||||
var previous = TokenType.WhiteSpace;
|
||||
@@ -240,6 +288,11 @@ namespace FlaxEditor.Utilities
|
||||
}
|
||||
else if (type == TokenType.Variable)
|
||||
{
|
||||
if (previous == TokenType.Number)
|
||||
{
|
||||
previous = TokenType.Operator;
|
||||
yield return new Token(TokenType.Operator, "*");
|
||||
}
|
||||
// Continue till the end of the variable
|
||||
while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable)
|
||||
{
|
||||
@@ -335,7 +388,7 @@ namespace FlaxEditor.Utilities
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParsingException("unknown variable");
|
||||
throw new ParsingException($"unknown variable : {token.Value}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -372,6 +425,15 @@ namespace FlaxEditor.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
// if stack has more than one item we're not finished with evaluating
|
||||
// we assume the remaining values are all factors to be multiplied
|
||||
if (stack.Count > 1)
|
||||
{
|
||||
var v1 = stack.Pop();
|
||||
while (stack.Count > 0)
|
||||
v1 *= stack.Pop();
|
||||
return v1;
|
||||
}
|
||||
return stack.Pop();
|
||||
}
|
||||
|
||||
|
||||
41
Source/Editor/Utilities/Units.cs
Normal file
41
Source/Editor/Utilities/Units.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEditor.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Units display utilities for Editor.
|
||||
/// </summary>
|
||||
public class Units
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor of units per meter.
|
||||
/// </summary>
|
||||
public static readonly float Meters2Units = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// False to always show game units without any postfix.
|
||||
/// </summary>
|
||||
public static bool UseUnitsFormatting = true;
|
||||
|
||||
/// <summary>
|
||||
/// Add a space between numbers and units for readability.
|
||||
/// </summary>
|
||||
public static bool SeparateValueAndUnit = true;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the distance unit is chosen on the magnitude, otherwise it's meters.
|
||||
/// </summary>
|
||||
public static bool AutomaticUnitsFormatting = true;
|
||||
|
||||
/// <summary>
|
||||
/// Return the unit according to user settings.
|
||||
/// </summary>
|
||||
/// <param name="unit">The unit name.</param>
|
||||
/// <returns>The formatted text.</returns>
|
||||
public static string Unit(string unit)
|
||||
{
|
||||
if (SeparateValueAndUnit)
|
||||
return $" {unit}";
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
@@ -243,6 +243,63 @@ namespace FlaxEditor.Utilities
|
||||
500000, 1000000, 5000000, 10000000, 100000000
|
||||
};
|
||||
|
||||
internal delegate void DrawCurveTick(float tick, float strength);
|
||||
|
||||
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
|
||||
{
|
||||
if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
|
||||
tickStrengths = new float[tickSteps.Length];
|
||||
|
||||
// Find the strength for each modulo number tick marker
|
||||
var pixelsInRange = pixelRange / (max - min);
|
||||
var smallestTick = 0;
|
||||
var biggestTick = tickSteps.Length - 1;
|
||||
for (int i = tickSteps.Length - 1; i >= 0; i--)
|
||||
{
|
||||
// Calculate how far apart these modulo tick steps are spaced
|
||||
float tickSpacing = tickSteps[i] * pixelsInRange;
|
||||
|
||||
// Calculate the strength of the tick markers based on the spacing
|
||||
tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
|
||||
|
||||
// Beyond threshold the ticks don't get any bigger or fatter
|
||||
if (tickStrengths[i] >= 1)
|
||||
biggestTick = i;
|
||||
|
||||
// Do not show small tick markers
|
||||
if (tickSpacing <= minDistanceBetweenTicks)
|
||||
{
|
||||
smallestTick = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var tickLevels = biggestTick - smallestTick + 1;
|
||||
|
||||
// Draw all tick levels
|
||||
for (int level = 0; level < tickLevels; level++)
|
||||
{
|
||||
float strength = tickStrengths[smallestTick + level];
|
||||
if (strength <= Mathf.Epsilon)
|
||||
continue;
|
||||
|
||||
// Draw all ticks
|
||||
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1);
|
||||
var lStep = tickSteps[l];
|
||||
var lNextStep = tickSteps[l + 1];
|
||||
int startTick = Mathf.FloorToInt(min / lStep);
|
||||
int endTick = Mathf.CeilToInt(max / lStep);
|
||||
for (int i = startTick; i <= endTick; i++)
|
||||
{
|
||||
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
|
||||
continue;
|
||||
var tick = i * lStep;
|
||||
drawTick(tick, strength);
|
||||
}
|
||||
}
|
||||
|
||||
return new Int2(smallestTick, biggestTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified path string contains any invalid character.
|
||||
/// </summary>
|
||||
@@ -1187,6 +1244,71 @@ namespace FlaxEditor.Utilities
|
||||
return StringUtils.GetPathWithoutExtension(path);
|
||||
}
|
||||
|
||||
private static string InternalFormat(double value, string format, FlaxEngine.Utils.ValueCategory category)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case FlaxEngine.Utils.ValueCategory.Distance:
|
||||
if (!Units.AutomaticUnitsFormatting)
|
||||
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
|
||||
var absValue = Mathf.Abs(value);
|
||||
// in case a unit != cm this would be (value / Meters2Units * 100)
|
||||
if (absValue < Units.Meters2Units)
|
||||
return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("cm");
|
||||
if (absValue < Units.Meters2Units * 1000)
|
||||
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
|
||||
return (value / 1000 / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("km");
|
||||
case FlaxEngine.Utils.ValueCategory.Angle: return value.ToString(format, CultureInfo.InvariantCulture) + "°";
|
||||
case FlaxEngine.Utils.ValueCategory.Time: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("s");
|
||||
// some fonts have a symbol for that: "\u33A7"
|
||||
case FlaxEngine.Utils.ValueCategory.Speed: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s");
|
||||
case FlaxEngine.Utils.ValueCategory.Acceleration: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s²");
|
||||
case FlaxEngine.Utils.ValueCategory.Area: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m²");
|
||||
case FlaxEngine.Utils.ValueCategory.Volume: return (value / Units.Meters2Units / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m³");
|
||||
case FlaxEngine.Utils.ValueCategory.Mass: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("kg");
|
||||
case FlaxEngine.Utils.ValueCategory.Force: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("N");
|
||||
case FlaxEngine.Utils.ValueCategory.Torque: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("Nm");
|
||||
case FlaxEngine.Utils.ValueCategory.None:
|
||||
default: return FormatFloat(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a float value either as-is, with a distance unit or with a degree sign.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to format.</param>
|
||||
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string FormatFloat(float value, FlaxEngine.Utils.ValueCategory category)
|
||||
{
|
||||
if (float.IsPositiveInfinity(value) || value == float.MaxValue)
|
||||
return "Infinity";
|
||||
if (float.IsNegativeInfinity(value) || value == float.MinValue)
|
||||
return "-Infinity";
|
||||
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
|
||||
return FormatFloat(value);
|
||||
const string format = "G7";
|
||||
return InternalFormat(value, format, category);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a double value either as-is, with a distance unit or with a degree sign
|
||||
/// </summary>
|
||||
/// <param name="value">The value to format.</param>
|
||||
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string FormatFloat(double value, FlaxEngine.Utils.ValueCategory category)
|
||||
{
|
||||
if (double.IsPositiveInfinity(value) || value == double.MaxValue)
|
||||
return "Infinity";
|
||||
if (double.IsNegativeInfinity(value) || value == double.MinValue)
|
||||
return "-Infinity";
|
||||
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
|
||||
return FormatFloat(value);
|
||||
const string format = "G15";
|
||||
return InternalFormat(value, format, category);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the floating point value (double precision) into the readable text representation.
|
||||
/// </summary>
|
||||
@@ -1198,7 +1320,7 @@ namespace FlaxEditor.Utilities
|
||||
return "Infinity";
|
||||
if (float.IsNegativeInfinity(value) || value == float.MinValue)
|
||||
return "-Infinity";
|
||||
string str = value.ToString("r", CultureInfo.InvariantCulture);
|
||||
string str = value.ToString("R", CultureInfo.InvariantCulture);
|
||||
return FormatFloat(str, value < 0);
|
||||
}
|
||||
|
||||
@@ -1213,7 +1335,7 @@ namespace FlaxEditor.Utilities
|
||||
return "Infinity";
|
||||
if (double.IsNegativeInfinity(value) || value == double.MinValue)
|
||||
return "-Infinity";
|
||||
string str = value.ToString("r", CultureInfo.InvariantCulture);
|
||||
string str = value.ToString("R", CultureInfo.InvariantCulture);
|
||||
return FormatFloat(str, value < 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
|
||||
|
||||
void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw)
|
||||
{
|
||||
if (!actor || !actor->IsActiveInHierarchy())
|
||||
return;
|
||||
auto& view = renderContext.View;
|
||||
const BoundingFrustum frustum = view.Frustum;
|
||||
Matrix m1, m2, world;
|
||||
@@ -208,8 +210,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
|
||||
draw.DrawState = &drawState;
|
||||
draw.Deformation = nullptr;
|
||||
|
||||
// Support custom icons through types, but not onces that were added through actors,
|
||||
// since they cant register while in prefab view anyway
|
||||
// Support custom icons through types, but not ones that were added through actors, since they cant register while in prefab view anyway
|
||||
if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture))
|
||||
{
|
||||
// Use custom texture
|
||||
|
||||
@@ -6,9 +6,7 @@ using Real = System.Double;
|
||||
using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
@@ -85,86 +83,8 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
_moveStartTime = Time.UnscaledGameTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize the actor.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to preview.</param>
|
||||
public void ShowActor(Actor actor)
|
||||
{
|
||||
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
|
||||
ShowSphere(ref sphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actors to show.</param>
|
||||
/// <param name="orientation">The used orientation.</param>
|
||||
public void ShowActor(Actor actor, ref Quaternion orientation)
|
||||
{
|
||||
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
|
||||
ShowSphere(ref sphere, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
public void ShowActors(List<SceneGraphNode> selection)
|
||||
{
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
BoundingSphere mergesSphere = BoundingSphere.Empty;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
selection[i].GetEditorSphere(out var sphere);
|
||||
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
|
||||
}
|
||||
|
||||
if (mergesSphere == BoundingSphere.Empty)
|
||||
return;
|
||||
ShowSphere(ref mergesSphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
/// <param name="orientation">The used orientation.</param>
|
||||
public void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
|
||||
{
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
BoundingSphere mergesSphere = BoundingSphere.Empty;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
selection[i].GetEditorSphere(out var sphere);
|
||||
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
|
||||
}
|
||||
|
||||
if (mergesSphere == BoundingSphere.Empty)
|
||||
return;
|
||||
ShowSphere(ref mergesSphere, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
public void ShowSphere(ref BoundingSphere sphere)
|
||||
{
|
||||
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
|
||||
ShowSphere(ref sphere, ref q);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
/// <param name="orientation">The camera orientation.</param>
|
||||
public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
|
||||
/// <inheritdoc />
|
||||
public override void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
|
||||
{
|
||||
Vector3 position;
|
||||
if (Viewport.UseOrthographicProjection)
|
||||
|
||||
@@ -6,6 +6,9 @@ using Real = System.Double;
|
||||
using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
@@ -33,6 +36,90 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
/// </summary>
|
||||
public virtual bool UseMovementSpeed => true;
|
||||
|
||||
/// <summary>
|
||||
/// Focuses the viewport on the current selection of the gizmo.
|
||||
/// </summary>
|
||||
/// <param name="gizmos">The gizmo collection (from viewport).</param>
|
||||
/// <param name="orientation">The target view orientation.</param>
|
||||
public virtual void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
|
||||
{
|
||||
var transformGizmo = gizmos.Get<TransformGizmo>();
|
||||
if (transformGizmo == null || transformGizmo.SelectedParents.Count == 0)
|
||||
return;
|
||||
if (gizmos.Active != null)
|
||||
{
|
||||
var gizmoBounds = gizmos.Active.FocusBounds;
|
||||
if (gizmoBounds != BoundingSphere.Empty)
|
||||
{
|
||||
ShowSphere(ref gizmoBounds, ref orientation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ShowActors(transformGizmo.SelectedParents, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize the actor.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to preview.</param>
|
||||
public virtual void ShowActor(Actor actor)
|
||||
{
|
||||
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
|
||||
ShowSphere(ref sphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
public void ShowActors(List<SceneGraphNode> selection)
|
||||
{
|
||||
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
|
||||
ShowActors(selection, ref q);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="selection">The actors to show.</param>
|
||||
/// <param name="orientation">The used orientation.</param>
|
||||
public virtual void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
|
||||
{
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
BoundingSphere mergesSphere = BoundingSphere.Empty;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
selection[i].GetEditorSphere(out var sphere);
|
||||
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
|
||||
}
|
||||
|
||||
if (mergesSphere == BoundingSphere.Empty)
|
||||
return;
|
||||
ShowSphere(ref mergesSphere, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
public void ShowSphere(ref BoundingSphere sphere)
|
||||
{
|
||||
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
|
||||
ShowSphere(ref sphere, ref q);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the camera to visualize given world area defined by the sphere.
|
||||
/// </summary>
|
||||
/// <param name="sphere">The sphere.</param>
|
||||
/// <param name="orientation">The camera orientation.</param>
|
||||
public virtual void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
|
||||
{
|
||||
SetArcBallView(orientation, sphere.Center, sphere.Radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view orientation and position to match the arc ball camera style view for the given target object bounds.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -41,6 +44,7 @@ namespace FlaxEditor.Viewport
|
||||
Gizmos[i].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EditorViewport Viewport => this;
|
||||
|
||||
@@ -66,19 +70,19 @@ namespace FlaxEditor.Viewport
|
||||
public bool IsControlDown => _input.IsControlDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root);
|
||||
public bool SnapToGround => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToGround.Process(Root);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
|
||||
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Float2 MouseDelta => _mouseDelta * 1000;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);
|
||||
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift);
|
||||
public bool UseDuplicate => Root?.GetKey(KeyboardKeys.Shift) ?? false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Undo Undo { get; }
|
||||
@@ -92,6 +96,9 @@ namespace FlaxEditor.Viewport
|
||||
/// <inheritdoc />
|
||||
public abstract void Spawn(Actor actor);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void OpenContextMenu();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
|
||||
|
||||
@@ -121,5 +128,284 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static void AddGizmoViewportWidgets(EditorViewport viewport, TransformGizmo transformGizmo, bool useProjectCache = false)
|
||||
{
|
||||
var editor = Editor.Instance;
|
||||
var inputOptions = editor.Options.Options.Input;
|
||||
|
||||
if (useProjectCache)
|
||||
{
|
||||
// Initialize snapping enabled from cached values
|
||||
if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState))
|
||||
transformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
|
||||
transformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
|
||||
transformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
|
||||
transformGizmo.TranslationSnapValue = float.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
|
||||
transformGizmo.RotationSnapValue = float.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
|
||||
transformGizmo.ScaleSnapValue = float.Parse(cachedState);
|
||||
if (editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
|
||||
transformGizmo.ActiveTransformSpace = space;
|
||||
}
|
||||
|
||||
// Transform space widget
|
||||
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
|
||||
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
|
||||
Parent = transformSpaceWidget
|
||||
};
|
||||
transformSpaceToggle.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.ToggleTransformSpace();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
|
||||
};
|
||||
transformSpaceWidget.Parent = viewport;
|
||||
|
||||
// Scale snapping widget
|
||||
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.ScaleSnapEnabled,
|
||||
TooltipText = "Enable scale snapping",
|
||||
Parent = scaleSnappingWidget
|
||||
};
|
||||
enableScaleSnapping.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.ScaleSnapEnabled = !transformGizmo.ScaleSnapEnabled;
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("ScaleSnapState", transformGizmo.ScaleSnapEnabled.ToString());
|
||||
};
|
||||
var scaleSnappingCM = new ContextMenu();
|
||||
var scaleSnapping = new ViewportWidgetButton(transformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
|
||||
{
|
||||
TooltipText = "Scale snapping values"
|
||||
};
|
||||
for (int i = 0; i < ScaleSnapValues.Length; i++)
|
||||
{
|
||||
var v = ScaleSnapValues[i];
|
||||
var button = scaleSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
scaleSnappingCM.ButtonClicked += button =>
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
transformGizmo.ScaleSnapValue = v;
|
||||
scaleSnapping.Text = v.ToString();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("ScaleSnapValue", transformGizmo.ScaleSnapValue.ToString("N"));
|
||||
};
|
||||
scaleSnappingCM.VisibleChanged += control =>
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(transformGizmo.ScaleSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
};
|
||||
scaleSnapping.Parent = scaleSnappingWidget;
|
||||
scaleSnappingWidget.Parent = viewport;
|
||||
|
||||
// Rotation snapping widget
|
||||
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.RotationSnapEnabled,
|
||||
TooltipText = "Enable rotation snapping",
|
||||
Parent = rotateSnappingWidget
|
||||
};
|
||||
enableRotateSnapping.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.RotationSnapEnabled = !transformGizmo.RotationSnapEnabled;
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("RotationSnapState", transformGizmo.RotationSnapEnabled.ToString());
|
||||
};
|
||||
var rotateSnappingCM = new ContextMenu();
|
||||
var rotateSnapping = new ViewportWidgetButton(transformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
|
||||
{
|
||||
TooltipText = "Rotation snapping values"
|
||||
};
|
||||
for (int i = 0; i < RotateSnapValues.Length; i++)
|
||||
{
|
||||
var v = RotateSnapValues[i];
|
||||
var button = rotateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
rotateSnappingCM.ButtonClicked += button =>
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
transformGizmo.RotationSnapValue = v;
|
||||
rotateSnapping.Text = v.ToString();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("RotationSnapValue", transformGizmo.RotationSnapValue.ToString("N"));
|
||||
};
|
||||
rotateSnappingCM.VisibleChanged += control =>
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(transformGizmo.RotationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
};
|
||||
rotateSnapping.Parent = rotateSnappingWidget;
|
||||
rotateSnappingWidget.Parent = viewport;
|
||||
|
||||
// Translation snapping widget
|
||||
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
|
||||
{
|
||||
Checked = transformGizmo.TranslationSnapEnable,
|
||||
TooltipText = "Enable position snapping",
|
||||
Parent = translateSnappingWidget
|
||||
};
|
||||
enableTranslateSnapping.Toggled += _ =>
|
||||
{
|
||||
transformGizmo.TranslationSnapEnable = !transformGizmo.TranslationSnapEnable;
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TranslateSnapState", transformGizmo.TranslationSnapEnable.ToString());
|
||||
};
|
||||
var translateSnappingCM = new ContextMenu();
|
||||
var translateSnapping = new ViewportWidgetButton(transformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
|
||||
{
|
||||
TooltipText = "Position snapping values"
|
||||
};
|
||||
if (transformGizmo.TranslationSnapValue < 0.0f)
|
||||
translateSnapping.Text = "Bounding Box";
|
||||
for (int i = 0; i < TranslateSnapValues.Length; i++)
|
||||
{
|
||||
var v = TranslateSnapValues[i];
|
||||
var button = translateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
|
||||
buttonBB.Tag = -1.0f;
|
||||
translateSnappingCM.ButtonClicked += button =>
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
transformGizmo.TranslationSnapValue = v;
|
||||
if (v < 0.0f)
|
||||
translateSnapping.Text = "Bounding Box";
|
||||
else
|
||||
translateSnapping.Text = v.ToString();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TranslateSnapValue", transformGizmo.TranslationSnapValue.ToString("N"));
|
||||
};
|
||||
translateSnappingCM.VisibleChanged += control =>
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
};
|
||||
translateSnapping.Parent = translateSnappingWidget;
|
||||
translateSnappingWidget.Parent = viewport;
|
||||
|
||||
// Gizmo mode widget
|
||||
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Translate,
|
||||
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
|
||||
Checked = true,
|
||||
Parent = gizmoMode
|
||||
};
|
||||
gizmoModeTranslate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate;
|
||||
var gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Rotate,
|
||||
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
gizmoModeRotate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate;
|
||||
var gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Scale,
|
||||
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
gizmoModeScale.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale;
|
||||
gizmoMode.Parent = viewport;
|
||||
transformGizmo.ModeChanged += () =>
|
||||
{
|
||||
var mode = transformGizmo.ActiveMode;
|
||||
gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
|
||||
gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
|
||||
gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
|
||||
};
|
||||
|
||||
// Setup input actions
|
||||
viewport.InputActions.Add(options => options.TranslateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
viewport.InputActions.Add(options => options.RotateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
viewport.InputActions.Add(options => options.ScaleMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
viewport.InputActions.Add(options => options.ToggleTransformSpace, () =>
|
||||
{
|
||||
transformGizmo.ToggleTransformSpace();
|
||||
if (useProjectCache)
|
||||
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
|
||||
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
|
||||
});
|
||||
}
|
||||
|
||||
internal static readonly float[] TranslateSnapValues =
|
||||
{
|
||||
0.1f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
100.0f,
|
||||
1000.0f,
|
||||
};
|
||||
|
||||
internal static readonly float[] RotateSnapValues =
|
||||
{
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
15.0f,
|
||||
30.0f,
|
||||
45.0f,
|
||||
60.0f,
|
||||
90.0f,
|
||||
};
|
||||
|
||||
internal static readonly float[] ScaleSnapValues =
|
||||
{
|
||||
0.05f,
|
||||
0.1f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
2.0f,
|
||||
4.0f,
|
||||
6.0f,
|
||||
8.0f,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Newtonsoft.Json;
|
||||
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
|
||||
|
||||
namespace FlaxEditor.Viewport
|
||||
@@ -154,6 +153,7 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
// Input
|
||||
|
||||
internal bool _disableInputUpdate;
|
||||
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
|
||||
private int _deltaFilteringStep;
|
||||
private Float2 _startPos;
|
||||
@@ -704,9 +704,9 @@ namespace FlaxEditor.Viewport
|
||||
// Camera Viewpoints
|
||||
{
|
||||
var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
|
||||
for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
|
||||
for (int i = 0; i < CameraViewpointValues.Length; i++)
|
||||
{
|
||||
var co = EditorViewportCameraViewpointValues[i];
|
||||
var co = CameraViewpointValues[i];
|
||||
var button = cameraView.AddButton(co.Name);
|
||||
button.Tag = co.Orientation;
|
||||
}
|
||||
@@ -899,9 +899,9 @@ namespace FlaxEditor.Viewport
|
||||
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
|
||||
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32;
|
||||
viewFlags.AddSeparator();
|
||||
for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++)
|
||||
for (int i = 0; i < ViewFlagsValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportViewFlagsValues[i];
|
||||
var v = ViewFlagsValues[i];
|
||||
var button = viewFlags.AddButton(v.Name);
|
||||
button.CloseMenuOnClick = false;
|
||||
button.Tag = v.Mode;
|
||||
@@ -933,9 +933,9 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
});
|
||||
debugView.AddSeparator();
|
||||
for (int i = 0; i < EditorViewportViewModeValues.Length; i++)
|
||||
for (int i = 0; i < ViewModeValues.Length; i++)
|
||||
{
|
||||
ref var v = ref EditorViewportViewModeValues[i];
|
||||
ref var v = ref ViewModeValues[i];
|
||||
if (v.Options != null)
|
||||
{
|
||||
var childMenu = debugView.AddChildMenu(v.Name).ContextMenu;
|
||||
@@ -989,12 +989,12 @@ namespace FlaxEditor.Viewport
|
||||
#endregion View mode widget
|
||||
}
|
||||
|
||||
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Front").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Front").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
|
||||
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
|
||||
InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown);
|
||||
InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1));
|
||||
InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1));
|
||||
@@ -1497,6 +1497,9 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (_disableInputUpdate)
|
||||
return;
|
||||
|
||||
// Update camera
|
||||
bool useMovementSpeed = false;
|
||||
if (_camera != null)
|
||||
@@ -1535,7 +1538,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height));
|
||||
_prevInput = _input;
|
||||
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl));
|
||||
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
|
||||
if (canUseInput && ContainsFocus && hit == null)
|
||||
_input.Gather(win.Window, useMouse);
|
||||
else
|
||||
@@ -1868,7 +1871,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CameraViewpoint[] EditorViewportCameraViewpointValues =
|
||||
private readonly CameraViewpoint[] CameraViewpointValues =
|
||||
{
|
||||
new CameraViewpoint("Front", new Float3(0, 180, 0)),
|
||||
new CameraViewpoint("Back", new Float3(0, 0, 0)),
|
||||
@@ -1899,7 +1902,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ViewModeOptions[] EditorViewportViewModeValues =
|
||||
private static readonly ViewModeOptions[] ViewModeValues =
|
||||
{
|
||||
new ViewModeOptions(ViewMode.Default, "Default"),
|
||||
new ViewModeOptions(ViewMode.Unlit, "Unlit"),
|
||||
@@ -1971,7 +1974,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ViewFlagOptions[] EditorViewportViewFlagsValues =
|
||||
private static readonly ViewFlagOptions[] ViewFlagsValues =
|
||||
{
|
||||
new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"),
|
||||
new ViewFlagOptions(ViewFlags.Shadows, "Shadows"),
|
||||
@@ -2006,16 +2009,13 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
if (cm.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)cm;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b && b.Tag != null)
|
||||
{
|
||||
var v = (ViewFlags)b.Tag;
|
||||
b.Icon = (Task.View.Flags & v) != 0
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
b.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2024,7 +2024,6 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
if (cm.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)cm;
|
||||
var layersMask = Task.ViewLayersMask;
|
||||
foreach (var e in ccm.Items)
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Modes;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -28,13 +25,6 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
private readonly ContextMenuButton _showGridButton;
|
||||
private readonly ContextMenuButton _showNavigationButton;
|
||||
private readonly ViewportWidgetButton _gizmoModeTranslate;
|
||||
private readonly ViewportWidgetButton _gizmoModeRotate;
|
||||
private readonly ViewportWidgetButton _gizmoModeScale;
|
||||
|
||||
private readonly ViewportWidgetButton _translateSnapping;
|
||||
private readonly ViewportWidgetButton _rotateSnapping;
|
||||
private readonly ViewportWidgetButton _scaleSnapping;
|
||||
|
||||
private SelectionOutline _customSelectionOutline;
|
||||
|
||||
@@ -196,7 +186,6 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
_editor = editor;
|
||||
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
|
||||
var inputOptions = editor.Options.Options.Input;
|
||||
|
||||
// Prepare rendering task
|
||||
Task.ActorsSource = ActorsSources.Scenes;
|
||||
@@ -222,8 +211,7 @@ namespace FlaxEditor.Viewport
|
||||
// Add transformation gizmo
|
||||
TransformGizmo = new TransformGizmo(this);
|
||||
TransformGizmo.ApplyTransformation += ApplyTransform;
|
||||
TransformGizmo.ModeChanged += OnGizmoModeChanged;
|
||||
TransformGizmo.Duplicate += Editor.Instance.SceneEditing.Duplicate;
|
||||
TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate;
|
||||
Gizmos.Active = TransformGizmo;
|
||||
|
||||
// Add grid
|
||||
@@ -232,144 +220,8 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
editor.SceneEditing.SelectionChanged += OnSelectionChanged;
|
||||
|
||||
// Initialize snapping enabled from cached values
|
||||
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState))
|
||||
TransformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
|
||||
TransformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
|
||||
TransformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
|
||||
TransformGizmo.TranslationSnapValue = float.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
|
||||
TransformGizmo.RotationSnapValue = float.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
|
||||
TransformGizmo.ScaleSnapValue = float.Parse(cachedState);
|
||||
if (_editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
|
||||
TransformGizmo.ActiveTransformSpace = space;
|
||||
|
||||
// Transform space widget
|
||||
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
|
||||
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
|
||||
Parent = transformSpaceWidget
|
||||
};
|
||||
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
|
||||
transformSpaceWidget.Parent = this;
|
||||
|
||||
// Scale snapping widget
|
||||
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ScaleSnapEnabled,
|
||||
TooltipText = "Enable scale snapping",
|
||||
Parent = scaleSnappingWidget
|
||||
};
|
||||
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
|
||||
|
||||
var scaleSnappingCM = new ContextMenu();
|
||||
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
|
||||
{
|
||||
TooltipText = "Scale snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportScaleSnapValues[i];
|
||||
var button = scaleSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
|
||||
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
|
||||
_scaleSnapping.Parent = scaleSnappingWidget;
|
||||
scaleSnappingWidget.Parent = this;
|
||||
|
||||
// Rotation snapping widget
|
||||
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.RotationSnapEnabled,
|
||||
TooltipText = "Enable rotation snapping",
|
||||
Parent = rotateSnappingWidget
|
||||
};
|
||||
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
|
||||
|
||||
var rotateSnappingCM = new ContextMenu();
|
||||
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
|
||||
{
|
||||
TooltipText = "Rotation snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportRotateSnapValues[i];
|
||||
var button = rotateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
|
||||
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
|
||||
_rotateSnapping.Parent = rotateSnappingWidget;
|
||||
rotateSnappingWidget.Parent = this;
|
||||
|
||||
// Translation snapping widget
|
||||
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.TranslationSnapEnable,
|
||||
TooltipText = "Enable position snapping",
|
||||
Parent = translateSnappingWidget
|
||||
};
|
||||
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
|
||||
|
||||
var translateSnappingCM = new ContextMenu();
|
||||
_translateSnapping = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
|
||||
{
|
||||
TooltipText = "Position snapping values"
|
||||
};
|
||||
if (TransformGizmo.TranslationSnapValue < 0.0f)
|
||||
_translateSnapping.Text = "Bounding Box";
|
||||
|
||||
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportTranslateSnapValues[i];
|
||||
var button = translateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
|
||||
buttonBB.Tag = -1.0f;
|
||||
|
||||
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
|
||||
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
|
||||
_translateSnapping.Parent = translateSnappingWidget;
|
||||
translateSnappingWidget.Parent = this;
|
||||
|
||||
// Gizmo mode widget
|
||||
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Translate,
|
||||
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
|
||||
Checked = true,
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Rotate,
|
||||
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Scale,
|
||||
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeScale.Toggled += OnGizmoModeToggle;
|
||||
gizmoMode.Parent = this;
|
||||
// Gizmo widgets
|
||||
AddGizmoViewportWidgets(this, TransformGizmo);
|
||||
|
||||
// Show grid widget
|
||||
_showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled);
|
||||
@@ -400,14 +252,6 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
|
||||
// Setup input actions
|
||||
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
InputActions.Add(options => options.ToggleTransformSpace, () =>
|
||||
{
|
||||
OnTransformSpaceToggle(transformSpaceToggle);
|
||||
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
|
||||
});
|
||||
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
|
||||
InputActions.Add(options => options.FocusSelection, FocusSelection);
|
||||
InputActions.Add(options => options.RotateSelection, RotateSelection);
|
||||
@@ -486,7 +330,7 @@ namespace FlaxEditor.Viewport
|
||||
};
|
||||
|
||||
// Spawn
|
||||
Editor.Instance.SceneEditing.Spawn(actor, parent);
|
||||
_editor.SceneEditing.Spawn(actor, parent);
|
||||
}
|
||||
|
||||
private void OnBegin(RenderTask task, GPUContext context)
|
||||
@@ -552,7 +396,7 @@ namespace FlaxEditor.Viewport
|
||||
var task = renderContext.Task;
|
||||
|
||||
// Render editor primitives, gizmo and debug shapes in debug view modes
|
||||
// Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers
|
||||
// Note: can use Output buffer as both input and output because EditorPrimitives is using an intermediate buffer
|
||||
if (EditorPrimitives && EditorPrimitives.CanRender())
|
||||
{
|
||||
EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output);
|
||||
@@ -581,161 +425,6 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGizmoModeToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
|
||||
}
|
||||
|
||||
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
|
||||
_editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString());
|
||||
}
|
||||
|
||||
private void OnRotateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
|
||||
_editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString());
|
||||
}
|
||||
|
||||
private void OnScaleSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
|
||||
_editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString());
|
||||
}
|
||||
|
||||
private void OnTransformSpaceToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ToggleTransformSpace();
|
||||
_editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString());
|
||||
}
|
||||
|
||||
private void OnGizmoModeChanged()
|
||||
{
|
||||
// Update all viewport widgets status
|
||||
var mode = TransformGizmo.ActiveMode;
|
||||
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
|
||||
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
|
||||
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportScaleSnapValues =
|
||||
{
|
||||
0.05f,
|
||||
0.1f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
2.0f,
|
||||
4.0f,
|
||||
6.0f,
|
||||
8.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetScaleSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.ScaleSnapValue = v;
|
||||
_scaleSnapping.Text = v.ToString();
|
||||
_editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N"));
|
||||
}
|
||||
|
||||
private void OnWidgetScaleSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportRotateSnapValues =
|
||||
{
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
15.0f,
|
||||
30.0f,
|
||||
45.0f,
|
||||
60.0f,
|
||||
90.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetRotateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.RotationSnapValue = v;
|
||||
_rotateSnapping.Text = v.ToString();
|
||||
_editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N"));
|
||||
}
|
||||
|
||||
private void OnWidgetRotateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportTranslateSnapValues =
|
||||
{
|
||||
0.1f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
100.0f,
|
||||
1000.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.TranslationSnapValue = v;
|
||||
if (v < 0.0f)
|
||||
_translateSnapping.Text = "Bounding Box";
|
||||
else
|
||||
_translateSnapping.Text = v.ToString();
|
||||
_editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N"));
|
||||
}
|
||||
|
||||
private void OnWidgetTranslateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
var selection = _editor.SceneEditing.Selection;
|
||||
@@ -761,7 +450,7 @@ namespace FlaxEditor.Viewport
|
||||
Vector3 gizmoPosition = TransformGizmo.Position;
|
||||
|
||||
// Rotate selected objects
|
||||
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode;
|
||||
bool isPlayMode = _editor.StateMachine.IsPlayMode;
|
||||
TransformGizmo.StartTransforming();
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
@@ -819,14 +508,7 @@ namespace FlaxEditor.Viewport
|
||||
/// <param name="orientation">The target view orientation.</param>
|
||||
public void FocusSelection(ref Quaternion orientation)
|
||||
{
|
||||
if (TransformGizmo.SelectedParents.Count == 0)
|
||||
return;
|
||||
|
||||
var gizmoBounds = Gizmos.Active.FocusBounds;
|
||||
if (gizmoBounds != BoundingSphere.Empty)
|
||||
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
|
||||
else
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
|
||||
ViewportCamera.FocusSelection(Gizmos, ref orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -843,7 +525,7 @@ namespace FlaxEditor.Viewport
|
||||
Vector3 gizmoPosition = TransformGizmo.Position;
|
||||
|
||||
// Transform selected objects
|
||||
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode;
|
||||
bool isPlayMode = _editor.StateMachine.IsPlayMode;
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
var obj = selection[i];
|
||||
@@ -985,7 +667,14 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
var parent = actor.Parent ?? Level.GetScene(0);
|
||||
actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
|
||||
Editor.Instance.SceneEditing.Spawn(actor);
|
||||
_editor.SceneEditing.Spawn(actor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OpenContextMenu()
|
||||
{
|
||||
var mouse = PointFromWindow(Root.MousePosition);
|
||||
_editor.Windows.SceneWin.ShowContextMenu(this, mouse);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -5,12 +5,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Previews;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -30,7 +28,6 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
public PrefabWindowViewport Viewport;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRender()
|
||||
{
|
||||
return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled;
|
||||
@@ -42,19 +39,34 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
[HideInEditor]
|
||||
private sealed class PrefabUIEditorRoot : UIEditorRoot
|
||||
{
|
||||
private readonly PrefabWindowViewport _viewport;
|
||||
private bool UI => _viewport._hasUILinkedCached;
|
||||
|
||||
public PrefabUIEditorRoot(PrefabWindowViewport viewport)
|
||||
: base(true)
|
||||
{
|
||||
_viewport = viewport;
|
||||
Parent = viewport;
|
||||
}
|
||||
|
||||
public override bool EnableInputs => !UI;
|
||||
public override bool EnableSelecting => UI;
|
||||
public override bool EnableBackground => UI;
|
||||
public override TransformGizmo TransformGizmo => _viewport.TransformGizmo;
|
||||
}
|
||||
|
||||
private readonly PrefabWindow _window;
|
||||
private UpdateDelegate _update;
|
||||
|
||||
private readonly ViewportWidgetButton _gizmoModeTranslate;
|
||||
private readonly ViewportWidgetButton _gizmoModeRotate;
|
||||
private readonly ViewportWidgetButton _gizmoModeScale;
|
||||
|
||||
private ViewportWidgetButton _translateSnappng;
|
||||
private ViewportWidgetButton _rotateSnapping;
|
||||
private ViewportWidgetButton _scaleSnapping;
|
||||
|
||||
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
|
||||
private PrefabSpritesRenderer _spritesRenderer;
|
||||
private IntPtr _tempDebugDrawContext;
|
||||
|
||||
private bool _hasUILinkedCached;
|
||||
private PrefabUIEditorRoot _uiRoot;
|
||||
|
||||
/// <summary>
|
||||
/// Drag and drop handlers
|
||||
@@ -107,144 +119,74 @@ namespace FlaxEditor.Viewport
|
||||
// Add transformation gizmo
|
||||
TransformGizmo = new TransformGizmo(this);
|
||||
TransformGizmo.ApplyTransformation += ApplyTransform;
|
||||
TransformGizmo.ModeChanged += OnGizmoModeChanged;
|
||||
TransformGizmo.Duplicate += _window.Duplicate;
|
||||
Gizmos.Active = TransformGizmo;
|
||||
|
||||
// Transform space widget
|
||||
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
|
||||
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
|
||||
Parent = transformSpaceWidget
|
||||
};
|
||||
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
|
||||
transformSpaceWidget.Parent = this;
|
||||
// Use custom root for UI controls
|
||||
_uiRoot = new PrefabUIEditorRoot(this);
|
||||
_uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport
|
||||
_uiParentLink = _uiRoot.UIRoot;
|
||||
|
||||
// Scale snapping widget
|
||||
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ScaleSnapEnabled,
|
||||
TooltipText = "Enable scale snapping",
|
||||
Parent = scaleSnappingWidget
|
||||
};
|
||||
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
|
||||
|
||||
var scaleSnappingCM = new ContextMenu();
|
||||
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
|
||||
{
|
||||
TooltipText = "Scale snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportScaleSnapValues[i];
|
||||
var button = scaleSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
|
||||
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
|
||||
_scaleSnapping.Parent = scaleSnappingWidget;
|
||||
scaleSnappingWidget.Parent = this;
|
||||
|
||||
// Rotation snapping widget
|
||||
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateSnap32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.RotationSnapEnabled,
|
||||
TooltipText = "Enable rotation snapping",
|
||||
Parent = rotateSnappingWidget
|
||||
};
|
||||
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
|
||||
|
||||
var rotateSnappingCM = new ContextMenu();
|
||||
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
|
||||
{
|
||||
TooltipText = "Rotation snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportRotateSnapValues[i];
|
||||
var button = rotateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
|
||||
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
|
||||
_rotateSnapping.Parent = rotateSnappingWidget;
|
||||
rotateSnappingWidget.Parent = this;
|
||||
|
||||
// Translation snapping widget
|
||||
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid32, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.TranslationSnapEnable,
|
||||
TooltipText = "Enable position snapping",
|
||||
Parent = translateSnappingWidget
|
||||
};
|
||||
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
|
||||
|
||||
var translateSnappingCM = new ContextMenu();
|
||||
_translateSnappng = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
|
||||
{
|
||||
TooltipText = "Position snapping values"
|
||||
};
|
||||
|
||||
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportTranslateSnapValues[i];
|
||||
var button = translateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
|
||||
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
|
||||
_translateSnappng.Parent = translateSnappingWidget;
|
||||
translateSnappingWidget.Parent = this;
|
||||
|
||||
// Gizmo mode widget
|
||||
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Translate,
|
||||
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
|
||||
Checked = true,
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Rotate,
|
||||
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true)
|
||||
{
|
||||
Tag = TransformGizmoBase.Mode.Scale,
|
||||
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeScale.Toggled += OnGizmoModeToggle;
|
||||
gizmoMode.Parent = this;
|
||||
EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo);
|
||||
|
||||
// Setup input actions
|
||||
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
InputActions.Add(options => options.ToggleTransformSpace, () =>
|
||||
{
|
||||
OnTransformSpaceToggle(transformSpaceToggle);
|
||||
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
|
||||
});
|
||||
InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
|
||||
|
||||
SetUpdate(ref _update, OnUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the viewport's gizmos, especially to toggle between 3D and UI editing modes.
|
||||
/// </summary>
|
||||
internal void UpdateGizmoMode()
|
||||
{
|
||||
// Skip if gizmo mode was unmodified
|
||||
if (_hasUILinked == _hasUILinkedCached)
|
||||
return;
|
||||
_hasUILinkedCached = _hasUILinked;
|
||||
|
||||
if (_hasUILinked)
|
||||
{
|
||||
// UI widget
|
||||
Gizmos.Active = null;
|
||||
ViewportCamera = new UIEditorCamera { UIEditor = _uiRoot };
|
||||
|
||||
// Hide 3D visuals
|
||||
ShowEditorPrimitives = false;
|
||||
ShowDefaultSceneActors = false;
|
||||
ShowDebugDraw = false;
|
||||
|
||||
// Show whole UI on startup
|
||||
var canvas = (CanvasRootControl)_uiParentLink.Children.FirstOrDefault(x => x is CanvasRootControl);
|
||||
if (canvas != null)
|
||||
ViewportCamera.ShowActor(canvas.Canvas);
|
||||
else if (Instance is UIControl)
|
||||
ViewportCamera.ShowActor(Instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic prefab
|
||||
Gizmos.Active = TransformGizmo;
|
||||
ViewportCamera = new FPSCamera();
|
||||
}
|
||||
|
||||
// Update default components usage
|
||||
bool defaultFeatures = !_hasUILinked;
|
||||
_disableInputUpdate = _hasUILinked;
|
||||
_spritesRenderer.Enabled = defaultFeatures;
|
||||
SelectionOutline.Enabled = defaultFeatures;
|
||||
_showDefaultSceneButton.Visible = defaultFeatures;
|
||||
_cameraWidget.Visible = defaultFeatures;
|
||||
_cameraButton.Visible = defaultFeatures;
|
||||
_orthographicModeButton.Visible = defaultFeatures;
|
||||
Task.Enabled = defaultFeatures;
|
||||
UseAutomaticTaskManagement = defaultFeatures;
|
||||
TintColor = defaultFeatures ? Color.White : Color.Transparent;
|
||||
}
|
||||
|
||||
private void OnUpdate(float deltaTime)
|
||||
{
|
||||
UpdateGizmoMode();
|
||||
for (int i = 0; i < Gizmos.Count; i++)
|
||||
{
|
||||
Gizmos[i].Update(deltaTime);
|
||||
@@ -259,11 +201,19 @@ namespace FlaxEditor.Viewport
|
||||
var selectedParents = TransformGizmo.SelectedParents;
|
||||
if (selectedParents.Count > 0)
|
||||
{
|
||||
// Use temporary Debug Draw context to pull any debug shapes drawing in Scene Graph Nodes - those are used in OnDebugDraw down below
|
||||
if (_tempDebugDrawContext == IntPtr.Zero)
|
||||
_tempDebugDrawContext = DebugDraw.AllocateContext();
|
||||
DebugDraw.SetContext(_tempDebugDrawContext);
|
||||
DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f);
|
||||
|
||||
for (int i = 0; i < selectedParents.Count; i++)
|
||||
{
|
||||
if (selectedParents[i].IsActiveInHierarchy)
|
||||
selectedParents[i].OnDebugDraw(_debugDrawData);
|
||||
}
|
||||
|
||||
DebugDraw.SetContext(IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +257,7 @@ namespace FlaxEditor.Viewport
|
||||
public void ShowSelectedActors()
|
||||
{
|
||||
var orient = ViewOrientation;
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
|
||||
ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -338,7 +288,7 @@ namespace FlaxEditor.Viewport
|
||||
public bool SnapToGround => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
|
||||
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Float2 MouseDelta => _mouseDelta * 1000;
|
||||
@@ -367,6 +317,13 @@ namespace FlaxEditor.Viewport
|
||||
_window.Spawn(actor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OpenContextMenu()
|
||||
{
|
||||
var mouse = PointFromWindow(Root.MousePosition);
|
||||
_window.ShowContextMenu(this, ref mouse);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
|
||||
|
||||
@@ -386,151 +343,6 @@ namespace FlaxEditor.Viewport
|
||||
root.UpdateCallbacksToRemove.Add(_update);
|
||||
}
|
||||
|
||||
private void OnGizmoModeToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
|
||||
}
|
||||
|
||||
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
|
||||
}
|
||||
|
||||
private void OnRotateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
|
||||
}
|
||||
|
||||
private void OnScaleSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
|
||||
}
|
||||
|
||||
private void OnTransformSpaceToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ToggleTransformSpace();
|
||||
}
|
||||
|
||||
private void OnGizmoModeChanged()
|
||||
{
|
||||
// Update all viewport widgets status
|
||||
var mode = TransformGizmo.ActiveMode;
|
||||
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
|
||||
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
|
||||
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportScaleSnapValues =
|
||||
{
|
||||
0.05f,
|
||||
0.1f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
2.0f,
|
||||
4.0f,
|
||||
6.0f,
|
||||
8.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetScaleSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.ScaleSnapValue = v;
|
||||
_scaleSnapping.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetScaleSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportRotateSnapValues =
|
||||
{
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
15.0f,
|
||||
30.0f,
|
||||
45.0f,
|
||||
60.0f,
|
||||
90.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetRotateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.RotationSnapValue = v;
|
||||
_rotateSnapping.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetRotateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportTranslateSnapValues =
|
||||
{
|
||||
0.1f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
100.0f,
|
||||
1000.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.TranslationSnapValue = v;
|
||||
_translateSnappng.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetTranslateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection));
|
||||
@@ -585,23 +397,6 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Selected UI controls outline
|
||||
for (var i = 0; i < _window.Selection.Count; i++)
|
||||
{
|
||||
if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
|
||||
{
|
||||
var control = controlActor.Control;
|
||||
var bounds = Rectangle.FromPoints(control.PointToParent(this, Float2.Zero), control.PointToParent(this, control.Size));
|
||||
Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnLeftMouseButtonUp()
|
||||
{
|
||||
@@ -744,14 +539,7 @@ namespace FlaxEditor.Viewport
|
||||
/// <param name="orientation">The target view orientation.</param>
|
||||
public void FocusSelection(ref Quaternion orientation)
|
||||
{
|
||||
if (TransformGizmo.SelectedParents.Count == 0)
|
||||
return;
|
||||
|
||||
var gizmoBounds = Gizmos.Active.FocusBounds;
|
||||
if (gizmoBounds != BoundingSphere.Empty)
|
||||
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
|
||||
else
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
|
||||
ViewportCamera.FocusSelection(Gizmos, ref orientation);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -776,6 +564,13 @@ namespace FlaxEditor.Viewport
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
if (_tempDebugDrawContext != IntPtr.Zero)
|
||||
{
|
||||
DebugDraw.FreeContext(_tempDebugDrawContext);
|
||||
_tempDebugDrawContext = IntPtr.Zero;
|
||||
}
|
||||
FlaxEngine.Object.Destroy(ref SelectionOutline);
|
||||
FlaxEngine.Object.Destroy(ref _spritesRenderer);
|
||||
|
||||
@@ -799,6 +594,15 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
base.OnDebugDraw(context, ref renderContext);
|
||||
|
||||
// Collect selected objects debug shapes again when DebugDraw is active with a custom context
|
||||
_debugDrawData.Clear();
|
||||
var selectedParents = TransformGizmo.SelectedParents;
|
||||
for (int i = 0; i < selectedParents.Count; i++)
|
||||
{
|
||||
if (selectedParents[i].IsActiveInHierarchy)
|
||||
selectedParents[i].OnDebugDraw(_debugDrawData);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
/// <seealso cref="FlaxEditor.Viewport.EditorViewport" />
|
||||
public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner
|
||||
{
|
||||
private ContextMenuButton _showDefaultSceneButton;
|
||||
internal ContextMenuButton _showDefaultSceneButton;
|
||||
private IntPtr _debugDrawContext;
|
||||
private bool _debugDrawEnable;
|
||||
private bool _editorPrimitivesEnable;
|
||||
@@ -239,6 +239,8 @@ namespace FlaxEditor.Viewport.Previews
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
Object.Destroy(ref PreviewLight);
|
||||
Object.Destroy(ref EnvProbe);
|
||||
Object.Destroy(ref Sky);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
@@ -13,19 +13,11 @@ namespace FlaxEditor.Viewport.Previews
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class PrefabPreview : AssetPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently spawned prefab instance owner. Used to link some actors such as UIControl to preview scene and view.
|
||||
/// </summary>
|
||||
internal static PrefabPreview LoadingPreview;
|
||||
|
||||
/// <summary>
|
||||
/// The list of active prefab previews. Used to link some actors such as UIControl to preview scene and view.
|
||||
/// </summary>
|
||||
internal static List<PrefabPreview> ActivePreviews;
|
||||
|
||||
private Prefab _prefab;
|
||||
private Actor _instance;
|
||||
internal UIControl customControlLinked;
|
||||
private UIControl _uiControlLinked;
|
||||
internal bool _hasUILinked;
|
||||
internal ContainerControl _uiParentLink;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the prefab asset to preview.
|
||||
@@ -54,13 +46,10 @@ namespace FlaxEditor.Viewport.Previews
|
||||
_prefab.WaitForLoaded();
|
||||
|
||||
// Spawn prefab
|
||||
LoadingPreview = this;
|
||||
var instance = PrefabManager.SpawnPrefab(_prefab, null);
|
||||
LoadingPreview = null;
|
||||
if (instance == null)
|
||||
{
|
||||
_prefab = null;
|
||||
ActivePreviews.Remove(this);
|
||||
throw new Exception("Failed to spawn a prefab for the preview.");
|
||||
}
|
||||
|
||||
@@ -84,11 +73,11 @@ namespace FlaxEditor.Viewport.Previews
|
||||
if (_instance)
|
||||
{
|
||||
// Unlink UI control
|
||||
if (customControlLinked)
|
||||
if (_uiControlLinked)
|
||||
{
|
||||
if (customControlLinked.Control?.Parent == this)
|
||||
customControlLinked.Control.Parent = null;
|
||||
customControlLinked = null;
|
||||
if (_uiControlLinked.Control?.Parent == _uiParentLink)
|
||||
_uiControlLinked.Control.Parent = null;
|
||||
_uiControlLinked = null;
|
||||
}
|
||||
|
||||
// Remove for the preview
|
||||
@@ -96,27 +85,51 @@ namespace FlaxEditor.Viewport.Previews
|
||||
}
|
||||
|
||||
_instance = value;
|
||||
_hasUILinked = false;
|
||||
|
||||
if (_instance)
|
||||
{
|
||||
// Add to the preview
|
||||
Task.AddCustomActor(_instance);
|
||||
|
||||
// Link UI canvases to the preview
|
||||
LinkCanvas(_instance);
|
||||
UpdateLinkage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLinkage()
|
||||
{
|
||||
// Clear flag
|
||||
_hasUILinked = false;
|
||||
|
||||
// Link UI canvases to the preview (eg. after canvas added to the prefab)
|
||||
LinkCanvas(_instance);
|
||||
|
||||
// Link UI control to the preview
|
||||
if (_uiControlLinked == null &&
|
||||
_instance is UIControl uiControl &&
|
||||
uiControl.Control != null &&
|
||||
uiControl.Control.Parent == null)
|
||||
{
|
||||
uiControl.Control.Parent = _uiParentLink;
|
||||
_uiControlLinked = uiControl;
|
||||
_hasUILinked = true;
|
||||
}
|
||||
else if (_uiControlLinked != null)
|
||||
_hasUILinked = true;
|
||||
}
|
||||
|
||||
private void LinkCanvas(Actor actor)
|
||||
{
|
||||
if (actor is UICanvas uiCanvas)
|
||||
uiCanvas.EditorOverride(Task, this);
|
||||
{
|
||||
uiCanvas.EditorOverride(Task, _uiParentLink);
|
||||
if (uiCanvas.GUI.Parent == _uiParentLink)
|
||||
_hasUILinked = true;
|
||||
}
|
||||
|
||||
var children = actor.ChildrenCount;
|
||||
for (int i = 0; i < children; i++)
|
||||
{
|
||||
LinkCanvas(actor.GetChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,9 +139,8 @@ namespace FlaxEditor.Viewport.Previews
|
||||
public PrefabPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
if (ActivePreviews == null)
|
||||
ActivePreviews = new List<PrefabPreview>();
|
||||
ActivePreviews.Add(this);
|
||||
// Link to itself by default
|
||||
_uiParentLink = this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -138,15 +150,13 @@ namespace FlaxEditor.Viewport.Previews
|
||||
|
||||
if (_instance != null)
|
||||
{
|
||||
// Link UI canvases to the preview (eg. after canvas added to the prefab)
|
||||
LinkCanvas(_instance);
|
||||
UpdateLinkage();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
ActivePreviews.Remove(this);
|
||||
Prefab = null;
|
||||
|
||||
base.OnDestroy();
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace FlaxEditor.Viewport
|
||||
var gridPlane = new Plane(Vector3.Zero, Vector3.Up);
|
||||
var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
|
||||
hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags);
|
||||
var girdGizmo = (GridGizmo)_owner.Gizmos.FirstOrDefault(x => x is GridGizmo);
|
||||
var girdGizmo = _owner.Gizmos.Get<GridGizmo>();
|
||||
if (hit != null)
|
||||
{
|
||||
// Use hit location
|
||||
@@ -180,7 +180,7 @@ namespace FlaxEditor.Viewport
|
||||
var location = hitLocation + new Vector3(0, bottomToCenter, 0);
|
||||
|
||||
// Apply grid snapping if enabled
|
||||
var transformGizmo = (TransformGizmo)_owner.Gizmos.FirstOrDefault(x => x is TransformGizmo);
|
||||
var transformGizmo = _owner.Gizmos.Get<TransformGizmo>();
|
||||
if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable))
|
||||
{
|
||||
float snapValue = transformGizmo.TranslationSnapValue;
|
||||
|
||||
@@ -186,6 +186,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
base.Initialize(layout);
|
||||
|
||||
// Ignore import settings GUI if the type is not animation. This removes the import UI if the animation asset was not created using an import.
|
||||
if (proxy.ImportSettings.Settings.Type != FlaxEngine.Tools.ModelTool.ModelType.Animation)
|
||||
return;
|
||||
|
||||
// Import Settings
|
||||
{
|
||||
var group = layout.Group("Import Settings");
|
||||
|
||||
@@ -76,37 +76,37 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
// Transparency
|
||||
|
||||
[EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")]
|
||||
[EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")]
|
||||
public MaterialTransparentLightingMode TransparentLightingMode;
|
||||
|
||||
[EditorOrder(205), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")]
|
||||
[EditorOrder(205), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")]
|
||||
public bool EnableReflections;
|
||||
|
||||
[VisibleIf(nameof(EnableReflections))]
|
||||
[EditorOrder(210), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")]
|
||||
[EditorOrder(210), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")]
|
||||
public bool EnableScreenSpaceReflections;
|
||||
|
||||
[EditorOrder(210), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")]
|
||||
[EditorOrder(210), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")]
|
||||
public bool EnableFog;
|
||||
|
||||
[EditorOrder(220), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")]
|
||||
[EditorOrder(220), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")]
|
||||
public bool EnableDistortion;
|
||||
|
||||
[EditorOrder(224), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")]
|
||||
[EditorOrder(224), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")]
|
||||
public bool EnableGlobalIllumination;
|
||||
|
||||
[EditorOrder(225), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")]
|
||||
[EditorOrder(225), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")]
|
||||
public bool PixelNormalOffsetRefraction;
|
||||
|
||||
[EditorOrder(230), DefaultValue(0.12f), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
|
||||
[EditorOrder(230), DefaultValue(0.12f), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
|
||||
public float OpacityThreshold;
|
||||
|
||||
// Tessellation
|
||||
|
||||
[EditorOrder(300), DefaultValue(TessellationMethod.None), EditorDisplay("Tessellation"), Tooltip("Mesh tessellation method.")]
|
||||
[EditorOrder(300), DefaultValue(TessellationMethod.None), VisibleIf(nameof(IsStandard)), EditorDisplay("Tessellation"), Tooltip("Mesh tessellation method.")]
|
||||
public TessellationMethod TessellationMode;
|
||||
|
||||
[EditorOrder(310), DefaultValue(15), EditorDisplay("Tessellation"), Tooltip("Maximum triangle tessellation factor."), Limit(1, 60, 0.01f)]
|
||||
[EditorOrder(310), DefaultValue(15), VisibleIf(nameof(IsStandard)), EditorDisplay("Tessellation"), Tooltip("Maximum triangle tessellation factor."), Limit(1, 60, 0.01f)]
|
||||
public int MaxTessellationFactor;
|
||||
|
||||
// Misc
|
||||
@@ -120,10 +120,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
[EditorOrder(420), DefaultValue(0.3f), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
|
||||
public float MaskThreshold;
|
||||
|
||||
[EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")]
|
||||
[EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), VisibleIf(nameof(IsDecal)), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")]
|
||||
public MaterialDecalBlendingMode DecalBlendingMode;
|
||||
|
||||
[EditorOrder(440), DefaultValue(MaterialPostFxLocation.AfterPostProcessingPass), EditorDisplay("Misc"), Tooltip("The post fx material rendering location.")]
|
||||
[EditorOrder(440), DefaultValue(MaterialPostFxLocation.AfterPostProcessingPass), VisibleIf(nameof(IsPostProcess)), EditorDisplay("Misc"), Tooltip("The post fx material rendering location.")]
|
||||
public MaterialPostFxLocation PostFxLocation;
|
||||
|
||||
// Parameters
|
||||
@@ -140,6 +140,12 @@ namespace FlaxEditor.Windows.Assets
|
||||
set => throw new Exception("No setter.");
|
||||
}
|
||||
|
||||
// Visibility conditionals
|
||||
|
||||
private bool IsPostProcess => Domain == MaterialDomain.PostProcess;
|
||||
private bool IsDecal => Domain == MaterialDomain.Decal;
|
||||
private bool IsStandard => Domain == MaterialDomain.Surface || Domain == MaterialDomain.Terrain || Domain == MaterialDomain.Particle || Domain == MaterialDomain.Deformable;
|
||||
|
||||
/// <summary>
|
||||
/// Gathers parameters from the specified material.
|
||||
/// </summary>
|
||||
|
||||
@@ -360,10 +360,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent control.</param>
|
||||
/// <param name="location">The location (within a given control).</param>
|
||||
private void ShowContextMenu(Control parent, ref Float2 location)
|
||||
internal void ShowContextMenu(Control parent, ref Float2 location)
|
||||
{
|
||||
var contextMenu = CreateContextMenu();
|
||||
|
||||
contextMenu.Show(parent, location);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.GUI;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Windows.Assets
|
||||
@@ -64,8 +62,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
private void OnSelectionUndo(SceneGraphNode[] toSelect)
|
||||
{
|
||||
Selection.Clear();
|
||||
Selection.AddRange(toSelect);
|
||||
|
||||
foreach (var e in toSelect)
|
||||
{
|
||||
if (e != null)
|
||||
Selection.Add(e);
|
||||
}
|
||||
OnSelectionChanges();
|
||||
}
|
||||
|
||||
@@ -118,11 +119,13 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// <param name="nodes">The nodes.</param>
|
||||
public void Select(List<SceneGraphNode> nodes)
|
||||
{
|
||||
nodes?.RemoveAll(x => x == null);
|
||||
if (nodes == null || nodes.Count == 0)
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.ArraysEqual(Selection, nodes))
|
||||
return;
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(0, 0, 0, 18 + 6),
|
||||
};
|
||||
_searchBox = new SearchBox()
|
||||
_searchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
|
||||
Parent = headerPanel,
|
||||
@@ -140,7 +140,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
};
|
||||
_searchBox.TextChanged += OnSearchBoxTextChanged;
|
||||
|
||||
_treePanel = new Panel()
|
||||
// Prefab structure tree
|
||||
_treePanel = new Panel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f),
|
||||
@@ -148,8 +149,6 @@ namespace FlaxEditor.Windows.Assets
|
||||
IsScrollable = true,
|
||||
Parent = sceneTreePanel,
|
||||
};
|
||||
|
||||
// Prefab structure tree
|
||||
Graph = new LocalSceneGraph(new CustomRootNode(this));
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
_tree = new PrefabTree
|
||||
@@ -316,11 +315,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
return;
|
||||
|
||||
// Restore
|
||||
_viewport.Prefab = _asset;
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
OnPrefabOpened();
|
||||
_undo.Clear();
|
||||
ClearEditedFlag();
|
||||
}
|
||||
@@ -346,6 +341,16 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrefabOpened()
|
||||
{
|
||||
_viewport.Prefab = _asset;
|
||||
_viewport.UpdateGizmoMode();
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Save()
|
||||
{
|
||||
@@ -355,6 +360,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
try
|
||||
{
|
||||
Editor.Scene.OnSaveStart(_viewport._uiParentLink);
|
||||
|
||||
// Simply update changes
|
||||
Editor.Prefabs.ApplyAll(_viewport.Instance);
|
||||
|
||||
@@ -371,6 +378,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Editor.Scene.OnSaveEnd(_viewport._uiParentLink);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -411,13 +422,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
return;
|
||||
}
|
||||
|
||||
_viewport.Prefab = _asset;
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
OnPrefabOpened();
|
||||
_focusCamera = true;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.Expand(true);
|
||||
|
||||
_undo.Clear();
|
||||
ClearEditedFlag();
|
||||
|
||||
@@ -462,11 +468,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
_viewport.Prefab = null;
|
||||
if (_asset.IsLoaded)
|
||||
{
|
||||
_viewport.Prefab = _asset;
|
||||
Graph.MainActor = _viewport.Instance;
|
||||
Selection.Clear();
|
||||
Select(Graph.Main);
|
||||
Graph.Root.TreeNode.ExpandAll(true);
|
||||
OnPrefabOpened();
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -478,7 +480,6 @@ namespace FlaxEditor.Windows.Assets
|
||||
if (_focusCamera && _viewport.Task.FrameCount > 1)
|
||||
{
|
||||
_focusCamera = false;
|
||||
|
||||
Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds);
|
||||
_viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f);
|
||||
}
|
||||
|
||||
@@ -281,6 +281,9 @@ namespace FlaxEditor.Windows
|
||||
_view.OnDelete += Delete;
|
||||
_view.OnDuplicate += Duplicate;
|
||||
_view.OnPaste += Paste;
|
||||
|
||||
_view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
|
||||
InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
|
||||
}
|
||||
|
||||
private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace FlaxEditor.Windows
|
||||
private Tabs _tabs;
|
||||
private EditorOptions _options;
|
||||
private ToolStripButton _saveButton;
|
||||
private readonly Undo _undo;
|
||||
private readonly List<Tab> _customTabs = new List<Tab>();
|
||||
|
||||
/// <summary>
|
||||
@@ -33,6 +34,12 @@ namespace FlaxEditor.Windows
|
||||
: base(editor, true, ScrollBars.None)
|
||||
{
|
||||
Title = "Editor Options";
|
||||
|
||||
// Undo
|
||||
_undo = new Undo();
|
||||
_undo.UndoDone += OnUndoRedo;
|
||||
_undo.RedoDone += OnUndoRedo;
|
||||
_undo.ActionDone += OnUndoRedo;
|
||||
|
||||
var toolstrip = new ToolStrip
|
||||
{
|
||||
@@ -58,9 +65,19 @@ namespace FlaxEditor.Windows
|
||||
CreateTab("Visual", () => _options.Visual);
|
||||
CreateTab("Source Code", () => _options.SourceCode);
|
||||
CreateTab("Theme", () => _options.Theme);
|
||||
|
||||
// Setup input actions
|
||||
InputActions.Add(options => options.Undo, _undo.PerformUndo);
|
||||
InputActions.Add(options => options.Redo, _undo.PerformRedo);
|
||||
InputActions.Add(options => options.Save, SaveData);
|
||||
|
||||
_tabs.SelectedTabIndex = 0;
|
||||
}
|
||||
|
||||
private void OnUndoRedo(IUndoAction action)
|
||||
{
|
||||
MarkAsEdited();
|
||||
}
|
||||
|
||||
private Tab CreateTab(string name, Func<object> getValue)
|
||||
{
|
||||
@@ -73,7 +90,7 @@ namespace FlaxEditor.Windows
|
||||
Parent = tab
|
||||
};
|
||||
|
||||
var settings = new CustomEditorPresenter(null);
|
||||
var settings = new CustomEditorPresenter(_undo);
|
||||
settings.Panel.Parent = panel;
|
||||
settings.Panel.Tag = getValue;
|
||||
settings.Modified += MarkAsEdited;
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace FlaxEditor.Windows
|
||||
protected EditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars)
|
||||
: base(editor.UI.MasterPanel, hideOnClose, scrollBars)
|
||||
{
|
||||
AutoFocus = true;
|
||||
Editor = editor;
|
||||
|
||||
InputActions.Add(options => options.ContentFinder, () =>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.Options;
|
||||
@@ -194,133 +195,14 @@ namespace FlaxEditor.Windows
|
||||
public bool Active;
|
||||
}
|
||||
|
||||
private class GameRoot : ContainerControl
|
||||
/// <summary>
|
||||
/// Root control for game UI preview in Editor. Supports basic UI editing via <see cref="UIEditorRoot"/>.
|
||||
/// </summary>
|
||||
private class GameRoot : UIEditorRoot
|
||||
{
|
||||
public bool EnableEvents => !Time.GamePaused;
|
||||
|
||||
public override bool RayCast(ref Float2 location, out Control hit)
|
||||
{
|
||||
return RayCastChildren(ref location, out hit);
|
||||
}
|
||||
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise = false)
|
||||
{
|
||||
if (precise)
|
||||
return false;
|
||||
return base.ContainsPoint(ref location, precise);
|
||||
}
|
||||
|
||||
public override bool OnCharInput(char c)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnCharInput(c);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return DragDropEffect.None;
|
||||
|
||||
return base.OnDragDrop(ref location, data);
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return DragDropEffect.None;
|
||||
|
||||
return base.OnDragEnter(ref location, data);
|
||||
}
|
||||
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return DragDropEffect.None;
|
||||
|
||||
return base.OnDragMove(ref location, data);
|
||||
}
|
||||
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
public override void OnKeyUp(KeyboardKeys key)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnKeyUp(key);
|
||||
}
|
||||
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return;
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
if (!EnableEvents)
|
||||
return false;
|
||||
|
||||
return base.OnMouseWheel(location, delta);
|
||||
}
|
||||
public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode;
|
||||
public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused;
|
||||
public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -348,13 +230,9 @@ namespace FlaxEditor.Windows
|
||||
// Override the game GUI root
|
||||
_guiRoot = new GameRoot
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
//Visible = false,
|
||||
AutoFocus = false,
|
||||
Parent = _viewport
|
||||
};
|
||||
RootControl.GameRoot = _guiRoot;
|
||||
RootControl.GameRoot = _guiRoot.UIRoot;
|
||||
|
||||
SizeChanged += control => { ResizeViewport(); };
|
||||
|
||||
@@ -382,6 +260,56 @@ namespace FlaxEditor.Windows
|
||||
Editor.Instance.Windows.ProfilerWin.Clear();
|
||||
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
|
||||
});
|
||||
InputActions.Add(options => options.Save, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SaveAll();
|
||||
});
|
||||
InputActions.Add(options => options.Undo, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.PerformUndo();
|
||||
Focus();
|
||||
});
|
||||
InputActions.Add(options => options.Redo, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.PerformRedo();
|
||||
Focus();
|
||||
});
|
||||
InputActions.Add(options => options.Cut, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Cut();
|
||||
});
|
||||
InputActions.Add(options => options.Copy, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Copy();
|
||||
});
|
||||
InputActions.Add(options => options.Paste, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Paste();
|
||||
});
|
||||
InputActions.Add(options => options.Duplicate, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Duplicate();
|
||||
});
|
||||
InputActions.Add(options => options.Delete, () =>
|
||||
{
|
||||
if (Editor.IsPlayMode)
|
||||
return;
|
||||
Editor.Instance.SceneEditing.Delete();
|
||||
});
|
||||
}
|
||||
|
||||
private void ChangeViewportRatio(ViewportScaleOptions v)
|
||||
@@ -916,35 +844,6 @@ namespace FlaxEditor.Windows
|
||||
Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
|
||||
// Selected UI controls outline
|
||||
bool drawAnySelectedControl = false;
|
||||
// TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
|
||||
var selection = Editor.SceneEditing.Selection;
|
||||
for (var i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
|
||||
{
|
||||
if (!drawAnySelectedControl)
|
||||
{
|
||||
drawAnySelectedControl = true;
|
||||
Render2D.PushTransform(ref _viewport._cachedTransform);
|
||||
}
|
||||
var options = Editor.Options.Options.Visual;
|
||||
var control = controlActor.Control;
|
||||
var bounds = control.EditorBounds;
|
||||
var p1 = control.PointToParent(_viewport, bounds.UpperLeft);
|
||||
var p2 = control.PointToParent(_viewport, bounds.UpperRight);
|
||||
var p3 = control.PointToParent(_viewport, bounds.BottomLeft);
|
||||
var p4 = control.PointToParent(_viewport, bounds.BottomRight);
|
||||
var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4));
|
||||
var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
|
||||
}
|
||||
}
|
||||
if (drawAnySelectedControl)
|
||||
Render2D.PopTransform();
|
||||
|
||||
// Play mode hints and overlay
|
||||
if (Editor.StateMachine.IsPlayMode)
|
||||
{
|
||||
|
||||
@@ -65,6 +65,11 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
public OutputLogWindow Window;
|
||||
|
||||
/// <summary>
|
||||
/// The input actions collection to processed during user input.
|
||||
/// </summary>
|
||||
public InputActionsContainer InputActions = new InputActionsContainer();
|
||||
|
||||
/// <summary>
|
||||
/// The default text style.
|
||||
/// </summary>
|
||||
@@ -80,6 +85,14 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
public TextBlockStyle ErrorStyle;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (InputActions.Process(Editor.Instance, this, key))
|
||||
return true;
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnParseTextBlocks()
|
||||
{
|
||||
@@ -201,6 +214,9 @@ namespace FlaxEditor.Windows
|
||||
// Setup editor options
|
||||
Editor.Options.OptionsChanged += OnEditorOptionsChanged;
|
||||
OnEditorOptionsChanged(Editor.Options.Options);
|
||||
|
||||
_output.InputActions.Add(options => options.Search, () => _searchBox.Focus());
|
||||
InputActions.Add(options => options.Search, () => _searchBox.Focus());
|
||||
|
||||
GameCooker.Event += OnGameCookerEvent;
|
||||
ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.SceneGraph;
|
||||
@@ -258,10 +257,9 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent control.</param>
|
||||
/// <param name="location">The location (within a given control).</param>
|
||||
private void ShowContextMenu(Control parent, Float2 location)
|
||||
internal void ShowContextMenu(Control parent, Float2 location)
|
||||
{
|
||||
var contextMenu = CreateContextMenu();
|
||||
|
||||
contextMenu.Show(parent, location);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace FlaxEditor.Windows
|
||||
InputActions.Add(options => options.RotateMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
InputActions.Add(options => options.ScaleMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
InputActions.Add(options => options.FocusSelection, () => Editor.Windows.EditWin.Viewport.FocusSelection());
|
||||
InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection());
|
||||
InputActions.Add(options => options.Rename, Rename);
|
||||
}
|
||||
|
||||
@@ -221,7 +222,6 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
if (!Editor.StateMachine.CurrentState.CanEditScene)
|
||||
return;
|
||||
|
||||
ShowContextMenu(node, location);
|
||||
}
|
||||
|
||||
|
||||
@@ -474,8 +474,8 @@ BehaviorUpdateResult BehaviorTreeMoveToNode::Update(const BehaviorUpdateContext&
|
||||
state->NavAgentRadius = navMesh->Properties.Agent.Radius;
|
||||
|
||||
// Place start and end on navmesh
|
||||
navMesh->ProjectPoint(state->Path.First(), state->Path.First());
|
||||
navMesh->ProjectPoint(state->Path.Last(), state->Path.Last());
|
||||
navMesh->FindClosestPoint(state->Path.First(), state->Path.First());
|
||||
navMesh->FindClosestPoint(state->Path.Last(), state->Path.Last());
|
||||
|
||||
// Calculate offset between path and the agent (aka feet offset)
|
||||
state->AgentOffset = state->Path.First() - agentLocation;
|
||||
|
||||
@@ -281,10 +281,26 @@ Asset::LoadResult SceneAnimation::load()
|
||||
track.TrackStateIndex = TrackStatesCount++;
|
||||
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
|
||||
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
|
||||
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
|
||||
int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
|
||||
if (trackData->ValueSize == 0)
|
||||
{
|
||||
// When using json data (from non-POD types) read the sum of all keyframes data
|
||||
const int32 keyframesDataStart = stream.GetPosition();
|
||||
for (int32 j = 0; j < trackData->KeyframesCount; j++)
|
||||
{
|
||||
stream.Move<float>(); // Time
|
||||
int32 jsonLen;
|
||||
stream.ReadInt32(&jsonLen);
|
||||
stream.Move(jsonLen);
|
||||
}
|
||||
const int32 keyframesDataEnd = stream.GetPosition();
|
||||
stream.SetPosition(keyframesDataStart);
|
||||
keyframesDataSize = keyframesDataEnd - keyframesDataStart;
|
||||
}
|
||||
trackRuntime->ValueSize = trackData->ValueSize;
|
||||
trackRuntime->KeyframesCount = trackData->KeyframesCount;
|
||||
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
|
||||
trackRuntime->KeyframesSize = keyframesDataSize;
|
||||
needsParent = true;
|
||||
break;
|
||||
}
|
||||
@@ -298,6 +314,7 @@ Asset::LoadResult SceneAnimation::load()
|
||||
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
|
||||
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
|
||||
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3);
|
||||
ASSERT(trackData->ValueSize > 0);
|
||||
trackRuntime->ValueSize = trackData->ValueSize;
|
||||
trackRuntime->KeyframesCount = trackData->KeyframesCount;
|
||||
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
|
||||
@@ -375,6 +392,7 @@ Asset::LoadResult SceneAnimation::load()
|
||||
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
|
||||
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
|
||||
trackRuntime->ValueSize = trackData->ValueSize;
|
||||
ASSERT(trackData->ValueSize > 0);
|
||||
trackRuntime->KeyframesCount = trackData->KeyframesCount;
|
||||
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime));
|
||||
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackData->KeyframesCount);
|
||||
|
||||
@@ -289,6 +289,7 @@ public:
|
||||
/// The keyframes array (items count is KeyframesCount). Each keyframe is represented by pair of time (of type float) and the value data (of size ValueSize).
|
||||
/// </summary>
|
||||
void* Keyframes;
|
||||
int32 KeyframesSize;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Engine/Level/SceneObjectsFactory.h"
|
||||
#include "Engine/Level/Actors/Camera.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Audio/AudioClip.h"
|
||||
#include "Engine/Audio/AudioSource.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MField.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MMethod.h"
|
||||
#include "Engine/Scripting/Internal/ManagedSerialization.h"
|
||||
|
||||
// This could be Update, LateUpdate or FixedUpdate
|
||||
#define UPDATE_POINT Update
|
||||
@@ -370,47 +372,96 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
case SceneAnimation::Track::Types::KeyframesProperty:
|
||||
case SceneAnimation::Track::Types::ObjectReferenceProperty:
|
||||
{
|
||||
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
|
||||
const int32 count = trackDataKeyframes->KeyframesCount;
|
||||
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
|
||||
const int32 count = trackRuntime->KeyframesCount;
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
// Find the keyframe at time
|
||||
int32 keyframeSize = sizeof(float) + trackDataKeyframes->ValueSize;
|
||||
#define GET_KEY_TIME(idx) *(float*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (idx))
|
||||
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
|
||||
int32 start = 0;
|
||||
int32 searchLength = count;
|
||||
while (searchLength > 0)
|
||||
// If size is 0 then track uses Json storage for keyframes data (variable memory length of keyframes), otherwise it's optimized simple data with O(1) access
|
||||
if (trackRuntime->ValueSize != 0)
|
||||
{
|
||||
const int32 half = searchLength >> 1;
|
||||
int32 mid = start + half;
|
||||
if (keyTime < GET_KEY_TIME(mid))
|
||||
// Find the keyframe at time (binary search)
|
||||
int32 keyframeSize = sizeof(float) + trackRuntime->ValueSize;
|
||||
#define GET_KEY_TIME(idx) *(float*)((byte*)trackRuntime->Keyframes + keyframeSize * (idx))
|
||||
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
|
||||
int32 start = 0;
|
||||
int32 searchLength = count;
|
||||
while (searchLength > 0)
|
||||
{
|
||||
searchLength = half;
|
||||
const int32 half = searchLength >> 1;
|
||||
int32 mid = start + half;
|
||||
if (keyTime < GET_KEY_TIME(mid))
|
||||
{
|
||||
searchLength = half;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = mid + 1;
|
||||
searchLength -= half + 1;
|
||||
}
|
||||
}
|
||||
int32 leftKey = Math::Max(0, start - 1);
|
||||
#undef GET_KEY_TIME
|
||||
|
||||
// Return the value
|
||||
void* value = (void*)((byte*)trackRuntime->Keyframes + keyframeSize * (leftKey) + sizeof(float));
|
||||
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
|
||||
{
|
||||
// Object ref track uses Guid for object Id storage
|
||||
Guid id = *(Guid*)value;
|
||||
_objectsMapping.TryGet(id, id);
|
||||
auto obj = Scripting::FindObject<ScriptingObject>(id);
|
||||
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
|
||||
*(void**)target = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = mid + 1;
|
||||
searchLength -= half + 1;
|
||||
// POD memory
|
||||
Platform::MemoryCopy(target, value, trackRuntime->ValueSize);
|
||||
}
|
||||
}
|
||||
int32 leftKey = Math::Max(0, start - 1);
|
||||
#undef GET_KEY_TIME
|
||||
|
||||
// Return the value
|
||||
void* value = (void*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (leftKey) + sizeof(float));
|
||||
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
|
||||
{
|
||||
Guid id = *(Guid*)value;
|
||||
_objectsMapping.TryGet(id, id);
|
||||
auto obj = Scripting::FindObject<ScriptingObject>(id);
|
||||
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
|
||||
*(void**)target = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(target, value, trackDataKeyframes->ValueSize);
|
||||
// Clear pointer
|
||||
*(void**)target = nullptr;
|
||||
|
||||
// Find the keyframe at time (linear search)
|
||||
MemoryReadStream stream((byte*)trackRuntime->Keyframes, trackRuntime->KeyframesSize);
|
||||
int32 prevKeyPos = sizeof(float);
|
||||
int32 jsonLen;
|
||||
for (int32 key = 0; key < count; key++)
|
||||
{
|
||||
float keyTime;
|
||||
stream.ReadFloat(&keyTime);
|
||||
if (keyTime > time)
|
||||
break;
|
||||
prevKeyPos = stream.GetPosition();
|
||||
stream.ReadInt32(&jsonLen);
|
||||
stream.Move(jsonLen);
|
||||
}
|
||||
|
||||
// Read json text
|
||||
stream.SetPosition(prevKeyPos);
|
||||
stream.ReadInt32(&jsonLen);
|
||||
const StringAnsiView json((const char*)stream.GetPositionHandle(), jsonLen);
|
||||
|
||||
// Create empty value of the keyframe type
|
||||
const auto trackData = track.GetData<SceneAnimation::KeyframesPropertyTrack::Data>();
|
||||
const StringAnsiView propertyTypeName(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength);
|
||||
MClass* klass = Scripting::FindClass(propertyTypeName);
|
||||
if (!klass)
|
||||
return false;
|
||||
MObject* obj = MCore::Object::New(klass);
|
||||
if (!obj)
|
||||
return false;
|
||||
if (!klass->IsValueType())
|
||||
MCore::Object::Init(obj);
|
||||
|
||||
// Deserialize value from json
|
||||
ManagedSerialization::Deserialize(json, obj);
|
||||
|
||||
// Set value
|
||||
*(void**)target = obj;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -479,13 +530,13 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
}
|
||||
case SceneAnimation::Track::Types::StringProperty:
|
||||
{
|
||||
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
|
||||
const int32 count = trackDataKeyframes->KeyframesCount;
|
||||
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
|
||||
const int32 count = trackRuntime->KeyframesCount;
|
||||
if (count == 0)
|
||||
return false;
|
||||
const auto keyframesTimes = (float*)((byte*)trackDataKeyframes + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
|
||||
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackDataKeyframes->KeyframesCount);
|
||||
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackDataKeyframes->KeyframesCount);
|
||||
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
|
||||
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount);
|
||||
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount);
|
||||
|
||||
// Find the keyframe at time
|
||||
#define GET_KEY_TIME(idx) keyframesTimes[idx]
|
||||
@@ -522,7 +573,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
auto& childTrack = anim->Tracks[childTrackIndex];
|
||||
if (childTrack.Disabled || childTrack.ParentIndex != trackIndex)
|
||||
continue;
|
||||
const auto childTrackRuntimeData = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
|
||||
const auto childTrackRuntime = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
|
||||
auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex];
|
||||
|
||||
// Cache field
|
||||
@@ -532,7 +583,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
if (!type)
|
||||
continue;
|
||||
MClass* mclass = MCore::Type::GetClass(type);
|
||||
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
|
||||
childTrackState.Field = mclass->GetField(childTrackRuntime->PropertyName);
|
||||
if (!childTrackState.Field)
|
||||
continue;
|
||||
}
|
||||
@@ -956,7 +1007,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
|
||||
{
|
||||
// Set the value
|
||||
if (MCore::Type::IsPointer(valueType))
|
||||
auto valueTypes = MCore::Type::GetType(valueType);
|
||||
if (valueTypes == MTypes::Object || MCore::Type::IsPointer(valueType))
|
||||
value = (void*)*(intptr*)value;
|
||||
if (state.Property)
|
||||
{
|
||||
|
||||
171
Source/Engine/Audio/AudioBackendTools.h
Normal file
171
Source/Engine/Audio/AudioBackendTools.h
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
|
||||
/// <summary>
|
||||
/// The helper class for that handles active audio backend operations.
|
||||
/// </summary>
|
||||
class AudioBackendTools
|
||||
{
|
||||
public:
|
||||
struct Settings
|
||||
{
|
||||
float Volume = 1.0f;
|
||||
float DopplerFactor = 1.0f;
|
||||
};
|
||||
|
||||
struct Listener
|
||||
{
|
||||
Vector3 Velocity;
|
||||
Vector3 Position;
|
||||
Quaternion Orientation;
|
||||
};
|
||||
|
||||
struct Source
|
||||
{
|
||||
bool Is3D;
|
||||
float Volume;
|
||||
float Pitch;
|
||||
float Pan;
|
||||
float MinDistance;
|
||||
float Attenuation;
|
||||
float DopplerFactor;
|
||||
Vector3 Velocity;
|
||||
Vector3 Position;
|
||||
Quaternion Orientation;
|
||||
};
|
||||
|
||||
enum Channels
|
||||
{
|
||||
FrontLeft = 0,
|
||||
FrontRight = 1,
|
||||
FontCenter = 2,
|
||||
BackLeft = 3,
|
||||
BackRight = 4,
|
||||
SideLeft = 5,
|
||||
SideRight = 6,
|
||||
MaxChannels
|
||||
};
|
||||
|
||||
struct SoundMix
|
||||
{
|
||||
float Pitch;
|
||||
float Volume;
|
||||
float Channels[MaxChannels];
|
||||
|
||||
void VolumeIntoChannels()
|
||||
{
|
||||
for (float& c : Channels)
|
||||
c *= Volume;
|
||||
Volume = 1.0f;
|
||||
}
|
||||
};
|
||||
|
||||
static SoundMix CalculateSoundMix(const Settings& settings, const Listener& listener, const Source& source, int32 channelCount = 2)
|
||||
{
|
||||
ASSERT_LOW_LAYER(channelCount <= MaxChannels);
|
||||
SoundMix mix;
|
||||
mix.Pitch = source.Pitch;
|
||||
mix.Volume = source.Volume * settings.Volume;
|
||||
Platform::MemoryClear(mix.Channels, sizeof(mix.Channels));
|
||||
if (source.Is3D)
|
||||
{
|
||||
const Transform listenerTransform(listener.Position, listener.Orientation);
|
||||
float distance = (float)Vector3::Distance(listener.Position, source.Position);
|
||||
float gain = 1;
|
||||
|
||||
// Calculate attenuation (OpenAL formula for mode: AL_INVERSE_DISTANCE_CLAMPED)
|
||||
// [https://www.openal.org/documentation/openal-1.1-specification.pdf]
|
||||
distance = Math::Clamp(distance, source.MinDistance, MAX_float);
|
||||
const float dst = source.MinDistance + source.Attenuation * (distance - source.MinDistance);
|
||||
if (dst > 0)
|
||||
gain = source.MinDistance / dst;
|
||||
mix.Volume *= Math::Saturate(gain);
|
||||
|
||||
// Calculate panning
|
||||
// Ramy Sadek and Chris Kyriakakis, 2004, "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations"
|
||||
// [https://www.researchgate.net/publication/235080603_A_Novel_Multichannel_Panning_Method_for_Standard_and_Arbitrary_Loudspeaker_Configurations]
|
||||
static const Float3 ChannelDirections[MaxChannels] =
|
||||
{
|
||||
Float3(-1.0, 0.0, -1.0).GetNormalized(),
|
||||
Float3(1.0, 0.0, -1.0).GetNormalized(),
|
||||
Float3(0.0, 0.0, -1.0).GetNormalized(),
|
||||
Float3(-1.0, 0.0, 1.0).GetNormalized(),
|
||||
Float3(1.0, 0.0, 1.0).GetNormalized(),
|
||||
Float3(-1.0, 0.0, 0.0).GetNormalized(),
|
||||
Float3(1.0, 0.0, 0.0).GetNormalized(),
|
||||
};
|
||||
const Float3 sourceInListenerSpace = (Float3)listenerTransform.WorldToLocal(source.Position);
|
||||
const Float3 sourceToListener = Float3::Normalize(sourceInListenerSpace);
|
||||
float sqGainsSum = 0.0f;
|
||||
for (int32 i = 0; i < channelCount; i++)
|
||||
{
|
||||
float othersSum = 0.0f;
|
||||
for (int32 j = 0; j < channelCount; j++)
|
||||
othersSum += (1.0f + Float3::Dot(ChannelDirections[i], ChannelDirections[j])) * 0.5f;
|
||||
const float sqGain = Math::Square(0.5f * Math::Pow(1.0f + Float3::Dot(ChannelDirections[i], sourceToListener), 2.0f) / othersSum);
|
||||
sqGainsSum += sqGain;
|
||||
mix.Channels[i] = sqGain;
|
||||
}
|
||||
for (int32 i = 0; i < channelCount; i++)
|
||||
mix.Channels[i] = Math::Sqrt(mix.Channels[i] / sqGainsSum);
|
||||
|
||||
// Calculate doppler
|
||||
const Float3 velocityInListenerSpace = (Float3)listenerTransform.WorldToLocalVector(source.Velocity - listener.Velocity);
|
||||
const float velocity = velocityInListenerSpace.Length();
|
||||
const float dopplerFactor = settings.DopplerFactor * source.DopplerFactor;
|
||||
if (dopplerFactor > 0.0f && velocity > 0.0f)
|
||||
{
|
||||
constexpr float speedOfSound = 343.3f * 100.0f * 100.0f; // in air, in Flax units
|
||||
const float approachingFactor = Float3::Dot(sourceToListener, velocityInListenerSpace.GetNormalized());
|
||||
const float dopplerPitch = speedOfSound / (speedOfSound + velocity * approachingFactor);
|
||||
mix.Pitch *= Math::Clamp(dopplerPitch, 0.1f, 10.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float panLeft = Math::Min(1.0f - source.Pan, 1.0f);
|
||||
const float panRight = Math::Min(1.0f + source.Pan, 1.0f);
|
||||
switch (channelCount)
|
||||
{
|
||||
case 1:
|
||||
mix.Channels[0] = 1.0f;
|
||||
break;
|
||||
case 2:
|
||||
default: // TODO: handle other channel configuration (eg. 7.1 or 5.1)
|
||||
mix.Channels[FrontLeft] = panLeft;
|
||||
mix.Channels[FrontRight] = panRight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mix;
|
||||
}
|
||||
|
||||
static void MapChannels(int32 sourceChannels, int32 outputChannels, float channels[MaxChannels], float* outputMatrix)
|
||||
{
|
||||
Platform::MemoryClear(outputMatrix, sizeof(float) * sourceChannels * outputChannels);
|
||||
switch (outputChannels)
|
||||
{
|
||||
case 1:
|
||||
outputMatrix[0] = channels[FrontLeft];
|
||||
break;
|
||||
case 2:
|
||||
default: // TODO: implement multi-channel support (eg. 5.1, 7.1)
|
||||
if (sourceChannels == 1)
|
||||
{
|
||||
outputMatrix[0] = channels[FrontLeft];
|
||||
outputMatrix[1] = channels[FrontRight];
|
||||
}
|
||||
else if (sourceChannels == 2)
|
||||
{
|
||||
outputMatrix[0] = channels[FrontLeft];
|
||||
outputMatrix[1] = 0.0f;
|
||||
outputMatrix[2] = 0.0f;
|
||||
outputMatrix[3] = channels[FrontRight];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Config/Settings.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
/// <summary>
|
||||
/// Audio settings container.
|
||||
@@ -11,6 +10,7 @@
|
||||
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AudioSettings : public SettingsBase
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(AudioSettings);
|
||||
API_AUTO_SERIALIZATION();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -46,12 +46,4 @@ public:
|
||||
|
||||
// [SettingsBase]
|
||||
void Apply() override;
|
||||
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
|
||||
{
|
||||
DESERIALIZE(DisableAudio);
|
||||
DESERIALIZE(DopplerFactor);
|
||||
DESERIALIZE(MuteOnFocusLoss);
|
||||
DESERIALIZE(EnableHRTF);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#if AUDIO_API_XAUDIO2
|
||||
|
||||
#include "AudioBackendXAudio2.h"
|
||||
#include "Engine/Audio/AudioSettings.h"
|
||||
#include "Engine/Audio/AudioBackendTools.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
@@ -22,10 +22,11 @@
|
||||
// Documentation: https://docs.microsoft.com/en-us/windows/desktop/xaudio2/xaudio2-apis-portal
|
||||
#include <xaudio2.h>
|
||||
//#include <xaudio2fx.h>
|
||||
#include <x3daudio.h>
|
||||
//#include <x3daudio.h>
|
||||
|
||||
// TODO: implement multi-channel support (eg. 5.1, 7.1)
|
||||
#define MAX_INPUT_CHANNELS 2
|
||||
#define MAX_OUTPUT_CHANNELS 8
|
||||
#define MAX_OUTPUT_CHANNELS 2
|
||||
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS)
|
||||
#if ENABLE_ASSERTION
|
||||
#define XAUDIO2_CHECK_ERROR(method) \
|
||||
@@ -36,18 +37,12 @@
|
||||
#else
|
||||
#define XAUDIO2_CHECK_ERROR(method)
|
||||
#endif
|
||||
#define FLAX_COORD_SCALE 0.01f // units are meters
|
||||
#define FLAX_DST_TO_XAUDIO(x) x * FLAX_COORD_SCALE
|
||||
#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * FLAX_COORD_SCALE, vec.Y * FLAX_COORD_SCALE, vec.Z * FLAX_COORD_SCALE)
|
||||
#define FLAX_VEL_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * (FLAX_COORD_SCALE*FLAX_COORD_SCALE), vec.Y * (FLAX_COORD_SCALE*FLAX_COORD_SCALE), vec.Z * (FLAX_COORD_SCALE*FLAX_COORD_SCALE))
|
||||
#define FLAX_VEC_TO_XAUDIO(vec) (*((X3DAUDIO_VECTOR*)&vec))
|
||||
|
||||
namespace XAudio2
|
||||
{
|
||||
struct Listener
|
||||
struct Listener : AudioBackendTools::Listener
|
||||
{
|
||||
AudioListener* AudioListener;
|
||||
X3DAUDIO_LISTENER Data;
|
||||
|
||||
Listener()
|
||||
{
|
||||
@@ -57,7 +52,6 @@ namespace XAudio2
|
||||
void Init()
|
||||
{
|
||||
AudioListener = nullptr;
|
||||
Data.pCone = nullptr;
|
||||
}
|
||||
|
||||
bool IsFree() const
|
||||
@@ -67,21 +61,13 @@ namespace XAudio2
|
||||
|
||||
void UpdateTransform()
|
||||
{
|
||||
const Vector3& position = AudioListener->GetPosition();
|
||||
const Quaternion& orientation = AudioListener->GetOrientation();
|
||||
const Vector3 front = orientation * Vector3::Forward;
|
||||
const Vector3 top = orientation * Vector3::Up;
|
||||
|
||||
Data.OrientFront = FLAX_VEC_TO_XAUDIO(front);
|
||||
Data.OrientTop = FLAX_VEC_TO_XAUDIO(top);
|
||||
Data.Position = FLAX_POS_TO_XAUDIO(position);
|
||||
Position = AudioListener->GetPosition();
|
||||
Orientation = AudioListener->GetOrientation();
|
||||
}
|
||||
|
||||
void UpdateVelocity()
|
||||
{
|
||||
const Vector3& velocity = AudioListener->GetVelocity();
|
||||
|
||||
Data.Velocity = FLAX_VEL_TO_XAUDIO(velocity);
|
||||
Velocity = AudioListener->GetVelocity();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,23 +109,18 @@ namespace XAudio2
|
||||
void PeekSamples();
|
||||
};
|
||||
|
||||
struct Source
|
||||
struct Source : AudioBackendTools::Source
|
||||
{
|
||||
IXAudio2SourceVoice* Voice;
|
||||
X3DAUDIO_EMITTER Data;
|
||||
WAVEFORMATEX Format;
|
||||
XAUDIO2_SEND_DESCRIPTOR Destination;
|
||||
float Pitch;
|
||||
float Pan;
|
||||
float StartTimeForQueueBuffer;
|
||||
float LastBufferStartTime;
|
||||
float DopplerFactor;
|
||||
uint64 LastBufferStartSamplesPlayed;
|
||||
int32 BuffersProcessed;
|
||||
int32 Channels;
|
||||
bool IsDirty;
|
||||
bool Is3D;
|
||||
bool IsPlaying;
|
||||
bool IsForceMono3D;
|
||||
VoiceCallback Callback;
|
||||
|
||||
Source()
|
||||
@@ -150,8 +131,6 @@ namespace XAudio2
|
||||
void Init()
|
||||
{
|
||||
Voice = nullptr;
|
||||
Platform::MemoryClear(&Data, sizeof(Data));
|
||||
Data.CurveDistanceScaler = 1.0f;
|
||||
Destination.Flags = 0;
|
||||
Destination.pOutputVoice = nullptr;
|
||||
Pitch = 1.0f;
|
||||
@@ -172,21 +151,13 @@ namespace XAudio2
|
||||
|
||||
void UpdateTransform(const AudioSource* source)
|
||||
{
|
||||
const Vector3& position = source->GetPosition();
|
||||
const Quaternion& orientation = source->GetOrientation();
|
||||
const Vector3 front = orientation * Vector3::Forward;
|
||||
const Vector3 top = orientation * Vector3::Up;
|
||||
|
||||
Data.OrientFront = FLAX_VEC_TO_XAUDIO(front);
|
||||
Data.OrientTop = FLAX_VEC_TO_XAUDIO(top);
|
||||
Data.Position = FLAX_POS_TO_XAUDIO(position);
|
||||
Position = source->GetPosition();
|
||||
Orientation = source->GetOrientation();
|
||||
}
|
||||
|
||||
void UpdateVelocity(const AudioSource* source)
|
||||
{
|
||||
const Vector3& velocity = source->GetVelocity();
|
||||
|
||||
Data.Velocity = FLAX_VEL_TO_XAUDIO(velocity);
|
||||
Velocity = source->GetVelocity();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -214,11 +185,9 @@ namespace XAudio2
|
||||
|
||||
IXAudio2* Instance = nullptr;
|
||||
IXAudio2MasteringVoice* MasteringVoice = nullptr;
|
||||
X3DAUDIO_HANDLE X3DInstance;
|
||||
DWORD ChannelMask;
|
||||
UINT32 SampleRate;
|
||||
UINT32 Channels;
|
||||
int32 Channels;
|
||||
bool ForceDirty = true;
|
||||
AudioBackendTools::Settings Settings;
|
||||
Listener Listeners[AUDIO_MAX_LISTENERS];
|
||||
CriticalSection Locker;
|
||||
ChunkedArray<Source, 32> Sources;
|
||||
@@ -387,7 +356,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
|
||||
const auto& header = clip->AudioHeader;
|
||||
auto& format = aSource->Format;
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
|
||||
format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write if FeatureFlags::SpatialMultiChannel is unset)
|
||||
format.nSamplesPerSec = header.Info.SampleRate;
|
||||
format.wBitsPerSample = header.Info.BitDepth;
|
||||
format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8));
|
||||
@@ -408,26 +377,25 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
|
||||
if (FAILED(hr))
|
||||
return;
|
||||
|
||||
sourceID++; // 0 is invalid ID so shift them
|
||||
source->SourceIDs.Add(sourceID);
|
||||
|
||||
// Prepare source state
|
||||
aSource->Callback.Source = source;
|
||||
aSource->IsDirty = true;
|
||||
aSource->Data.ChannelCount = format.nChannels;
|
||||
aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance());
|
||||
aSource->Is3D = source->Is3D();
|
||||
aSource->IsForceMono3D = header.Is3D && header.Info.NumChannels > 1;
|
||||
aSource->Pitch = source->GetPitch();
|
||||
aSource->Pan = source->GetPan();
|
||||
aSource->DopplerFactor = source->GetDopplerFactor();
|
||||
aSource->Volume = source->GetVolume();
|
||||
aSource->MinDistance = source->GetMinDistance();
|
||||
aSource->Attenuation = source->GetAttenuation();
|
||||
aSource->Channels = format.nChannels;
|
||||
aSource->UpdateTransform(source);
|
||||
aSource->UpdateVelocity(source);
|
||||
hr = aSource->Voice->SetVolume(source->GetVolume());
|
||||
XAUDIO2_CHECK_ERROR(SetVolume);
|
||||
|
||||
// 0 is invalid ID so shift them
|
||||
sourceID++;
|
||||
|
||||
source->SourceIDs.Add(sourceID);
|
||||
|
||||
source->Restore();
|
||||
}
|
||||
|
||||
@@ -462,6 +430,7 @@ void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source)
|
||||
auto aSource = XAudio2::GetSource(source);
|
||||
if (aSource && aSource->Voice)
|
||||
{
|
||||
aSource->Volume = source->GetVolume();
|
||||
const HRESULT hr = aSource->Voice->SetVolume(source->GetVolume());
|
||||
XAUDIO2_CHECK_ERROR(SetVolume);
|
||||
}
|
||||
@@ -546,17 +515,10 @@ void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source)
|
||||
auto aSource = XAudio2::GetSource(source);
|
||||
if (aSource)
|
||||
{
|
||||
// TODO: implement attenuation settings for 3d audio
|
||||
auto clip = source->Clip.Get();
|
||||
if (clip && clip->IsLoaded())
|
||||
{
|
||||
const auto& header = clip->AudioHeader;
|
||||
aSource->Data.ChannelCount = source->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
|
||||
aSource->IsForceMono3D = header.Is3D && header.Info.NumChannels > 1;
|
||||
}
|
||||
aSource->Is3D = source->Is3D();
|
||||
aSource->MinDistance = source->GetMinDistance();
|
||||
aSource->Attenuation = source->GetAttenuation();
|
||||
aSource->DopplerFactor = source->GetDopplerFactor();
|
||||
aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance());
|
||||
aSource->IsDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -655,7 +617,7 @@ float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source
|
||||
aSource->Voice->GetState(&state);
|
||||
const uint32 numChannels = clipInfo.NumChannels;
|
||||
const uint32 totalSamples = clipInfo.NumSamples / numChannels;
|
||||
const uint32 sampleRate = clipInfo.SampleRate;// / clipInfo.NumChannels;
|
||||
const uint32 sampleRate = clipInfo.SampleRate; // / clipInfo.NumChannels;
|
||||
state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
|
||||
time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, sampleRate));
|
||||
}
|
||||
@@ -770,6 +732,8 @@ void AudioBackendXAudio2::Buffer_Delete(uint32 bufferId)
|
||||
|
||||
void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info)
|
||||
{
|
||||
CHECK(info.NumChannels <= MAX_INPUT_CHANNELS);
|
||||
|
||||
XAudio2::Locker.Lock();
|
||||
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
|
||||
XAudio2::Locker.Unlock();
|
||||
@@ -796,6 +760,7 @@ void AudioBackendXAudio2::Base_OnActiveDeviceChanged()
|
||||
|
||||
void AudioBackendXAudio2::Base_SetDopplerFactor(float value)
|
||||
{
|
||||
XAudio2::Settings.DopplerFactor = value;
|
||||
XAudio2::MarkAllDirty();
|
||||
}
|
||||
|
||||
@@ -803,6 +768,7 @@ void AudioBackendXAudio2::Base_SetVolume(float value)
|
||||
{
|
||||
if (XAudio2::MasteringVoice)
|
||||
{
|
||||
XAudio2::Settings.Volume = 1.0f; // Volume is applied via MasteringVoice
|
||||
const HRESULT hr = XAudio2::MasteringVoice->SetVolume(value);
|
||||
XAUDIO2_CHECK_ERROR(SetVolume);
|
||||
}
|
||||
@@ -830,7 +796,8 @@ bool AudioBackendXAudio2::Base_Init()
|
||||
}
|
||||
XAUDIO2_VOICE_DETAILS details;
|
||||
XAudio2::MasteringVoice->GetVoiceDetails(&details);
|
||||
XAudio2::SampleRate = details.InputSampleRate;
|
||||
#if 0
|
||||
// TODO: implement multi-channel support (eg. 5.1, 7.1)
|
||||
XAudio2::Channels = details.InputChannels;
|
||||
hr = XAudio2::MasteringVoice->GetChannelMask(&XAudio2::ChannelMask);
|
||||
if (FAILED(hr))
|
||||
@@ -838,19 +805,10 @@ bool AudioBackendXAudio2::Base_Init()
|
||||
LOG(Error, "Failed to get XAudio2 mastering voice channel mask. Error: 0x{0:x}", hr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialize spatial audio subsystem
|
||||
DWORD dwChannelMask;
|
||||
XAudio2::MasteringVoice->GetChannelMask(&dwChannelMask);
|
||||
hr = X3DAudioInitialize(dwChannelMask, X3DAUDIO_SPEED_OF_SOUND, XAudio2::X3DInstance);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG(Error, "Failed to initalize XAudio2 3D support. Error: 0x{0:x}", hr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Info
|
||||
LOG(Info, "XAudio2: {0} channels at {1} kHz (channel mask {2})", XAudio2::Channels, XAudio2::SampleRate / 1000.0f, XAudio2::ChannelMask);
|
||||
#else
|
||||
XAudio2::Channels = 2;
|
||||
#endif
|
||||
LOG(Info, "XAudio2: {0} channels at {1} kHz", XAudio2::Channels, details.InputSampleRate / 1000.0f);
|
||||
|
||||
// Dummy device
|
||||
devices.Resize(1);
|
||||
@@ -864,53 +822,19 @@ void AudioBackendXAudio2::Base_Update()
|
||||
{
|
||||
// Update dirty voices
|
||||
const auto listener = XAudio2::GetListener();
|
||||
const float dopplerFactor = AudioSettings::Get()->DopplerFactor;
|
||||
float matrixCoefficients[MAX_CHANNELS_MATRIX_SIZE];
|
||||
X3DAUDIO_DSP_SETTINGS dsp = { 0 };
|
||||
dsp.DstChannelCount = XAudio2::Channels;
|
||||
dsp.pMatrixCoefficients = matrixCoefficients;
|
||||
float outputMatrix[MAX_CHANNELS_MATRIX_SIZE];
|
||||
for (int32 i = 0; i < XAudio2::Sources.Count(); i++)
|
||||
{
|
||||
auto& source = XAudio2::Sources[i];
|
||||
if (source.IsFree() || !(source.IsDirty || XAudio2::ForceDirty))
|
||||
continue;
|
||||
|
||||
dsp.SrcChannelCount = source.Data.ChannelCount;
|
||||
if (source.Is3D && listener)
|
||||
{
|
||||
// 3D sound
|
||||
X3DAudioCalculate(XAudio2::X3DInstance, &listener->Data, &source.Data, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER, &dsp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2D sound
|
||||
dsp.DopplerFactor = 1.0f;
|
||||
Platform::MemoryClear(dsp.pMatrixCoefficients, sizeof(matrixCoefficients));
|
||||
dsp.pMatrixCoefficients[0] = 1.0f;
|
||||
if (source.Format.nChannels == 1)
|
||||
{
|
||||
dsp.pMatrixCoefficients[1] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
dsp.pMatrixCoefficients[3] = 1.0f;
|
||||
}
|
||||
const float panLeft = Math::Min(1.0f - source.Pan, 1.0f);
|
||||
const float panRight = Math::Min(1.0f + source.Pan, 1.0f);
|
||||
if (source.Format.nChannels >= 2)
|
||||
{
|
||||
dsp.pMatrixCoefficients[0] *= panLeft;
|
||||
dsp.pMatrixCoefficients[3] *= panRight;
|
||||
}
|
||||
}
|
||||
if (source.IsForceMono3D)
|
||||
{
|
||||
// Hack to fix playback speed for 3D clip that has auto-converted stereo to mono at runtime
|
||||
dsp.DopplerFactor *= 0.5f;
|
||||
}
|
||||
const float frequencyRatio = dopplerFactor * source.Pitch * dsp.DopplerFactor * source.DopplerFactor;
|
||||
source.Voice->SetFrequencyRatio(frequencyRatio);
|
||||
source.Voice->SetOutputMatrix(XAudio2::MasteringVoice, dsp.SrcChannelCount, dsp.DstChannelCount, dsp.pMatrixCoefficients);
|
||||
auto mix = AudioBackendTools::CalculateSoundMix(XAudio2::Settings, *listener, source, XAudio2::Channels);
|
||||
mix.VolumeIntoChannels();
|
||||
AudioBackendTools::MapChannels(source.Channels, XAudio2::Channels, mix.Channels, outputMatrix);
|
||||
|
||||
source.Voice->SetFrequencyRatio(mix.Pitch);
|
||||
source.Voice->SetOutputMatrix(XAudio2::MasteringVoice, source.Channels, XAudio2::Channels, outputMatrix);
|
||||
|
||||
source.IsDirty = false;
|
||||
}
|
||||
|
||||
@@ -584,7 +584,43 @@ Asset::LoadResult BinaryAsset::loadAsset()
|
||||
ASSERT(Storage && _header.ID.IsValid() && _header.TypeName.HasChars());
|
||||
|
||||
auto lock = Storage->Lock();
|
||||
return load();
|
||||
auto chunksToPreload = getChunksToPreload();
|
||||
if (chunksToPreload != 0)
|
||||
{
|
||||
// Ensure that any chunks that were requested before are loaded in memory (in case streaming flushed them out after timeout)
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
const auto chunk = _header.Chunks[i];
|
||||
if (GET_CHUNK_FLAG(i) & chunksToPreload && chunk && chunk->IsMissing())
|
||||
Storage->LoadAssetChunk(chunk);
|
||||
}
|
||||
}
|
||||
const LoadResult result = load();
|
||||
#if !BUILD_RELEASE
|
||||
if (result == LoadResult::MissingDataChunk)
|
||||
{
|
||||
// Provide more insights on potentially missing asset data chunk
|
||||
Char chunksBitMask[ASSET_FILE_DATA_CHUNKS + 1];
|
||||
Char chunksExistBitMask[ASSET_FILE_DATA_CHUNKS + 1];
|
||||
Char chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS + 1];
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (const FlaxChunk* chunk = _header.Chunks[i])
|
||||
{
|
||||
chunksBitMask[i] = '1';
|
||||
chunksExistBitMask[i] = chunk->ExistsInFile() ? '1' : '0';
|
||||
chunksLoadBitMask[i] = chunk->IsLoaded() ? '1' : '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
chunksBitMask[i] = chunksExistBitMask[i] = chunksLoadBitMask[i] = '0';
|
||||
}
|
||||
}
|
||||
chunksBitMask[ASSET_FILE_DATA_CHUNKS] = chunksExistBitMask[ASSET_FILE_DATA_CHUNKS] = chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS] = 0;
|
||||
LOG(Warning, "Asset reports missing data chunk. Chunks bitmask: {}, existing chunks: {} loaded chunks: {}. '{}'", chunksBitMask, chunksExistBitMask, chunksLoadBitMask, ToString());
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void BinaryAsset::releaseStorage()
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/DeleteMe.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Types/Stopwatch.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Serialization/FileReadStream.h"
|
||||
@@ -19,7 +21,7 @@ void AssetsCache::Init()
|
||||
{
|
||||
Entry e;
|
||||
int32 count;
|
||||
const DateTime loadStartTime = DateTime::Now();
|
||||
Stopwatch stopwatch;
|
||||
#if USE_EDITOR
|
||||
_path = Globals::ProjectCacheFolder / TEXT("AssetsCache.dat");
|
||||
#else
|
||||
@@ -138,8 +140,8 @@ void AssetsCache::Init()
|
||||
}
|
||||
}
|
||||
|
||||
const int32 loadTimeInMs = static_cast<int32>((DateTime::Now() - loadStartTime).GetTotalMilliseconds());
|
||||
LOG(Info, "Asset Cache loaded {0} entries in {1} ms ({2} rejected)", _registry.Count(), loadTimeInMs, rejectedCount);
|
||||
stopwatch.Stop();
|
||||
LOG(Info, "Asset Cache loaded {0} entries in {1}ms ({2} rejected)", _registry.Count(), stopwatch.GetMilliseconds(), rejectedCount);
|
||||
}
|
||||
|
||||
bool AssetsCache::Save()
|
||||
|
||||
@@ -163,7 +163,7 @@ bool ContentLoadingManagerService::Init()
|
||||
|
||||
// Calculate amount of loading threads to use
|
||||
const CPUInfo cpuInfo = Platform::GetCPUInfo();
|
||||
const int32 count = Math::Clamp(static_cast<int32>(LOADING_THREAD_PER_LOGICAL_CORE * (float)cpuInfo.LogicalProcessorCount), 1, 12);
|
||||
const int32 count = Math::Clamp(Math::CeilToInt(LOADING_THREAD_PER_LOGICAL_CORE * (float)cpuInfo.LogicalProcessorCount), 1, 12);
|
||||
LOG(Info, "Creating {0} content loading threads...", count);
|
||||
|
||||
// Create loading threads
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#endif
|
||||
#include "FlaxChunk.h"
|
||||
@@ -81,36 +82,17 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the amount of created asset chunks.
|
||||
/// </summary>
|
||||
/// <returns>Created asset chunks</returns>
|
||||
int32 GetChunksCount() const
|
||||
{
|
||||
int32 result = 0;
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (Chunks[i] != nullptr)
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int32 GetChunksCount() const;
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all chunks. Warning! Chunks are managed internally, use with caution!
|
||||
/// </summary>
|
||||
void DeleteChunks()
|
||||
{
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
SAFE_DELETE(Chunks[i]);
|
||||
}
|
||||
}
|
||||
void DeleteChunks();
|
||||
|
||||
/// <summary>
|
||||
/// Unlinks all chunks.
|
||||
/// </summary>
|
||||
void UnlinkChunks()
|
||||
{
|
||||
Platform::MemoryClear(Chunks, sizeof(Chunks));
|
||||
}
|
||||
void UnlinkChunks();
|
||||
|
||||
/// <summary>
|
||||
/// Gets string with a human-readable info about that header
|
||||
|
||||
@@ -78,9 +78,9 @@ public:
|
||||
FlaxChunkFlags Flags = FlaxChunkFlags::None;
|
||||
|
||||
/// <summary>
|
||||
/// The last usage time (atomic, ticks of DateTime in UTC).
|
||||
/// The last usage time.
|
||||
/// </summary>
|
||||
int64 LastAccessTime = 0;
|
||||
double LastAccessTime = 0.0;
|
||||
|
||||
/// <summary>
|
||||
/// The chunk data.
|
||||
|
||||
@@ -20,6 +20,30 @@
|
||||
#endif
|
||||
#include <ThirdParty/LZ4/lz4.h>
|
||||
|
||||
int32 AssetHeader::GetChunksCount() const
|
||||
{
|
||||
int32 result = 0;
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (Chunks[i] != nullptr)
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AssetHeader::DeleteChunks()
|
||||
{
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
SAFE_DELETE(Chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetHeader::UnlinkChunks()
|
||||
{
|
||||
Platform::MemoryClear(Chunks, sizeof(Chunks));
|
||||
}
|
||||
|
||||
String AssetHeader::ToString() const
|
||||
{
|
||||
return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount());
|
||||
@@ -27,7 +51,7 @@ String AssetHeader::ToString() const
|
||||
|
||||
void FlaxChunk::RegisterUsage()
|
||||
{
|
||||
Platform::AtomicStore(&LastAccessTime, DateTime::NowUTC().Ticks);
|
||||
LastAccessTime = Platform::GetTimeSeconds();
|
||||
}
|
||||
|
||||
const int32 FlaxStorage::MagicCode = 1180124739;
|
||||
@@ -235,7 +259,9 @@ uint32 FlaxStorage::GetRefCount() const
|
||||
|
||||
bool FlaxStorage::ShouldDispose() const
|
||||
{
|
||||
return Platform::AtomicRead((int64*)&_refCount) == 0 && Platform::AtomicRead((int64*)&_chunksLock) == 0 && DateTime::NowUTC() - _lastRefLostTime >= TimeSpan::FromMilliseconds(500);
|
||||
return Platform::AtomicRead((int64*)&_refCount) == 0 &&
|
||||
Platform::AtomicRead((int64*)&_chunksLock) == 0 &&
|
||||
Platform::GetTimeSeconds() - _lastRefLostTime >= 0.5; // TTL in seconds
|
||||
}
|
||||
|
||||
uint32 FlaxStorage::GetMemoryUsage() const
|
||||
@@ -1374,12 +1400,13 @@ void FlaxStorage::Tick()
|
||||
if (Platform::AtomicRead(&_chunksLock) != 0)
|
||||
return;
|
||||
|
||||
const auto now = DateTime::NowUTC();
|
||||
const double now = Platform::GetTimeSeconds();
|
||||
bool wasAnyUsed = false;
|
||||
const float unusedDataChunksLifetime = ContentStorageManager::UnusedDataChunksLifetime.GetTotalSeconds();
|
||||
for (int32 i = 0; i < _chunks.Count(); i++)
|
||||
{
|
||||
auto chunk = _chunks[i];
|
||||
const bool wasUsed = (now - DateTime(Platform::AtomicRead(&chunk->LastAccessTime))) < ContentStorageManager::UnusedDataChunksLifetime;
|
||||
auto chunk = _chunks.Get()[i];
|
||||
const bool wasUsed = (now - chunk->LastAccessTime) < unusedDataChunksLifetime;
|
||||
if (!wasUsed && chunk->IsLoaded())
|
||||
{
|
||||
chunk->Unload();
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "Engine/Core/Object.h"
|
||||
#include "Engine/Core/Delegate.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
#include "Engine/Serialization/FileReadStream.h"
|
||||
@@ -90,7 +89,7 @@ protected:
|
||||
// State
|
||||
int64 _refCount;
|
||||
int64 _chunksLock;
|
||||
DateTime _lastRefLostTime;
|
||||
double _lastRefLostTime;
|
||||
CriticalSection _loadLocker;
|
||||
|
||||
// Storage
|
||||
@@ -115,7 +114,7 @@ private:
|
||||
Platform::InterlockedDecrement(&_refCount);
|
||||
if (Platform::AtomicRead(&_refCount) == 0)
|
||||
{
|
||||
_lastRefLostTime = DateTime::NowUTC();
|
||||
_lastRefLostTime = Platform::GetTimeSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Config/Settings.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Content/Asset.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
@@ -15,8 +14,15 @@
|
||||
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings);
|
||||
API_AUTO_SERIALIZATION();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Name of the output app created by the build system. Used to rename main executable (eg. MyGame.exe) or final package name (eg. MyGame.apk). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")")
|
||||
String OutputName = TEXT("${PROJECT_NAME}");
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to.
|
||||
/// </summary>
|
||||
@@ -100,21 +106,4 @@ public:
|
||||
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
|
||||
/// </summary>
|
||||
static BuildSettings* Get();
|
||||
|
||||
// [SettingsBase]
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
|
||||
{
|
||||
DESERIALIZE(MaxAssetsPerPackage);
|
||||
DESERIALIZE(MaxPackageSizeMB);
|
||||
DESERIALIZE(ContentKey);
|
||||
DESERIALIZE(ForDistribution);
|
||||
DESERIALIZE(SkipPackaging);
|
||||
DESERIALIZE(AdditionalAssets);
|
||||
DESERIALIZE(AdditionalAssetFolders);
|
||||
DESERIALIZE(ShadersNoOptimize);
|
||||
DESERIALIZE(ShadersGenerateDebugData);
|
||||
DESERIALIZE(SkipDefaultFonts);
|
||||
DESERIALIZE(SkipDotnetPackaging);
|
||||
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Streaming/StreamingSettings.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#if FLAX_TESTS || USE_EDITOR
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user