Merge remote-tracking branch 'upstream/master' into FocusSelectedVJControls
This commit is contained in:
@@ -174,7 +174,9 @@ void EditorAnalytics::StartSession()
|
||||
// Bind events
|
||||
GameCooker::OnEvent.Bind<RegisterGameCookingStart>();
|
||||
ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Bind<RegisterLightmapsBuildingStart>();
|
||||
#if LOG_ENABLE
|
||||
Log::Logger::OnError.Bind<RegisterError>();
|
||||
#endif
|
||||
}
|
||||
|
||||
void EditorAnalytics::EndSession()
|
||||
@@ -187,7 +189,9 @@ void EditorAnalytics::EndSession()
|
||||
// Unbind events
|
||||
GameCooker::OnEvent.Unbind<RegisterGameCookingStart>();
|
||||
ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Unbind<RegisterLightmapsBuildingStart>();
|
||||
#if LOG_ENABLE
|
||||
Log::Logger::OnError.Unbind<RegisterError>();
|
||||
#endif
|
||||
|
||||
// End session
|
||||
{
|
||||
|
||||
@@ -117,7 +117,8 @@ namespace FlaxEditor.Content.Create
|
||||
|
||||
private static bool IsValid(Type type)
|
||||
{
|
||||
return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType;
|
||||
var controlTypes = Editor.Instance.CodeEditing.Controls.Get();
|
||||
return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType && controlTypes.Contains(new ScriptType(type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -281,6 +281,13 @@ namespace FlaxEditor.Content
|
||||
|
||||
private void CacheData()
|
||||
{
|
||||
if (!_asset)
|
||||
{
|
||||
_parameters = Utils.GetEmptyArray<ScriptMemberInfo>();
|
||||
_methods = Utils.GetEmptyArray<ScriptMemberInfo>();
|
||||
_attributes = Utils.GetEmptyArray<Attribute>();
|
||||
return;
|
||||
}
|
||||
if (_parameters != null)
|
||||
return;
|
||||
if (_asset.WaitForLoaded())
|
||||
@@ -344,13 +351,13 @@ namespace FlaxEditor.Content
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => Path.GetFileNameWithoutExtension(_asset.Path);
|
||||
public string Name => _asset ? Path.GetFileNameWithoutExtension(_asset.Path) : null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Namespace => string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string TypeName => JsonSerializer.GetStringID(_asset.ID);
|
||||
public string TypeName => _asset ? JsonSerializer.GetStringID(_asset.ID) : null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPublic => true;
|
||||
|
||||
@@ -130,6 +130,11 @@ namespace FlaxEditor.Content
|
||||
eyeAdaptation.Mode = EyeAdaptationMode.None;
|
||||
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
|
||||
preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
|
||||
|
||||
var antiAliasing = preview.PostFxVolume.AntiAliasing;
|
||||
antiAliasing.Mode = AntialiasingMode.FastApproximateAntialiasing;
|
||||
antiAliasing.OverrideFlags |= AntiAliasingSettingsOverride.Mode;
|
||||
preview.PostFxVolume.AntiAliasing = antiAliasing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,6 @@ class PlatformTools;
|
||||
#define GAME_BUILD_DOTNET_RUNTIME_MAX_VER 9
|
||||
#endif
|
||||
|
||||
#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=" MACRO_TO_STR(GAME_BUILD_DOTNET_RUNTIME_MIN_VER))
|
||||
#else
|
||||
#define GAME_BUILD_DOTNET_VER TEXT("")
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Game building options. Used as flags.
|
||||
/// </summary>
|
||||
@@ -374,6 +367,8 @@ public:
|
||||
/// </summary>
|
||||
void GetBuildPlatformName(const Char*& platform, const Char*& architecture) const;
|
||||
|
||||
String GetDotnetCommandArg() const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#if PLATFORM_TOOLS_WINDOWS
|
||||
#include "Platform/Windows/WindowsPlatformTools.h"
|
||||
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
|
||||
@@ -311,6 +312,14 @@ void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& archi
|
||||
}
|
||||
}
|
||||
|
||||
String CookingData::GetDotnetCommandArg() const
|
||||
{
|
||||
int32 version = Tools->GetDotnetVersion();
|
||||
if (version == 0)
|
||||
return String::Empty;
|
||||
return String::Format(TEXT("-dotnet={}"), version);
|
||||
}
|
||||
|
||||
void CookingData::StepProgress(const String& info, const float stepProgress) const
|
||||
{
|
||||
const float singleStepProgress = 1.0f / (StepsCount + 1);
|
||||
@@ -380,6 +389,7 @@ bool GameCooker::IsCancelRequested()
|
||||
|
||||
PlatformTools* GameCooker::GetTools(BuildPlatform platform)
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
PlatformTools* result = nullptr;
|
||||
if (!Tools.TryGet(platform, result))
|
||||
{
|
||||
@@ -471,6 +481,7 @@ bool GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration,
|
||||
LOG(Error, "Build platform {0} is not supported.", ::ToString(platform));
|
||||
return true;
|
||||
}
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
// Setup
|
||||
CancelFlag = 0;
|
||||
@@ -624,6 +635,7 @@ void GameCookerImpl::ReportProgress(const String& info, float totalProgress)
|
||||
|
||||
void GameCookerImpl::OnCollectAssets(HashSet<Guid>& assets)
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
if (Internal_OnCollectAssets == nullptr)
|
||||
{
|
||||
auto c = GameCooker::GetStaticClass();
|
||||
@@ -651,6 +663,7 @@ void GameCookerImpl::OnCollectAssets(HashSet<Guid>& assets)
|
||||
|
||||
bool GameCookerImpl::Build()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
CookingData& data = *Data;
|
||||
LOG(Info, "Starting Game Cooker...");
|
||||
LOG(Info, "Platform: {0}, Configuration: {2}, Options: {1}", ::ToString(data.Platform), (int32)data.Options, ::ToString(data.Configuration));
|
||||
@@ -670,8 +683,7 @@ bool GameCookerImpl::Build()
|
||||
|
||||
MCore::Thread::Attach();
|
||||
|
||||
// Build Started
|
||||
if (!EnumHasAnyFlags(data.Options, BuildOptions::NoCook))
|
||||
// Build start
|
||||
{
|
||||
CallEvent(GameCooker::EventType::BuildStarted);
|
||||
data.Tools->OnBuildStarted(data);
|
||||
@@ -744,8 +756,8 @@ bool GameCookerImpl::Build()
|
||||
}
|
||||
IsRunning = false;
|
||||
CancelFlag = 0;
|
||||
if (!EnumHasAnyFlags(data.Options, BuildOptions::NoCook))
|
||||
{
|
||||
// Build end
|
||||
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
|
||||
Steps[stepIndex]->OnBuildEnded(data, failed);
|
||||
data.Tools->OnBuildEnded(data, failed);
|
||||
@@ -778,6 +790,8 @@ int32 GameCookerImpl::ThreadFunction()
|
||||
|
||||
bool GameCookerService::Init()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
auto editorAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
|
||||
editorAssembly->Unloading.Bind(OnEditorAssemblyUnloading);
|
||||
GameCooker::OnCollectAssets.Bind(OnCollectAssets);
|
||||
@@ -789,6 +803,7 @@ void GameCookerService::Update()
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
ScopeLock lock(ProgressLocker);
|
||||
|
||||
if (ProgressMsg.HasChars())
|
||||
|
||||
@@ -15,26 +15,32 @@
|
||||
#include "Editor/ProjectInfo.h"
|
||||
#include "Editor/Utilities/EditorUtilities.h"
|
||||
|
||||
GDKPlatformTools::GDKPlatformTools()
|
||||
String GetGDK()
|
||||
{
|
||||
// Find GDK
|
||||
Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), _gdkPath);
|
||||
if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath))
|
||||
String gdk;
|
||||
Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), gdk);
|
||||
if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk))
|
||||
{
|
||||
_gdkPath.Clear();
|
||||
Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), _gdkPath);
|
||||
if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath))
|
||||
gdk.Clear();
|
||||
Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), gdk);
|
||||
if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk))
|
||||
{
|
||||
_gdkPath.Clear();
|
||||
gdk.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_gdkPath.EndsWith(TEXT("GRDK\\")))
|
||||
_gdkPath.Remove(_gdkPath.Length() - 6);
|
||||
else if (_gdkPath.EndsWith(TEXT("GRDK")))
|
||||
_gdkPath.Remove(_gdkPath.Length() - 5);
|
||||
if (gdk.EndsWith(TEXT("GRDK\\")))
|
||||
gdk.Remove(gdk.Length() - 6);
|
||||
else if (gdk.EndsWith(TEXT("GRDK")))
|
||||
gdk.Remove(gdk.Length() - 5);
|
||||
}
|
||||
}
|
||||
return gdk;
|
||||
}
|
||||
|
||||
GDKPlatformTools::GDKPlatformTools()
|
||||
{
|
||||
_gdkPath = GetGDK();
|
||||
}
|
||||
|
||||
DotNetAOTModes GDKPlatformTools::UseAOT() const
|
||||
@@ -121,7 +127,7 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
|
||||
validName.Add('\0');
|
||||
|
||||
sb.Append(TEXT("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
|
||||
sb.Append(TEXT("<Game configVersion=\"0\">\n"));
|
||||
sb.Append(TEXT("<Game configVersion=\"1\">\n"));
|
||||
sb.AppendFormat(TEXT(" <Identity Name=\"{0}\" Publisher=\"{1}\" Version=\"{2}\"/>\n"),
|
||||
validName.Get(),
|
||||
platformSettings->PublisherName.HasChars() ? platformSettings->PublisherName : TEXT("CN=") + gameSettings->CompanyName,
|
||||
@@ -195,4 +201,9 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 GDKPlatformTools::GetDotnetVersion() const
|
||||
{
|
||||
return GAME_BUILD_DOTNET_RUNTIME_MIN_VER;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -26,6 +26,7 @@ public:
|
||||
public:
|
||||
|
||||
// [PlatformTools]
|
||||
int32 GetDotnetVersion() const override;
|
||||
DotNetAOTModes UseAOT() const override;
|
||||
bool OnDeployBinaries(CookingData& data) override;
|
||||
};
|
||||
|
||||
@@ -186,7 +186,7 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
|
||||
ADD_ENTRY("CFBundlePackageType", "APPL");
|
||||
ADD_ENTRY("NSPrincipalClass", "NSApplication");
|
||||
ADD_ENTRY("LSApplicationCategoryType", "public.app-category.games");
|
||||
ADD_ENTRY("LSMinimumSystemVersion", "10.15");
|
||||
ADD_ENTRY("LSMinimumSystemVersion", "13");
|
||||
ADD_ENTRY("CFBundleIconFile", "icon.icns");
|
||||
ADD_ENTRY_STR("CFBundleExecutable", executableName);
|
||||
ADD_ENTRY_STR("CFBundleIdentifier", appIdentifier);
|
||||
@@ -231,6 +231,8 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
|
||||
LOG(Info, "Building app package...");
|
||||
{
|
||||
const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg");
|
||||
if (FileSystem::FileExists(dmgPath))
|
||||
FileSystem::DeleteFile(dmgPath);
|
||||
CreateProcessSettings procSettings;
|
||||
procSettings.HiddenWindow = true;
|
||||
procSettings.WorkingDirectory = data.OriginalOutputPath;
|
||||
|
||||
@@ -528,6 +528,9 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
|
||||
void WindowsPlatformTools::OnBuildStarted(CookingData& data)
|
||||
{
|
||||
if (EnumHasAllFlags(data.Options, BuildOptions::NoCook))
|
||||
return;
|
||||
|
||||
// Remove old executable
|
||||
Array<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly);
|
||||
|
||||
@@ -70,6 +70,20 @@ public:
|
||||
/// </summary>
|
||||
virtual ArchitectureType GetArchitecture() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the .Net version to use for the cooked game.
|
||||
/// </summary>
|
||||
virtual int32 GetDotnetVersion() const
|
||||
{
|
||||
#if OFFICIAL_BUILD
|
||||
// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
|
||||
return GAME_BUILD_DOTNET_RUNTIME_MIN_VER;
|
||||
#else
|
||||
// Use the highest version found on a system (Flax.Build will decide)
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value indicating whenever platform requires AOT (needs C# assemblies to be precompiled).
|
||||
/// </summary>
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
#include "Engine/Serialization/JsonTools.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Editor/Cooker/PlatformTools.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/ProjectInfo.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Editor/Utilities/EditorUtilities.h"
|
||||
#if PLATFORM_MAC
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
@@ -127,7 +128,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
|
||||
const String dst = dstPath / StringUtils::GetFileName(file);
|
||||
if (dst == file)
|
||||
continue;
|
||||
if (FileSystem::CopyFile(dst, file))
|
||||
if (EditorUtilities::CopyFileIfNewer(dst, file))
|
||||
{
|
||||
data.Error(String::Format(TEXT("Failed to copy file from {0} to {1}."), file, dst));
|
||||
return true;
|
||||
@@ -189,7 +190,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
|
||||
const String logFile = data.CacheDirectory / TEXT("CompileLog.txt");
|
||||
auto args = String::Format(
|
||||
TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5} {6}"),
|
||||
target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), GAME_BUILD_DOTNET_VER);
|
||||
target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), data.GetDotnetCommandArg());
|
||||
#if PLATFORM_WINDOWS
|
||||
if (data.Platform == BuildPlatform::LinuxX64)
|
||||
#elif PLATFORM_LINUX
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "Engine/Engine/Base/GameBase.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/Enums.h"
|
||||
#if PLATFORM_TOOLS_WINDOWS
|
||||
@@ -525,6 +526,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
|
||||
#if PLATFORM_TOOLS_XBOX_SCARLETT
|
||||
case BuildPlatform::XboxScarlett:
|
||||
{
|
||||
options.Platform = PlatformType::XboxScarlett;
|
||||
const char* platformDefineName = "PLATFORM_XBOX_SCARLETT";
|
||||
COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE);
|
||||
break;
|
||||
@@ -1366,7 +1368,10 @@ bool CookAssetsStep::Perform(CookingData& data)
|
||||
{
|
||||
typeName = e.TypeName;
|
||||
}
|
||||
LOG(Info, "{0}: {1:>4} assets of total size {2}", typeName, e.Count, Utilities::BytesToText(e.ContentSize));
|
||||
if (e.Count == 1)
|
||||
LOG(Info, "{0}: 1 asset of total size {1}", typeName, Utilities::BytesToText(e.ContentSize));
|
||||
else
|
||||
LOG(Info, "{0}: {1:>4} assets of total size {2}", typeName, e.Count, Utilities::BytesToText(e.ContentSize));
|
||||
}
|
||||
LOG(Info, "");
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
{
|
||||
// Ask Flax.Build to provide .NET SDK location for the current platform
|
||||
String sdks;
|
||||
bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory);
|
||||
bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), data.GetDotnetCommandArg()), data.CacheDirectory);
|
||||
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
|
||||
int32 idx = sdks.Find(TEXT("DotNetSdk, "), StringSearchCase::CaseSensitive);
|
||||
if (idx != -1)
|
||||
@@ -200,7 +200,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
String sdks;
|
||||
const Char *platformName, *archName;
|
||||
data.GetBuildPlatformName(platformName, archName);
|
||||
String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, GAME_BUILD_DOTNET_VER);
|
||||
String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, data.GetDotnetCommandArg());
|
||||
bool failed = ScriptsBuilder::RunBuildTool(args, data.CacheDirectory);
|
||||
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
|
||||
Array<String> parts;
|
||||
@@ -244,10 +244,13 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
}
|
||||
if (version.IsEmpty())
|
||||
{
|
||||
int32 minVer = GAME_BUILD_DOTNET_RUNTIME_MIN_VER, maxVer = GAME_BUILD_DOTNET_RUNTIME_MAX_VER;
|
||||
if (srcDotnetFromEngine)
|
||||
{
|
||||
// Detect version from runtime files inside Engine Platform folder
|
||||
for (int32 i = GAME_BUILD_DOTNET_RUNTIME_MAX_VER; i >= GAME_BUILD_DOTNET_RUNTIME_MIN_VER; i--)
|
||||
if (data.Tools->GetDotnetVersion() != 0)
|
||||
minVer = maxVer = data.Tools->GetDotnetVersion();
|
||||
for (int32 i = maxVer; i >= minVer; i--)
|
||||
{
|
||||
// Check runtime files inside Engine Platform folder
|
||||
String testPath1 = srcDotnet / String::Format(TEXT("lib/net{}.0"), i);
|
||||
@@ -262,7 +265,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
}
|
||||
if (version.IsEmpty())
|
||||
{
|
||||
data.Error(String::Format(TEXT("Failed to find supported .NET {} version for the current host platform."), GAME_BUILD_DOTNET_RUNTIME_MIN_VER));
|
||||
data.Error(String::Format(TEXT("Failed to find supported .NET {} version (min {}) for {} platform."), maxVer, minVer, platformName));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -364,7 +367,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt");
|
||||
String args = String::Format(
|
||||
TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\" {}"),
|
||||
logFile, data.DataOutputPath, GAME_BUILD_DOTNET_VER);
|
||||
logFile, data.DataOutputPath, data.GetDotnetCommandArg());
|
||||
for (const String& define : data.CustomDefines)
|
||||
{
|
||||
args += TEXT(" -D");
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
|
||||
{
|
||||
const DotNetAOTModes aotMode = data.Tools->UseAOT();
|
||||
if (aotMode == DotNetAOTModes::None)
|
||||
if (aotMode == DotNetAOTModes::None || EnumHasAllFlags(data.Options, BuildOptions::NoCook))
|
||||
return;
|
||||
const auto& buildSettings = *BuildSettings::Get();
|
||||
|
||||
@@ -59,6 +59,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
|
||||
data.StepProgress(infoMsg, 0);
|
||||
|
||||
// Override Newtonsoft.Json with AOT-version (one that doesn't use System.Reflection.Emit)
|
||||
// TODO: remove it since EngineModule does properly reference AOT lib now
|
||||
EditorUtilities::CopyFileIfNewer(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.dll"), Globals::StartupFolder / TEXT("Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll"));
|
||||
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.xml"));
|
||||
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.pdb"));
|
||||
@@ -69,7 +70,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
|
||||
const String logFile = data.CacheDirectory / TEXT("AOTLog.txt");
|
||||
String args = String::Format(
|
||||
TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\" {}"),
|
||||
logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, GAME_BUILD_DOTNET_VER);
|
||||
logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, data.GetDotnetCommandArg());
|
||||
if (!buildSettings.SkipUnusedDotnetLibsPackaging)
|
||||
args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs)
|
||||
for (const String& define : data.CustomDefines)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Types/Stopwatch.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
@@ -69,6 +71,7 @@ MTypeObject* CustomEditorsUtil::GetCustomEditor(MTypeObject* refType)
|
||||
|
||||
bool CustomEditorsUtilService::Init()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
TRACK_ASSEMBLY(((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly);
|
||||
Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded);
|
||||
|
||||
@@ -77,6 +80,8 @@ bool CustomEditorsUtilService::Init()
|
||||
|
||||
void OnAssemblyLoaded(MAssembly* assembly)
|
||||
{
|
||||
PROFILE_CPU_NAMED("CustomEditors.OnAssemblyLoaded");
|
||||
PROFILE_MEM(Editor);
|
||||
Stopwatch stopwatch;
|
||||
|
||||
// Prepare FlaxEngine
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
public class AudioSourceEditor : ActorEditor
|
||||
{
|
||||
private Label _infoLabel;
|
||||
private Slider _slider;
|
||||
private AudioSource.States _slideStartState;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
@@ -28,6 +30,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
_infoLabel = playbackGroup.Label(string.Empty).Label;
|
||||
_infoLabel.AutoHeight = true;
|
||||
|
||||
// Play back slider
|
||||
var sliderElement = playbackGroup.CustomContainer<Slider>();
|
||||
_slider = sliderElement.CustomControl;
|
||||
_slider.ThumbSize = new Float2(_slider.ThumbSize.X * 0.5f, _slider.ThumbSize.Y);
|
||||
_slider.SlidingStart += OnSlidingStart;
|
||||
_slider.SlidingEnd += OnSlidingEnd;
|
||||
|
||||
var grid = playbackGroup.UniformGrid();
|
||||
var gridControl = grid.CustomControl;
|
||||
gridControl.ClipChildren = false;
|
||||
@@ -40,6 +49,38 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlidingEnd()
|
||||
{
|
||||
foreach (var value in Values)
|
||||
{
|
||||
if (value is AudioSource audioSource && audioSource.Clip)
|
||||
{
|
||||
switch (_slideStartState)
|
||||
{
|
||||
case AudioSource.States.Playing:
|
||||
audioSource.Play();
|
||||
break;
|
||||
case AudioSource.States.Paused:
|
||||
case AudioSource.States.Stopped:
|
||||
audioSource.Pause();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlidingStart()
|
||||
{
|
||||
foreach (var value in Values)
|
||||
{
|
||||
if (value is AudioSource audioSource && audioSource.Clip)
|
||||
{
|
||||
_slideStartState = audioSource.State;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
@@ -51,7 +92,29 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
foreach (var value in Values)
|
||||
{
|
||||
if (value is AudioSource audioSource && audioSource.Clip)
|
||||
{
|
||||
text += $"Time: {audioSource.Time:##0.0}s / {audioSource.Clip.Length:##0.0}s\n";
|
||||
_slider.Maximum = audioSource.Clip.Length;
|
||||
_slider.Minimum = 0;
|
||||
if (_slider.IsSliding)
|
||||
{
|
||||
if (audioSource.State != AudioSource.States.Playing)
|
||||
{
|
||||
// Play to move slider correctly
|
||||
audioSource.Play();
|
||||
audioSource.Time = _slider.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
audioSource.Time = _slider.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_slider.Value = audioSource.Time;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
_infoLabel.Text = text;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
@@ -11,7 +12,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
[CustomEditor(typeof(EnvironmentProbe)), DefaultEditor]
|
||||
public class EnvironmentProbeEditor : ActorEditor
|
||||
{
|
||||
private FlaxEngine.GUI.Button _bake;
|
||||
private Button _bake;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
@@ -20,8 +21,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
|
||||
if (Values.HasDifferentTypes == false)
|
||||
{
|
||||
layout.Space(10);
|
||||
_bake = layout.Button("Bake").Button;
|
||||
var group = layout.Group("Bake");
|
||||
group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
|
||||
_bake = group.Button("Bake").Button;
|
||||
_bake.Clicked += BakeButtonClicked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
private void OnImageClicked(Image image, MouseButton button)
|
||||
{
|
||||
var texture = Values[0] as GPUTexture;
|
||||
if (!texture)
|
||||
if (!texture || button != MouseButton.Right)
|
||||
return;
|
||||
var menu = new ContextMenu();
|
||||
menu.AddButton("Save...", () => Screenshot.Capture(Values[0] as GPUTexture));
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="NavMeshBoundsVolume"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(NavMeshBoundsVolume)), DefaultEditor]
|
||||
internal class NavMeshBoundsVolumeEditor : ActorEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (Values.HasDifferentTypes == false)
|
||||
{
|
||||
var button = layout.Button("Build");
|
||||
button.Button.Clicked += OnBuildClicked;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBuildClicked()
|
||||
{
|
||||
foreach (var value in Values)
|
||||
{
|
||||
if (value is NavMeshBoundsVolume volume)
|
||||
{
|
||||
Navigation.BuildNavMesh(volume.Box, volume.Scene);
|
||||
Editor.Instance.Scene.MarkSceneEdited(volume.Scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Emit;
|
||||
using FlaxEditor.CustomEditors.GUI;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
ScriptName = scriptName;
|
||||
TooltipText = "Create a new script";
|
||||
DrawHighlights = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4;
|
||||
_addScriptsButton = new Button
|
||||
{
|
||||
TooltipText = "Add new scripts to the actor",
|
||||
TooltipText = "Add new scripts to the actor.",
|
||||
AnchorPreset = AnchorPresets.MiddleCenter,
|
||||
Text = buttonText,
|
||||
Parent = this,
|
||||
@@ -114,7 +115,16 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
cm.TextChanged += text =>
|
||||
{
|
||||
if (!IsValidScriptName(text))
|
||||
{
|
||||
// Remove NewScriptItems
|
||||
List<Control> newScriptItems = cm.ItemsPanel.Children.FindAll(c => c is NewScriptItem);
|
||||
foreach (var item in newScriptItems)
|
||||
{
|
||||
cm.ItemsPanel.RemoveChild(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem))
|
||||
{
|
||||
// If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time
|
||||
@@ -876,7 +886,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
// Add drag button to the group
|
||||
var scriptDrag = new DragImage
|
||||
{
|
||||
TooltipText = "Script reference",
|
||||
TooltipText = "Script reference.",
|
||||
AutoFocus = true,
|
||||
IsScrollable = false,
|
||||
Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
|
||||
@@ -899,14 +909,17 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
settingsButton.Tag = script;
|
||||
settingsButton.Clicked += OnSettingsButtonClicked;
|
||||
|
||||
group.Panel.HeaderTextMargin = new Margin(scriptDrag.Right - 12, 15, 2, 2);
|
||||
// Adjust margin to not overlap with other ui elements in the header
|
||||
group.Panel.HeaderTextMargin = group.Panel.HeaderTextMargin with { Left = scriptDrag.Right - 12, Right = settingsButton.Width + Utilities.Constants.UIMargin };
|
||||
group.Object(values, editor);
|
||||
// Remove drop down arrows and containment lines if no objects in the group
|
||||
if (group.Children.Count == 0)
|
||||
{
|
||||
group.Panel.Close();
|
||||
group.Panel.ArrowImageOpened = null;
|
||||
group.Panel.ArrowImageClosed = null;
|
||||
group.Panel.EnableContainmentLines = false;
|
||||
group.Panel.CanOpenClose = false;
|
||||
}
|
||||
|
||||
// Scripts arrange bar
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
@@ -19,8 +20,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
if (Values.HasDifferentTypes == false)
|
||||
{
|
||||
// Add 'Bake' button
|
||||
layout.Space(10);
|
||||
var button = layout.Button("Bake");
|
||||
var group = layout.Group("Bake");
|
||||
group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
|
||||
var button = group.Button("Bake");
|
||||
button.Button.Clicked += BakeButtonClicked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (Picker == null)
|
||||
return;
|
||||
var differentValues = HasDifferentValues;
|
||||
Picker.DifferentValues = differentValues;
|
||||
if (!differentValues)
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
menu.AddButton("Copy", linkedEditor.Copy);
|
||||
var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
|
||||
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
|
||||
b.Enabled = !Editor._readOnly && Editor._canResize;
|
||||
b = menu.AddButton("Paste", linkedEditor.Paste);
|
||||
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
|
||||
|
||||
@@ -407,7 +407,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
menu.AddButton("Copy", linkedEditor.Copy);
|
||||
var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
|
||||
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
|
||||
b.Enabled = !Editor._readOnly && Editor._canResize;
|
||||
var paste = menu.AddButton("Paste", linkedEditor.Paste);
|
||||
paste.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
|
||||
|
||||
@@ -422,7 +422,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
moveDownButton.Enabled = Index + 1 < Editor.Count;
|
||||
}
|
||||
|
||||
menu.AddButton("Remove", OnRemoveClicked);
|
||||
b = menu.AddButton("Remove", OnRemoveClicked);
|
||||
b.Enabled = !Editor._readOnly && Editor._canResize;
|
||||
|
||||
menu.Show(panel, location);
|
||||
}
|
||||
@@ -449,6 +450,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
protected bool NotNullItems;
|
||||
|
||||
private IntValueBox _sizeBox;
|
||||
private Label _label;
|
||||
private Color _background;
|
||||
private int _elementsCount, _minCount, _maxCount;
|
||||
private bool _readOnly;
|
||||
@@ -565,7 +567,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
Parent = dropPanel,
|
||||
};
|
||||
|
||||
var label = new Label
|
||||
_label = new Label
|
||||
{
|
||||
Text = "Size",
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
@@ -591,11 +593,12 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
panel.Panel.Offsets = new Margin(7, 7, 0, 0);
|
||||
panel.Panel.BackgroundColor = _background;
|
||||
var elementType = ElementType;
|
||||
var bindingAttr = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public;
|
||||
bool single = elementType.IsPrimitive ||
|
||||
elementType.Equals(new ScriptType(typeof(string))) ||
|
||||
elementType.IsEnum ||
|
||||
(elementType.GetFields().Length == 1 && elementType.GetProperties().Length == 0) ||
|
||||
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
|
||||
(elementType.GetFields(bindingAttr).Length == 1 && elementType.GetProperties(bindingAttr).Length == 0) ||
|
||||
(elementType.GetProperties(bindingAttr).Length == 1 && elementType.GetFields(bindingAttr).Length == 0) ||
|
||||
elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
|
||||
elementType.Equals(new ScriptType(typeof(SettingsBase)));
|
||||
if (_cachedDropPanels == null)
|
||||
@@ -649,7 +652,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
panel.Panel.Size = new Float2(0, 18);
|
||||
panel.Panel.Margin = new Margin(0, 0, Utilities.Constants.UIMargin, 0);
|
||||
|
||||
var removeButton = panel.Button("-", "Remove the last item");
|
||||
var removeButton = panel.Button("-", "Remove the last item.");
|
||||
removeButton.Button.Size = new Float2(16, 16);
|
||||
removeButton.Button.Enabled = size > _minCount;
|
||||
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||
@@ -660,7 +663,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
Resize(Count - 1);
|
||||
};
|
||||
|
||||
var addButton = panel.Button("+", "Add a new item");
|
||||
var addButton = panel.Button("+", "Add a new item.");
|
||||
addButton.Button.Size = new Float2(16, 16);
|
||||
addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount;
|
||||
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||
@@ -671,8 +674,10 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
Resize(Count + 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Layout.ContainerControl.SizeChanged += OnLayoutSizeChanged;
|
||||
}
|
||||
|
||||
private void OnSetupContextMenu(ContextMenu menu, DropPanel panel)
|
||||
{
|
||||
if (menu.Items.Any(x => x is ContextMenuButton b && b.Text.Equals("Open All", StringComparison.Ordinal)))
|
||||
@@ -695,10 +700,24 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
});
|
||||
}
|
||||
|
||||
private void OnLayoutSizeChanged(Control control)
|
||||
{
|
||||
if (Layout.ContainerControl is DropPanel dropPanel)
|
||||
{
|
||||
// Hide "Size" text when array editor title overlaps
|
||||
var headerTextSize = dropPanel.HeaderTextFont.GetFont().MeasureText(dropPanel.HeaderText);
|
||||
if (headerTextSize.X + DropPanel.DropDownIconSize >= _label.Left)
|
||||
_label.TextColor = _label.TextColorHighlighted = Color.Transparent;
|
||||
else
|
||||
_label.TextColor = _label.TextColorHighlighted = FlaxEngine.GUI.Style.Current.Foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_sizeBox = null;
|
||||
Layout.ContainerControl.SizeChanged -= OnLayoutSizeChanged;
|
||||
|
||||
base.Deinitialize();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ internal class UIControlRefPickerControl : FlaxObjectRefPickerControl
|
||||
/// <inheritdoc />
|
||||
protected override bool IsValid(Object obj)
|
||||
{
|
||||
return obj == null || (obj is UIControl control && control.Control.GetType() == ControlType);
|
||||
return obj == null || (obj is UIControl control && ControlType.IsAssignableFrom(control.Control.GetType()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
// Show prefab diff when reference value type is different
|
||||
var color = Color.Transparent;
|
||||
if (Values.HasReferenceValue && CanRevertReferenceValue && Values[0].GetType() != Values.ReferenceValue.GetType())
|
||||
if (Values.HasReferenceValue && CanRevertReferenceValue && Values[0]?.GetType() != Values.ReferenceValue?.GetType())
|
||||
color = FlaxEngine.GUI.Style.Current.BackgroundSelected;
|
||||
_typeItem.Labels[0].HighlightStripColor = color;
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
public event Action<TypePickerControl> TypePickerValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The custom callback for types validation. Cane be used to implement a rule for types to pick.
|
||||
/// The custom callback for types validation. Can be used to implement a rule for types to pick.
|
||||
/// </summary>
|
||||
public Func<ScriptType, bool> CheckValid;
|
||||
|
||||
@@ -353,7 +353,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
if (!string.IsNullOrEmpty(typeReference.CheckMethod))
|
||||
{
|
||||
var parentType = ParentEditor.Values[0].GetType();
|
||||
var parentEditor = ParentEditor;
|
||||
// Find actual parent editor if parent editor is collection editor
|
||||
while (parentEditor.GetType().IsAssignableTo(typeof(CollectionEditor)))
|
||||
parentEditor = parentEditor.ParentEditor;
|
||||
|
||||
var parentType = parentEditor.Values[0].GetType();
|
||||
|
||||
var method = parentType.GetMethod(typeReference.CheckMethod, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method != null)
|
||||
{
|
||||
|
||||
@@ -44,7 +44,8 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
{
|
||||
var style = Style.Current;
|
||||
var settingsButtonSize = Panel.HeaderHeight;
|
||||
return new Image
|
||||
Panel.HeaderTextMargin = Panel.HeaderTextMargin with { Right = settingsButtonSize + Utilities.Constants.UIMargin };
|
||||
; return new Image
|
||||
{
|
||||
TooltipText = "Settings",
|
||||
AutoFocus = true,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/ShadowsOfMordor/Builder.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
#if PLATFORM_LINUX
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
@@ -47,6 +48,7 @@ void Editor::CloseSplashScreen()
|
||||
|
||||
bool Editor::CheckProjectUpgrade()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
const auto versionFilePath = Globals::ProjectCacheFolder / TEXT("version");
|
||||
|
||||
// Load version cache file
|
||||
@@ -266,8 +268,8 @@ bool Editor::CheckProjectUpgrade()
|
||||
// Check if last version was older
|
||||
else if (lastVersion.Major < FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor < FLAXENGINE_VERSION_MINOR))
|
||||
{
|
||||
LOG(Warning, "The project was opened with the older editor version last time");
|
||||
const auto result = MessageBox::Show(TEXT("The project was opened with the older editor version last time. Loading it may modify existing data so older editor version won't open it. Do you want to perform a backup before or cancel operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
|
||||
LOG(Warning, "The project was last opened with an older editor version");
|
||||
const auto result = MessageBox::Show(TEXT("The project was last opened with an older editor version.\nLoading it may modify existing data, which can result in older editor versions being unable to open it.\n\nDo you want to perform a backup before or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
|
||||
if (result == DialogResult::Yes)
|
||||
{
|
||||
if (BackupProject())
|
||||
@@ -289,8 +291,8 @@ bool Editor::CheckProjectUpgrade()
|
||||
// Check if last version was newer
|
||||
else if (lastVersion.Major > FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor > FLAXENGINE_VERSION_MINOR))
|
||||
{
|
||||
LOG(Warning, "The project was opened with the newer editor version last time");
|
||||
const auto result = MessageBox::Show(TEXT("The project was opened with the newer editor version last time. Loading it may fail and corrupt existing data. Do you want to perform a backup before or cancel operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
|
||||
LOG(Warning, "The project was last opened with a newer editor version");
|
||||
const auto result = MessageBox::Show(TEXT("The project was last opened with a newer editor version.\nLoading it may fail and corrupt existing data.\n\nDo you want to perform a backup before loading or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
|
||||
if (result == DialogResult::Yes)
|
||||
{
|
||||
if (BackupProject())
|
||||
@@ -366,6 +368,8 @@ bool Editor::BackupProject()
|
||||
|
||||
int32 Editor::LoadProduct()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
// Flax Editor product
|
||||
Globals::ProductName = TEXT("Flax Editor");
|
||||
Globals::CompanyName = TEXT("Flax");
|
||||
@@ -626,6 +630,7 @@ int32 Editor::LoadProduct()
|
||||
|
||||
Window* Editor::CreateMainWindow()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
Window* window = Managed->GetMainWindow();
|
||||
|
||||
#if PLATFORM_LINUX
|
||||
@@ -662,6 +667,7 @@ bool Editor::Init()
|
||||
return true;
|
||||
}
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
// If during last lightmaps baking engine crashed we could try to restore the progress
|
||||
ShadowsOfMordor::Builder::Instance()->CheckIfRestoreState();
|
||||
@@ -693,11 +699,13 @@ bool Editor::Init()
|
||||
|
||||
void Editor::BeforeRun()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
Managed->BeforeRun();
|
||||
}
|
||||
|
||||
void Editor::BeforeExit()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
CloseSplashScreen();
|
||||
|
||||
Managed->Exit();
|
||||
@@ -708,6 +716,8 @@ void Editor::BeforeExit()
|
||||
|
||||
void EditorImpl::OnUpdate()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
// Update c# editor
|
||||
Editor::Managed->Update();
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ using FlaxEngine.Assertions;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Interop;
|
||||
using FlaxEngine.Json;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
@@ -51,6 +52,7 @@ namespace FlaxEditor
|
||||
private readonly List<EditorModule> _modules = new List<EditorModule>(16);
|
||||
private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode, _autoExit;
|
||||
private string _projectToOpen;
|
||||
private bool _projectIsNew;
|
||||
private float _lastAutoSaveTimer, _autoExitTimeout = 0.1f;
|
||||
private Button _saveNowButton;
|
||||
private Button _cancelSaveButton;
|
||||
@@ -737,11 +739,12 @@ namespace FlaxEditor
|
||||
var procSettings = new CreateProcessSettings
|
||||
{
|
||||
FileName = Platform.ExecutableFilePath,
|
||||
Arguments = string.Format("-project \"{0}\"", _projectToOpen),
|
||||
Arguments = string.Format("-project \"{0}\"" + (_projectIsNew ? " -new" : string.Empty), _projectToOpen),
|
||||
ShellExecute = true,
|
||||
WaitForEnd = false,
|
||||
HiddenWindow = false,
|
||||
};
|
||||
_projectIsNew = false;
|
||||
_projectToOpen = null;
|
||||
Platform.CreateProcess(ref procSettings);
|
||||
}
|
||||
@@ -790,6 +793,24 @@ namespace FlaxEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the given project. Afterwards closes this project with running editor and opens the given project.
|
||||
/// </summary>
|
||||
/// <param name="projectFilePath">The project file path.</param>
|
||||
public void NewProject(string projectFilePath)
|
||||
{
|
||||
if (projectFilePath == null)
|
||||
{
|
||||
MessageBox.Show("Missing project");
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache project path and start editor exit (it will open new instance on valid closing)
|
||||
_projectToOpen = StringUtils.NormalizePath(Path.GetDirectoryName(projectFilePath));
|
||||
_projectIsNew = true;
|
||||
Windows.MainWindow.Close(ClosingReason.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes this project with running editor and opens the given project.
|
||||
/// </summary>
|
||||
@@ -1350,7 +1371,7 @@ namespace FlaxEditor
|
||||
public void BuildCSG()
|
||||
{
|
||||
var scenes = Level.Scenes;
|
||||
scenes.ToList().ForEach(x => x.BuildCSG(0));
|
||||
scenes.ForEach(x => x.BuildCSG(0));
|
||||
Scene.MarkSceneEdited(scenes);
|
||||
}
|
||||
|
||||
@@ -1360,7 +1381,7 @@ namespace FlaxEditor
|
||||
public void BuildNavMesh()
|
||||
{
|
||||
var scenes = Level.Scenes;
|
||||
scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
|
||||
Navigation.BuildNavMesh();
|
||||
Scene.MarkSceneEdited(scenes);
|
||||
}
|
||||
|
||||
@@ -1370,6 +1391,7 @@ namespace FlaxEditor
|
||||
public void BuildAllMeshesSDF()
|
||||
{
|
||||
var models = new List<Model>();
|
||||
var forceRebuild = Input.GetKey(KeyboardKeys.F);
|
||||
Scene.ExecuteOnGraph(node =>
|
||||
{
|
||||
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
|
||||
@@ -1379,7 +1401,7 @@ namespace FlaxEditor
|
||||
model != null &&
|
||||
!models.Contains(model) &&
|
||||
!model.IsVirtual &&
|
||||
model.SDF.Texture == null)
|
||||
(forceRebuild || model.SDF.Texture == null))
|
||||
{
|
||||
models.Add(model);
|
||||
}
|
||||
@@ -1392,7 +1414,17 @@ namespace FlaxEditor
|
||||
{
|
||||
var model = models[i];
|
||||
Log($"[{i}/{models.Count}] Generating SDF for {model}");
|
||||
if (!model.GenerateSDF())
|
||||
float resolutionScale = 1.0f, backfacesThreshold = 0.6f;
|
||||
int lodIndex = 6;
|
||||
bool useGPU = true;
|
||||
var sdf = model.SDF;
|
||||
if (sdf.Texture != null)
|
||||
{
|
||||
// Preserve options set on this model
|
||||
resolutionScale = sdf.ResolutionScale;
|
||||
lodIndex = sdf.LOD;
|
||||
}
|
||||
if (!model.GenerateSDF(resolutionScale, lodIndex, true, backfacesThreshold, useGPU))
|
||||
model.Save();
|
||||
}
|
||||
});
|
||||
@@ -1567,7 +1599,7 @@ namespace FlaxEditor
|
||||
if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null)
|
||||
result = dockedTo.SelectedTab.Size;
|
||||
else
|
||||
result = gameWin.Viewport.Size;
|
||||
result = gameWin.Viewport.ContentSize;
|
||||
|
||||
result *= root.DpiScale;
|
||||
result = Float2.Round(result);
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace FlaxEditor.GUI
|
||||
/// <summary>
|
||||
/// The column title horizontal text alignment
|
||||
/// </summary>
|
||||
public TextAlignment TitleAlignment = TextAlignment.Near;
|
||||
public TextAlignment TitleAlignment = TextAlignment.Center;
|
||||
|
||||
/// <summary>
|
||||
/// The column title margin.
|
||||
|
||||
@@ -502,6 +502,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
// Keyboard navigation around the menu
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.ArrowDown:
|
||||
@@ -526,6 +527,20 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.ArrowRight:
|
||||
for (int i = 0; i < _panel.Children.Count; i++)
|
||||
{
|
||||
if (_panel.Children[i] is ContextMenuChildMenu item && item.Visible && item.IsFocused && !item.ContextMenu.IsOpened)
|
||||
{
|
||||
item.ShowChild(this);
|
||||
item.ContextMenu._panel.Children.FirstOrDefault(x => x is ContextMenuButton && x.Visible)?.Focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.ArrowLeft:
|
||||
ParentCM?.RootWindow.Focus();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -72,6 +72,11 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
/// </summary>
|
||||
public bool HasChildCMOpened => _childCM != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent context menu (if exists).
|
||||
/// </summary>
|
||||
public ContextMenuBase ParentCM => _parentCM;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the topmost context menu.
|
||||
/// </summary>
|
||||
@@ -81,9 +86,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
{
|
||||
var cm = this;
|
||||
while (cm._parentCM != null && cm._isSubMenu)
|
||||
{
|
||||
cm = cm._parentCM;
|
||||
}
|
||||
return cm;
|
||||
}
|
||||
}
|
||||
@@ -108,15 +111,21 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
/// </summary>
|
||||
public bool UseInput = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional flag that can disable UI navigation (tab/enter).
|
||||
/// </summary>
|
||||
public bool UseNavigation = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContextMenuBase"/> class.
|
||||
/// </summary>
|
||||
public ContextMenuBase()
|
||||
: base(0, 0, 120, 32)
|
||||
{
|
||||
_direction = ContextMenuDirection.RightDown;
|
||||
Visible = false;
|
||||
AutoFocus = true;
|
||||
|
||||
_direction = ContextMenuDirection.RightDown;
|
||||
_isSubMenu = true;
|
||||
}
|
||||
|
||||
@@ -593,6 +602,21 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
case KeyboardKeys.Escape:
|
||||
Hide();
|
||||
return true;
|
||||
case KeyboardKeys.Return:
|
||||
if (UseNavigation && Root?.FocusedControl != null)
|
||||
{
|
||||
Root.SubmitFocused();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.Tab:
|
||||
if (UseNavigation && Root != null)
|
||||
{
|
||||
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
|
||||
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
CloseMenuOnClick = false;
|
||||
}
|
||||
|
||||
private void ShowChild(ContextMenu parentContextMenu)
|
||||
internal void ShowChild(ContextMenu parentContextMenu)
|
||||
{
|
||||
// Hide parent CM popups and set itself as child
|
||||
var vAlign = parentContextMenu.ItemsAreaMargin.Top;
|
||||
|
||||
@@ -522,6 +522,16 @@ namespace FlaxEditor.GUI
|
||||
cm.AddButton("Show whole curve", _editor.ShowWholeCurve);
|
||||
cm.AddButton("Reset view", _editor.ResetView);
|
||||
}
|
||||
cm.AddSeparator();
|
||||
var presetCm = cm.AddChildMenu("Apply preset");
|
||||
foreach (var value in Enum.GetValues(typeof(CurvePreset)))
|
||||
{
|
||||
CurvePreset preset = (CurvePreset)value;
|
||||
string name = Utilities.Utils.GetPropertyNameUI(preset.ToString());
|
||||
var b = presetCm.ContextMenu.AddButton(name, () => _editor.ApplyPreset(preset));
|
||||
b.Enabled = !(_editor is LinearCurveEditor<T> && (preset != CurvePreset.Constant && preset != CurvePreset.Linear));
|
||||
}
|
||||
|
||||
_editor.OnShowContextMenu(cm, selectionCount);
|
||||
cm.Show(this, location);
|
||||
}
|
||||
@@ -619,6 +629,33 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of avaliable curve presets for the <see cref="CurveEditor{T}"/>.
|
||||
/// </summary>
|
||||
public enum CurvePreset
|
||||
{
|
||||
/// <summary>
|
||||
/// A curve where every point has the same value.
|
||||
/// </summary>
|
||||
Constant,
|
||||
/// <summary>
|
||||
/// A curve linear curve.
|
||||
/// </summary>
|
||||
Linear,
|
||||
/// <summary>
|
||||
/// A curve that starts a slowly and then accelerates until the end.
|
||||
/// </summary>
|
||||
EaseIn,
|
||||
/// <summary>
|
||||
/// A curve that starts a steep and then flattens until the end.
|
||||
/// </summary>
|
||||
EaseOut,
|
||||
/// <summary>
|
||||
/// A combination of the <see cref="CurvePreset.EaseIn"/> and <see cref="CurvePreset.EaseOut"/> preset.
|
||||
/// </summary>
|
||||
Smoothstep
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnKeyframesDeselect(IKeyframesEditor editor)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,48 @@ namespace FlaxEditor.GUI
|
||||
/// <seealso cref="CurveEditorBase" />
|
||||
public abstract partial class CurveEditor<T> : CurveEditorBase where T : new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single point in a <see cref="CurveEditorPreset"/>.
|
||||
/// </summary>
|
||||
protected struct CurvePresetPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The time.
|
||||
/// </summary>
|
||||
public float Time;
|
||||
|
||||
/// <summary>
|
||||
/// The value.
|
||||
/// </summary>
|
||||
public float Value;
|
||||
|
||||
/// <summary>
|
||||
/// The in tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
|
||||
/// </summary>
|
||||
public float TangentIn;
|
||||
|
||||
/// <summary>
|
||||
/// The out tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
|
||||
/// </summary>
|
||||
public float TangentOut;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A curve preset.
|
||||
/// </summary>
|
||||
protected struct CurveEditorPreset()
|
||||
{
|
||||
/// <summary>
|
||||
/// If the tangents will be linear or smooth.
|
||||
/// </summary>
|
||||
public bool LinearTangents;
|
||||
|
||||
/// <summary>
|
||||
/// The points of the preset.
|
||||
/// </summary>
|
||||
public List<CurvePresetPoint> Points;
|
||||
}
|
||||
|
||||
private class Popup : ContextMenuBase
|
||||
{
|
||||
private CustomEditorPresenter _presenter;
|
||||
@@ -26,11 +68,12 @@ namespace FlaxEditor.GUI
|
||||
private List<int> _keyframeIndices;
|
||||
private bool _isDirty;
|
||||
|
||||
public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float height = 140.0f)
|
||||
: this(editor, height)
|
||||
public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float maxHeight = 140.0f)
|
||||
: this(editor, maxHeight)
|
||||
{
|
||||
_presenter.Select(selection);
|
||||
_presenter.OpenAllGroups();
|
||||
Size = new Float2(Size.X, Mathf.Min(_presenter.ContainerControl.Size.Y, maxHeight));
|
||||
_keyframeIndices = keyframeIndices;
|
||||
if (keyframeIndices != null && selection.Length != keyframeIndices.Count)
|
||||
throw new Exception();
|
||||
@@ -169,7 +212,7 @@ namespace FlaxEditor.GUI
|
||||
if (IsSelected)
|
||||
color = Editor.ContainsFocus ? style.SelectionBorder : Color.Lerp(style.ForegroundDisabled, style.SelectionBorder, 0.4f);
|
||||
if (IsMouseOver)
|
||||
color *= 1.1f;
|
||||
color *= 1.5f;
|
||||
Render2D.FillRectangle(rect, color);
|
||||
}
|
||||
|
||||
@@ -285,7 +328,7 @@ namespace FlaxEditor.GUI
|
||||
/// <summary>
|
||||
/// The keyframes size.
|
||||
/// </summary>
|
||||
protected static readonly Float2 KeyframesSize = new Float2(7.0f);
|
||||
protected static readonly Float2 KeyframesSize = new Float2(8.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The colors for the keyframe points.
|
||||
@@ -326,6 +369,63 @@ namespace FlaxEditor.GUI
|
||||
private Color _labelsColor;
|
||||
private Font _labelsFont;
|
||||
|
||||
/// <summary>
|
||||
/// Preset values for <see cref="CurvePreset"/> to be applied to a <see cref="CurveEditor{T}"/>.
|
||||
/// </summary>
|
||||
protected Dictionary<CurvePreset, CurveEditorPreset> Presets = new Dictionary<CurvePreset, CurveEditorPreset>
|
||||
{
|
||||
{ CurvePreset.Constant, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = true,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.EaseIn, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = false,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = -1.4f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.EaseOut, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = false,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 1.4f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.Linear, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = true,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
{ CurvePreset.Smoothstep, new CurveEditorPreset
|
||||
{
|
||||
LinearTangents = false,
|
||||
Points = new List<CurvePresetPoint>
|
||||
{
|
||||
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
|
||||
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The keyframe UI points.
|
||||
/// </summary>
|
||||
@@ -568,6 +668,28 @@ namespace FlaxEditor.GUI
|
||||
/// <param name="indicesToRemove">The list of indices of the keyframes to remove.</param>
|
||||
protected abstract void RemoveKeyframesInternal(HashSet<int> indicesToRemove);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert a float to the type of the type wildcard of the curve editor.
|
||||
/// </summary>
|
||||
/// <param name="value">The float.</param>
|
||||
/// <returns>The converted value.</returns>
|
||||
public static object ConvertCurvePresetValueToCurveEditorType(float value)
|
||||
{
|
||||
if (typeof(T) == typeof(Float2))
|
||||
return new Float2(value);
|
||||
if (typeof(T) == typeof(Float3))
|
||||
return new Float3(value);
|
||||
if (typeof(T) == typeof(Float4))
|
||||
return new Float4(value);
|
||||
if (typeof(T) == typeof(Vector2))
|
||||
return new Vector2(value);
|
||||
if (typeof(T) == typeof(Vector3))
|
||||
return new Vector3(value);
|
||||
if (typeof(T) == typeof(Vector4))
|
||||
return new Vector4(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when showing a context menu. Can be used to add custom buttons with actions.
|
||||
/// </summary>
|
||||
@@ -752,6 +874,17 @@ namespace FlaxEditor.GUI
|
||||
ShowCurve(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a <see cref="CurvePreset"/> to the curve editor.
|
||||
/// </summary>
|
||||
/// <param name="preset">The preset.</param>
|
||||
public virtual void ApplyPreset(CurvePreset preset)
|
||||
{
|
||||
// Remove existing keyframes
|
||||
SelectAll();
|
||||
RemoveKeyframes();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Evaluate(out object result, float time, bool loop = false)
|
||||
{
|
||||
@@ -1028,6 +1161,31 @@ namespace FlaxEditor.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
bool left = key == KeyboardKeys.ArrowLeft;
|
||||
bool right = key == KeyboardKeys.ArrowRight;
|
||||
bool up = key == KeyboardKeys.ArrowUp;
|
||||
bool down = key == KeyboardKeys.ArrowDown;
|
||||
|
||||
if (left || right || up || down)
|
||||
{
|
||||
bool shift = Root.GetKey(KeyboardKeys.Shift);
|
||||
bool alt = Root.GetKey(KeyboardKeys.Alt);
|
||||
float deltaValue = 10f;
|
||||
if (shift || alt)
|
||||
deltaValue = shift ? 2.5f : 5f;
|
||||
|
||||
Float2 moveDelta = Float2.Zero;
|
||||
if (left || right)
|
||||
moveDelta.X = left ? -deltaValue : deltaValue;
|
||||
if (up || down)
|
||||
moveDelta.Y = up ? -deltaValue : deltaValue;
|
||||
|
||||
_contents.OnMoveStart(Float2.Zero);
|
||||
_contents.OnMove(moveDelta);
|
||||
_contents.OnMoveEnd(Float2.Zero);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1526,6 +1684,22 @@ namespace FlaxEditor.GUI
|
||||
_tangents[i].Visible = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ApplyPreset(CurvePreset preset)
|
||||
{
|
||||
base.ApplyPreset(preset);
|
||||
|
||||
CurveEditorPreset data = Presets[preset];
|
||||
foreach (var point in data.Points)
|
||||
{
|
||||
float time = point.Time;
|
||||
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
|
||||
AddKeyframe(time, value);
|
||||
}
|
||||
|
||||
ShowWholeCurve();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawCurve(ref Rectangle viewRect)
|
||||
{
|
||||
@@ -2312,6 +2486,30 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ApplyPreset(CurvePreset preset)
|
||||
{
|
||||
base.ApplyPreset(preset);
|
||||
|
||||
CurveEditorPreset data = Presets[preset];
|
||||
|
||||
foreach (var point in data.Points)
|
||||
{
|
||||
float time = point.Time;
|
||||
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
|
||||
object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)point.TangentIn);
|
||||
object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)point.TangentOut);
|
||||
|
||||
AddKeyframe(time, value, tangentIn, tangentOut);
|
||||
}
|
||||
|
||||
SelectAll();
|
||||
if (data.LinearTangents)
|
||||
SetTangentsLinear();
|
||||
|
||||
ShowWholeCurve();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void SetScaleInternal(ref Float2 scale)
|
||||
{
|
||||
|
||||
@@ -76,6 +76,8 @@ namespace FlaxEditor.GUI.Dialogs
|
||||
public ColorSelector(float wheelSize)
|
||||
: base(0, 0, wheelSize, wheelSize)
|
||||
{
|
||||
AutoFocus = true;
|
||||
|
||||
_colorWheelSprite = Editor.Instance.Icons.ColorWheel128;
|
||||
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
var childPanels = _childPanels.ToArray();
|
||||
if (childPanels.Length != 0)
|
||||
{
|
||||
// Move tabs from child panels into this one
|
||||
// Fallback: move tabs from child panels into this one.
|
||||
DockWindow selectedTab = null;
|
||||
foreach (var childPanel in childPanels)
|
||||
{
|
||||
@@ -490,7 +490,8 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
// Unlink splitter
|
||||
var splitterParent = splitter.Parent;
|
||||
Assert.IsNotNull(splitterParent);
|
||||
if (splitterParent == null)
|
||||
return;
|
||||
splitter.Parent = null;
|
||||
|
||||
// Move controls from second split panel to the split panel parent
|
||||
@@ -507,17 +508,63 @@ namespace FlaxEditor.GUI.Docking
|
||||
splitter.Dispose();
|
||||
}
|
||||
}
|
||||
else if (IsMaster && _childPanels.Count != 0)
|
||||
{
|
||||
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
|
||||
return;
|
||||
}
|
||||
else if (!IsMaster)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
else if (_childPanels.Count != 0)
|
||||
{
|
||||
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
|
||||
return;
|
||||
}
|
||||
else if (!IsMaster)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool CollapseEmptyTabsProxy()
|
||||
{
|
||||
if (TabsCount == 0 && ChildPanelsCount > 0)
|
||||
{
|
||||
return TryCollapseSplitter(_tabsProxy?.Parent as Panel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryCollapseSplitter(Panel removedPanelParent)
|
||||
{
|
||||
if (removedPanelParent == null)
|
||||
return false;
|
||||
if (!(removedPanelParent.Parent is SplitPanel tabsSplitter))
|
||||
return false;
|
||||
|
||||
var splitterParent = tabsSplitter.Parent;
|
||||
if (splitterParent == null)
|
||||
return false;
|
||||
tabsSplitter.Parent = null;
|
||||
|
||||
var scrPanel = removedPanelParent == tabsSplitter.Panel2 ? tabsSplitter.Panel1 : tabsSplitter.Panel2;
|
||||
var srcPanelChildrenCount = scrPanel.ChildrenCount;
|
||||
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
|
||||
{
|
||||
scrPanel.GetChild(i).Parent = splitterParent;
|
||||
}
|
||||
Assert.IsTrue(scrPanel.ChildrenCount == 0);
|
||||
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
|
||||
|
||||
tabsSplitter.Dispose();
|
||||
if (_tabsProxy != null && _tabsProxy.Parent == removedPanelParent)
|
||||
_tabsProxy = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
|
||||
{
|
||||
DockWindow(state, window, autoSelect, splitterValue);
|
||||
|
||||
@@ -70,8 +70,6 @@ namespace FlaxEditor.GUI.Docking
|
||||
internal DockPanelProxy(DockPanel panel)
|
||||
: base(0, 0, 64, 64)
|
||||
{
|
||||
AutoFocus = false;
|
||||
|
||||
_panel = panel;
|
||||
AnchorPreset = AnchorPresets.StretchAll;
|
||||
Offsets = Margin.Zero;
|
||||
|
||||
@@ -129,11 +129,39 @@ namespace FlaxEditor.GUI.Input
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
var r = new Rectangle(0, 0, Width, Height);
|
||||
bool isTransparent = _value.A < 1;
|
||||
|
||||
Render2D.FillRectangle(r, _value);
|
||||
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
|
||||
var style = Style.Current;
|
||||
var fullRect = new Rectangle(0, 0, Width, Height);
|
||||
var colorRect = new Rectangle(0, 0, isTransparent ? Width * 0.7f : Width, Height);
|
||||
|
||||
if (isTransparent)
|
||||
{
|
||||
var alphaRect = new Rectangle(colorRect.Right, 0, Width - colorRect.Right, Height);
|
||||
|
||||
// Draw checkerboard pattern to part of the color value box
|
||||
Render2D.FillRectangle(alphaRect, Color.White);
|
||||
var smallRectSize = 7.9f;
|
||||
var numHor = Mathf.CeilToInt(alphaRect.Width / smallRectSize);
|
||||
var numVer = Mathf.CeilToInt(alphaRect.Height / smallRectSize);
|
||||
for (int i = 0; i < numHor; i++)
|
||||
{
|
||||
for (int j = 0; j < numVer; j++)
|
||||
{
|
||||
if ((i + j) % 2 == 0)
|
||||
{
|
||||
var rect = new Rectangle(alphaRect.X + smallRectSize * i, alphaRect.Y + smallRectSize * j, new Float2(smallRectSize));
|
||||
Render2D.PushClip(alphaRect);
|
||||
Render2D.FillRectangle(rect, Color.Gray);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
}
|
||||
}
|
||||
Render2D.FillRectangle(alphaRect, _value);
|
||||
}
|
||||
|
||||
Render2D.FillRectangle(colorRect, _value with { A = 1 });
|
||||
Render2D.DrawRectangle(fullRect, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -368,6 +368,8 @@ namespace FlaxEditor.GUI.Input
|
||||
public SliderControl(float value, float x = 0, float y = 0, float width = 120, float min = float.MinValue, float max = float.MaxValue)
|
||||
: base(x, y, width, TextBox.DefaultHeight)
|
||||
{
|
||||
AutoFocus = true;
|
||||
|
||||
_min = min;
|
||||
_max = max;
|
||||
_value = Mathf.Clamp(value, min, max);
|
||||
|
||||
@@ -99,6 +99,11 @@ namespace FlaxEditor.GUI.Input
|
||||
/// </summary>
|
||||
public event Action SlidingEnd;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, pressing the arrow up or down key increments/ decrements the value.
|
||||
/// </summary>
|
||||
public bool ArrowKeysIncrement = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the slider speed. Use value 0 to disable and hide slider UI.
|
||||
/// </summary>
|
||||
@@ -239,6 +244,27 @@ namespace FlaxEditor.GUI.Input
|
||||
ResetViewOffset();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (ArrowKeysIncrement && (key == KeyboardKeys.ArrowUp || key == KeyboardKeys.ArrowDown))
|
||||
{
|
||||
bool altDown = Root.GetKey(KeyboardKeys.Alt);
|
||||
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
|
||||
bool controlDown = Root.GetKey(KeyboardKeys.Control);
|
||||
float deltaValue = altDown ? 0.1f : (shiftDown ? 10f : (controlDown ? 100f : 1f));
|
||||
float slideDelta = key == KeyboardKeys.ArrowUp ? deltaValue : -deltaValue;
|
||||
|
||||
_startSlideValue = Value;
|
||||
ApplySliding(slideDelta);
|
||||
EndSliding();
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
public float SortScore;
|
||||
|
||||
/// <summary>
|
||||
/// Wether the query highlights should be draw.
|
||||
/// </summary>
|
||||
public bool DrawHighlights = true;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when items gets clicked by the user.
|
||||
/// </summary>
|
||||
@@ -165,7 +170,7 @@ namespace FlaxEditor.GUI
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted);
|
||||
|
||||
// Draw all highlights
|
||||
if (_highlights != null)
|
||||
if (DrawHighlights && _highlights != null)
|
||||
{
|
||||
var color = style.ProgressNormal * 0.6f;
|
||||
for (int i = 0; i < _highlights.Count; i++)
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Text;
|
||||
using FlaxEditor.GUI.Timeline.Undo;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Json;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
@@ -54,7 +55,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
var paramTypeName = LoadName(stream);
|
||||
e.EventParamsTypes[i] = TypeUtils.GetManagedType(paramTypeName);
|
||||
if (e.EventParamsTypes[i] == null)
|
||||
{
|
||||
Editor.LogError($"Unknown type {paramTypeName}.");
|
||||
isInvalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInvalid)
|
||||
@@ -82,7 +86,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
for (int j = 0; j < paramsCount; j++)
|
||||
{
|
||||
stream.Read(dataBuffer, 0, e.EventParamsSizes[j]);
|
||||
key.Parameters[j] = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), e.EventParamsTypes[j]);
|
||||
key.Parameters[j] = Utilities.Utils.ByteArrayToStructure(handle.AddrOfPinnedObject(), e.EventParamsTypes[j], e.EventParamsSizes[j]);
|
||||
}
|
||||
|
||||
events[i] = new KeyframesEditor.Keyframe
|
||||
@@ -125,8 +129,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
|
||||
for (int j = 0; j < paramsCount; j++)
|
||||
{
|
||||
Marshal.StructureToPtr(key.Parameters[j], ptr, true);
|
||||
Marshal.Copy(ptr, dataBuffer, 0, e.EventParamsSizes[j]);
|
||||
Utilities.Utils.StructureToByteArray(key.Parameters[j], e.EventParamsSizes[j], ptr, dataBuffer);
|
||||
stream.Write(dataBuffer, 0, e.EventParamsSizes[j]);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +156,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// <summary>
|
||||
/// The event key data.
|
||||
/// </summary>
|
||||
public struct EventKey
|
||||
public struct EventKey : ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// The parameters values.
|
||||
@@ -178,6 +181,26 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
sb.Append(')');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Clone()
|
||||
{
|
||||
if (Parameters == null)
|
||||
return new EventKey();
|
||||
|
||||
// Deep clone parameter values (especially boxed value types need to be duplicated to avoid referencing the same ones)
|
||||
var parameters = new object[Parameters.Length];
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var p = Parameters[i];
|
||||
if (p == null || p is FlaxEngine.Object)
|
||||
parameters[i] = Parameters[i];
|
||||
else
|
||||
parameters[i] = JsonSerializer.Deserialize(JsonSerializer.Serialize(p), p.GetType());
|
||||
}
|
||||
|
||||
return new EventKey { Parameters = parameters };
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -234,6 +257,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
var time = Timeline.CurrentTime;
|
||||
if (!TryGetValue(out var value))
|
||||
value = Events.Evaluate(time);
|
||||
value = ((ICloneable)value).Clone();
|
||||
|
||||
// Find event at the current location
|
||||
for (int i = Events.Keyframes.Count - 1; i >= 0; i--)
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
{
|
||||
var time = stream.ReadSingle();
|
||||
stream.Read(dataBuffer, 0, e.ValueSize);
|
||||
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
|
||||
var value = Utilities.Utils.ByteArrayToStructure(handle.AddrOfPinnedObject(), propertyType, e.ValueSize);
|
||||
|
||||
keyframes[i] = new KeyframesEditor.Keyframe
|
||||
{
|
||||
@@ -142,8 +142,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
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);
|
||||
Utilities.Utils.StructureToByteArray(keyframe.Value, e.ValueSize, ptr, dataBuffer);
|
||||
stream.Write(keyframe.Time);
|
||||
stream.Write(dataBuffer);
|
||||
}
|
||||
|
||||
@@ -447,8 +447,8 @@ namespace FlaxEditor.GUI.Tree
|
||||
// Select previous parent child
|
||||
var select = nodeParent.GetChild(myIndex - 1) as TreeNode;
|
||||
|
||||
// Select last child if is valid and expanded and has any children
|
||||
if (select != null && select.IsExpanded && select.HasAnyVisibleChild)
|
||||
// Get bottom most child node
|
||||
while (select != null && select.IsExpanded && select.HasAnyVisibleChild)
|
||||
{
|
||||
select = select.GetChild(select.ChildrenCount - 1) as TreeNode;
|
||||
}
|
||||
|
||||
@@ -319,6 +319,8 @@ namespace FlaxEditor.GUI.Tree
|
||||
public TreeNode(bool canChangeOrder, SpriteHandle iconCollapsed, SpriteHandle iconOpened)
|
||||
: base(0, 0, 64, 16)
|
||||
{
|
||||
AutoFocus = true;
|
||||
|
||||
_canChangeOrder = canChangeOrder;
|
||||
_animationProgress = 1.0f;
|
||||
_cachedHeight = _headerHeight;
|
||||
@@ -1138,8 +1140,11 @@ namespace FlaxEditor.GUI.Tree
|
||||
ParentTree.DraggedOverNode = this;
|
||||
|
||||
// Expand node if mouse goes over arrow
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
|
||||
{
|
||||
Expand(true);
|
||||
ParentTree?.FlushPendingPerformLayout();
|
||||
}
|
||||
|
||||
result = OnDragEnterHeader(data);
|
||||
}
|
||||
@@ -1170,8 +1175,11 @@ namespace FlaxEditor.GUI.Tree
|
||||
ParentTree.DraggedOverNode = this;
|
||||
|
||||
// Expand node if mouse goes over arrow
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
|
||||
{
|
||||
Expand(true);
|
||||
ParentTree?.FlushPendingPerformLayout();
|
||||
}
|
||||
|
||||
if (!_isDragOverHeader)
|
||||
result = OnDragEnterHeader(data);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace FlaxEditor.Gizmo
|
||||
public abstract class GizmoBase
|
||||
{
|
||||
private IGizmoOwner _owner;
|
||||
private bool _visible = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gizmo owner.
|
||||
@@ -34,6 +35,11 @@ namespace FlaxEditor.Gizmo
|
||||
/// </summary>
|
||||
public virtual BoundingSphere FocusBounds => BoundingSphere.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this gizmo is visible.
|
||||
/// </summary>
|
||||
public bool Visible { get { return _visible; } set { _visible = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GizmoBase"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -155,6 +155,7 @@ namespace FlaxEditor.Gizmo
|
||||
// Ensure player is not moving objects
|
||||
if (ActiveAxis != Axis.None)
|
||||
return;
|
||||
Profiler.BeginEvent("Pick");
|
||||
|
||||
// Get mouse ray and try to hit any object
|
||||
var ray = Owner.MouseRay;
|
||||
@@ -243,6 +244,8 @@ namespace FlaxEditor.Gizmo
|
||||
{
|
||||
sceneEditing.Deselect();
|
||||
}
|
||||
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -36,11 +36,12 @@ public sealed class ViewportRubberBandSelector
|
||||
/// Triggers the start of a rubber band selection.
|
||||
/// </summary>
|
||||
/// <returns>True if selection started, otherwise false.</returns>
|
||||
public bool TryStartingRubberBandSelection()
|
||||
public bool TryStartingRubberBandSelection(Float2 mousePosition)
|
||||
{
|
||||
if (!_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
|
||||
{
|
||||
_tryStartRubberBand = true;
|
||||
_cachedStartingMousePosition = mousePosition;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -82,12 +83,15 @@ public sealed class ViewportRubberBandSelector
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tryStartRubberBand && (Mathf.Abs(_owner.MouseDelta.X) > 0.1f || Mathf.Abs(_owner.MouseDelta.Y) > 0.1f) && canStart)
|
||||
if (_tryStartRubberBand && canStart)
|
||||
{
|
||||
_isRubberBandSpanning = true;
|
||||
_cachedStartingMousePosition = mousePosition;
|
||||
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
|
||||
_tryStartRubberBand = false;
|
||||
var delta = mousePosition - _cachedStartingMousePosition;
|
||||
if (Mathf.Abs(delta.X) > 0.1f || Mathf.Abs(delta.Y) > 0.1f)
|
||||
{
|
||||
_isRubberBandSpanning = true;
|
||||
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
|
||||
_tryStartRubberBand = false;
|
||||
}
|
||||
}
|
||||
else if (_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Engine/Platform/WindowsManager.h"
|
||||
#include "Engine/Content/Assets/VisualScript.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
#include "Engine/CSG/CSGBuilder.h"
|
||||
#include "Engine/Engine/CommandLine.h"
|
||||
#include "Engine/Renderer/ProbesRenderer.h"
|
||||
@@ -75,7 +76,7 @@ void OnLightmapsBuildFinished(bool failed)
|
||||
OnLightmapsBake(ShadowsOfMordor::BuildProgressStep::GenerateLightmapCharts, 0, 0, false);
|
||||
}
|
||||
|
||||
void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
|
||||
void OnBakeEvent(bool started, Actor* e)
|
||||
{
|
||||
if (Internal_EnvProbeBake == nullptr)
|
||||
{
|
||||
@@ -83,7 +84,7 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
|
||||
ASSERT(Internal_EnvProbeBake);
|
||||
}
|
||||
|
||||
MObject* probeObj = e.Actor ? e.Actor->GetManagedInstance() : nullptr;
|
||||
MObject* probeObj = e ? e->GetManagedInstance() : nullptr;
|
||||
|
||||
MainThreadManagedInvokeAction::ParamsBuilder params;
|
||||
params.AddParam(started);
|
||||
@@ -91,12 +92,12 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
|
||||
MainThreadManagedInvokeAction::Invoke(Internal_EnvProbeBake, params);
|
||||
}
|
||||
|
||||
void OnRegisterBake(const ProbesRenderer::Entry& e)
|
||||
void OnRegisterBake(Actor* e)
|
||||
{
|
||||
OnBakeEvent(true, e);
|
||||
}
|
||||
|
||||
void OnFinishBake(const ProbesRenderer::Entry& e)
|
||||
void OnFinishBake(Actor* e)
|
||||
{
|
||||
OnBakeEvent(false, e);
|
||||
}
|
||||
@@ -157,7 +158,9 @@ ManagedEditor::ManagedEditor()
|
||||
lightmapsBuilder->OnBuildProgress.Bind<OnLightmapsBuildProgress>();
|
||||
lightmapsBuilder->OnBuildFinished.Bind<OnLightmapsBuildFinished>();
|
||||
CSG::Builder::OnBrushModified.Bind<OnBrushModified>();
|
||||
#if LOG_ENABLE
|
||||
Log::Logger::OnMessage.Bind<OnLogMessage>();
|
||||
#endif
|
||||
VisualScripting::DebugFlow.Bind<OnVisualScriptingDebugFlow>();
|
||||
}
|
||||
|
||||
@@ -173,7 +176,9 @@ ManagedEditor::~ManagedEditor()
|
||||
lightmapsBuilder->OnBuildProgress.Unbind<OnLightmapsBuildProgress>();
|
||||
lightmapsBuilder->OnBuildFinished.Unbind<OnLightmapsBuildFinished>();
|
||||
CSG::Builder::OnBrushModified.Unbind<OnBrushModified>();
|
||||
#if LOG_ENABLE
|
||||
Log::Logger::OnMessage.Unbind<OnLogMessage>();
|
||||
#endif
|
||||
VisualScripting::DebugFlow.Unbind<OnVisualScriptingDebugFlow>();
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace FlaxEditor.Modules
|
||||
if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren)
|
||||
{
|
||||
var bounds = actor.BoxWithChildren;
|
||||
Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,6 +673,7 @@ namespace FlaxEditor.Modules
|
||||
pasteAction.Do(out _, out var nodeParents);
|
||||
|
||||
// Select spawned objects (parents only)
|
||||
newSelection.Clear();
|
||||
newSelection.AddRange(nodeParents);
|
||||
var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo);
|
||||
selectAction.Do();
|
||||
@@ -711,7 +712,11 @@ namespace FlaxEditor.Modules
|
||||
|
||||
private void OnActorChildNodesDispose(ActorNode node)
|
||||
{
|
||||
if (Selection.Count == 0)
|
||||
return;
|
||||
|
||||
// TODO: cache if selection contains any actor child node and skip this loop if no need to iterate
|
||||
// TODO: or build a hash set with selected nodes for quick O(1) checks (cached until selection changes)
|
||||
|
||||
// Deselect child nodes
|
||||
for (int i = 0; i < node.ChildNodes.Count; i++)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEngine;
|
||||
@@ -662,6 +663,48 @@ namespace FlaxEditor.Modules
|
||||
//node?.TreeNode.OnActiveChanged();
|
||||
}
|
||||
|
||||
private void OnActorDestroyChildren(Actor actor)
|
||||
{
|
||||
// Instead of doing OnActorParentChanged for every child lets remove all of them at once from that actor
|
||||
ActorNode node = GetActorNode(actor);
|
||||
if (node != null)
|
||||
{
|
||||
if (Editor.SceneEditing.HasSthSelected)
|
||||
{
|
||||
// Clear selection if one of the removed actors is selected
|
||||
var selection = new HashSet<Actor>();
|
||||
foreach (var e in Editor.SceneEditing.Selection)
|
||||
{
|
||||
if (e is ActorNode q && q.Actor)
|
||||
selection.Add(q.Actor);
|
||||
}
|
||||
var count = actor.ChildrenCount;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var child = actor.GetChild(i);
|
||||
if (selection.Contains(child))
|
||||
{
|
||||
Editor.SceneEditing.Deselect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all child nodes (upfront remove all nodes to run faster)
|
||||
for (int i = 0; i < node.ChildNodes.Count; i++)
|
||||
{
|
||||
if (node.ChildNodes[i] is ActorNode child)
|
||||
child.parentNode = null;
|
||||
}
|
||||
node.TreeNode.DisposeChildren();
|
||||
for (int i = 0; i < node.ChildNodes.Count; i++)
|
||||
{
|
||||
node.ChildNodes[i].Dispose();
|
||||
}
|
||||
node.ChildNodes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor node.
|
||||
/// </summary>
|
||||
@@ -713,6 +756,7 @@ namespace FlaxEditor.Modules
|
||||
Level.ActorOrderInParentChanged += OnActorOrderInParentChanged;
|
||||
Level.ActorNameChanged += OnActorNameChanged;
|
||||
Level.ActorActiveChanged += OnActorActiveChanged;
|
||||
Level.ActorDestroyChildren += OnActorDestroyChildren;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -730,6 +774,7 @@ namespace FlaxEditor.Modules
|
||||
Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged;
|
||||
Level.ActorNameChanged -= OnActorNameChanged;
|
||||
Level.ActorActiveChanged -= OnActorActiveChanged;
|
||||
Level.ActorDestroyChildren -= OnActorDestroyChildren;
|
||||
|
||||
// Cleanup graph
|
||||
Root.Dispose();
|
||||
|
||||
@@ -125,6 +125,7 @@ namespace FlaxEditor.Modules
|
||||
private ContextMenuButton _menuToolsProfilerWindow;
|
||||
private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault;
|
||||
private ContextMenuButton _menuToolsTakeScreenshot;
|
||||
private ContextMenuButton _menuToolsOpenLocalFolder;
|
||||
private ContextMenuChildMenu _menuWindowApplyWindowLayout;
|
||||
|
||||
private ToolStripButton _toolStripSaveAll;
|
||||
@@ -638,6 +639,7 @@ namespace FlaxEditor.Modules
|
||||
_menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
|
||||
_menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile);
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("New project", NewProject);
|
||||
cm.AddButton("Open project...", OpenProject);
|
||||
cm.AddButton("Reload project", ReloadProject);
|
||||
cm.AddButton("Open project folder", () => FileSystem.ShowFileExplorer(Editor.Instance.GameProject.ProjectFolderPath));
|
||||
@@ -668,7 +670,7 @@ namespace FlaxEditor.Modules
|
||||
if (item != null)
|
||||
Editor.ContentEditing.Open(item);
|
||||
});
|
||||
cm.AddButton("Editor Options", () => Editor.Windows.EditorOptionsWin.Show());
|
||||
cm.AddButton("Editor Options", inputOptions.EditorOptionsWindow, () => Editor.Windows.EditorOptionsWin.Show());
|
||||
|
||||
// Scene
|
||||
MenuScene = MainMenu.AddButton("Scene");
|
||||
@@ -713,6 +715,7 @@ namespace FlaxEditor.Modules
|
||||
_menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG);
|
||||
_menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh);
|
||||
_menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF);
|
||||
_menuToolsBuildAllMeshesSDF.LinkTooltip("Generates Sign Distance Field texture for all meshes used in loaded scenes. Use with 'F' key pressed to force rebuild SDF for meshes with existing one.");
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
|
||||
_menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel());
|
||||
@@ -723,6 +726,16 @@ namespace FlaxEditor.Modules
|
||||
_menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot);
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show());
|
||||
cm.AddSeparator();
|
||||
var childMenu = cm.AddChildMenu("Open Product Local folder");
|
||||
childMenu.ContextMenu.AddButton("Editor", () => FileSystem.ShowFileExplorer(Globals.ProductLocalFolder));
|
||||
_menuToolsOpenLocalFolder = childMenu.ContextMenu.AddButton("Game", () =>
|
||||
{
|
||||
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
GameSettings settings = GameSettings.Load<GameSettings>();
|
||||
string path = Path.Combine(localAppData, settings.CompanyName, settings.ProductName);
|
||||
FileSystem.ShowFileExplorer(path);
|
||||
});
|
||||
|
||||
// Window
|
||||
MenuWindow = MainMenu.AddButton("Window");
|
||||
@@ -929,6 +942,17 @@ namespace FlaxEditor.Modules
|
||||
MasterPanel.Offsets = new Margin(0, 0, ToolStrip.Bottom, StatusBar.Height);
|
||||
}
|
||||
|
||||
private void NewProject()
|
||||
{
|
||||
// Ask user to create project file
|
||||
if (FileSystem.ShowSaveFileDialog(Editor.Windows.MainWindow, null, "Project files (*.flaxproj)\0*.flaxproj\0All files (*.*)\0*.*\0", false, "Create project file", out var files))
|
||||
return;
|
||||
if (files != null && files.Length > 0)
|
||||
{
|
||||
Editor.NewProject(files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenProject()
|
||||
{
|
||||
// Ask user to select project file
|
||||
@@ -1049,6 +1073,10 @@ namespace FlaxEditor.Modules
|
||||
_menuToolsBuildNavMesh.Enabled = canEdit;
|
||||
_menuToolsCancelBuilding.Enabled = GameCooker.IsRunning;
|
||||
_menuToolsSetTheCurrentSceneViewAsDefault.Enabled = Level.ScenesCount > 0;
|
||||
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
GameSettings settings = GameSettings.Load<GameSettings>();
|
||||
string path = Path.Combine(localAppData, settings.CompanyName, settings.ProductName);
|
||||
_menuToolsOpenLocalFolder.Enabled = Directory.Exists(path);
|
||||
|
||||
c.PerformLayout();
|
||||
}
|
||||
|
||||
@@ -491,10 +491,15 @@ namespace FlaxEditor.Modules
|
||||
Editor.LogWarning("Empty panel inside layout.");
|
||||
p.RemoveIt();
|
||||
}
|
||||
else
|
||||
{
|
||||
p.CollapseEmptyTabsProxy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panel.SelectTab(selectedTab);
|
||||
panel.CollapseEmptyTabsProxy();
|
||||
}
|
||||
|
||||
private static void SaveBounds(XmlWriter writer, Window win)
|
||||
@@ -896,9 +901,11 @@ namespace FlaxEditor.Modules
|
||||
|
||||
if (type.IsAssignableTo(typeof(AssetEditorWindow)))
|
||||
{
|
||||
var ctor = type.GetConstructor(new Type[] { typeof(Editor), typeof(AssetItem) });
|
||||
var assetItem = Editor.ContentDatabase.FindAsset(winData.AssetItemID);
|
||||
var assetType = assetItem.GetType();
|
||||
var ctor = type.GetConstructor(new Type[] { typeof(Editor), assetType });
|
||||
var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem });
|
||||
|
||||
win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue);
|
||||
if (winData.DockState == DockState.Float)
|
||||
{
|
||||
|
||||
@@ -387,6 +387,14 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Viewport"), EditorOrder(1760)]
|
||||
public InputBinding ToggleOrthographic = new InputBinding(KeyboardKeys.NumpadDecimal);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "G")]
|
||||
[EditorDisplay("Viewport"), EditorOrder(1770)]
|
||||
public InputBinding ToggleGameView = new InputBinding(KeyboardKeys.G);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "P")]
|
||||
[EditorDisplay("Viewport"), EditorOrder(1770)]
|
||||
public InputBinding ToggleNavMeshVisibility = new InputBinding(KeyboardKeys.P);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Views
|
||||
@@ -571,6 +579,10 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("View Flags"), EditorOrder(3260)]
|
||||
public InputBinding DebugDraw = new InputBinding(KeyboardKeys.Alpha4, KeyboardKeys.Control, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "None")]
|
||||
[EditorDisplay("View Flags"), EditorOrder(3270)]
|
||||
public InputBinding Particles = new InputBinding(KeyboardKeys.None);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interface
|
||||
@@ -650,6 +662,10 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Windows"), EditorOrder(4020)]
|
||||
public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Control+Comma")]
|
||||
[EditorDisplay("Windows"), EditorOrder(4030)]
|
||||
public InputBinding EditorOptionsWindow = new InputBinding(KeyboardKeys.Comma, KeyboardKeys.Control);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Node Editors
|
||||
|
||||
@@ -150,5 +150,26 @@ namespace FlaxEditor.Options
|
||||
[DefaultValue(typeof(Color), "0.5,0.5,0.5,1.0")]
|
||||
[EditorDisplay("Grid"), EditorOrder(310), Tooltip("The color for the viewport grid.")]
|
||||
public Color ViewportGridColor { get; set; } = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum size used for viewport icons.
|
||||
/// </summary>
|
||||
[DefaultValue(7.0f), Limit(1.0f, 1000.0f, 5.0f)]
|
||||
[EditorDisplay("Viewport Icons"), EditorOrder(400)]
|
||||
public float IconsMinimumSize { get; set; } = 7.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum size used for viewport icons.
|
||||
/// </summary>
|
||||
[DefaultValue(30.0f), Limit(1.0f, 1000.0f, 5.0f)]
|
||||
[EditorDisplay("Viewport Icons"), EditorOrder(410)]
|
||||
public float IconsMaximumSize { get; set; } = 30.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the distance towards the camera at which the max icon scale will be applied. Set to 0 to disable scaling the icons based on the distance to the camera.
|
||||
/// </summary>
|
||||
[DefaultValue(1000.0f), Limit(0.0f, 20000.0f, 5.0f)]
|
||||
[EditorDisplay("Viewport Icons"), EditorOrder(410)]
|
||||
public float MaxSizeDistance { get; set; } = 1000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Core/Math/Quaternion.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Engine/Serialization/JsonTools.h"
|
||||
#include <ThirdParty/pugixml/pugixml.hpp>
|
||||
@@ -327,6 +328,7 @@ ProjectInfo* ProjectInfo::Load(const String& path)
|
||||
}
|
||||
|
||||
// Load
|
||||
PROFILE_MEM(Editor);
|
||||
auto project = New<ProjectInfo>();
|
||||
if (project->LoadProject(path))
|
||||
{
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
if (value is BoxCollider collider)
|
||||
collider.AutoResize(!_keepLocalOrientation);
|
||||
}
|
||||
Presenter.OnModified();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -555,7 +555,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
var options = Editor.Instance.Options.Options.General;
|
||||
if (options.AutoRebuildNavMesh)
|
||||
{
|
||||
Navigation.BuildNavMesh(collider.Scene, collider.Box, options.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(collider.Box, options.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model used by this actor.
|
||||
/// </summary>
|
||||
public Model Model => ((StaticModel)Actor).Model;
|
||||
|
||||
/// <inheritdoc />
|
||||
public StaticModelNode(Actor actor)
|
||||
: base(actor)
|
||||
@@ -120,12 +125,12 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
|
||||
// Check if every selected node is a primitive
|
||||
// Check if every selected node is a primitive or has collision asset
|
||||
var selection = GetSelection(window);
|
||||
bool autoOptionEnabled = true;
|
||||
foreach (var node in selection)
|
||||
{
|
||||
if (node is StaticModelNode staticModelNode && !staticModelNode.IsPrimitive)
|
||||
if (node is StaticModelNode staticModelNode && (!staticModelNode.IsPrimitive && GetCollisionData(staticModelNode.Model) == null))
|
||||
{
|
||||
autoOptionEnabled = false;
|
||||
break;
|
||||
@@ -201,6 +206,54 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
return Array.Empty<SceneGraphNode>();
|
||||
}
|
||||
|
||||
private static bool TryCollisionData(Model model, BinaryAssetItem assetItem, out CollisionData collisionData)
|
||||
{
|
||||
collisionData = FlaxEngine.Content.Load<CollisionData>(assetItem.ID);
|
||||
if (collisionData)
|
||||
{
|
||||
var options = collisionData.Options;
|
||||
if (options.Model == model.ID || options.Model == Guid.Empty)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private CollisionData GetCollisionData(Model model)
|
||||
{
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
// Check if there already is collision data for that model to reuse
|
||||
var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
|
||||
if (modelItem?.ParentFolder != null)
|
||||
{
|
||||
foreach (var child in modelItem.ParentFolder.Children)
|
||||
{
|
||||
// Check if there is collision that was made with this model
|
||||
if (child is BinaryAssetItem b && b.IsOfType<CollisionData>())
|
||||
{
|
||||
if (TryCollisionData(model, b, out var collisionData))
|
||||
return collisionData;
|
||||
}
|
||||
|
||||
// Check if there is an auto-imported collision
|
||||
if (child is ContentFolder childFolder && childFolder.ShortName == modelItem.ShortName)
|
||||
{
|
||||
foreach (var childFolderChild in childFolder.Children)
|
||||
{
|
||||
if (childFolderChild is BinaryAssetItem c && c.IsOfType<CollisionData>())
|
||||
{
|
||||
if (TryCollisionData(model, c, out var collisionData))
|
||||
return collisionData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CreateAuto(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
{
|
||||
// Special case for in-built Editor models that can use analytical collision
|
||||
@@ -243,6 +296,15 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
collider.LocalPosition = new Vector3(0, 50.0f, 0);
|
||||
collider.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
var collider = new MeshCollider
|
||||
{
|
||||
Transform = actor.Transform,
|
||||
CollisionData = GetCollisionData(model),
|
||||
};
|
||||
spawner(collider);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateBox(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
|
||||
@@ -97,13 +97,16 @@ namespace FlaxEditor.SceneGraph
|
||||
/// <returns>Hit object or null if there is no intersection at all.</returns>
|
||||
public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None)
|
||||
{
|
||||
Profiler.BeginEvent("RayCastScene");
|
||||
var data = new RayCastData
|
||||
{
|
||||
Ray = ray,
|
||||
View = view,
|
||||
Flags = flags
|
||||
};
|
||||
return RayCast(ref data, out distance, out _);
|
||||
var result = RayCast(ref data, out distance, out _);
|
||||
Profiler.EndEvent();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,13 +120,16 @@ namespace FlaxEditor.SceneGraph
|
||||
/// <returns>Hit object or null if there is no intersection at all.</returns>
|
||||
public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, out Vector3 normal, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None)
|
||||
{
|
||||
Profiler.BeginEvent("RayCastScene");
|
||||
var data = new RayCastData
|
||||
{
|
||||
Ray = ray,
|
||||
View = view,
|
||||
Flags = flags
|
||||
};
|
||||
return RayCast(ref data, out distance, out normal);
|
||||
var result = RayCast(ref data, out distance, out normal);
|
||||
Profiler.EndEvent();
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static Quaternion RaycastNormalRotation(ref Vector3 normal)
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FlaxEditor.SceneGraph
|
||||
/// <summary>
|
||||
/// The parent node.
|
||||
/// </summary>
|
||||
protected SceneGraphNode parentNode;
|
||||
internal SceneGraphNode parentNode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children list.
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Platform/Thread.h"
|
||||
#include "Engine/Threading/IRunnable.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
|
||||
void OnAsyncBegin(Thread* thread);
|
||||
void OnAsyncEnd();
|
||||
@@ -232,6 +233,8 @@ void OnAsyncEnd()
|
||||
|
||||
bool CodeEditingManagerService::Init()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
// Try get editors
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
VisualStudioEditor::FindEditors(&CodeEditors);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/Script.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
@@ -77,7 +78,7 @@ namespace ScriptsBuilderImpl
|
||||
void onScriptsReloadEnd();
|
||||
void onScriptsLoaded();
|
||||
|
||||
void GetClassName(const StringAnsi& fullname, StringAnsi& className);
|
||||
void GetClassName(const StringAnsiView fullname, StringAnsi& className);
|
||||
|
||||
void onCodeEditorAsyncOpenBegin()
|
||||
{
|
||||
@@ -272,7 +273,7 @@ bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
|
||||
return RunBuildTool(args);
|
||||
}
|
||||
|
||||
void ScriptsBuilderImpl::GetClassName(const StringAnsi& fullname, StringAnsi& className)
|
||||
void ScriptsBuilderImpl::GetClassName(const StringAnsiView fullname, StringAnsi& className)
|
||||
{
|
||||
const auto lastDotIndex = fullname.FindLast('.');
|
||||
if (lastDotIndex != -1)
|
||||
@@ -413,6 +414,7 @@ void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*&
|
||||
|
||||
bool ScriptsBuilderImpl::compileGameScriptsAsyncInner()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
LOG(Info, "Starting scripts compilation...");
|
||||
CallEvent(EventType::CompileStarted);
|
||||
|
||||
@@ -519,6 +521,8 @@ void ScriptsBuilderImpl::onEditorAssemblyUnloading(MAssembly* assembly)
|
||||
|
||||
bool ScriptsBuilderImpl::compileGameScriptsAsync()
|
||||
{
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
// Start
|
||||
{
|
||||
ScopeLock scopeLock(_locker);
|
||||
@@ -562,6 +566,7 @@ bool ScriptsBuilderService::Init()
|
||||
// Check flag
|
||||
if (_isInited)
|
||||
return false;
|
||||
PROFILE_MEM(Editor);
|
||||
_isInited = true;
|
||||
|
||||
// Link for Editor assembly unload event to clear cached Internal_OnCompilationEnd to prevent errors
|
||||
@@ -659,6 +664,9 @@ bool ScriptsBuilderService::Init()
|
||||
|
||||
void ScriptsBuilderService::Update()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Editor);
|
||||
|
||||
// Send compilation events
|
||||
{
|
||||
ScopeLock scopeLock(_compileEventsLocker);
|
||||
|
||||
@@ -406,6 +406,8 @@ namespace FlaxEngine.Utilities
|
||||
{
|
||||
if (type == ScriptType.Null)
|
||||
return null;
|
||||
if (type.BaseType == null)
|
||||
return type.Type;
|
||||
while (type.Type == null)
|
||||
type = type.BaseType;
|
||||
return type.Type;
|
||||
|
||||
@@ -229,20 +229,20 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
|
||||
{
|
||||
// Check if show additional nodes in the current surface context
|
||||
if (activeCM != _cmStateMachineMenu)
|
||||
{
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
|
||||
|
||||
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,6 +233,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public BlendPointsEditor(Animation.MultiBlend node, bool is2D, float x, float y, float width, float height)
|
||||
: base(x, y, width, height)
|
||||
{
|
||||
AutoFocus = true;
|
||||
|
||||
_node = node;
|
||||
_is2D = is2D;
|
||||
}
|
||||
|
||||
@@ -726,7 +726,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled)
|
||||
{
|
||||
if (handled)
|
||||
if (handled || Surface.Context != Context)
|
||||
return;
|
||||
|
||||
// Check click over the connection
|
||||
@@ -751,7 +751,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
private void OnSurfaceMouseDoubleClick(ref Float2 mouse, MouseButton buttons, ref bool handled)
|
||||
{
|
||||
if (handled)
|
||||
if (handled || Surface.Context != Context)
|
||||
return;
|
||||
|
||||
// Check double click over the connection
|
||||
|
||||
@@ -6,6 +6,7 @@ using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
@@ -123,7 +124,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
case MaterialDomain.Particle:
|
||||
case MaterialDomain.Deformable:
|
||||
{
|
||||
bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit;
|
||||
bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit && info.ShadingModel != MaterialShadingModel.CustomLit;
|
||||
bool isOpaque = info.BlendMode == MaterialBlendMode.Opaque;
|
||||
bool withTess = info.TessellationMode != TessellationMethod.None;
|
||||
|
||||
GetBox(MaterialNodeBoxes.Color).IsActive = isNotUnlit;
|
||||
@@ -134,8 +136,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
GetBox(MaterialNodeBoxes.Roughness).IsActive = isNotUnlit;
|
||||
GetBox(MaterialNodeBoxes.AmbientOcclusion).IsActive = isNotUnlit;
|
||||
GetBox(MaterialNodeBoxes.Normal).IsActive = isNotUnlit;
|
||||
GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || info.BlendMode != MaterialBlendMode.Opaque;
|
||||
GetBox(MaterialNodeBoxes.Refraction).IsActive = info.BlendMode != MaterialBlendMode.Opaque;
|
||||
GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || !isOpaque;
|
||||
GetBox(MaterialNodeBoxes.Refraction).IsActive = !isOpaque;
|
||||
GetBox(MaterialNodeBoxes.PositionOffset).IsActive = true;
|
||||
GetBox(MaterialNodeBoxes.TessellationMultiplier).IsActive = withTess;
|
||||
GetBox(MaterialNodeBoxes.WorldDisplacement).IsActive = withTess;
|
||||
@@ -260,6 +262,93 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
#if false // TODO: finish code editor based on RichTextBoxBase with text block parsing for custom styling
|
||||
internal sealed class CustomCodeTextBox : RichTextBoxBase
|
||||
{
|
||||
protected override void OnParseTextBlocks()
|
||||
{
|
||||
base.OnParseTextBlocks();
|
||||
|
||||
// Single block for a whole text
|
||||
// TODO: implement code parsing with HLSL syntax
|
||||
var font = Style.Current.FontMedium;
|
||||
var style = new TextBlockStyle
|
||||
{
|
||||
Font = new FontReference(font),
|
||||
Color = Style.Current.Foreground,
|
||||
BackgroundSelectedBrush = new SolidColorBrush(Style.Current.BackgroundSelected),
|
||||
};
|
||||
_textBlocks.Clear();
|
||||
_textBlocks.Add(new TextBlock
|
||||
{
|
||||
Range = new TextRange
|
||||
{
|
||||
StartIndex = 0,
|
||||
EndIndex = TextLength,
|
||||
},
|
||||
Style = style,
|
||||
Bounds = new Rectangle(Float2.Zero, font.MeasureText(Text)),
|
||||
});
|
||||
}
|
||||
#else
|
||||
internal sealed class CustomCodeTextBox : TextBox
|
||||
{
|
||||
#endif
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Draw border
|
||||
if (!IsFocused)
|
||||
Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BorderNormal);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CustomCodeNode : ResizableSurfaceNode
|
||||
{
|
||||
private CustomCodeTextBox _textBox;
|
||||
|
||||
public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_sizeValueIndex = Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
|
||||
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
|
||||
if (nodeArch.TypeID == 8)
|
||||
{
|
||||
pos += new Float2(60, 0);
|
||||
size = new Float2(172, 200);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos += new Float2(0, 40);
|
||||
size = new Float2(300, 200);
|
||||
}
|
||||
_textBox = new CustomCodeTextBox
|
||||
{
|
||||
IsMultiline = true,
|
||||
Location = pos,
|
||||
Size = size,
|
||||
Parent = this,
|
||||
AnchorMax = Float2.One,
|
||||
};
|
||||
_textBox.EditEnd += () => SetValue(0, _textBox.Text);
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_textBox.Text = (string)Values[0];
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
_textBox.Text = (string)Values[0];
|
||||
}
|
||||
}
|
||||
|
||||
internal enum MaterialTemplateInputsMapping
|
||||
{
|
||||
/// <summary>
|
||||
@@ -410,13 +499,15 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 8,
|
||||
Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch),
|
||||
Title = "Custom Code",
|
||||
Description = "Custom HLSL shader code expression",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(300, 200),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
"// Here you can add HLSL code\nOutput0 = Input0;"
|
||||
"// Here you can add HLSL code\nOutput0 = Input0;",
|
||||
new Float2(300, 200),
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -433,8 +524,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Output(1, "Output1", typeof(Float4), 9),
|
||||
NodeElementArchetype.Factory.Output(2, "Output2", typeof(Float4), 10),
|
||||
NodeElementArchetype.Factory.Output(3, "Output3", typeof(Float4), 11),
|
||||
|
||||
NodeElementArchetype.Factory.TextBox(60, 0, 175, 200, 0),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -874,6 +963,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 38,
|
||||
Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch),
|
||||
Title = "Custom Global Code",
|
||||
Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
@@ -883,6 +973,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
"// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
|
||||
true,
|
||||
(int)MaterialTemplateInputsMapping.Utilities,
|
||||
new Float2(300, 240),
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -890,7 +981,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Text(20, 0, "Enabled"),
|
||||
NodeElementArchetype.Factory.Text(0, 20, "Location"),
|
||||
NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)),
|
||||
NodeElementArchetype.Factory.TextBox(0, 40, 300, 200, 0),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
|
||||
@@ -1100,6 +1100,27 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.ComboBox(2 + 20, 0, 116)
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 5,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsSet(id, context, arch, groupArch),
|
||||
Title = "Set Parameter",
|
||||
Description = "Parameter value setter invoked when the animation pose is evaluated (output pose comes from input)",
|
||||
Flags = NodeFlags.AnimGraph,
|
||||
Size = new Float2(140, 40),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
Guid.Empty,
|
||||
null
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0),
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 2),
|
||||
NodeElementArchetype.Factory.Input(1, string.Empty, true, ScriptType.Null, 1, 1),
|
||||
NodeElementArchetype.Factory.ComboBox(2 + 20, 0, 116)
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,14 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
TextureGroup = 4,
|
||||
}
|
||||
|
||||
internal class SampleTextureNode : SurfaceNode
|
||||
internal class TextureSamplerNode : SurfaceNode
|
||||
{
|
||||
private ComboBox _textureGroupPicker;
|
||||
protected int _samplerTypeValueIndex = -1;
|
||||
protected int _textureGroupValueIndex = -1;
|
||||
protected int _level = 5;
|
||||
|
||||
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
protected TextureSamplerNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
@@ -48,13 +51,13 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
if ((int)Values[0] == (int)CommonSamplerType.TextureGroup)
|
||||
if ((int)Values[_samplerTypeValueIndex] == (int)CommonSamplerType.TextureGroup)
|
||||
{
|
||||
if (_textureGroupPicker == null)
|
||||
{
|
||||
_textureGroupPicker = new ComboBox
|
||||
{
|
||||
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * 5),
|
||||
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
|
||||
Width = 100,
|
||||
Parent = this,
|
||||
};
|
||||
@@ -71,7 +74,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_textureGroupPicker.Visible = true;
|
||||
}
|
||||
_textureGroupPicker.SelectedIndexChanged -= OnSelectedTextureGroupChanged;
|
||||
_textureGroupPicker.SelectedIndex = (int)Values[2];
|
||||
_textureGroupPicker.SelectedIndex = (int)Values[_textureGroupValueIndex];
|
||||
_textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged;
|
||||
}
|
||||
else if (_textureGroupPicker != null)
|
||||
@@ -83,7 +86,39 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
private void OnSelectedTextureGroupChanged(ComboBox comboBox)
|
||||
{
|
||||
SetValue(2, _textureGroupPicker.SelectedIndex);
|
||||
SetValue(_textureGroupValueIndex, _textureGroupPicker.SelectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SampleTextureNode : TextureSamplerNode
|
||||
{
|
||||
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_samplerTypeValueIndex = 0;
|
||||
_textureGroupValueIndex = 2;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TriplanarSampleTextureNode : TextureSamplerNode
|
||||
{
|
||||
public TriplanarSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_samplerTypeValueIndex = 3;
|
||||
_textureGroupValueIndex = 5;
|
||||
_level = 5;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ProceduralSampleTextureNode : TextureSamplerNode
|
||||
{
|
||||
public ProceduralSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_samplerTypeValueIndex = 0;
|
||||
_textureGroupValueIndex = 2;
|
||||
_level = 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,9 +315,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
0,
|
||||
-1.0f,
|
||||
0,
|
||||
(int)CommonSamplerType.LinearClamp, // Sampler
|
||||
-1.0f, // Level
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -402,6 +437,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 16,
|
||||
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
|
||||
Title = "Triplanar Texture",
|
||||
Description = "Projects a texture using world-space coordinates with triplanar mapping.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
@@ -411,8 +447,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Float3.One, // Scale
|
||||
1.0f, // Blend
|
||||
Float2.Zero, // Offset
|
||||
2, // Sampler
|
||||
(int)CommonSamplerType.LinearWrap, // Sampler
|
||||
false, // Local
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -430,17 +467,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 17,
|
||||
Create = (id, context, arch, groupArch) => new SampleTextureNode(id, context, arch, groupArch),
|
||||
Create = (id, context, arch, groupArch) => new ProceduralSampleTextureNode(id, context, arch, groupArch),
|
||||
Title = "Procedural Sample Texture",
|
||||
Description = "Samples a texture to create a more natural look with less obvious tiling.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(240, 110),
|
||||
Size = new Float2(240, 130),
|
||||
ConnectionsHints = ConnectionsHint.Vector,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
2,
|
||||
-1.0f,
|
||||
0,
|
||||
(int)CommonSamplerType.LinearWrap, // Sampler
|
||||
-1.0f, // Level
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -448,8 +485,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Input(1, "UVs", true, null, 1),
|
||||
NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Float2), 3),
|
||||
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 4),
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
|
||||
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4, 100, 0, typeof(CommonSamplerType))
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 3, "Sampler"),
|
||||
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 3, 100, 0, typeof(CommonSamplerType))
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -469,6 +506,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 23,
|
||||
Title = "Triplanar Normal Map",
|
||||
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
|
||||
Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(280, 100),
|
||||
@@ -477,8 +515,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Float3.One, // Scale
|
||||
1.0f, // Blend
|
||||
Float2.Zero, // Offset
|
||||
2, // Sampler
|
||||
(int)CommonSamplerType.LinearWrap, // Sampler
|
||||
false, // Local
|
||||
0, // Texture Group
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
|
||||
@@ -453,7 +453,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
private class CurveNode<T> : SurfaceNode where T : struct
|
||||
private class CurveNode<T> : ResizableSurfaceNode where T : struct
|
||||
{
|
||||
private BezierCurveEditor<T> _curve;
|
||||
private bool _isSavingCurve;
|
||||
@@ -467,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Create = (id, context, arch, groupArch) => new CurveNode<T>(id, context, arch, groupArch),
|
||||
Description = "An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.",
|
||||
Flags = NodeFlags.AllGraphs,
|
||||
Size = new Float2(400, 180.0f),
|
||||
Size = new Float2(400, 180),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
// Keyframes count
|
||||
@@ -491,6 +491,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
0.0f, zero, zero, zero,
|
||||
0.0f, zero, zero, zero,
|
||||
0.0f, zero, zero, zero,
|
||||
|
||||
new Float2(400, 180),
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
@@ -504,30 +506,52 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public CurveNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_sizeValueIndex = 29; // Index of the Size stored in Values array
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Create curve editor
|
||||
var upperLeft = GetBox(0).BottomLeft;
|
||||
var upperRight = GetBox(1).BottomRight;
|
||||
float curveMargin = 20.0f;
|
||||
|
||||
_curve = new BezierCurveEditor<T>
|
||||
{
|
||||
MaxKeyframes = 7,
|
||||
Bounds = new Rectangle(upperLeft + new Float2(curveMargin, 10.0f), upperRight.X - upperLeft.X - curveMargin * 2.0f, 140.0f),
|
||||
Parent = this
|
||||
Parent = this,
|
||||
AnchorMax = Float2.One,
|
||||
};
|
||||
_curve.Edited += OnCurveEdited;
|
||||
_curve.UnlockChildrenRecursive();
|
||||
_curve.PerformLayout();
|
||||
|
||||
// Sync keyframes
|
||||
UpdateCurveKeyframes();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Ensure the whole curve is shown
|
||||
_curve.ShowWholeCurve();
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
if (!_isSavingCurve)
|
||||
{
|
||||
UpdateCurveKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCurveEdited()
|
||||
{
|
||||
if (_isSavingCurve)
|
||||
@@ -553,17 +577,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_isSavingCurve = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
if (!_isSavingCurve)
|
||||
{
|
||||
UpdateCurveKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCurveKeyframes()
|
||||
{
|
||||
var count = (int)Values[0];
|
||||
@@ -1390,7 +1403,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, "Time", typeof(float), 0),
|
||||
NodeElementArchetype.Factory.Output(1, "Unscaled Time", typeof(float), 1),
|
||||
NodeElementArchetype.Factory.Output(1, "Scaled Time", typeof(float), 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -1575,7 +1588,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
Guid.Empty,
|
||||
string.Empty
|
||||
string.Empty,
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
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 FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
@@ -18,6 +15,7 @@ namespace FlaxEditor.Surface
|
||||
class AttributesEditor : ContextMenuBase
|
||||
{
|
||||
private CustomEditorPresenter _presenter;
|
||||
private Proxy _proxy;
|
||||
private byte[] _oldData;
|
||||
|
||||
private class Proxy
|
||||
@@ -72,11 +70,11 @@ namespace FlaxEditor.Surface
|
||||
/// Initializes a new instance of the <see cref="AttributesEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="attributes">The attributes list to edit.</param>
|
||||
/// <param name="attributeType">The allowed attribute types to use.</param>
|
||||
public AttributesEditor(Attribute[] attributes, IList<Type> attributeType)
|
||||
/// <param name="attributeTypes">The allowed attribute types to use.</param>
|
||||
public AttributesEditor(Attribute[] attributes, IList<Type> attributeTypes)
|
||||
{
|
||||
// Context menu dimensions
|
||||
const float width = 340.0f;
|
||||
const float width = 375.0f;
|
||||
const float height = 370.0f;
|
||||
Size = new Float2(width, height);
|
||||
|
||||
@@ -88,61 +86,68 @@ namespace FlaxEditor.Surface
|
||||
Parent = this
|
||||
};
|
||||
|
||||
// Buttons
|
||||
float buttonsWidth = (width - 16.0f) * 0.5f;
|
||||
// Ok and Cancel Buttons
|
||||
float buttonsWidth = (width - 12.0f) * 0.5f;
|
||||
float buttonsHeight = 20.0f;
|
||||
var cancelButton = new Button(4.0f, title.Bottom + 4.0f, buttonsWidth, buttonsHeight)
|
||||
var okButton = new Button(4.0f, Bottom - 4.0f - buttonsHeight, buttonsWidth, buttonsHeight)
|
||||
{
|
||||
Text = "Ok",
|
||||
Parent = this
|
||||
};
|
||||
okButton.Clicked += OnOkButtonClicked;
|
||||
var cancelButton = new Button(okButton.Right + 4.0f, okButton.Y, buttonsWidth, buttonsHeight)
|
||||
{
|
||||
Text = "Cancel",
|
||||
Parent = this
|
||||
};
|
||||
cancelButton.Clicked += Hide;
|
||||
var okButton = new Button(cancelButton.Right + 4.0f, cancelButton.Y, buttonsWidth, buttonsHeight)
|
||||
{
|
||||
Text = "OK",
|
||||
Parent = this
|
||||
};
|
||||
okButton.Clicked += OnOkButtonClicked;
|
||||
|
||||
// Actual panel
|
||||
// Actual panel used to display attributes
|
||||
var panel1 = new Panel(ScrollBars.Vertical)
|
||||
{
|
||||
Bounds = new Rectangle(0, okButton.Bottom + 4.0f, width, height - okButton.Bottom - 2.0f),
|
||||
Bounds = new Rectangle(0, title.Bottom + 4.0f, width, height - buttonsHeight - title.Height - 14.0f),
|
||||
Parent = this
|
||||
};
|
||||
var editor = new CustomEditorPresenter(null);
|
||||
editor.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop;
|
||||
editor.Panel.IsScrollable = true;
|
||||
editor.Panel.Parent = panel1;
|
||||
editor.Panel.Tag = attributeType;
|
||||
editor.Panel.Tag = attributeTypes;
|
||||
_presenter = editor;
|
||||
|
||||
// Cache 'previous' state to check if attributes were edited after operation
|
||||
_oldData = SurfaceMeta.GetAttributesData(attributes);
|
||||
|
||||
editor.Select(new Proxy
|
||||
_proxy = new Proxy
|
||||
{
|
||||
Value = attributes,
|
||||
});
|
||||
};
|
||||
editor.Select(_proxy);
|
||||
|
||||
_presenter.Modified += OnPresenterModified;
|
||||
OnPresenterModified();
|
||||
}
|
||||
|
||||
private void OnPresenterModified()
|
||||
{
|
||||
if (_proxy.Value.Length == 0)
|
||||
{
|
||||
var label = _presenter.Label("No attributes.\nPress the \"+\" button to add a new one and then select an attribute type using the \"Type\" dropdown.", TextAlignment.Center);
|
||||
label.Label.Wrapping = TextWrapping.WrapWords;
|
||||
label.Control.Height = 35f;
|
||||
label.Label.Margin = new Margin(10f);
|
||||
label.Label.TextColor = label.Label.TextColorHighlighted = Style.Current.ForegroundGrey;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOkButtonClicked()
|
||||
{
|
||||
var newValue = ((Proxy)_presenter.Selection[0]).Value;
|
||||
for (int i = 0; i < newValue.Length; i++)
|
||||
{
|
||||
if (newValue[i] == null)
|
||||
{
|
||||
MessageBox.Show("One of the attributes is null. Please set it to the valid object.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
newValue = newValue.Where(v => v != null).ToArray();
|
||||
|
||||
var newData = SurfaceMeta.GetAttributesData(newValue);
|
||||
if (!_oldData.SequenceEqual(newData))
|
||||
{
|
||||
Edited?.Invoke(newValue);
|
||||
}
|
||||
|
||||
Hide();
|
||||
}
|
||||
@@ -183,7 +188,9 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
_presenter = null;
|
||||
_oldData = null;
|
||||
_proxy = null;
|
||||
Edited = null;
|
||||
_presenter.Modified -= OnPresenterModified;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -101,12 +101,12 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
|
||||
{
|
||||
activeCM.ShowExpanded = true;
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
|
||||
|
||||
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
/// Visject context menu item clicked delegate.
|
||||
/// </summary>
|
||||
/// <param name="clickedItem">The item that was clicked</param>
|
||||
/// <param name="selectedBox">The currently user-selected box. Can be null.</param>
|
||||
public delegate void ItemClickedDelegate(VisjectCMItem clickedItem, Elements.Box selectedBox);
|
||||
/// <param name="selectedBoxes">The currently user-selected boxes. Can be empty/ null.</param>
|
||||
public delegate void ItemClickedDelegate(VisjectCMItem clickedItem, List<Elements.Box> selectedBoxes);
|
||||
|
||||
/// <summary>
|
||||
/// Visject Surface node archetype spawn ability checking delegate.
|
||||
@@ -53,7 +53,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
private Panel _panel1;
|
||||
private VerticalPanel _groupsPanel;
|
||||
private readonly ParameterGetterDelegate _parametersGetter;
|
||||
private Elements.Box _selectedBox;
|
||||
private List<Elements.Box> _selectedBoxes = new List<Elements.Box>();
|
||||
private NodeArchetype _parameterGetNodeArchetype;
|
||||
private NodeArchetype _parameterSetNodeArchetype;
|
||||
|
||||
@@ -411,7 +411,8 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
if (!IsLayoutLocked)
|
||||
{
|
||||
group.UnlockChildrenRecursive();
|
||||
if (_contextSensitiveSearchEnabled && _selectedBox != null)
|
||||
// TODO: Improve filtering to be based on boxes with the most common things instead of first box
|
||||
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 && _selectedBoxes[0] != null)
|
||||
UpdateFilters();
|
||||
else
|
||||
SortGroups();
|
||||
@@ -423,9 +424,10 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
OnSearchFilterChanged();
|
||||
}
|
||||
}
|
||||
else if (_contextSensitiveSearchEnabled)
|
||||
else if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0)
|
||||
{
|
||||
group.EvaluateVisibilityWithBox(_selectedBox);
|
||||
// TODO: Filtering could be improved here as well
|
||||
group.EvaluateVisibilityWithBox(_selectedBoxes[0]);
|
||||
}
|
||||
|
||||
Profiler.EndEvent();
|
||||
@@ -460,8 +462,8 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
Parent = group
|
||||
};
|
||||
}
|
||||
if (_contextSensitiveSearchEnabled)
|
||||
group.EvaluateVisibilityWithBox(_selectedBox);
|
||||
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0)
|
||||
group.EvaluateVisibilityWithBox(_selectedBoxes[0]);
|
||||
group.SortChildren();
|
||||
if (ShowExpanded)
|
||||
group.Open(false);
|
||||
@@ -474,7 +476,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
|
||||
if (!isLayoutLocked)
|
||||
{
|
||||
if (_contextSensitiveSearchEnabled && _selectedBox != null)
|
||||
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count != 0 && _selectedBoxes[0] != null)
|
||||
UpdateFilters();
|
||||
else
|
||||
SortGroups();
|
||||
@@ -583,7 +585,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
|
||||
private void UpdateFilters()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null)
|
||||
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes[0] == null)
|
||||
{
|
||||
ResetView();
|
||||
Profiler.EndEvent();
|
||||
@@ -592,7 +594,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
|
||||
// Update groups
|
||||
LockChildrenRecursive();
|
||||
var contextSensitiveSelectedBox = _contextSensitiveSearchEnabled ? _selectedBox : null;
|
||||
var contextSensitiveSelectedBox = _contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 ? _selectedBoxes[0] : null;
|
||||
for (int i = 0; i < _groups.Count; i++)
|
||||
{
|
||||
_groups[i].UpdateFilter(_searchBox.Text, contextSensitiveSelectedBox);
|
||||
@@ -640,7 +642,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
public void OnClickItem(VisjectCMItem item)
|
||||
{
|
||||
Hide();
|
||||
ItemClicked?.Invoke(item, _selectedBox);
|
||||
ItemClicked?.Invoke(item, _selectedBoxes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -666,12 +668,12 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
for (int i = 0; i < _groups.Count; i++)
|
||||
{
|
||||
_groups[i].ResetView();
|
||||
if (_contextSensitiveSearchEnabled)
|
||||
_groups[i].EvaluateVisibilityWithBox(_selectedBox);
|
||||
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0)
|
||||
_groups[i].EvaluateVisibilityWithBox(_selectedBoxes[0]);
|
||||
}
|
||||
UnlockChildrenRecursive();
|
||||
|
||||
if (_contextSensitiveSearchEnabled && _selectedBox != null)
|
||||
if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 && _selectedBoxes[0] != null)
|
||||
UpdateFilters();
|
||||
else
|
||||
SortGroups();
|
||||
@@ -772,10 +774,10 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent control to attach to it.</param>
|
||||
/// <param name="location">Popup menu origin location in parent control coordinates.</param>
|
||||
/// <param name="startBox">The currently selected box that the new node will get connected to. Can be null</param>
|
||||
public void Show(Control parent, Float2 location, Elements.Box startBox)
|
||||
/// <param name="startBoxes">The currently selected boxes that the new node will get connected to. Can be empty/ null</param>
|
||||
public void Show(Control parent, Float2 location, List<Elements.Box> startBoxes)
|
||||
{
|
||||
_selectedBox = startBox;
|
||||
_selectedBoxes = startBoxes;
|
||||
base.Show(parent, location);
|
||||
}
|
||||
|
||||
|
||||
@@ -544,35 +544,39 @@ namespace FlaxEditor.Surface.Elements
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
if (_originalTooltipText != null)
|
||||
{
|
||||
TooltipText = _originalTooltipText;
|
||||
}
|
||||
if (_isMouseDown)
|
||||
{
|
||||
_isMouseDown = false;
|
||||
if (Surface.CanEdit)
|
||||
{
|
||||
if (!IsOutput && HasSingleConnection)
|
||||
if (IsOutput && Input.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
var connectedBox = Connections[0];
|
||||
List<Box> connectedBoxes = new List<Box>(Connections);
|
||||
|
||||
for (int i = 0; i < connectedBoxes.Count; i++)
|
||||
{
|
||||
BreakConnection(connectedBoxes[i]);
|
||||
Surface.ConnectingStart(connectedBoxes[i], true);
|
||||
}
|
||||
}
|
||||
else if (!IsOutput && HasSingleConnection)
|
||||
{
|
||||
var otherBox = Connections[0];
|
||||
if (Surface.Undo != null && Surface.Undo.Enabled)
|
||||
{
|
||||
var action = new ConnectBoxesAction((InputBox)this, (OutputBox)connectedBox, false);
|
||||
BreakConnection(connectedBox);
|
||||
var action = new ConnectBoxesAction((InputBox)this, (OutputBox)otherBox, false);
|
||||
BreakConnection(otherBox);
|
||||
action.End();
|
||||
Surface.AddBatchedUndoAction(action);
|
||||
Surface.MarkAsEdited();
|
||||
}
|
||||
else
|
||||
{
|
||||
BreakConnection(connectedBox);
|
||||
}
|
||||
Surface.ConnectingStart(connectedBox);
|
||||
BreakConnection(otherBox);
|
||||
Surface.ConnectingStart(otherBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
Surface.ConnectingStart(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
base.OnMouseLeave();
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
{
|
||||
ParentNode = parentNode;
|
||||
Archetype = archetype;
|
||||
AutoFocus = true;
|
||||
|
||||
var back = Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
|
||||
182
Source/Editor/Surface/ResizableSurfaceNode.cs
Normal file
182
Source/Editor/Surface/ResizableSurfaceNode.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
/// <summary>
|
||||
/// Visject Surface node control that cna be resized.
|
||||
/// </summary>
|
||||
/// <seealso cref="SurfaceNode" />
|
||||
[HideInEditor]
|
||||
public class ResizableSurfaceNode : SurfaceNode
|
||||
{
|
||||
private Float2 _startResizingSize;
|
||||
private Float2 _startResizingCornerOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the node is currently being resized.
|
||||
/// </summary>
|
||||
protected bool _isResizing;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the Float2 value in the node values list to store node size.
|
||||
/// </summary>
|
||||
protected int _sizeValueIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum node size.
|
||||
/// </summary>
|
||||
protected Float2 _sizeMin = new Float2(240, 160);
|
||||
|
||||
/// <summary>
|
||||
/// Node resizing rectangle bounds.
|
||||
/// </summary>
|
||||
protected Rectangle _resizeButtonRect;
|
||||
|
||||
private Float2 SizeValue
|
||||
{
|
||||
get => (Float2)Values[_sizeValueIndex];
|
||||
set => SetValue(_sizeValueIndex, value, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ResizableSurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSelect(ref Float2 location)
|
||||
{
|
||||
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
// Reapply the curve node size
|
||||
var size = SizeValue;
|
||||
if (Surface != null && Surface.GridSnappingEnabled)
|
||||
size = Surface.SnapToGrid(size, true);
|
||||
Resize(size.X, size.Y);
|
||||
|
||||
base.OnSurfaceLoaded(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
var size = SizeValue;
|
||||
Resize(size.X, size.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
if (Surface.CanEdit)
|
||||
{
|
||||
var style = Style.Current;
|
||||
if (_isResizing)
|
||||
{
|
||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||
}
|
||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
if (_isResizing)
|
||||
EndResizing();
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
if (_isResizing)
|
||||
EndResizing();
|
||||
|
||||
base.OnEndMouseCapture();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
|
||||
{
|
||||
// Start resizing
|
||||
_isResizing = true;
|
||||
_startResizingSize = Size;
|
||||
_startResizingCornerOffset = Size - location;
|
||||
StartMouseCapture();
|
||||
Cursor = CursorType.SizeNWSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (_isResizing)
|
||||
{
|
||||
var emptySize = CalculateNodeSize(0, 0);
|
||||
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, _sizeMin);
|
||||
Resize(size.X, size.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
const float buttonMargin = Constants.NodeCloseButtonMargin;
|
||||
const float buttonSize = Constants.NodeCloseButtonSize;
|
||||
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
|
||||
}
|
||||
|
||||
private void EndResizing()
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
EndMouseCapture();
|
||||
_isResizing = false;
|
||||
if (_startResizingSize != Size)
|
||||
{
|
||||
var emptySize = CalculateNodeSize(0, 0);
|
||||
SizeValue = Size - emptySize;
|
||||
Surface.MarkAsEdited(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
/// <seealso cref="SurfaceNode" />
|
||||
[HideInEditor]
|
||||
public class SurfaceComment : SurfaceNode
|
||||
public class SurfaceComment : ResizableSurfaceNode
|
||||
{
|
||||
private Rectangle _colorButtonRect;
|
||||
private Rectangle _resizeButtonRect;
|
||||
private Float2 _startResizingSize;
|
||||
private readonly TextBox _renameTextBox;
|
||||
|
||||
/// <summary>
|
||||
/// True if sizing tool is in use.
|
||||
/// </summary>
|
||||
protected bool _isResizing;
|
||||
|
||||
/// <summary>
|
||||
/// True if rename textbox is active in order to rename comment
|
||||
/// </summary>
|
||||
@@ -52,12 +45,6 @@ namespace FlaxEditor.Surface
|
||||
set => SetValue(1, value, false);
|
||||
}
|
||||
|
||||
private Float2 SizeValue
|
||||
{
|
||||
get => (Float2)Values[2];
|
||||
set => SetValue(2, value, false);
|
||||
}
|
||||
|
||||
private int OrderValue
|
||||
{
|
||||
get => (int)Values[3];
|
||||
@@ -68,6 +55,8 @@ namespace FlaxEditor.Surface
|
||||
public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_sizeValueIndex = 2; // Index of the Size stored in Values array
|
||||
_sizeMin = new Float2(140.0f, Constants.NodeHeaderSize);
|
||||
_renameTextBox = new TextBox(false, 0, 0, Width)
|
||||
{
|
||||
Height = Constants.NodeHeaderSize,
|
||||
@@ -86,10 +75,6 @@ namespace FlaxEditor.Surface
|
||||
// Read node data
|
||||
Title = TitleValue;
|
||||
Color = ColorValue;
|
||||
var size = SizeValue;
|
||||
if (Surface != null && Surface.GridSnappingEnabled)
|
||||
size = Surface.SnapToGrid(size, true);
|
||||
Size = size;
|
||||
|
||||
// Order
|
||||
// Backwards compatibility - When opening with an older version send the old comments to the back
|
||||
@@ -126,27 +111,6 @@ namespace FlaxEditor.Surface
|
||||
// Read node data
|
||||
Title = TitleValue;
|
||||
Color = ColorValue;
|
||||
Size = SizeValue;
|
||||
}
|
||||
|
||||
private void EndResizing()
|
||||
{
|
||||
// Clear state
|
||||
_isResizing = false;
|
||||
|
||||
if (_startResizingSize != Size)
|
||||
{
|
||||
SizeValue = Size;
|
||||
Surface.MarkAsEdited(false);
|
||||
}
|
||||
|
||||
EndMouseCapture();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSelect(ref Float2 location)
|
||||
{
|
||||
return _headerRect.MakeOffsetted(Location).Contains(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -158,6 +122,8 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
const float headerSize = Constants.NodeHeaderSize;
|
||||
const float buttonMargin = Constants.NodeCloseButtonMargin;
|
||||
const float buttonSize = Constants.NodeCloseButtonSize;
|
||||
@@ -214,22 +180,22 @@ namespace FlaxEditor.Surface
|
||||
if (!_isRenaming)
|
||||
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
|
||||
|
||||
// Close button
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Color button
|
||||
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Check if is resizing
|
||||
if (_isResizing)
|
||||
if (Surface.CanEdit)
|
||||
{
|
||||
// Draw overlay
|
||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||
}
|
||||
// Close button
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Resize button
|
||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
// Color button
|
||||
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Resize button
|
||||
if (_isResizing)
|
||||
{
|
||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||
}
|
||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
// Selection outline
|
||||
if (_isSelected)
|
||||
@@ -244,88 +210,28 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
protected override Float2 CalculateNodeSize(float width, float height)
|
||||
{
|
||||
return Size;
|
||||
// No margins or headers
|
||||
return new Float2(width, height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
// Check if was resizing
|
||||
if (_isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
}
|
||||
|
||||
// Check if was renaming
|
||||
if (_isRenaming)
|
||||
{
|
||||
Rename(_renameTextBox.Text);
|
||||
StopRenaming();
|
||||
}
|
||||
|
||||
// Base
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
// Check if was resizing
|
||||
if (_isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnEndMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise)
|
||||
{
|
||||
return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
// Check if can start resizing
|
||||
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
|
||||
{
|
||||
// Start sliding
|
||||
_isResizing = true;
|
||||
_startResizingSize = Size;
|
||||
StartMouseCapture();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
// Check if is resizing
|
||||
if (_isResizing)
|
||||
{
|
||||
// Update size
|
||||
var size = Float2.Max(location, new Float2(140.0f, _headerRect.Bottom));
|
||||
if (Surface.GridSnappingEnabled)
|
||||
size = Surface.SnapToGrid(size, true);
|
||||
Size = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Base
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
@@ -391,12 +297,6 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isResizing)
|
||||
{
|
||||
EndResizing();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (base.OnMouseUp(location, button))
|
||||
return true;
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace FlaxEditor.Surface
|
||||
protected SurfaceControl(VisjectSurfaceContext context, float width, float height)
|
||||
: base(0, 0, width, height)
|
||||
{
|
||||
AutoFocus = true;
|
||||
ClipChildren = false;
|
||||
|
||||
Surface = context.Surface;
|
||||
|
||||
@@ -151,6 +151,8 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
protected virtual Color FooterColor => GroupArchetype.Color;
|
||||
|
||||
private Float2 mouseDownMousePosition;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size of the node including header, footer, and margins.
|
||||
/// </summary>
|
||||
@@ -429,27 +431,6 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public bool HasIndependentBoxes => Archetype.IndependentBoxes != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this node has dependent boxes with assigned valid types. Otherwise any box has no dependent type assigned.
|
||||
/// </summary>
|
||||
public bool HasDependentBoxesSetup
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Archetype.DependentBoxes == null || Archetype.IndependentBoxes == null)
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < Archetype.DependentBoxes.Length; i++)
|
||||
{
|
||||
var b = GetBox(Archetype.DependentBoxes[i]);
|
||||
if (b != null && b.CurrentType == b.DefaultType)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly List<SurfaceNode> UpdateStack = new List<SurfaceNode>();
|
||||
|
||||
/// <summary>
|
||||
@@ -917,7 +898,7 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
public override bool OnTestTooltipOverControl(ref Float2 location)
|
||||
{
|
||||
return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsBoxSelecting;
|
||||
return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsSelecting;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1075,7 +1056,7 @@ namespace FlaxEditor.Surface
|
||||
|
||||
// Header
|
||||
var headerColor = style.BackgroundHighlighted;
|
||||
if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting)
|
||||
if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting)
|
||||
headerColor *= 1.07f;
|
||||
Render2D.FillRectangle(_headerRect, headerColor);
|
||||
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
|
||||
@@ -1083,7 +1064,7 @@ namespace FlaxEditor.Surface
|
||||
// Close button
|
||||
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
|
||||
{
|
||||
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting;
|
||||
bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting;
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
@@ -1121,7 +1102,7 @@ namespace FlaxEditor.Surface
|
||||
if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
|
||||
return true;
|
||||
if (button == MouseButton.Right)
|
||||
return true;
|
||||
mouseDownMousePosition = Input.Mouse.Position;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -1133,7 +1114,7 @@ namespace FlaxEditor.Surface
|
||||
return true;
|
||||
|
||||
// Close/ delete
|
||||
bool canDelete = !Surface.IsConnecting && !Surface.WasBoxSelecting && !Surface.WasMovingSelection;
|
||||
bool canDelete = !Surface.IsConnecting && !Surface.WasSelecting && !Surface.WasMovingSelection;
|
||||
if (button == MouseButton.Left && canDelete && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
|
||||
{
|
||||
Surface.Delete(this);
|
||||
@@ -1143,6 +1124,10 @@ namespace FlaxEditor.Surface
|
||||
// Secondary Context Menu
|
||||
if (button == MouseButton.Right)
|
||||
{
|
||||
float distance = Float2.Distance(mouseDownMousePosition, Input.Mouse.Position);
|
||||
if (distance > 2.5f)
|
||||
return true;
|
||||
|
||||
if (!IsSelected)
|
||||
Surface.Select(this);
|
||||
var tmp = PointToParent(ref location);
|
||||
|
||||
@@ -400,7 +400,7 @@ namespace FlaxEditor.Surface
|
||||
return scriptType.GetGenericTypeDefinition() == typeof(Dictionary<,>);
|
||||
}
|
||||
var managedType = TypeUtils.GetType(scriptType);
|
||||
return !TypeUtils.IsDelegate(managedType);
|
||||
return managedType != null && !TypeUtils.IsDelegate(managedType);
|
||||
}
|
||||
|
||||
internal static bool IsValidVisualScriptFunctionType(ScriptType scriptType)
|
||||
@@ -408,7 +408,7 @@ namespace FlaxEditor.Surface
|
||||
if (scriptType.IsGenericType || scriptType.IsStatic || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true))
|
||||
return false;
|
||||
var managedType = TypeUtils.GetType(scriptType);
|
||||
return !TypeUtils.IsDelegate(managedType);
|
||||
return managedType != null && !TypeUtils.IsDelegate(managedType);
|
||||
}
|
||||
|
||||
internal static string GetVisualScriptTypeDescription(ScriptType type)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
@@ -233,11 +235,15 @@ namespace FlaxEditor.Surface
|
||||
/// Begins connecting surface objects action.
|
||||
/// </summary>
|
||||
/// <param name="instigator">The connection instigator (eg. start box).</param>
|
||||
public void ConnectingStart(IConnectionInstigator instigator)
|
||||
/// <param name="additive">If the instigator should be added to the list of instigators.</param>
|
||||
public void ConnectingStart(IConnectionInstigator instigator, bool additive = false)
|
||||
{
|
||||
if (instigator != null && instigator != _connectionInstigator)
|
||||
if (instigator != null && instigator != _connectionInstigators)
|
||||
{
|
||||
_connectionInstigator = instigator;
|
||||
if (!additive)
|
||||
_connectionInstigators.Clear();
|
||||
|
||||
_connectionInstigators.Add(instigator);
|
||||
StartMouseCapture();
|
||||
}
|
||||
}
|
||||
@@ -257,22 +263,30 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="end">The end object (eg. end box).</param>
|
||||
public void ConnectingEnd(IConnectionInstigator end)
|
||||
{
|
||||
// Ensure that there was a proper start box
|
||||
if (_connectionInstigator == null)
|
||||
// Ensure that there is at least one connection instigator
|
||||
if (_connectionInstigators.Count == 0)
|
||||
return;
|
||||
|
||||
var start = _connectionInstigator;
|
||||
_connectionInstigator = null;
|
||||
|
||||
// Check if boxes are different and end box is specified
|
||||
if (start == end || end == null)
|
||||
return;
|
||||
|
||||
// Connect them
|
||||
if (start.CanConnectWith(end))
|
||||
List<IConnectionInstigator> instigators = new List<IConnectionInstigator>(_connectionInstigators);
|
||||
for (int i = 0; i < instigators.Count; i++)
|
||||
{
|
||||
start.Connect(end);
|
||||
var start = instigators[i];
|
||||
|
||||
// Check if boxes are different and end box is specified
|
||||
if (start == end || end == null)
|
||||
return;
|
||||
|
||||
// Properly handle connecting to a socket that already has a connection
|
||||
if (end is Box e && !e.IsOutput && start is Box s && e.AreConnected(s))
|
||||
e.BreakConnection(s);
|
||||
|
||||
// Connect them
|
||||
if (start.CanConnectWith(end))
|
||||
start.Connect(end);
|
||||
}
|
||||
|
||||
// Reset instigator list
|
||||
_connectionInstigators.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,10 +261,10 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
/// <param name="activeCM">The active context menu to show.</param>
|
||||
/// <param name="location">The display location on the surface control.</param>
|
||||
/// <param name="startBox">The start box.</param>
|
||||
protected virtual void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
|
||||
/// <param name="startBoxes">The start boxes.</param>
|
||||
protected virtual void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
|
||||
{
|
||||
activeCM.Show(this, location, startBox);
|
||||
activeCM.Show(this, location, startBoxes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -298,8 +298,10 @@ namespace FlaxEditor.Surface
|
||||
|
||||
_cmStartPos = location;
|
||||
|
||||
// Offset added in case the user doesn't like the box and wants to quickly get rid of it by clicking
|
||||
OnShowPrimaryMenu(_activeVisjectCM, _cmStartPos + ContextMenuOffset, _connectionInstigator as Box);
|
||||
List<Box> startBoxes = new List<Box>(_connectionInstigators.Where(c => c is Box).Cast<Box>());
|
||||
|
||||
// Position offset added so the user can quickly close the menu by clicking
|
||||
OnShowPrimaryMenu(_activeVisjectCM, _cmStartPos + ContextMenuOffset, startBoxes);
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
@@ -513,17 +515,15 @@ namespace FlaxEditor.Surface
|
||||
private void OnPrimaryMenuVisibleChanged(Control primaryMenu)
|
||||
{
|
||||
if (!primaryMenu.Visible)
|
||||
{
|
||||
_connectionInstigator = null;
|
||||
}
|
||||
_connectionInstigators.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles Visject CM item click event by spawning the selected item.
|
||||
/// </summary>
|
||||
/// <param name="visjectCmItem">The item.</param>
|
||||
/// <param name="selectedBox">The selected box.</param>
|
||||
protected virtual void OnPrimaryMenuButtonClick(VisjectCMItem visjectCmItem, Box selectedBox)
|
||||
/// <param name="selectedBoxes">The selected boxes.</param>
|
||||
protected virtual void OnPrimaryMenuButtonClick(VisjectCMItem visjectCmItem, List<Box> selectedBoxes)
|
||||
{
|
||||
if (!CanEdit)
|
||||
return;
|
||||
@@ -550,34 +550,36 @@ namespace FlaxEditor.Surface
|
||||
// Auto select new node
|
||||
Select(node);
|
||||
|
||||
if (selectedBox != null)
|
||||
for (int i = 0; i < selectedBoxes.Count; i++)
|
||||
{
|
||||
Box endBox = null;
|
||||
foreach (var box in node.GetBoxes().Where(box => box.IsOutput != selectedBox.IsOutput))
|
||||
Box currentBox = selectedBoxes[i];
|
||||
if (currentBox != null)
|
||||
{
|
||||
if (selectedBox.IsOutput)
|
||||
Box endBox = null;
|
||||
foreach (var box in node.GetBoxes().Where(box => box.IsOutput != currentBox.IsOutput))
|
||||
{
|
||||
if (box.CanUseType(selectedBox.CurrentType))
|
||||
if (currentBox.IsOutput)
|
||||
{
|
||||
endBox = box;
|
||||
break;
|
||||
if (box.CanUseType(currentBox.CurrentType))
|
||||
{
|
||||
endBox = box;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selectedBox.CanUseType(box.CurrentType))
|
||||
else
|
||||
{
|
||||
endBox = box;
|
||||
break;
|
||||
if (currentBox.CanUseType(box.CurrentType))
|
||||
{
|
||||
endBox = box;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endBox == null && selectedBox.CanUseType(box.CurrentType))
|
||||
{
|
||||
endBox = box;
|
||||
if (endBox == null && currentBox.CanUseType(box.CurrentType))
|
||||
endBox = box;
|
||||
}
|
||||
TryConnect(currentBox, endBox);
|
||||
}
|
||||
TryConnect(selectedBox, endBox);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,13 +595,8 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
// If the user is patiently waiting for his box to get connected to the newly created one fulfill his wish!
|
||||
|
||||
_connectionInstigator = startBox;
|
||||
|
||||
if (!IsConnecting)
|
||||
{
|
||||
ConnectingStart(startBox);
|
||||
}
|
||||
ConnectingEnd(endBox);
|
||||
|
||||
// Smart-Select next box
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
@@ -126,40 +127,45 @@ namespace FlaxEditor.Surface
|
||||
/// <remarks>Called only when user is connecting nodes.</remarks>
|
||||
protected virtual void DrawConnectingLine()
|
||||
{
|
||||
// Get start position
|
||||
var startPos = _connectionInstigator.ConnectionOrigin;
|
||||
|
||||
// Check if mouse is over any of box
|
||||
var cmVisible = _activeVisjectCM != null && _activeVisjectCM.Visible;
|
||||
var endPos = cmVisible ? _rootControl.PointFromParent(ref _cmStartPos) : _rootControl.PointFromParent(ref _mousePos);
|
||||
Color lineColor = Style.Colors.Connecting;
|
||||
if (_lastInstigatorUnderMouse != null && !cmVisible)
|
||||
{
|
||||
// Check if can connect objects
|
||||
bool canConnect = _connectionInstigator.CanConnectWith(_lastInstigatorUnderMouse);
|
||||
lineColor = canConnect ? Style.Colors.ConnectingValid : Style.Colors.ConnectingInvalid;
|
||||
endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
|
||||
}
|
||||
|
||||
Float2 actualStartPos = startPos;
|
||||
Float2 actualEndPos = endPos;
|
||||
|
||||
if (_connectionInstigator is Archetypes.Tools.RerouteNode)
|
||||
List<IConnectionInstigator> instigators = new List<IConnectionInstigator>(_connectionInstigators);
|
||||
for (int i = 0; i < instigators.Count; i++)
|
||||
{
|
||||
if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
|
||||
IConnectionInstigator currentInstigator = instigators[i];
|
||||
Float2 currentStartPosition = currentInstigator.ConnectionOrigin;
|
||||
|
||||
// Check if mouse is over any box
|
||||
if (_lastInstigatorUnderMouse != null && !cmVisible)
|
||||
{
|
||||
// Check if can connect objects
|
||||
bool canConnect = currentInstigator.CanConnectWith(_lastInstigatorUnderMouse);
|
||||
lineColor = canConnect ? Style.Colors.ConnectingValid : Style.Colors.ConnectingInvalid;
|
||||
endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
|
||||
}
|
||||
|
||||
Float2 actualStartPos = currentStartPosition;
|
||||
Float2 actualEndPos = endPos;
|
||||
|
||||
if (currentInstigator is Archetypes.Tools.RerouteNode)
|
||||
{
|
||||
if (endPos.X < currentStartPosition.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
|
||||
{
|
||||
actualStartPos = endPos;
|
||||
actualEndPos = currentStartPosition;
|
||||
}
|
||||
}
|
||||
else if (currentInstigator is Box { IsOutput: false })
|
||||
{
|
||||
actualStartPos = endPos;
|
||||
actualEndPos = startPos;
|
||||
actualEndPos = currentStartPosition;
|
||||
}
|
||||
}
|
||||
else if (_connectionInstigator is Box { IsOutput: false })
|
||||
{
|
||||
actualStartPos = endPos;
|
||||
actualEndPos = startPos;
|
||||
}
|
||||
|
||||
// Draw connection
|
||||
_connectionInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
|
||||
// Draw connection
|
||||
currentInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -226,10 +232,10 @@ namespace FlaxEditor.Surface
|
||||
_rootControl.DrawComments();
|
||||
|
||||
// Reset input flags here because this is the closest to Update we have
|
||||
WasBoxSelecting = IsBoxSelecting;
|
||||
WasSelecting = IsSelecting;
|
||||
WasMovingSelection = IsMovingSelection;
|
||||
|
||||
if (IsBoxSelecting)
|
||||
if (IsSelecting)
|
||||
{
|
||||
DrawSelection();
|
||||
}
|
||||
|
||||
@@ -176,10 +176,10 @@ namespace FlaxEditor.Surface
|
||||
if (connectedNodes.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < connectedNodes.Count - 1; i++)
|
||||
for (int i = 0; i < connectedNodes.Count; i++)
|
||||
{
|
||||
SurfaceNode nodeA = connectedNodes[i];
|
||||
List<Box> connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.IsOutput && b.HasAnyConnection).ToList();
|
||||
List<Box> connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.HasAnyConnection).ToList();
|
||||
|
||||
for (int j = 0; j < connectedOutputBoxes.Count; j++)
|
||||
{
|
||||
|
||||
@@ -121,6 +121,8 @@ namespace FlaxEditor.Surface
|
||||
|
||||
private void UpdateSelectionRectangle()
|
||||
{
|
||||
if (Root == null)
|
||||
return;
|
||||
var p1 = _rootControl.PointFromParent(ref _leftMouseDownPos);
|
||||
var p2 = _rootControl.PointFromParent(ref _mousePos);
|
||||
var selectionRect = Rectangle.FromPoints(p1, p2);
|
||||
@@ -291,7 +293,7 @@ namespace FlaxEditor.Surface
|
||||
if (_leftMouseDown)
|
||||
{
|
||||
// Connecting
|
||||
if (_connectionInstigator != null)
|
||||
if (_connectionInstigators.Count > 0)
|
||||
{
|
||||
}
|
||||
// Moving
|
||||
@@ -461,14 +463,15 @@ namespace FlaxEditor.Surface
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
// Check if user is connecting boxes
|
||||
if (_connectionInstigator != null)
|
||||
if (_connectionInstigators.Count > 0)
|
||||
return true;
|
||||
|
||||
// Base
|
||||
bool handled = base.OnMouseDown(location, button);
|
||||
if (!handled)
|
||||
CustomMouseDown?.Invoke(ref location, button, ref handled);
|
||||
if (handled)
|
||||
var root = Root;
|
||||
if (handled || root == null)
|
||||
{
|
||||
// Clear flags
|
||||
_isMovingSelection = false;
|
||||
@@ -522,11 +525,11 @@ namespace FlaxEditor.Surface
|
||||
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
|
||||
{
|
||||
// Check if user is pressing control
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
if (root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
AddToSelection(controlUnderMouse);
|
||||
}
|
||||
else if (Root.GetKey(KeyboardKeys.Shift))
|
||||
else if (root.GetKey(KeyboardKeys.Shift))
|
||||
{
|
||||
RemoveFromSelection(controlUnderMouse);
|
||||
}
|
||||
@@ -538,7 +541,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
// Start moving selected nodes
|
||||
if (!Root.GetKey(KeyboardKeys.Shift))
|
||||
if (!root.GetKey(KeyboardKeys.Shift))
|
||||
{
|
||||
StartMouseCapture();
|
||||
_movingSelectionViewPos = _rootControl.Location;
|
||||
@@ -558,7 +561,7 @@ namespace FlaxEditor.Surface
|
||||
// Start selecting or commenting
|
||||
StartMouseCapture();
|
||||
|
||||
if (!Root.GetKey(KeyboardKeys.Control) && !Root.GetKey(KeyboardKeys.Shift))
|
||||
if (!root.GetKey(KeyboardKeys.Control) && !root.GetKey(KeyboardKeys.Shift))
|
||||
{
|
||||
ClearSelection();
|
||||
}
|
||||
@@ -607,7 +610,7 @@ namespace FlaxEditor.Surface
|
||||
_movingNodesDelta = Float2.Zero;
|
||||
}
|
||||
// Connecting
|
||||
else if (_connectionInstigator != null)
|
||||
else if (_connectionInstigators.Count > 0)
|
||||
{
|
||||
}
|
||||
// Selecting
|
||||
@@ -679,7 +682,7 @@ namespace FlaxEditor.Surface
|
||||
ShowPrimaryMenu(_cmStartPos);
|
||||
}
|
||||
// Letting go of a connection or right clicking while creating a connection
|
||||
else if (!_isMovingSelection && _connectionInstigator != null && !IsPrimaryMenuOpened)
|
||||
else if (!_isMovingSelection && _connectionInstigators.Count > 0 && !IsPrimaryMenuOpened)
|
||||
{
|
||||
_cmStartPos = location;
|
||||
Cursor = CursorType.Default;
|
||||
@@ -730,7 +733,12 @@ namespace FlaxEditor.Surface
|
||||
|
||||
if (HasNodesSelection)
|
||||
{
|
||||
var keyMoveRange = 50;
|
||||
var keyMoveDelta = 50;
|
||||
bool altDown = RootWindow.GetKey(KeyboardKeys.Alt);
|
||||
bool shiftDown = RootWindow.GetKey(KeyboardKeys.Shift);
|
||||
if (altDown || shiftDown)
|
||||
keyMoveDelta = shiftDown ? 10 : 25;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.Backspace:
|
||||
@@ -758,17 +766,18 @@ namespace FlaxEditor.Surface
|
||||
Box selectedBox = GetSelectedBox(SelectedNodes);
|
||||
if (selectedBox != null)
|
||||
{
|
||||
Box toSelect = (key == KeyboardKeys.ArrowUp) ? selectedBox?.ParentNode.GetPreviousBox(selectedBox) : selectedBox?.ParentNode.GetNextBox(selectedBox);
|
||||
if (toSelect != null && toSelect.IsOutput == selectedBox.IsOutput)
|
||||
{
|
||||
Select(toSelect.ParentNode);
|
||||
toSelect.ParentNode.SelectBox(toSelect);
|
||||
}
|
||||
int delta = key == KeyboardKeys.ArrowDown ? 1 : -1;
|
||||
List<Box> boxes = selectedBox.ParentNode.GetBoxes().FindAll(b => b.IsOutput == selectedBox.IsOutput);
|
||||
int selectedIndex = boxes.IndexOf(selectedBox);
|
||||
Box toSelect = boxes[Mathf.Wrap(selectedIndex + delta, 0, boxes.Count - 1)];
|
||||
|
||||
Select(toSelect.ParentNode);
|
||||
toSelect.ParentNode.SelectBox(toSelect);
|
||||
}
|
||||
else if (!IsMovingSelection && CanEdit)
|
||||
{
|
||||
// Move selected nodes
|
||||
var delta = new Float2(0, key == KeyboardKeys.ArrowUp ? -keyMoveRange : keyMoveRange);
|
||||
var delta = new Float2(0, key == KeyboardKeys.ArrowUp ? -keyMoveDelta : keyMoveDelta);
|
||||
MoveSelectedNodes(delta);
|
||||
}
|
||||
return true;
|
||||
@@ -781,12 +790,8 @@ namespace FlaxEditor.Surface
|
||||
if (selectedBox != null)
|
||||
{
|
||||
Box toSelect = null;
|
||||
if ((key == KeyboardKeys.ArrowRight && selectedBox.IsOutput) || (key == KeyboardKeys.ArrowLeft && !selectedBox.IsOutput))
|
||||
if (((key == KeyboardKeys.ArrowRight && selectedBox.IsOutput) || (key == KeyboardKeys.ArrowLeft && !selectedBox.IsOutput)) && selectedBox.HasAnyConnection)
|
||||
{
|
||||
if (_selectedConnectionIndex < 0 || _selectedConnectionIndex >= selectedBox.Connections.Count)
|
||||
{
|
||||
_selectedConnectionIndex = 0;
|
||||
}
|
||||
toSelect = selectedBox.Connections[_selectedConnectionIndex];
|
||||
}
|
||||
else
|
||||
@@ -814,7 +819,7 @@ namespace FlaxEditor.Surface
|
||||
else if (!IsMovingSelection && CanEdit)
|
||||
{
|
||||
// Move selected nodes
|
||||
var delta = new Float2(key == KeyboardKeys.ArrowLeft ? -keyMoveRange : keyMoveRange, 0);
|
||||
var delta = new Float2(key == KeyboardKeys.ArrowLeft ? -keyMoveDelta : keyMoveDelta, 0);
|
||||
MoveSelectedNodes(delta);
|
||||
}
|
||||
return true;
|
||||
@@ -830,13 +835,9 @@ namespace FlaxEditor.Surface
|
||||
return true;
|
||||
|
||||
if (Root.GetKey(KeyboardKeys.Shift))
|
||||
{
|
||||
_selectedConnectionIndex = ((_selectedConnectionIndex - 1) % connectionCount + connectionCount) % connectionCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedConnectionIndex = (_selectedConnectionIndex + 1) % connectionCount;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace FlaxEditor.Surface
|
||||
Enabled = false;
|
||||
|
||||
// Clean data
|
||||
_connectionInstigator = null;
|
||||
_connectionInstigators.Clear();
|
||||
_lastInstigatorUnderMouse = null;
|
||||
|
||||
var failed = RootContext.Load();
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// The connection start.
|
||||
/// </summary>
|
||||
protected IConnectionInstigator _connectionInstigator;
|
||||
protected List<IConnectionInstigator> _connectionInstigators = new List<IConnectionInstigator>();
|
||||
|
||||
/// <summary>
|
||||
/// The last connection instigator under mouse.
|
||||
@@ -232,19 +232,19 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is box selecting nodes.
|
||||
/// Gets a value indicating whether user is selecting nodes.
|
||||
/// </summary>
|
||||
public bool IsBoxSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null;
|
||||
public bool IsSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigators.Count == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user was previously box selecting nodes.
|
||||
/// Gets a value indicating whether user was previously selecting nodes.
|
||||
/// </summary>
|
||||
public bool WasBoxSelecting { get; private set; }
|
||||
public bool WasSelecting { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is moving selected nodes.
|
||||
/// </summary>
|
||||
public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigator == null;
|
||||
public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigators.Count == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user was previously moving selected nodes.
|
||||
@@ -254,7 +254,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is connecting nodes.
|
||||
/// </summary>
|
||||
public bool IsConnecting => _connectionInstigator != null;
|
||||
public bool IsConnecting => _connectionInstigators.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the left mouse button is down.
|
||||
|
||||
@@ -178,19 +178,31 @@ namespace FlaxEditor.Surface
|
||||
|
||||
// Update boxes types for nodes that dependant box types based on incoming connections
|
||||
{
|
||||
bool keepUpdating = false;
|
||||
int updateLimit = 100;
|
||||
bool keepUpdating = true;
|
||||
int updatesMin = 2, updatesMax = 100;
|
||||
do
|
||||
{
|
||||
keepUpdating = false;
|
||||
for (int i = 0; i < RootControl.Children.Count; i++)
|
||||
{
|
||||
if (RootControl.Children[i] is SurfaceNode node && !node.HasDependentBoxesSetup)
|
||||
if (RootControl.Children[i] is SurfaceNode node)
|
||||
{
|
||||
node.UpdateBoxesTypes();
|
||||
keepUpdating = true;
|
||||
var arch = node.Archetype;
|
||||
if (arch.DependentBoxes != null && arch.IndependentBoxes != null)
|
||||
{
|
||||
foreach (var boxId in arch.DependentBoxes)
|
||||
{
|
||||
var b = node.GetBox(boxId);
|
||||
if (b != null && b.CurrentType == b.DefaultType)
|
||||
{
|
||||
keepUpdating = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (keepUpdating && updateLimit-- > 0);
|
||||
} while ((keepUpdating && --updatesMax > 0) || --updatesMin > 0);
|
||||
}
|
||||
|
||||
Loaded?.Invoke(this);
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List<Box> startBoxes)
|
||||
{
|
||||
// Update nodes for method overrides
|
||||
Profiler.BeginEvent("Overrides");
|
||||
@@ -268,7 +268,7 @@ namespace FlaxEditor.Surface
|
||||
// Update nodes for invoke methods (async)
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBoxes);
|
||||
|
||||
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace FlaxEditor.Tools.Terrain
|
||||
{
|
||||
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
|
||||
{
|
||||
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace FlaxEditor.Tools.Terrain
|
||||
{
|
||||
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
|
||||
{
|
||||
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,11 +74,6 @@ struct TextureDataResult
|
||||
PixelFormat Format;
|
||||
Int2 Mip0Size;
|
||||
BytesContainer* Mip0DataPtr;
|
||||
|
||||
TextureDataResult()
|
||||
: Lock(FlaxStorage::LockData::Invalid)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false)
|
||||
@@ -149,13 +144,13 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool h
|
||||
|
||||
bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches, Texture* heightmap, float heightmapScale, Texture* splatmap1, Texture* splatmap2)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.GenerateTerrain");
|
||||
CHECK_RETURN(terrain && terrain->GetChunkSize() != 0, true);
|
||||
if (numberOfPatches.X < 1 || numberOfPatches.Y < 1)
|
||||
{
|
||||
LOG(Warning, "Cannot setup terain with no patches.");
|
||||
LOG(Warning, "Cannot setup terrain with no patches.");
|
||||
return false;
|
||||
}
|
||||
PROFILE_CPU_NAMED("Terrain.GenerateTerrain");
|
||||
|
||||
// Wait for assets to be loaded
|
||||
if (heightmap && heightmap->WaitForLoaded())
|
||||
@@ -178,7 +173,9 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
|
||||
terrain->AddPatches(numberOfPatches);
|
||||
|
||||
// Prepare data
|
||||
const auto heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
|
||||
const int32 heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
|
||||
const float heightmapSizeInv = 1.0f / (float)(heightmapSize - 1);
|
||||
const Float2 uvPerPatch = Float2::One / Float2(numberOfPatches);
|
||||
Array<float> heightmapData;
|
||||
heightmapData.Resize(heightmapSize * heightmapSize);
|
||||
|
||||
@@ -192,19 +189,17 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
|
||||
const auto sampler = PixelFormatSampler::Get(dataHeightmap.Format);
|
||||
|
||||
// Initialize with sub-range of the input heightmap
|
||||
const Vector2 uvPerPatch = Vector2::One / Vector2(numberOfPatches);
|
||||
const float heightmapSizeInv = 1.0f / (heightmapSize - 1);
|
||||
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
|
||||
{
|
||||
auto patch = terrain->GetPatch(patchIndex);
|
||||
const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
|
||||
const Float2 uvStart = Float2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
|
||||
|
||||
// Sample heightmap pixels with interpolation to get actual heightmap vertices locations
|
||||
for (int32 z = 0; z < heightmapSize; z++)
|
||||
{
|
||||
for (int32 x = 0; x < heightmapSize; x++)
|
||||
{
|
||||
const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch;
|
||||
const Float2 uv = uvStart + Float2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch;
|
||||
const Color color = sampler->SampleLinear(dataHeightmap.Mip0DataPtr->Get(), uv, dataHeightmap.Mip0Size, dataHeightmap.RowPitch);
|
||||
heightmapData[z * heightmapSize + x] = color.R * heightmapScale;
|
||||
}
|
||||
@@ -230,37 +225,30 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
|
||||
Texture* splatmaps[2] = { splatmap1, splatmap2 };
|
||||
Array<Color32> splatmapData;
|
||||
TextureDataResult data1;
|
||||
const Vector2 uvPerPatch = Vector2::One / Vector2(numberOfPatches);
|
||||
const float heightmapSizeInv = 1.0f / (heightmapSize - 1);
|
||||
for (int32 index = 0; index < ARRAY_COUNT(splatmaps); index++)
|
||||
{
|
||||
const auto splatmap = splatmaps[index];
|
||||
if (!splatmap)
|
||||
continue;
|
||||
|
||||
// Prepare data
|
||||
if (splatmapData.IsEmpty())
|
||||
splatmapData.Resize(heightmapSize * heightmapSize);
|
||||
|
||||
// Get splatmap data
|
||||
if (GetTextureDataForSampling(splatmap, data1))
|
||||
return true;
|
||||
const auto sampler = PixelFormatSampler::Get(data1.Format);
|
||||
|
||||
// Modify heightmap splatmaps with sub-range of the input splatmaps
|
||||
splatmapData.Resize(heightmapSize * heightmapSize);
|
||||
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
|
||||
{
|
||||
auto patch = terrain->GetPatch(patchIndex);
|
||||
|
||||
const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
|
||||
const Float2 uvStart = Float2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
|
||||
|
||||
// Sample splatmap pixels with interpolation to get actual splatmap values
|
||||
for (int32 z = 0; z < heightmapSize; z++)
|
||||
{
|
||||
for (int32 x = 0; x < heightmapSize; x++)
|
||||
{
|
||||
const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch;
|
||||
|
||||
const Float2 uv = uvStart + Float2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch;
|
||||
const Color color = sampler->SampleLinear(data1.Mip0DataPtr->Get(), uv, data1.Mip0Size, data1.RowPitch);
|
||||
|
||||
Color32 layers;
|
||||
@@ -374,63 +362,38 @@ Color32* TerrainTools::GetSplatMapData(Terrain* terrain, const Int2& patchCoord,
|
||||
|
||||
bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.ExportTerrain");
|
||||
CHECK_RETURN(terrain && terrain->GetPatchesCount() != 0, true);
|
||||
const auto firstPatch = terrain->GetPatch(0);
|
||||
|
||||
// Calculate texture size
|
||||
const int32 patchEdgeVertexCount = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
|
||||
const int32 patchVertexCount = patchEdgeVertexCount * patchEdgeVertexCount;
|
||||
|
||||
// Find size of heightmap in patches
|
||||
const auto firstPatch = terrain->GetPatch(0);
|
||||
Int2 start(firstPatch->GetX(), firstPatch->GetZ());
|
||||
Int2 end(start);
|
||||
for (int32 i = 0; i < terrain->GetPatchesCount(); i++)
|
||||
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
|
||||
{
|
||||
const int32 x = terrain->GetPatch(i)->GetX();
|
||||
const int32 y = terrain->GetPatch(i)->GetZ();
|
||||
|
||||
if (x < start.X)
|
||||
start.X = x;
|
||||
if (y < start.Y)
|
||||
start.Y = y;
|
||||
if (x > end.X)
|
||||
end.X = x;
|
||||
if (y > end.Y)
|
||||
end.Y = y;
|
||||
const auto patch = terrain->GetPatch(patchIndex);
|
||||
const Int2 pos(patch->GetX(), patch->GetZ());
|
||||
start = Int2::Min(start, pos);
|
||||
end = Int2::Max(end, pos);
|
||||
}
|
||||
const Int2 size = (end + 1) - start;
|
||||
|
||||
// Allocate - with space for non-existent patches
|
||||
// Allocate heightmap for a whole terrain (NumberOfPatches * 4x4 * ChunkSize + 1)
|
||||
const Int2 heightmapSize = size * Terrain::ChunksCountEdge * terrain->GetChunkSize() + 1;
|
||||
Array<float> heightmap;
|
||||
heightmap.Resize(patchVertexCount * size.X * size.Y);
|
||||
|
||||
// Set to any element, where: min < elem < max
|
||||
heightmap.Resize(heightmapSize.X * heightmapSize.Y);
|
||||
heightmap.SetAll(firstPatch->GetHeightmapData()[0]);
|
||||
|
||||
const int32 heightmapWidth = patchEdgeVertexCount * size.X;
|
||||
|
||||
// Fill heightmap with data
|
||||
// Fill heightmap with data from all patches
|
||||
const int32 rowSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
|
||||
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
|
||||
{
|
||||
// Pick a patch
|
||||
const auto patch = terrain->GetPatch(patchIndex);
|
||||
const float* data = patch->GetHeightmapData();
|
||||
|
||||
// Beginning of patch
|
||||
int32 dstIndex = (patch->GetX() - start.X) * patchEdgeVertexCount +
|
||||
(patch->GetZ() - start.Y) * size.Y * patchVertexCount;
|
||||
|
||||
// Iterate over lines in patch
|
||||
for (int32 z = 0; z < patchEdgeVertexCount; z++)
|
||||
{
|
||||
// Iterate over vertices in line
|
||||
for (int32 x = 0; x < patchEdgeVertexCount; x++)
|
||||
{
|
||||
heightmap[dstIndex + x] = data[z * patchEdgeVertexCount + x];
|
||||
}
|
||||
|
||||
dstIndex += heightmapWidth;
|
||||
}
|
||||
const Int2 pos(patch->GetX() - start.X, patch->GetZ() - start.Y);
|
||||
const float* src = patch->GetHeightmapData();
|
||||
float* dst = heightmap.Get() + pos.X * (rowSize - 1) + pos.Y * heightmapSize.X * (rowSize - 1);
|
||||
for (int32 row = 0; row < rowSize; row++)
|
||||
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
|
||||
}
|
||||
|
||||
// Interpolate to 16-bit int
|
||||
@@ -438,44 +401,42 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
|
||||
maxHeight = minHeight = heightmap[0];
|
||||
for (int32 i = 1; i < heightmap.Count(); i++)
|
||||
{
|
||||
float h = heightmap[i];
|
||||
float h = heightmap.Get()[i];
|
||||
if (maxHeight < h)
|
||||
maxHeight = h;
|
||||
else if (minHeight > h)
|
||||
minHeight = h;
|
||||
}
|
||||
|
||||
const float maxValue = 65535.0f;
|
||||
const float alpha = maxValue / (maxHeight - minHeight);
|
||||
const float alpha = MAX_uint16 / (maxHeight - minHeight);
|
||||
|
||||
// Storage for pixel data
|
||||
Array<uint16> byteHeightmap(heightmap.Capacity());
|
||||
|
||||
for (auto& elem : heightmap)
|
||||
Array<uint16> byteHeightmap;
|
||||
byteHeightmap.Resize(heightmap.Count());
|
||||
for (int32 i = 0; i < heightmap.Count(); i++)
|
||||
{
|
||||
byteHeightmap.Add(static_cast<uint16>(alpha * (elem - minHeight)));
|
||||
float height = heightmap.Get()[i];
|
||||
byteHeightmap.Get()[i] = static_cast<uint16>(alpha * (height - minHeight));
|
||||
}
|
||||
|
||||
// Create texture
|
||||
TextureData textureData;
|
||||
textureData.Height = textureData.Width = heightmapWidth;
|
||||
textureData.Width = heightmapSize.X;
|
||||
textureData.Height = heightmapSize.Y;
|
||||
textureData.Depth = 1;
|
||||
textureData.Format = PixelFormat::R16_UNorm;
|
||||
textureData.Items.Resize(1);
|
||||
textureData.Items[0].Mips.Resize(1);
|
||||
|
||||
// Fill mip data
|
||||
TextureMipData* srcMip = textureData.GetData(0, 0);
|
||||
srcMip->Data.Link(byteHeightmap.Get());
|
||||
srcMip->Lines = textureData.Height;
|
||||
srcMip->RowPitch = textureData.Width * 2; // 2 bytes per pixel for format R16
|
||||
srcMip->RowPitch = textureData.Width * sizeof(uint16);
|
||||
srcMip->DepthPitch = srcMip->Lines * srcMip->RowPitch;
|
||||
|
||||
// Find next non-existing file heightmap file
|
||||
FileSystem::NormalizePath(outputFolder);
|
||||
const String baseFileName(TEXT("heightmap"));
|
||||
String outputPath;
|
||||
for (int32 i = 0; i < MAX_int32; i++)
|
||||
for (int32 i = 0; i < 100; i++)
|
||||
{
|
||||
outputPath = outputFolder / baseFileName + StringUtils::ToString(i) + TEXT(".png");
|
||||
if (!FileSystem::FileExists(outputPath))
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
|
||||
if (_navmeshBoundsModifications != null)
|
||||
{
|
||||
foreach (var bounds in _navmeshBoundsModifications)
|
||||
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
|
||||
}
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(scene);
|
||||
@@ -217,11 +217,10 @@ namespace FlaxEditor.Tools.Terrain.Undo
|
||||
}
|
||||
|
||||
// Update navmesh
|
||||
var scene = Terrain.Scene;
|
||||
if (_navmeshBoundsModifications != null)
|
||||
{
|
||||
foreach (var bounds in _navmeshBoundsModifications)
|
||||
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
|
||||
}
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene);
|
||||
|
||||
@@ -303,7 +303,7 @@ namespace FlaxEditor.Actions
|
||||
if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren)
|
||||
{
|
||||
var bounds = node.Actor.BoxWithChildren;
|
||||
Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,8 @@ namespace FlaxEditor.Actions
|
||||
var obj = Object.Find<SceneObject>(ref item.ID);
|
||||
if (obj != null)
|
||||
{
|
||||
scenes.Add(obj.Parent.Scene);
|
||||
if (obj.Parent != null)
|
||||
scenes.Add(obj.Parent.Scene);
|
||||
if (obj is Actor actor)
|
||||
actor.SetParent(newParent, _worldPositionsStays, true);
|
||||
else
|
||||
|
||||
@@ -121,12 +121,12 @@ namespace FlaxEditor
|
||||
// Handle simple case where objects were moved just a little and use one navmesh build request to improve performance
|
||||
if (data.BeforeBounds.Intersects(ref data.AfterBounds))
|
||||
{
|
||||
Navigation.BuildNavMesh(data.Scene, BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
Navigation.BuildNavMesh(data.Scene, data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(data.Scene, data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation.BuildNavMesh(data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user