Merge branch 'FlaxEngine:master' into Debug

This commit is contained in:
NoriteSC
2023-10-06 22:16:11 +02:00
committed by GitHub
353 changed files with 9673 additions and 3995 deletions
Binary file not shown.
+26 -26
View File
@@ -2,35 +2,35 @@
using System.Collections.Generic;
using FlaxEngine;
namespace %namespace%
namespace %namespace%;
/// <summary>
/// %class% Script.
/// </summary>
public class %class% : Script
{
/// <summary>
/// %class% Script.
/// </summary>
public class %class% : Script
/// <inheritdoc/>
public override void OnStart()
{
/// <inheritdoc/>
public override void OnStart()
{
// Here you can add code that needs to be called when script is created, just before the first game update
}
/// <inheritdoc/>
public override void OnEnable()
{
// Here you can add code that needs to be called when script is enabled (eg. register for events)
}
// Here you can add code that needs to be called when script is created, just before the first game update
}
/// <inheritdoc/>
public override void OnEnable()
{
// Here you can add code that needs to be called when script is enabled (eg. register for events)
}
/// <inheritdoc/>
public override void OnDisable()
{
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
}
/// <inheritdoc/>
public override void OnDisable()
{
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
}
/// <inheritdoc/>
public override void OnUpdate()
{
// Here you can add code that needs to be called every frame
}
/// <inheritdoc/>
public override void OnUpdate()
{
// Here you can add code that needs to be called every frame
}
}
+1 -11
View File
@@ -11,16 +11,6 @@ for %%I in (Source\Logo.png) do if %%~zI LSS 2000 (
call "Development\Scripts\Windows\GetMSBuildPath.bat"
if errorlevel 1 goto Error_NoVisualStudioEnvironment
if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto Compile
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
for %%j in (15.0, Current) do (
if exist "%%i\MSBuild\%%j\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\%%j\Bin\MSBuild.exe"
goto Compile
)
)
)
:Compile
md Cache\Intermediate >nul 2>nul
dir /s /b Source\Tools\Flax.Build\*.cs >Cache\Intermediate\Flax.Build.Files.txt
@@ -44,7 +34,7 @@ goto Exit
echo CallBuildTool ERROR: The script is in invalid directory.
goto Exit
:Error_NoVisualStudioEnvironment
echo CallBuildTool ERROR: Missing Visual Studio 2015 or newer.
echo CallBuildTool ERROR: Missing Visual Studio 2022 or newer.
goto Exit
:Error_CompilationFailed
echo CallBuildTool ERROR: Failed to compile Flax.Build project.
+13 -53
View File
@@ -4,66 +4,26 @@ rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
set MSBUILD_PATH=
rem Look for MSBuild version 17.0 or later
if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto VsWhereNotFound
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe"
goto End
)
)
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe"
goto End
)
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe"
goto End
)
)
:VsWhereNotFound
if exist "%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" (
set MSBUILD_PATH="%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe"
goto End
rem Look for MSBuild version 17.0 or later in pre-release versions
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe"
goto End
)
)
call :GetInstallPath Microsoft\VisualStudio\SxS\VS7 15.0 MSBuild\15.0\bin\MSBuild.exe
if not errorlevel 1 goto End
call :GetInstallPath Microsoft\MSBuild\ToolsVersions\14.0 MSBuildToolsPath MSBuild.exe
if not errorlevel 1 goto End
call :GetInstallPath Microsoft\MSBuild\ToolsVersions\12.0 MSBuildToolsPath MSBuild.exe
if not errorlevel 1 goto End
call :GetInstallPath Microsoft\MSBuild\ToolsVersions\4.0 MSBuildToolsPath MSBuild.exe
if not errorlevel 1 goto End
echo GetMSBuildPath ERROR: Could not find MSBuild version 17.0 or later.
exit /B 1
:VsWhereNotFound
echo GetMSBuildPath ERROR: vswhere.exe was not found.
exit /B 1
:End
exit /B 0
:GetInstallPath
for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\%1 /v %2 2^>Nul') do (
if exist "%%B%%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\%1 /v %2 2^>Nul') do (
if exist "%%B%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do (
if exist "%%B%%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do (
if exist "%%B%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
exit /B 1
exit /B 0
+1 -1
View File
@@ -3,7 +3,7 @@
"Version": {
"Major": 1,
"Minor": 6,
"Build": 6344
"Build": 6345
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
+2
View File
@@ -291,6 +291,8 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=lightmaps/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Linearize/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lods/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Marshallable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=marshallers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mclass/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=memcpy/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=metalness/@EntryIndexedValue">True</s:Boolean>
+11 -4
View File
@@ -1,15 +1,22 @@
@echo off
rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
:: Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
setlocal
pushd
echo Generating Flax Engine project files...
rem Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments)
call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %*
:: Change the path to the script root
cd /D "%~dp0"
:: Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments)
call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %*
if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations
echo Building C# bindings...
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame
popd
echo Done!
exit /B 0
+5
View File
@@ -10,3 +10,8 @@ cd "`dirname "$0"`"
# Run Flax.Build to generate project files (also pass the arguments)
bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame
+5
View File
@@ -10,3 +10,8 @@ cd "`dirname "$0"`"
# Run Flax.Build to generate project files (also pass the arguments)
bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame
+4 -3
View File
@@ -31,19 +31,20 @@ Follow the instructions below to compile and run the engine from source.
* Install Visual Studio 2022 or newer
* Install Windows 8.1 SDK or newer (via Visual Studio Installer)
* Install Microsoft Visual C++ 2015 v140 toolset or newer (via Visual Studio Installer)
* Install .Net 7 SDK (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Install .NET 7 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Install Git with LFS
* Clone repo (with LFS)
* Run **GenerateProjectFiles.bat**
* Open `Flax.sln` and set solution configuration to **Editor.Development** and solution platform to **Win64**
* Set Flax (C++) or FlaxEngine (C#) as startup project
* Compile Flax project (hit F7 or CTRL+Shift+B)
* Optionally set Debug Type to **Managed Only (.NET Core)** to debug C#-only, or **Mixed (.NET Core)** to debug both C++ and C#
* Run Flax (hit F5 key)
## Linux
* Install Visual Studio Code
* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Ubuntu: `sudo apt install dotnet-sdk-7.0`
* Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
* Ubuntu: `sudo apt install vulkan-sdk`
@@ -66,7 +67,7 @@ Follow the instructions below to compile and run the engine from source.
## Mac
* Install XCode
* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
* Clone repo (with LFS)
* Run `GenerateProjectFiles.command`
@@ -68,7 +68,7 @@ namespace FlaxEditor.Content.GUI
_validDragOver = true;
result = DragDropEffect.Copy;
}
else if (_dragActors.HasValidDrag)
else if (_dragActors != null && _dragActors.HasValidDrag)
{
_validDragOver = true;
result = DragDropEffect.Move;
@@ -94,7 +94,7 @@ namespace FlaxEditor.Content.GUI
result = DragDropEffect.Copy;
}
// Check if drop actor(s)
else if (_dragActors.HasValidDrag)
else if (_dragActors != null && _dragActors.HasValidDrag)
{
// Import actors
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
@@ -1,144 +1,52 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Interop;
using FlaxEngine.Tools;
namespace FlaxEngine.Tools
{
partial class AudioTool
{
partial struct Options
{
private bool ShowBtiDepth => Format != AudioFormat.Vorbis;
}
}
}
namespace FlaxEditor.CustomEditors.Dedicated
{
/// <summary>
/// Custom editor for <see cref="FlaxEngine.Tools.AudioTool.Options"/>.
/// </summary>
[CustomEditor(typeof(FlaxEngine.Tools.AudioTool.Options)), DefaultEditor]
public class AudioToolOptionsEditor : GenericEditor
{
/// <inheritdoc />
protected override List<ItemInfo> GetItemsForType(ScriptType type)
{
// Show both fields and properties
return GetItemsForType(type, true, true);
}
}
}
namespace FlaxEditor.Content.Import
{
/// <summary>
/// Proxy object to present audio import settings in <see cref="ImportFilesDialog"/>.
/// </summary>
[HideInEditor]
public class AudioImportSettings
{
/// <summary>
/// A custom set of bit depth audio import sizes.
/// The settings data.
/// </summary>
public enum CustomBitDepth
{
/// <summary>
/// The 8.
/// </summary>
_8 = 8,
/// <summary>
/// The 16.
/// </summary>
_16 = 16,
/// <summary>
/// The 24.
/// </summary>
_24 = 24,
/// <summary>
/// The 32.
/// </summary>
_32 = 32,
}
/// <summary>
/// Converts the bit depth to enum.
/// </summary>
/// <param name="f">The bit depth.</param>
/// <returns>The converted enum.</returns>
public static CustomBitDepth ConvertBitDepth(int f)
{
FieldInfo[] fields = typeof(CustomBitDepth).GetFields();
for (int i = 0; i < fields.Length; i++)
{
var field = fields[i];
if (field.Name.Equals("value__"))
continue;
if (f == (int)field.GetRawConstantValue())
return (CustomBitDepth)f;
}
return CustomBitDepth._16;
}
/// <summary>
/// The audio data format to import the audio clip as.
/// </summary>
[EditorOrder(10), DefaultValue(AudioFormat.Vorbis), Tooltip("The audio data format to import the audio clip as.")]
public AudioFormat Format { get; set; } = AudioFormat.Vorbis;
/// <summary>
/// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.
/// </summary>
[EditorOrder(15), DefaultValue(0.4f), Limit(0, 1, 0.01f), Tooltip("The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.")]
public float CompressionQuality { get; set; } = 0.4f;
/// <summary>
/// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).
/// </summary>
[EditorOrder(20), DefaultValue(false), Tooltip("Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).")]
public bool DisableStreaming { get; set; } = false;
/// <summary>
/// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.
/// </summary>
[EditorOrder(30), DefaultValue(false), EditorDisplay(null, "Is 3D"), Tooltip("Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.")]
public bool Is3D { get; set; } = false;
/// <summary>
/// The size of a single sample in bits. The clip will be converted to this bit depth on import.
/// </summary>
[EditorOrder(40), DefaultValue(CustomBitDepth._16), Tooltip("The size of a single sample in bits. The clip will be converted to this bit depth on import.")]
public CustomBitDepth BitDepth { get; set; } = CustomBitDepth._16;
[StructLayout(LayoutKind.Sequential)]
internal struct InternalOptions
{
[MarshalAs(UnmanagedType.I1)]
public AudioFormat Format;
public byte DisableStreaming;
public byte Is3D;
public int BitDepth;
public float Quality;
}
internal void ToInternal(out InternalOptions options)
{
options = new InternalOptions
{
Format = Format,
DisableStreaming = (byte)(DisableStreaming ? 1 : 0),
Is3D = (byte)(Is3D ? 1 : 0),
Quality = CompressionQuality,
BitDepth = (int)BitDepth,
};
}
internal void FromInternal(ref InternalOptions options)
{
Format = options.Format;
DisableStreaming = options.DisableStreaming != 0;
Is3D = options.Is3D != 0;
CompressionQuality = options.Quality;
BitDepth = ConvertBitDepth(options.BitDepth);
}
/// <summary>
/// Tries the restore the asset import options from the target resource file.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="assetPath">The asset path.</param>
/// <returns>True settings has been restored, otherwise false.</returns>
public static bool TryRestore(ref AudioImportSettings options, string assetPath)
{
if (AudioImportEntry.Internal_GetAudioImportOptions(assetPath, out var internalOptions))
{
// Restore settings
options.FromInternal(ref internalOptions);
return true;
}
return false;
}
[EditorDisplay(null, EditorDisplayAttribute.InlineStyle)]
public AudioTool.Options Settings = AudioTool.Options.Default;
}
/// <summary>
@@ -147,7 +55,7 @@ namespace FlaxEditor.Content.Import
/// <seealso cref="AssetImportEntry" />
public partial class AudioImportEntry : AssetImportEntry
{
private AudioImportSettings _settings = new AudioImportSettings();
private AudioImportSettings _settings = new();
/// <summary>
/// Initializes a new instance of the <see cref="AudioImportEntry"/> class.
@@ -157,7 +65,7 @@ namespace FlaxEditor.Content.Import
: base(ref request)
{
// Try to restore target asset Audio import options (useful for fast reimport)
AudioImportSettings.TryRestore(ref _settings, ResultUrl);
Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl);
}
/// <inheritdoc />
@@ -166,27 +74,23 @@ namespace FlaxEditor.Content.Import
/// <inheritdoc />
public override bool TryOverrideSettings(object settings)
{
if (settings is AudioImportSettings o)
if (settings is AudioImportSettings s)
{
_settings = o;
_settings.Settings = s.Settings;
return true;
}
if (settings is AudioTool.Options o)
{
_settings.Settings = o;
return true;
}
return false;
}
/// <inheritdoc />
public override bool Import()
{
return Editor.Import(SourceUrl, ResultUrl, _settings);
return Editor.Import(SourceUrl, ResultUrl, _settings.Settings);
}
#region Internal Calls
[LibraryImport("FlaxEngine", EntryPoint = "AudioImportEntryInternal_GetAudioImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_GetAudioImportOptions(string path, out AudioImportSettings.InternalOptions result);
#endregion
}
}
@@ -139,7 +139,7 @@ namespace FlaxEditor.Content.Import
var menu = new ContextMenu();
menu.AddButton("Rename", OnRenameClicked);
menu.AddButton("Don't import", OnDontImportClicked);
menu.AddButton("Show in Explorer", OnShowInExplorerClicked);
menu.AddButton(Utilities.Constants.ShowInExplorer, OnShowInExplorerClicked);
menu.Tag = node;
menu.Show(node, location);
}
+9 -9
View File
@@ -486,7 +486,7 @@ namespace FlaxEditor.Content
else
Render2D.FillRectangle(rectangle, Color.Black);
}
/// <summary>
/// Draws the item thumbnail.
/// </summary>
@@ -684,7 +684,7 @@ namespace FlaxEditor.Content
var thumbnailSize = size.X;
thumbnailRect = new Rectangle(0, 0, thumbnailSize, thumbnailSize);
nameAlignment = TextAlignment.Center;
if (this is ContentFolder)
{
// Small shadow
@@ -692,7 +692,7 @@ namespace FlaxEditor.Content
var color = Color.Black.AlphaMultiplied(0.2f);
Render2D.FillRectangle(shadowRect, color);
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
if (isSelected)
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
else if (IsMouseOver)
@@ -706,14 +706,14 @@ namespace FlaxEditor.Content
var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1);
var color = Color.Black.AlphaMultiplied(0.2f);
Render2D.FillRectangle(shadowRect, color);
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
Render2D.FillRectangle(TextRectangle, style.LightBackground);
var accentHeight = 2 * view.ViewScale;
var barRect = new Rectangle(0, thumbnailRect.Height - accentHeight, clientRect.Width, accentHeight);
Render2D.FillRectangle(barRect, Color.DimGray);
DrawThumbnail(ref thumbnailRect, false);
if (isSelected)
{
@@ -733,18 +733,18 @@ namespace FlaxEditor.Content
var thumbnailSize = size.Y - 2 * DefaultMarginSize;
thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize);
nameAlignment = TextAlignment.Near;
if (isSelected)
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
else if (IsMouseOver)
Render2D.FillRectangle(clientRect, style.BackgroundHighlighted);
DrawThumbnail(ref thumbnailRect);
break;
}
default: throw new ArgumentOutOfRangeException();
}
// Draw short name
Render2D.PushClip(ref textRect);
Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f);
@@ -29,7 +29,7 @@ namespace FlaxEditor.Content
//_watcher.Changed += OnEvent;
_watcher.Created += OnEvent;
_watcher.Deleted += OnEvent;
//_watcher.Renamed += OnEvent;
_watcher.Renamed += OnEvent;
}
private void OnEvent(object sender, FileSystemEventArgs e)
@@ -5,6 +5,7 @@
#include "MacPlatformTools.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/Mac/MacPlatformSettings.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Core/Config/BuildSettings.h"
@@ -16,7 +17,9 @@
#include "Editor/ProjectInfo.h"
#include "Editor/Cooker/GameCooker.h"
#include "Editor/Utilities/EditorUtilities.h"
#include <ThirdParty/pugixml/pugixml.hpp>
#include "pugixml/pugixml_extra.hpp"
using namespace pugi;
IMPLEMENT_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
@@ -124,17 +127,35 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
LOG(Error, "Failed to export application icon.");
return true;
}
bool failed = Platform::RunProcess(TEXT("sips -z 16 16 icon_1024x1024.png --out icon_16x16.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_16x16@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_32x32.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 64 64 icon_1024x1024.png --out icon_32x32@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 128 128 icon_1024x1024.png --out icon_128x128.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_128x128@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_256x256.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_256x256@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_512x512.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 1024 1024 icon_1024x1024.png --out icon_512x512@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("iconutil -c icns icon.iconset"), iconFolderPath);
CreateProcessSettings procSettings;
procSettings.HiddenWindow = true;
procSettings.FileName = TEXT("/usr/bin/sips");
procSettings.WorkingDirectory = tmpFolderPath;
procSettings.Arguments = TEXT("-z 16 16 icon_1024x1024.png --out icon_16x16.png");
bool failed = false;
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 32 32 icon_1024x1024.png --out icon_16x16@2x.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 32 32 icon_1024x1024.png --out icon_32x32.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 64 64 icon_1024x1024.png --out icon_32x32@2x.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 128 128 icon_1024x1024.png --out icon_128x128.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 256 256 icon_1024x1024.png --out icon_128x128@2x.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 256 256 icon_1024x1024.png --out icon_256x256.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 512 512 icon_1024x1024.png --out icon_256x256@2x.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 512 512 icon_1024x1024.png --out icon_512x512.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.Arguments = TEXT("-z 1024 1024 icon_1024x1024.png --out icon_512x512@2x.png");
failed |= Platform::CreateProcess(procSettings);
procSettings.FileName = TEXT("/usr/bin/iconutil");
procSettings.Arguments = TEXT("-c icns icon.iconset");
procSettings.WorkingDirectory = iconFolderPath;
failed |= Platform::CreateProcess(procSettings);
if (failed)
{
LOG(Error, "Failed to export application icon.");
@@ -151,17 +172,17 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
const String plistPath = data.DataOutputPath / TEXT("Info.plist");
{
xml_document doc;
xml_node plist = doc.child_or_append(PUGIXML_TEXT("plist"));
xml_node_extra plist = xml_node_extra(doc).child_or_append(PUGIXML_TEXT("plist"));
plist.append_attribute(PUGIXML_TEXT("version")).set_value(PUGIXML_TEXT("1.0"));
xml_node dict = plist.child_or_append(PUGIXML_TEXT("dict"));
xml_node_extra dict = plist.child_or_append(PUGIXML_TEXT("dict"));
#define ADD_ENTRY(key, value) \
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
dict.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT(value))
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \
dict.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT(value))
#define ADD_ENTRY_STR(key, value) \
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \
{ std::u16string valueStr(value.GetText()); \
dict.append_child(PUGIXML_TEXT("string")).set_child_value(pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); }
dict.append_child_with_value(PUGIXML_TEXT("string"), pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); }
ADD_ENTRY("CFBundleDevelopmentRegion", "English");
ADD_ENTRY("CFBundlePackageType", "APPL");
@@ -175,22 +196,22 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
ADD_ENTRY_STR("CFBundleVersion", projectVersion);
ADD_ENTRY_STR("NSHumanReadableCopyright", gameSettings->CopyrightNotice);
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("CFBundleSupportedPlatforms"));
xml_node CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array"));
CFBundleSupportedPlatforms.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("MacOSX"));
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("CFBundleSupportedPlatforms"));
xml_node_extra CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array"));
CFBundleSupportedPlatforms.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("MacOSX"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture"));
xml_node LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict"));
dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture"));
xml_node_extra LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict"));
switch (_arch)
{
case ArchitectureType::x64:
LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("x86_64"));
LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("x86_64"));
break;
case ArchitectureType::ARM64:
LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("arm64"));
LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("arm64"));
break;
}
LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("10.15"));
LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("10.15"));
#undef ADD_ENTRY
#undef ADD_ENTRY_STR
@@ -210,16 +231,22 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
return false;
GameCooker::PackageFiles();
LOG(Info, "Building app package...");
const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg");
const String dmgCommand = String::Format(TEXT("hdiutil create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName);
const int32 result = Platform::RunProcess(dmgCommand, data.OriginalOutputPath);
if (result != 0)
{
data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result));
return true;
const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg");
CreateProcessSettings procSettings;
procSettings.HiddenWindow = true;
procSettings.WorkingDirectory = data.OriginalOutputPath;
procSettings.FileName = TEXT("/usr/bin/hdiutil");
procSettings.Arguments = String::Format(TEXT("create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName);
const int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
{
data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result));
return true;
}
// TODO: sign dmg
LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024);
}
// TODO: sign dmg
LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024);
return false;
}
@@ -260,15 +260,20 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
{
LOG(Info, "Building app package...");
const Char* configuration = data.Configuration == BuildConfiguration::Release ? TEXT("Release") : TEXT("Debug");
String command = String::Format(TEXT("xcodebuild -project FlaxGame.xcodeproj -configuration {} -scheme FlaxGame -archivePath FlaxGame.xcarchive archive"), configuration);
int32 result = Platform::RunProcess(command, data.OriginalOutputPath);
CreateProcessSettings procSettings;
procSettings.HiddenWindow = true;
procSettings.WorkingDirectory = data.OriginalOutputPath;
procSettings.FileName = TEXT("/usr/bin/xcodebuild");
procSettings.Arguments = String::Format(TEXT("-project FlaxGame.xcodeproj -configuration {} -scheme FlaxGame -archivePath FlaxGame.xcarchive archive"), configuration);
int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
{
data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result));
return true;
}
command = TEXT("xcodebuild -exportArchive -archivePath FlaxGame.xcarchive -allowProvisioningUpdates -exportPath . -exportOptionsPlist ExportOptions.plist");
result = Platform::RunProcess(command, data.OriginalOutputPath);
procSettings.FileName = TEXT("/usr/bin/xcodebuild");
procSettings.Arguments = TEXT("-exportArchive -archivePath FlaxGame.xcarchive -allowProvisioningUpdates -exportPath . -exportOptionsPlist ExportOptions.plist");
result = Platform::CreateProcess(procSettings);
if (result != 0)
{
data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result));
@@ -0,0 +1,26 @@
using FlaxEditor.CustomEditors.Editors;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated;
/// <summary>
/// The missing script editor.
/// </summary>
[CustomEditor(typeof(MissingScript)), DefaultEditor]
public class MissingScriptEditor : GenericEditor
{
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
if (layout.ContainerControl is not DropPanel dropPanel)
{
base.Initialize(layout);
return;
}
dropPanel.HeaderTextColor = Color.OrangeRed;
base.Initialize(layout);
}
}
@@ -25,9 +25,20 @@ namespace FlaxEditor.CustomEditors.Dedicated
get
{
// All selected particle effects use the same system
var effect = (ParticleEffect)Values[0];
var system = effect.ParticleSystem;
return system != null && Values.TrueForAll(x => (x as ParticleEffect)?.ParticleSystem == system);
var effect = Values[0] as ParticleEffect;
var system = effect ? effect.ParticleSystem : null;
if (system && Values.TrueForAll(x => x is ParticleEffect fx && fx && fx.ParticleSystem == system))
{
// All parameters can be accessed
var parameters = effect.Parameters;
foreach (var parameter in parameters)
{
if (!parameter)
return false;
}
return true;
}
return false;
}
}
@@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
private DragHandlers _dragHandlers;
private DragScriptItems _dragScripts;
private DragAssets _dragAssets;
private Button _addScriptsButton;
/// <summary>
/// The parent scripts editor.
@@ -40,16 +41,19 @@ namespace FlaxEditor.CustomEditors.Dedicated
AutoFocus = false;
// Add script button
float addScriptButtonWidth = 60.0f;
var addScriptButton = new Button
var buttonText = "Add script";
var textSize = Style.Current.FontMedium.MeasureText(buttonText);
float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4;
var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4;
_addScriptsButton = new Button
{
TooltipText = "Add new scripts to the actor",
AnchorPreset = AnchorPresets.MiddleCenter,
Text = "Add script",
Text = buttonText,
Parent = this,
Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, 18),
Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, buttonHeight),
};
addScriptButton.ButtonClicked += OnAddScriptButtonClicked;
_addScriptsButton.ButtonClicked += OnAddScriptButtonClicked;
}
private void OnAddScriptButtonClicked(Button button)
@@ -82,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var size = Size;
// Info
Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, 22, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
// Check if drag is over
if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag)
@@ -576,8 +580,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
return;
for (int j = 0; j < e.Length; j++)
{
var t1 = scripts[j]?.TypeName;
var t2 = e[j]?.TypeName;
var t1 = scripts[j] != null ? scripts[j].TypeName : null;
var t2 = e[j] != null ? e[j].TypeName : null;
if (t1 != t2)
return;
}
@@ -1,9 +1,12 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.Actions;
using FlaxEditor.SceneGraph.Actors;
using System;
using System.Collections.Generic;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Actions;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.GUI.Tabs;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -14,54 +17,853 @@ namespace FlaxEditor.CustomEditors.Dedicated
[CustomEditor(typeof(Spline)), DefaultEditor]
public class SplineEditor : ActorEditor
{
/// <summary>
/// Storage undo spline data
/// </summary>
private struct UndoData
{
public Spline Spline;
public BezierCurve<Transform>.Keyframe[] BeforeKeyframes;
}
/// <summary>
/// Basis for creating tangent manipulation types for bezier curves.
/// </summary>
private class EditTangentOptionBase
{
/// <summary>
/// Called when user set selected tangent mode.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnSetMode(Spline spline, int index)
{
}
/// <summary>
/// Called when user select a keyframe (spline point) of current selected spline on editor viewport.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnSelectKeyframe(Spline spline, int index)
{
}
/// <summary>
/// Called when user select a tangent of current keyframe selected from spline.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnSelectTangent(Spline spline, int index)
{
}
/// <summary>
/// Called when the tangent in from current keyframe selected from spline is moved on editor viewport.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnMoveTangentIn(Spline spline, int index)
{
}
/// <summary>
/// Called when the tangent out from current keyframe selected from spline is moved on editor viewport.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Current spline selected on editor viewport.</param>
public virtual void OnMoveTangentOut(Spline spline, int index)
{
}
}
/// <summary>
/// Edit curve options manipulate the curve as free mode
/// </summary>
private sealed class FreeTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
if (IsLinearTangentMode(spline, index) || IsSmoothInTangentMode(spline, index) || IsSmoothOutTangentMode(spline, index))
{
SetPointSmooth(spline, index);
}
}
}
/// <summary>
/// Edit curve options to set tangents to linear
/// </summary>
private sealed class LinearTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SetKeyframeLinear(spline, index);
// change the selection to tangent parent (a spline point / keyframe)
SetSelectSplinePointNode(spline, index);
}
}
/// <summary>
/// Edit curve options to align tangents of selected spline
/// </summary>
private sealed class AlignedTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SmoothIfNotAligned(spline, index);
}
/// <inheritdoc/>
public override void OnSelectKeyframe(Spline spline, int index)
{
SmoothIfNotAligned(spline, index);
}
/// <inheritdoc/>
public override void OnMoveTangentIn(Spline spline, int index)
{
SetPointAligned(spline, index, true);
}
/// <inheritdoc/>
public override void OnMoveTangentOut(Spline spline, int index)
{
SetPointAligned(spline, index, false);
}
private void SmoothIfNotAligned(Spline spline, int index)
{
if (!IsAlignedTangentMode(spline, index))
{
SetPointSmooth(spline, index);
}
}
private void SetPointAligned(Spline spline, int index, bool alignWithIn)
{
var keyframe = spline.GetSplineKeyframe(index);
var referenceTangent = alignWithIn ? keyframe.TangentIn : keyframe.TangentOut;
var otherTangent = !alignWithIn ? keyframe.TangentIn : keyframe.TangentOut;
// inverse of reference tangent
otherTangent.Translation = -referenceTangent.Translation.Normalized * otherTangent.Translation.Length;
if (alignWithIn)
keyframe.TangentOut = otherTangent;
if (!alignWithIn)
keyframe.TangentIn = otherTangent;
spline.SetSplineKeyframe(index, keyframe);
}
}
/// <summary>
/// Edit curve options manipulate the curve setting selected point
/// tangent in as smoothed but tangent out as linear
/// </summary>
private sealed class SmoothInTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SetTangentSmoothIn(spline, index);
SetSelectTangentIn(spline, index);
}
}
/// <summary>
/// Edit curve options manipulate the curve setting selected point
/// tangent in as linear but tangent out as smoothed
/// </summary>
private sealed class SmoothOutTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SetTangentSmoothOut(spline, index);
SetSelectTangentOut(spline, index);
}
}
private sealed class IconTab : Tab
{
private sealed class IconTabHeader : Tabs.TabHeader
{
public IconTabHeader(Tabs tabs, Tab tab)
: base(tabs, tab)
{
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (EnabledInHierarchy && Tab.Enabled)
((IconTab)Tab)._action();
return true;
}
public override void Draw()
{
base.Draw();
var tab = (IconTab)Tab;
var enabled = EnabledInHierarchy && tab.EnabledInHierarchy;
var style = FlaxEngine.GUI.Style.Current;
var size = Size;
var textHeight = 16.0f;
var iconSize = size.Y - textHeight;
var iconRect = new Rectangle((Width - iconSize) / 2, 0, iconSize, iconSize);
if (tab._mirrorIcon)
{
iconRect.Location.X += iconRect.Size.X;
iconRect.Size.X *= -1;
}
var color = style.Foreground;
if (!enabled)
color *= 0.6f;
Render2D.DrawSprite(tab._customIcon, iconRect, color);
Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center);
}
}
private readonly Action _action;
private readonly string _customText;
private readonly SpriteHandle _customIcon;
private readonly bool _mirrorIcon;
public IconTab(Action action, string text, SpriteHandle icon, bool mirrorIcon = false)
: base(string.Empty, SpriteHandle.Invalid)
{
_action = action;
_customText = text;
_customIcon = icon;
_mirrorIcon = mirrorIcon;
}
public override Tabs.TabHeader CreateHeader()
{
return new IconTabHeader((Tabs)Parent, this);
}
}
private EditTangentOptionBase _currentTangentMode;
private Tabs _selectedPointsTabs, _allPointsTabs;
private Tab _freeTangentTab;
private Tab _linearTangentTab;
private Tab _alignedTangentTab;
private Tab _smoothInTangentTab;
private Tab _smoothOutTangentTab;
private Tab _setLinearAllTangentsTab;
private Tab _setSmoothAllTangentsTab;
private bool _tanInChanged;
private bool _tanOutChanged;
private Vector3 _lastTanInPos;
private Vector3 _lastTanOutPos;
private Spline _selectedSpline;
private SplineNode.SplinePointNode _selectedPoint;
private SplineNode.SplinePointNode _lastPointSelected;
private SplineNode.SplinePointTangentNode _selectedTangentIn;
private SplineNode.SplinePointTangentNode _selectedTangentOut;
private UndoData[] _selectedSplinesUndoData;
private bool HasPointSelected => _selectedPoint != null;
private bool HasTangentsSelected => _selectedTangentIn != null || _selectedTangentOut != null;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (Values.HasDifferentTypes == false)
_currentTangentMode = new FreeTangentMode();
if (Values.HasDifferentTypes || !(Values[0] is Spline spline))
return;
_selectedSpline = spline;
layout.Space(10);
var tabSize = 46;
var icons = Editor.Instance.Icons;
layout.Header("Selected spline point");
_selectedPointsTabs = new Tabs
{
layout.Space(10);
var grid = layout.CustomContainer<UniformGridPanel>();
grid.CustomControl.SlotsHorizontally = 2;
grid.CustomControl.SlotsVertically = 1;
grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear;
grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth;
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
Parent = layout.ContainerControl,
};
_linearTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedLinear, "Linear", icons.SplineLinear64));
_freeTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedFree, "Free", icons.SplineFree64));
_alignedTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedAligned, "Aligned", icons.SplineAligned64));
_smoothInTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothIn, "Smooth In", icons.SplineSmoothIn64));
_smoothOutTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothOut, "Smooth Out", icons.SplineSmoothIn64, true));
_selectedPointsTabs.SelectedTabIndex = -1;
layout.Header("All spline points");
_allPointsTabs = new Tabs
{
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
Parent = layout.ContainerControl,
};
_setLinearAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsLinear, "Set Linear Tangents", icons.SplineLinear64));
_setSmoothAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsSmooth, "Set Smooth Tangents", icons.SplineAligned64));
_allPointsTabs.SelectedTabIndex = -1;
if (_selectedSpline)
_selectedSpline.SplineUpdated += OnSplineEdited;
SetSelectedTangentTypeAsCurrent();
UpdateEditTabsSelection();
UpdateButtonsEnabled();
}
/// <inheritdoc/>
protected override void Deinitialize()
{
if (_selectedSpline)
_selectedSpline.SplineUpdated -= OnSplineEdited;
}
private void OnSplineEdited()
{
UpdateEditTabsSelection();
UpdateButtonsEnabled();
}
/// <inheritdoc/>
public override void Refresh()
{
base.Refresh();
UpdateSelectedPoint();
UpdateSelectedTangent();
if (!CanEditTangent())
return;
var index = _lastPointSelected.Index;
var currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation;
var currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation;
if (_selectedTangentIn != null)
{
_tanInChanged = _lastTanInPos != currentTangentInPosition;
_lastTanInPos = currentTangentInPosition;
}
if (_selectedTangentOut != null)
{
_tanOutChanged = _lastTanOutPos != currentTangentOutPosition;
_lastTanOutPos = currentTangentOutPosition;
}
if (_tanInChanged)
_currentTangentMode.OnMoveTangentIn(_selectedSpline, index);
if (_tanOutChanged)
_currentTangentMode.OnMoveTangentOut(_selectedSpline, index);
currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation;
currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation;
// Update last tangents position after changes
if (_selectedSpline)
_lastTanInPos = currentTangentInPosition;
if (_selectedSpline)
_lastTanOutPos = currentTangentOutPosition;
_tanInChanged = false;
_tanOutChanged = false;
}
private void SetSelectedTangentTypeAsCurrent()
{
if (_lastPointSelected == null || _selectedPoint == null)
return;
if (IsLinearTangentMode(_selectedSpline, _lastPointSelected.Index))
SetModeLinear();
else if (IsAlignedTangentMode(_selectedSpline, _lastPointSelected.Index))
SetModeAligned();
else if (IsSmoothInTangentMode(_selectedSpline, _lastPointSelected.Index))
SetModeSmoothIn();
else if (IsSmoothOutTangentMode(_selectedSpline, _lastPointSelected.Index))
SetModeSmoothOut();
else if (IsFreeTangentMode(_selectedSpline, _lastPointSelected.Index))
SetModeFree();
}
private void UpdateEditTabsSelection()
{
_selectedPointsTabs.Enabled = CanEditTangent();
if (!_selectedPointsTabs.Enabled)
{
_selectedPointsTabs.SelectedTabIndex = -1;
return;
}
var isFree = _currentTangentMode is FreeTangentMode;
var isLinear = _currentTangentMode is LinearTangentMode;
var isAligned = _currentTangentMode is AlignedTangentMode;
var isSmoothIn = _currentTangentMode is SmoothInTangentMode;
var isSmoothOut = _currentTangentMode is SmoothOutTangentMode;
if (isFree)
_selectedPointsTabs.SelectedTab = _freeTangentTab;
else if (isLinear)
_selectedPointsTabs.SelectedTab = _linearTangentTab;
else if (isAligned)
_selectedPointsTabs.SelectedTab = _alignedTangentTab;
else if (isSmoothIn)
_selectedPointsTabs.SelectedTab = _smoothInTangentTab;
else if (isSmoothOut)
_selectedPointsTabs.SelectedTab = _smoothOutTangentTab;
else
_selectedPointsTabs.SelectedTabIndex = -1;
}
private void UpdateButtonsEnabled()
{
_linearTangentTab.Enabled = CanEditTangent();
_freeTangentTab.Enabled = CanEditTangent();
_alignedTangentTab.Enabled = CanEditTangent();
_smoothInTangentTab.Enabled = CanSetTangentSmoothIn();
_smoothOutTangentTab.Enabled = CanSetTangentSmoothOut();
_setLinearAllTangentsTab.Enabled = CanSetAllTangentsLinear();
_setSmoothAllTangentsTab.Enabled = CanSetAllTangentsSmooth();
}
private bool CanEditTangent()
{
return !HasDifferentTypes && !HasDifferentValues && (HasPointSelected || HasTangentsSelected);
}
private bool CanSetTangentSmoothIn()
{
if (!CanEditTangent())
return false;
return _lastPointSelected.Index != 0;
}
private bool CanSetTangentSmoothOut()
{
if (!CanEditTangent())
return false;
return _lastPointSelected.Index < _selectedSpline.SplinePointsCount - 1;
}
private bool CanSetAllTangentsSmooth()
{
return _selectedSpline != null;
}
private bool CanSetAllTangentsLinear()
{
return _selectedSpline != null;
}
private void SetModeLinear()
{
if (_currentTangentMode is LinearTangentMode)
return;
_currentTangentMode = new LinearTangentMode();
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
}
private void SetModeFree()
{
if (_currentTangentMode is FreeTangentMode)
return;
_currentTangentMode = new FreeTangentMode();
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
}
private void SetModeAligned()
{
if (_currentTangentMode is AlignedTangentMode)
return;
_currentTangentMode = new AlignedTangentMode();
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
}
private void SetModeSmoothIn()
{
if (_currentTangentMode is SmoothInTangentMode)
return;
_currentTangentMode = new SmoothInTangentMode();
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
}
private void SetModeSmoothOut()
{
if (_currentTangentMode is SmoothOutTangentMode)
return;
_currentTangentMode = new SmoothOutTangentMode();
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
}
private void UpdateSelectedPoint()
{
// works only if select one spline
if (_selectedSpline)
{
var currentSelected = Editor.Instance.SceneEditing.Selection[0];
if (currentSelected == _selectedPoint)
return;
if (currentSelected is SplineNode.SplinePointNode selectedPoint)
{
_selectedPoint = selectedPoint;
_lastPointSelected = _selectedPoint;
_currentTangentMode.OnSelectKeyframe(_selectedSpline, _lastPointSelected.Index);
}
else
{
_selectedPoint = null;
}
}
else
{
_selectedPoint = null;
}
SetSelectedTangentTypeAsCurrent();
UpdateEditTabsSelection();
UpdateButtonsEnabled();
}
private void UpdateSelectedTangent()
{
// works only if select one spline
if (_lastPointSelected == null || Editor.Instance.SceneEditing.SelectionCount != 1)
{
_selectedTangentIn = null;
_selectedTangentOut = null;
return;
}
var currentSelected = Editor.Instance.SceneEditing.Selection[0];
if (currentSelected is not SplineNode.SplinePointTangentNode selectedPoint)
{
_selectedTangentIn = null;
_selectedTangentOut = null;
return;
}
if (currentSelected == _selectedTangentIn)
return;
if (currentSelected == _selectedTangentOut)
return;
var index = _lastPointSelected.Index;
if (currentSelected.Transform == _selectedSpline.GetSplineTangent(index, true))
{
_selectedTangentIn = selectedPoint;
_selectedTangentOut = null;
_currentTangentMode.OnSelectTangent(_selectedSpline, index);
return;
}
if (currentSelected.Transform == _selectedSpline.GetSplineTangent(index, false))
{
_selectedTangentOut = selectedPoint;
_selectedTangentIn = null;
_currentTangentMode.OnSelectTangent(_selectedSpline, index);
return;
}
_selectedTangentIn = null;
_selectedTangentOut = null;
}
private void StartEditSpline()
{
if (Presenter.Undo != null && Presenter.Undo.Enabled)
{
// Capture 'before' state for undo
var splines = new List<UndoData>();
for (int i = 0; i < Values.Count; i++)
{
if (Values[i] is Spline spline)
{
splines.Add(new UndoData
{
Spline = spline,
BeforeKeyframes = spline.SplineKeyframes.Clone() as BezierCurve<Transform>.Keyframe[]
});
}
}
_selectedSplinesUndoData = splines.ToArray();
}
}
private void EndEditSpline()
{
// Update buttons state
UpdateEditTabsSelection();
if (Presenter.Undo != null && Presenter.Undo.Enabled)
{
// Add undo
foreach (var splineUndoData in _selectedSplinesUndoData)
{
Presenter.Undo.AddAction(new EditSplineAction(_selectedSpline, splineUndoData.BeforeKeyframes));
SplineNode.OnSplineEdited(splineUndoData.Spline);
Editor.Instance.Scene.MarkSceneEdited(splineUndoData.Spline.Scene);
}
}
}
private void OnSetSelectedLinear()
{
StartEditSpline();
SetModeLinear();
EndEditSpline();
}
private void OnSetSelectedFree()
{
StartEditSpline();
SetModeFree();
EndEditSpline();
}
private void OnSetSelectedAligned()
{
StartEditSpline();
SetModeAligned();
EndEditSpline();
}
private void OnSetSelectedSmoothIn()
{
StartEditSpline();
SetModeSmoothIn();
EndEditSpline();
}
private void OnSetSelectedSmoothOut()
{
StartEditSpline();
SetModeSmoothOut();
EndEditSpline();
}
private void OnSetTangentsLinear()
{
var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled;
for (int i = 0; i < Values.Count; i++)
{
if (Values[i] is Spline spline)
{
var before = enableUndo ? (BezierCurve<Transform>.Keyframe[])spline.SplineKeyframes.Clone() : null;
spline.SetTangentsLinear();
if (enableUndo)
Presenter.Undo.AddAction(new EditSplineAction(spline, before));
SplineNode.OnSplineEdited(spline);
Editor.Instance.Scene.MarkSceneEdited(spline.Scene);
}
}
StartEditSpline();
_selectedSpline.SetTangentsLinear();
_selectedSpline.UpdateSpline();
EndEditSpline();
}
private void OnSetTangentsSmooth()
{
var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled;
for (int i = 0; i < Values.Count; i++)
StartEditSpline();
_selectedSpline.SetTangentsSmooth();
_selectedSpline.UpdateSpline();
EndEditSpline();
}
private static bool IsFreeTangentMode(Spline spline, int index)
{
if (IsLinearTangentMode(spline, index) ||
IsAlignedTangentMode(spline, index) ||
IsSmoothInTangentMode(spline, index) ||
IsSmoothOutTangentMode(spline, index))
{
if (Values[i] is Spline spline)
return false;
}
return true;
}
private static bool IsLinearTangentMode(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
return keyframe.TangentIn.Translation.Length == 0 && keyframe.TangentOut.Translation.Length == 0;
}
private static bool IsAlignedTangentMode(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
var tangentIn = keyframe.TangentIn.Translation;
var tangentOut = keyframe.TangentOut.Translation;
if (tangentIn.Length == 0 || tangentOut.Length == 0)
return false;
var angleBetweenTwoTangents = Vector3.Dot(tangentIn.Normalized, tangentOut.Normalized);
if (angleBetweenTwoTangents < -0.99f)
return true;
return false;
}
private static bool IsSmoothInTangentMode(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
return keyframe.TangentIn.Translation.Length > 0 && keyframe.TangentOut.Translation.Length == 0;
}
private static bool IsSmoothOutTangentMode(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
return keyframe.TangentOut.Translation.Length > 0 && keyframe.TangentIn.Translation.Length == 0;
}
private static void SetKeyframeLinear(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
keyframe.TangentIn.Translation = Vector3.Zero;
keyframe.TangentOut.Translation = Vector3.Zero;
var lastSplineIndex = spline.SplinePointsCount - 1;
if (index == lastSplineIndex && spline.IsLoop)
{
var lastPoint = spline.GetSplineKeyframe(lastSplineIndex);
lastPoint.TangentIn.Translation = Vector3.Zero;
lastPoint.TangentOut.Translation = Vector3.Zero;
spline.SetSplineKeyframe(lastSplineIndex, lastPoint);
}
spline.SetSplineKeyframe(index, keyframe);
spline.UpdateSpline();
}
private static void SetTangentSmoothIn(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
// Auto smooth tangent if's linear
if (keyframe.TangentIn.Translation.Length == 0)
{
var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f);
var previousKeyframe = spline.GetSplineKeyframe(index - 1);
var tangentDirection = keyframe.Value.WorldToLocalVector(previousKeyframe.Value.Translation - keyframe.Value.Translation);
tangentDirection = tangentDirection.Normalized * smoothRange;
keyframe.TangentIn.Translation = tangentDirection;
}
keyframe.TangentOut.Translation = Vector3.Zero;
spline.SetSplineKeyframe(index, keyframe);
spline.UpdateSpline();
}
private static void SetTangentSmoothOut(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
// Auto smooth tangent if's linear
if (keyframe.TangentOut.Translation.Length == 0)
{
var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f);
var nextKeyframe = spline.GetSplineKeyframe(index + 1);
var tangentDirection = keyframe.Value.WorldToLocalVector(nextKeyframe.Value.Translation - keyframe.Value.Translation);
tangentDirection = tangentDirection.Normalized * smoothRange;
keyframe.TangentOut.Translation = tangentDirection;
}
keyframe.TangentIn.Translation = Vector3.Zero;
spline.SetSplineKeyframe(index, keyframe);
spline.UpdateSpline();
}
private static void SetPointSmooth(Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
var tangentInSize = keyframe.TangentIn.Translation.Length;
var tangentOutSize = keyframe.TangentOut.Translation.Length;
var isLastKeyframe = index >= spline.SplinePointsCount - 1;
var isFirstKeyframe = index <= 0;
var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplinePoint(index), 10f);
// Force smooth it's linear point
if (tangentInSize == 0f)
tangentInSize = smoothRange;
if (tangentOutSize == 0f)
tangentOutSize = smoothRange;
// Try get next / last keyframe
var nextKeyframe = !isLastKeyframe ? spline.GetSplineKeyframe(index + 1) : keyframe;
var previousKeyframe = !isFirstKeyframe ? spline.GetSplineKeyframe(index - 1) : keyframe;
// calc form from Spline.cpp -> SetTangentsSmooth
// get tangent direction
var tangentDirection = (keyframe.Value.Translation - previousKeyframe.Value.Translation + nextKeyframe.Value.Translation - keyframe.Value.Translation).Normalized;
keyframe.TangentIn.Translation = -tangentDirection;
keyframe.TangentOut.Translation = tangentDirection;
keyframe.TangentIn.Translation *= tangentInSize;
keyframe.TangentOut.Translation *= tangentOutSize;
spline.SetSplineKeyframe(index, keyframe);
spline.UpdateSpline();
}
private static SplineNode.SplinePointNode GetSplinePointNode(Spline spline, int index)
{
return (SplineNode.SplinePointNode)SceneGraphFactory.FindNode(spline.ID).ChildNodes[index];
}
private static SplineNode.SplinePointTangentNode GetSplineTangentInNode(Spline spline, int index)
{
var point = GetSplinePointNode(spline, index);
var tangentIn = spline.GetSplineTangent(index, true);
var tangentNodes = point.ChildNodes;
// find tangent in node comparing all child nodes position
for (int i = 0; i < tangentNodes.Count; i++)
{
if (tangentNodes[i].Transform.Translation == tangentIn.Translation)
{
var before = enableUndo ? (BezierCurve<Transform>.Keyframe[])spline.SplineKeyframes.Clone() : null;
spline.SetTangentsSmooth();
if (enableUndo)
Presenter.Undo.AddAction(new EditSplineAction(spline, before));
SplineNode.OnSplineEdited(spline);
Editor.Instance.Scene.MarkSceneEdited(spline.Scene);
return (SplineNode.SplinePointTangentNode)tangentNodes[i];
}
}
return null;
}
private static SplineNode.SplinePointTangentNode GetSplineTangentOutNode(Spline spline, int index)
{
var point = GetSplinePointNode(spline, index);
var tangentOut = spline.GetSplineTangent(index, false);
var tangentNodes = point.ChildNodes;
// find tangent out node comparing all child nodes position
for (int i = 0; i < tangentNodes.Count; i++)
{
if (tangentNodes[i].Transform.Translation == tangentOut.Translation)
{
return (SplineNode.SplinePointTangentNode)tangentNodes[i];
}
}
return null;
}
private static void SetSelectSplinePointNode(Spline spline, int index)
{
Editor.Instance.SceneEditing.Select(GetSplinePointNode(spline, index));
}
private static void SetSelectTangentIn(Spline spline, int index)
{
Editor.Instance.SceneEditing.Select(GetSplineTangentInNode(spline, index));
}
private static void SetSelectTangentOut(Spline spline, int index)
{
Editor.Instance.SceneEditing.Select(GetSplineTangentOutNode(spline, index));
}
}
}
@@ -422,12 +422,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Set control type button
var space = layout.Space(20);
float setTypeButtonWidth = 60.0f;
var buttonText = "Set Type";
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText);
float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4;
var setTypeButton = new Button
{
TooltipText = "Sets the control to the given type",
AnchorPreset = AnchorPresets.MiddleCenter,
Text = "Set Type",
Text = buttonText,
Parent = space.Spacer,
Bounds = new Rectangle((space.Spacer.Width - setTypeButtonWidth) / 2, 1, setTypeButtonWidth, 18),
};
@@ -88,20 +88,20 @@ namespace FlaxEditor.CustomEditors.Editors
LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked;
// Add button with the link icon
_linkButton = new Button
{
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32),
Parent = LinkedLabel,
Width = 18,
Height = 18,
AnchorPreset = AnchorPresets.TopLeft,
AnchorPreset = AnchorPresets.MiddleLeft,
};
_linkButton.Clicked += ToggleLink;
ToggleEnabled();
SetLinkStyle();
var x = LinkedLabel.Text.Value.Length * 7 + 5;
_linkButton.LocalX += x;
_linkButton.LocalY += 1;
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value);
_linkButton.LocalX += textSize.X + 10;
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{
menu.AddSeparator();
@@ -34,7 +34,9 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
element.CheckBox.Checked = (bool)Values[0];
var value = (bool?)Values[0];
if (value != null)
element.CheckBox.Checked = value.Value;
}
}
}
@@ -0,0 +1,146 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Default implementation of the inspector used to edit input event properties.
/// </summary>
[CustomEditor(typeof(InputEvent)), DefaultEditor]
public class InputEventEditor : CustomEditor
{
private ComboBox _comboBox;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
LinkedLabel.SetupContextMenu += OnSetupContextMenu;
var comboBoxElement = layout.ComboBox();
_comboBox = comboBoxElement.ComboBox;
var names = new List<string>();
foreach (var mapping in Input.ActionMappings)
{
if (!names.Contains(mapping.Name))
names.Add(mapping.Name);
}
_comboBox.Items = names;
if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name))
_comboBox.SelectedItem = inputEvent.Name;
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
{
var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null;
}
private void OnSelectedIndexChanged(ComboBox comboBox)
{
SetValue(comboBox.SelectedItem == null ? null : new InputEvent(comboBox.SelectedItem));
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (HasDifferentValues)
{
}
else
{
if (Values[0] is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name))
_comboBox.SelectedItem = inputEvent.Name;
else
_comboBox.SelectedItem = null;
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
if (LinkedLabel != null)
LinkedLabel.SetupContextMenu -= OnSetupContextMenu;
if (_comboBox != null)
_comboBox.SelectedIndexChanged -= OnSelectedIndexChanged;
_comboBox = null;
}
}
/// <summary>
/// Default implementation of the inspector used to edit input axis properties.
/// </summary>
[CustomEditor(typeof(InputAxis)), DefaultEditor]
public class InputAxisEditor : CustomEditor
{
private ComboBox _comboBox;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
LinkedLabel.SetupContextMenu += OnSetupContextMenu;
var comboBoxElement = layout.ComboBox();
_comboBox = comboBoxElement.ComboBox;
var names = new List<string>();
foreach (var mapping in Input.AxisMappings)
{
if (!names.Contains(mapping.Name))
names.Add(mapping.Name);
}
_comboBox.Items = names;
if (Values[0] is InputAxis inputAxis && names.Contains(inputAxis.Name))
_comboBox.SelectedItem = inputAxis.Name;
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
{
var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null;
}
private void OnSelectedIndexChanged(ComboBox comboBox)
{
SetValue(comboBox.SelectedItem == null ? null : new InputAxis(comboBox.SelectedItem));
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (HasDifferentValues)
{
}
else
{
if (Values[0] is InputAxis inputAxis && _comboBox.Items.Contains(inputAxis.Name))
_comboBox.SelectedItem = inputAxis.Name;
else
_comboBox.SelectedItem = null;
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
if (LinkedLabel != null)
LinkedLabel.SetupContextMenu -= OnSetupContextMenu;
if (_comboBox != null)
_comboBox.SelectedIndexChanged -= OnSelectedIndexChanged;
_comboBox = null;
}
}
}
@@ -160,7 +160,6 @@ namespace FlaxEditor.CustomEditors.Editors
var option = _options[comboBox.SelectedIndex];
if (option.Type != null)
value = option.Creator(option.Type);
}
SetValue(value);
RebuildLayoutOnRefresh();
@@ -623,13 +623,18 @@ namespace FlaxEditor.CustomEditors.Editors
{
_label = layout.ClickableLabel(GetText(out _)).CustomControl;
_label.RightClick += ShowPicker;
var buttonText = "...";
var button = new Button
{
Size = new Float2(16.0f),
Text = "...",
Text = buttonText,
TooltipText = "Edit...",
Parent = _label,
};
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText);
if (textSize.Y > button.Width)
button.Width = textSize.Y + 2;
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
button.Clicked += ShowPicker;
}
@@ -674,9 +679,9 @@ namespace FlaxEditor.CustomEditors.Editors
}
set
{
if (Values[0] is Tag[])
if (Values[0] is Tag[] || Values.Type.Type == typeof(Tag[]))
SetValue(value);
if (Values[0] is List<Tag>)
else if (Values[0] is List<Tag> || Values.Type.Type == typeof(List<Tag>))
SetValue(new List<Tag>(value));
}
}
@@ -175,7 +175,7 @@ namespace FlaxEditor.CustomEditors.GUI
{
// Clear flag
_mouseOverSplitter = false;
if (_cursorChanged)
{
Cursor = CursorType.Default;
+65 -19
View File
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
@@ -13,6 +14,7 @@ using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Modules;
using FlaxEditor.Modules.SourceCodeEditing;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.States;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -933,21 +935,6 @@ namespace FlaxEditor
Animation = 11,
}
/// <summary>
/// Imports the audio asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="settings">The settings.</param>
/// <returns>True if importing failed, otherwise false.</returns>
public static bool Import(string inputPath, string outputPath, AudioImportSettings settings)
{
if (settings == null)
throw new ArgumentNullException();
settings.ToInternal(out var internalOptions);
return Internal_ImportAudio(inputPath, outputPath, ref internalOptions);
}
/// <summary>
/// Serializes the given object to json asset.
/// </summary>
@@ -1274,6 +1261,69 @@ namespace FlaxEditor
Scene.MarkSceneEdited(scenes);
}
/// <summary>
/// Bakes all environmental probes in the scene.
/// </summary>
public void BakeAllEnvProbes()
{
Scene.ExecuteOnGraph(node =>
{
if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive)
{
((EnvironmentProbe)envProbeNode.Actor).Bake();
node.ParentScene.IsEdited = true;
}
else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene)
{
skyLight.Bake();
node.ParentScene.IsEdited = true;
}
return node.IsActive;
});
}
/// <summary>
/// Builds CSG for all open scenes.
/// </summary>
public void BuildCSG()
{
var scenes = Level.Scenes;
scenes.ToList().ForEach(x => x.BuildCSG(0));
Scene.MarkSceneEdited(scenes);
}
/// <summary>
/// Builds Nav mesh for all open scenes.
/// </summary>
public void BuildNavMesh()
{
var scenes = Level.Scenes;
scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
Scene.MarkSceneEdited(scenes);
}
/// <summary>
/// Builds SDF for all static models in the scene.
/// </summary>
public void BuildAllMeshesSDF()
{
// TODO: async maybe with progress reporting?
Scene.ExecuteOnGraph(node =>
{
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
{
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
{
Log("Generating SDF for " + staticModel.Model);
if (!staticModel.Model.GenerateSDF())
staticModel.Model.Save();
}
}
return true;
});
}
#endregion
#region Internal Calls
@@ -1602,10 +1652,6 @@ namespace FlaxEditor
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_ImportAudio", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize);
+3 -1
View File
@@ -36,7 +36,9 @@ namespace FlaxEditor
public static void OnEditorOptionsChanged(Options.EditorOptions options)
{
var param = _highlightMaterial?.GetParameter("Color");
if (!_highlightMaterial)
return;
var param = _highlightMaterial.GetParameter("Color");
if (param != null)
param.Value = options.Visual.HighlightColor;
}
+4
View File
@@ -97,6 +97,10 @@ namespace FlaxEditor
public SpriteHandle Build64;
public SpriteHandle Add64;
public SpriteHandle ShipIt64;
public SpriteHandle SplineFree64;
public SpriteHandle SplineLinear64;
public SpriteHandle SplineAligned64;
public SpriteHandle SplineSmoothIn64;
// 96px
public SpriteHandle Toolbox96;
+1 -1
View File
@@ -545,7 +545,7 @@ namespace FlaxEditor.GUI
Render2D.DrawRectangle(clientRect.MakeExpanded(-2.0f), borderColor);
// Check if has selected item
if (_selectedIndices.Count > 0)
if (_selectedIndices != null && _selectedIndices.Count > 0)
{
string text = _selectedIndices.Count == 1 ? _items[_selectedIndices[0]] : "Multiple Values";
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -269,6 +270,24 @@ namespace FlaxEditor.GUI.ContextMenu
return item;
}
/// <summary>
/// Adds the button.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="binding">The input binding.</param>
/// <param name="clicked">On button clicked event.</param>
/// <returns>Created context menu item control.</returns>
public ContextMenuButton AddButton(string text, InputBinding binding, Action clicked)
{
var item = new ContextMenuButton(this, text, binding.ToString())
{
Parent = _panel
};
item.Clicked += clicked;
SortButtons();
return item;
}
/// <summary>
/// Gets the child menu (with that name).
/// </summary>
@@ -154,6 +154,7 @@ namespace FlaxEditor.GUI.ContextMenu
}
// Unlock and perform controls update
Location = Float2.Zero;
UnlockChildrenRecursive();
PerformLayout();
@@ -162,7 +163,6 @@ namespace FlaxEditor.GUI.ContextMenu
var dpiSize = Size * dpiScale;
var locationWS = parent.PointToWindow(location);
var locationSS = parentWin.PointToScreen(locationWS);
Location = Float2.Zero;
var monitorBounds = Platform.GetMonitorBounds(locationSS);
var rightBottomLocationSS = locationSS + dpiSize;
bool isUp = false, isLeft = false;
+5 -1
View File
@@ -290,7 +290,11 @@ namespace FlaxEditor.GUI.Dialogs
OnCancel();
return true;
case KeyboardKeys.Tab:
Root?.Navigate(NavDirection.Next);
if (Root != null)
{
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
}
return true;
}
return false;
@@ -476,6 +476,7 @@ namespace FlaxEditor.GUI.Docking
settings.ShowInTaskbar = false;
settings.ActivateWhenFirstShown = false;
settings.IsTopmost = true;
settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
+2 -1
View File
@@ -292,7 +292,8 @@ namespace FlaxEditor.GUI
return true;
#if PLATFORM_WINDOWS
if (_useCustomWindowSystem)
var child = GetChildAtRecursive(location);
if (_useCustomWindowSystem && child is not Button && child is not MainMenuButton)
{
if (_window.IsMaximized)
_window.Restore();
+3
View File
@@ -37,6 +37,9 @@ namespace FlaxEditor.GUI
: base(0, 0, 100, height)
{
Depth = -1;
if (Height < Style.Current.FontMedium.Height)
Height = Style.Current.FontMedium.Height + 4;
}
/// <inheritdoc />
+9
View File
@@ -77,5 +77,14 @@ namespace FlaxEditor.GUI.Tabs
{
Deselected?.Invoke(this);
}
/// <summary>
/// Factory method for tabs header control (UI for the tabs strip to represent this tab).
/// </summary>
/// <returns>The tab header control.</returns>
public virtual Tabs.TabHeader CreateHeader()
{
return new Tabs.TabHeader((Tabs)Parent, this);
}
}
}
+72 -40
View File
@@ -24,11 +24,6 @@ namespace FlaxEditor.GUI.Tabs
/// </summary>
protected Tabs Tabs;
/// <summary>
/// The index of the tab.
/// </summary>
protected int Index;
/// <summary>
/// The tab.
/// </summary>
@@ -38,21 +33,22 @@ namespace FlaxEditor.GUI.Tabs
/// Initializes a new instance of the <see cref="TabHeader"/> class.
/// </summary>
/// <param name="tabs">The tabs.</param>
/// <param name="index">The tab index.</param>
/// <param name="tab">The tab.</param>
public TabHeader(Tabs tabs, int index, Tab tab)
public TabHeader(Tabs tabs, Tab tab)
: base(Float2.Zero, tabs._tabsSize)
{
Tabs = tabs;
Index = index;
Tab = tab;
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
Tabs.SelectedTabIndex = Index;
Tabs.Focus();
if (EnabledInHierarchy && Tab.Enabled)
{
Tabs.SelectedTab = Tab;
Tabs.Focus();
}
return true;
}
@@ -61,19 +57,20 @@ namespace FlaxEditor.GUI.Tabs
{
base.Draw();
// Cache data
var style = Style.Current;
var enabled = EnabledInHierarchy && Tab.EnabledInHierarchy;
var tabRect = new Rectangle(Float2.Zero, Size);
bool isTabSelected = Tabs._selectedIndex == Index;
bool isMouseOverTab = IsMouseOver;
var textOffset = Tabs._orientation == Orientation.Horizontal ? 0 : 8;
// Draw bar
if (isTabSelected)
if (Tabs.SelectedTab == Tab)
{
var color = style.BackgroundSelected;
if (!enabled)
color *= 0.6f;
if (Tabs._orientation == Orientation.Horizontal)
{
Render2D.FillRectangle(tabRect, style.BackgroundSelected);
Render2D.FillRectangle(tabRect, color);
}
else
{
@@ -84,10 +81,10 @@ namespace FlaxEditor.GUI.Tabs
fillRect.Size.X -= lefEdgeWidth;
fillRect.Location.X += lefEdgeWidth;
Render2D.FillRectangle(fillRect, style.Background);
Render2D.FillRectangle(leftEdgeRect, style.BackgroundSelected);
Render2D.FillRectangle(leftEdgeRect, color);
}
}
else if (isMouseOverTab)
else if (IsMouseOver && enabled)
{
Render2D.FillRectangle(tabRect, style.BackgroundHighlighted);
}
@@ -131,20 +128,15 @@ namespace FlaxEditor.GUI.Tabs
{
base.PerformLayoutBeforeChildren();
// Cache data
var tabsSize = Tabs._tabsSize;
var clientSize = GetClientArea();
tabsSize = Float2.Min(tabsSize, clientSize.Size);
var tabRect = new Rectangle(Float2.Zero, tabsSize);
var tabStripOffset = Tabs._orientation == Orientation.Horizontal ? new Float2(tabsSize.X, 0) : new Float2(0, tabsSize.Y);
// Arrange tab header controls
var pos = Float2.Zero;
var sizeMask = Tabs._orientation == Orientation.Horizontal ? Float2.UnitX : Float2.UnitY;
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is TabHeader tabHeader)
{
tabHeader.Bounds = tabRect;
tabRect.Offset(tabStripOffset);
tabHeader.Location = pos;
pos += tabHeader.Size * sizeMask;
}
}
}
@@ -160,6 +152,11 @@ namespace FlaxEditor.GUI.Tabs
/// </summary>
protected Float2 _tabsSize;
/// <summary>
/// Automatic tab size based on the fill axis.
/// </summary>
protected bool _autoTabsSizeAuto;
/// <summary>
/// The orientation.
/// </summary>
@@ -174,15 +171,31 @@ namespace FlaxEditor.GUI.Tabs
set
{
_tabsSize = value;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
if (!_autoTabsSizeAuto)
{
if (TabsPanel.Children[i] is TabHeader tabHeader)
tabHeader.Size = value;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader tabHeader)
tabHeader.Size = value;
}
}
PerformLayout();
}
}
/// <summary>
/// Enables automatic tabs size to fill the space.
/// </summary>
public bool AutoTabsSize
{
get => _autoTabsSizeAuto;
set
{
_autoTabsSizeAuto = value;
PerformLayout();
}
}
/// <summary>
/// Gets or sets the orientation.
/// </summary>
@@ -339,14 +352,10 @@ namespace FlaxEditor.GUI.Tabs
// Update tabs headers
TabsPanel.DisposeChildren();
int index = 0;
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Tab tab)
{
var tabHeader = new TabHeader(this, index++, tab);
TabsPanel.AddChild(tabHeader);
}
TabsPanel.AddChild(tab.CreateHeader());
}
TabsPanel.IsLayoutLocked = wasLocked;
@@ -371,23 +380,46 @@ namespace FlaxEditor.GUI.Tabs
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
var tabsSize = _tabsSize;
if (_autoTabsSizeAuto)
{
// Horizontal is default for Toolbox so tabs go to the right
int tabsCount = 0;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader)
tabsCount++;
}
if (tabsCount == 0)
tabsCount = 1;
if (_orientation == Orientation.Horizontal)
tabsSize.X = Width / tabsCount;
else
tabsSize.Y = Height / tabsCount;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader tabHeader)
tabHeader.Size = tabsSize;
}
}
// Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal ? new Float2(Width, _tabsSize.Y) : new Float2(_tabsSize.X, Height);
TabsPanel.Size = _orientation == Orientation.Horizontal
? new Float2(Width, tabsSize.Y)
: new Float2(tabsSize.X, Height);
// Hide all pages except selected one
var clientArea = _orientation == Orientation.Horizontal
? new Rectangle(0, _tabsSize.Y, Width, Height - _tabsSize.Y)
: new Rectangle(_tabsSize.X, 0, Width - _tabsSize.X, Height);
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Tab tab)
{
// Check if is selected or not
if (i - 1 == _selectedIndex)
{
// Show and fit size
tab.Visible = true;
tab.Bounds = clientArea;
tab.Bounds = _orientation == Orientation.Horizontal
? new Rectangle(0, tabsSize.Y, Width, Height - tabsSize.Y)
: new Rectangle(tabsSize.X, 0, Width - tabsSize.X, Height);
}
else
{
@@ -33,7 +33,7 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
public SceneAnimationPlayer Player
{
get => _player;
get => _player != null ? _player : null;
set
{
if (_player == value)
@@ -268,7 +268,7 @@ namespace FlaxEditor.GUI.Timeline
/// <inheritdoc />
public override void OnSeek(int frame)
{
if (_player?.Animation)
if (_player != null && _player.Animation)
{
_player.Animation.WaitForLoaded();
_player.Time = frame / _player.Animation.FramesPerSecond;
@@ -125,6 +125,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
fov = cam.FieldOfView;
customAspectRatio = cam.CustomAspectRatio;
view.RenderLayersMask = cam.RenderLayersMask;
view.Flags = cam.RenderFlags;
view.Mode = cam.RenderMode;
}
// Try to evaluate camera properties based on the animated tracks
@@ -50,43 +50,6 @@
Guid ManagedEditor::ObjectID(0x91970b4e, 0x99634f61, 0x84723632, 0x54c776af);
// Disable warning C4800: 'const byte': forcing value to bool 'true' or 'false' (performance warning)
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable : 4800)
#endif
struct InternalAudioOptions
{
AudioFormat Format;
byte DisableStreaming;
byte Is3D;
int32 BitDepth;
float Quality;
static void Convert(InternalAudioOptions* from, ImportAudio::Options* to)
{
to->Format = from->Format;
to->DisableStreaming = from->DisableStreaming;
to->Is3D = from->Is3D;
to->BitDepth = from->BitDepth;
to->Quality = from->Quality;
}
static void Convert(ImportAudio::Options* from, InternalAudioOptions* to)
{
to->Format = from->Format;
to->DisableStreaming = from->DisableStreaming;
to->Is3D = from->Is3D;
to->BitDepth = from->BitDepth;
to->Quality = from->Quality;
}
};
#if defined(_MSC_VER)
#pragma warning( pop )
#endif
// Pack log messages into a single scratch buffer to reduce dynamic memory allocations
CriticalSection CachedLogDataLocker;
Array<byte> CachedLogData;
@@ -295,16 +258,6 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_CanImport(MString* extensionObj)
return importer ? MUtils::ToString(importer->ResultExtension) : nullptr;
}
DEFINE_INTERNAL_CALL(bool) EditorInternal_ImportAudio(MString* inputPathObj, MString* outputPathObj, InternalAudioOptions* optionsObj)
{
ImportAudio::Options options;
InternalAudioOptions::Convert(optionsObj, &options);
String inputPath, outputPath;
MUtils::ToString(inputPathObj, inputPath);
MUtils::ToString(outputPathObj, outputPath);
return ManagedEditor::Import(inputPath, outputPath, &options);
}
DEFINE_INTERNAL_CALL(void) EditorInternal_GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize)
{
INTERNAL_CALL_CHECK(clip);
@@ -765,24 +718,6 @@ DEFINE_INTERNAL_CALL(MTypeObject*) CustomEditorsUtilInternal_GetCustomEditor(MTy
return CustomEditorsUtil::GetCustomEditor(targetType);
}
DEFINE_INTERNAL_CALL(bool) AudioImportEntryInternal_GetAudioImportOptions(MString* pathObj, InternalAudioOptions* result)
{
String path;
MUtils::ToString(pathObj, path);
FileSystem::NormalizePath(path);
ImportAudio::Options options;
if (ImportAudio::TryGetImportOptions(path, options))
{
// Convert into managed storage
InternalAudioOptions::Convert(&options, result);
return true;
}
return false;
}
DEFINE_INTERNAL_CALL(MArray*) LayersAndTagsSettingsInternal_GetCurrentLayers(int* layersCount)
{
*layersCount = Math::Max(1, Level::GetNonEmptyLayerNamesCount());
@@ -830,3 +765,15 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String
FileSystem::NormalizePath(assetPath);
return ImportModelFile::TryGetImportOptions(assetPath, options);
}
bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options)
{
return Import(inputPath, outputPath, (void*)&options);
}
bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath)
{
// Get options from model
FileSystem::NormalizePath(assetPath);
return ImportAudio::TryGetImportOptions(assetPath, options);
}
+20
View File
@@ -7,6 +7,7 @@
#include "Engine/ShadowsOfMordor/Types.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/AudioTool/AudioTool.h"
namespace CSG
{
@@ -190,6 +191,25 @@ public:
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) ModelTool::Options& options, String assetPath);
#endif
#if COMPILE_WITH_AUDIO_TOOL
/// <summary>
/// Imports the audio asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="options">The import settings.</param>
/// <returns>True if importing failed, otherwise false.</returns>
API_FUNCTION() static bool Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options);
/// <summary>
/// Tries the restore the asset import options from the target resource file.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="assetPath">The asset path.</param>
/// <returns>True settings has been restored, otherwise false.</returns>
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
#endif
private:
void OnEditorAssemblyLoaded(MAssembly* assembly);
+17 -5
View File
@@ -643,7 +643,8 @@ namespace FlaxEditor.Modules
/// Deletes the specified item.
/// </summary>
/// <param name="item">The item.</param>
public void Delete(ContentItem item)
/// <param name="deletedByUser">If the file was deleted by the user and not outside the editor.</param>
public void Delete(ContentItem item, bool deletedByUser = false)
{
if (item == null)
throw new ArgumentNullException();
@@ -667,12 +668,12 @@ namespace FlaxEditor.Modules
var children = folder.Children.ToArray();
for (int i = 0; i < children.Length; i++)
{
Delete(children[i]);
Delete(children[i], deletedByUser);
}
}
// Remove directory
if (Directory.Exists(path))
if (deletedByUser && Directory.Exists(path))
{
try
{
@@ -701,7 +702,7 @@ namespace FlaxEditor.Modules
// Delete asset by using content pool
FlaxEngine.Content.DeleteAsset(path);
}
else
else if (deletedByUser)
{
// Delete file
if (File.Exists(path))
@@ -847,7 +848,7 @@ namespace FlaxEditor.Modules
Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed"));
// Destroy it
Delete(child);
Delete(child, false);
i--;
}
@@ -930,6 +931,11 @@ namespace FlaxEditor.Modules
// Check if node already has that element (skip during init when we want to walk project dir very fast)
if (_isDuringFastSetup || !parent.Folder.ContainsChild(path))
{
#if PLATFORM_MAC
if (path.EndsWith(".DS_Store", StringComparison.Ordinal))
continue;
#endif
// Create file item
ContentItem item;
if (path.EndsWith(".cs"))
@@ -971,6 +977,11 @@ namespace FlaxEditor.Modules
// Check if node already has that element (skip during init when we want to walk project dir very fast)
if (_isDuringFastSetup || !parent.Folder.ContainsChild(path))
{
#if PLATFORM_MAC
if (path.EndsWith(".DS_Store", StringComparison.Ordinal))
continue;
#endif
// Create file item
ContentItem item = null;
if (FlaxEngine.Content.GetAssetInfo(path, out var assetInfo))
@@ -1196,6 +1207,7 @@ namespace FlaxEditor.Modules
{
case WatcherChangeTypes.Created:
case WatcherChangeTypes.Deleted:
case WatcherChangeTypes.Renamed:
{
lock (_dirtyNodes)
{
@@ -90,6 +90,12 @@ namespace FlaxEditor.Modules
hint = "Too long name.";
return false;
}
if (item.IsFolder && shortName.EndsWith("."))
{
hint = "Name cannot end with '.'";
return false;
}
// Find invalid characters
if (Utilities.Utils.HasInvalidPathChar(shortName))
@@ -120,7 +126,7 @@ namespace FlaxEditor.Modules
// Cache data
string sourcePath = item.Path;
string sourceFolder = System.IO.Path.GetDirectoryName(sourcePath);
string extension = System.IO.Path.GetExtension(sourcePath);
string extension = item.IsFolder ? "" : System.IO.Path.GetExtension(sourcePath);
string destinationPath = StringUtils.CombinePaths(sourceFolder, shortName + extension);
if (item.IsFolder)
+28 -26
View File
@@ -37,11 +37,6 @@ namespace FlaxEditor.Modules
/// </summary>
public event Action<Prefab, Actor> PrefabApplied;
/// <summary>
/// Locally cached actor for prefab creation.
/// </summary>
private Actor _prefabCreationActor;
internal PrefabsModule(Editor editor)
: base(editor)
{
@@ -65,13 +60,14 @@ namespace FlaxEditor.Modules
/// To create prefab manually (from code) use <see cref="PrefabManager.CreatePrefab"/> method.
/// </remarks>
/// <param name="selection">The scene selection to use.</param>
public void CreatePrefab(List<SceneGraphNode> selection)
/// <param name="prefabWindow">The prefab window that creates it.</param>
public void CreatePrefab(List<SceneGraphNode> selection, Windows.Assets.PrefabWindow prefabWindow = null)
{
if (selection == null)
selection = Editor.SceneEditing.Selection;
if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.CanCreatePrefab)
{
CreatePrefab(actorNode.Actor);
CreatePrefab(actorNode.Actor, true, prefabWindow);
}
}
@@ -92,7 +88,8 @@ namespace FlaxEditor.Modules
/// </summary>
/// <param name="actor">The root prefab actor.</param>
/// <param name="rename">Allow renaming or not</param>
public void CreatePrefab(Actor actor, bool rename)
/// <param name="prefabWindow">The prefab window that creates it.</param>
public void CreatePrefab(Actor actor, bool rename, Windows.Assets.PrefabWindow prefabWindow = null)
{
// Skip in invalid states
if (!Editor.StateMachine.CurrentState.CanEditContent)
@@ -105,42 +102,47 @@ namespace FlaxEditor.Modules
PrefabCreating?.Invoke(actor);
var proxy = Editor.ContentDatabase.GetProxy<Prefab>();
_prefabCreationActor = actor;
Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name, rename);
Editor.Windows.ContentWin.NewItem(proxy, actor, contentItem => OnPrefabCreated(contentItem, actor, prefabWindow), actor.Name, rename);
}
private void OnPrefabCreated(ContentItem contentItem)
private void OnPrefabCreated(ContentItem contentItem, Actor actor, Windows.Assets.PrefabWindow prefabWindow)
{
if (contentItem is PrefabItem prefabItem)
{
PrefabCreated?.Invoke(prefabItem);
}
// Skip in invalid states
if (!Editor.StateMachine.CurrentState.CanEditScene)
return;
Undo undo = null;
if (prefabWindow != null)
{
prefabWindow.MarkAsEdited();
undo = prefabWindow.Undo;
}
else
{
// Skip in invalid states
if (!Editor.StateMachine.CurrentState.CanEditScene)
return;
undo = Editor.Undo;
}
// Record undo for prefab creating (backend links the target instance with the prefab)
if (Editor.Undo.Enabled)
if (undo.Enabled)
{
if (!_prefabCreationActor)
if (!actor)
return;
var actorsList = new List<Actor>();
Utilities.Utils.GetActorsTree(actorsList, _prefabCreationActor);
Utilities.Utils.GetActorsTree(actorsList, actor);
var actions = new IUndoAction[actorsList.Count];
for (int i = 0; i < actorsList.Count; i++)
{
var action = BreakPrefabLinkAction.Linked(actorsList[i]);
actions[i] = action;
}
Undo.AddAction(new MultiUndoAction(actions));
_prefabCreationActor = null;
actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]);
undo.AddAction(new MultiUndoAction(actions));
}
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
Editor.Windows.PropertiesWin.Presenter.BuildLayout();
if (prefabWindow != null)
prefabWindow.Presenter.BuildLayout();
}
/// <summary>
@@ -129,6 +129,7 @@ namespace FlaxEditor.Modules
else
{
Editor.UI.UpdateProgress(string.Empty, 0);
Editor.UI.UpdateStatusBar();
}
}
+91 -100
View File
@@ -42,8 +42,10 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuFileSaveScenes;
private ContextMenuButton _menuFileCloseScenes;
private ContextMenuButton _menuFileOpenScriptsProject;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
private ContextMenuButton _menuSaveAll;
private ContextMenuButton _menuFileRecompileScripts;
private ContextMenuButton _menuFileSaveAll;
private ContextMenuButton _menuEditUndo;
private ContextMenuButton _menuEditRedo;
private ContextMenuButton _menuEditCut;
@@ -62,15 +64,19 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuGamePlayCurrentScenes;
private ContextMenuButton _menuGameStop;
private ContextMenuButton _menuGamePause;
private ContextMenuButton _menuGameCookAndRun;
private ContextMenuButton _menuGameRunCookedGame;
private ContextMenuButton _menuToolsBuildScenes;
private ContextMenuButton _menuToolsBakeLightmaps;
private ContextMenuButton _menuToolsClearLightmaps;
private ContextMenuButton _menuToolsBakeAllEnvProbes;
private ContextMenuButton _menuToolsBuildCSGMesh;
private ContextMenuButton _menuToolsBuildNavMesh;
private ContextMenuButton _menuToolsBuildAllMesgesSDF;
private ContextMenuButton _menuToolsBuildAllMeshesSDF;
private ContextMenuButton _menuToolsCancelBuilding;
private ContextMenuButton _menuToolsProfilerWindow;
private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault;
private ContextMenuButton _menuToolsTakeScreenshot;
private ContextMenuChildMenu _menuWindowApplyWindowLayout;
private ToolStripButton _toolStripSaveAll;
@@ -279,7 +285,7 @@ namespace FlaxEditor.Modules
Color color;
if (Editor.StateMachine.IsPlayMode)
color = Color.OrangeRed;
color = Style.Current.Statusbar.PlayMode;
else
color = Style.Current.BackgroundSelected;
@@ -293,6 +299,11 @@ namespace FlaxEditor.Modules
else
text = "Ready";
if(ProgressVisible)
{
color = Style.Current.Statusbar.Loading;
}
StatusBar.Text = text;
StatusBar.StatusColor = color;
_contentStats = contentStats;
@@ -338,7 +349,7 @@ namespace FlaxEditor.Modules
internal void ProgressFailed(string message)
{
_progressFailed = true;
StatusBar.StatusColor = Color.Red;
StatusBar.StatusColor = Style.Current.Statusbar.Failed;
StatusBar.Text = message;
_outputLogButton.Visible = true;
}
@@ -391,6 +402,10 @@ namespace FlaxEditor.Modules
{
UpdateStatusBar();
}
else if(ProgressVisible)
{
UpdateStatusBar();
}
}
private class CustomWindowBorderControl : Control
@@ -510,13 +525,13 @@ namespace FlaxEditor.Modules
MenuFile = MainMenu.AddButton("File");
var cm = MenuFile.ContextMenu;
cm.VisibleChanged += OnMenuFileShowHide;
_menuSaveAll = cm.AddButton("Save All", inputOptions.Save.ToString(), Editor.SaveAll);
_menuFileSaveScenes = cm.AddButton("Save scenes", Editor.Scene.SaveScenes);
_menuFileCloseScenes = cm.AddButton("Close scenes", Editor.Scene.CloseAllScenes);
_menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll);
_menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes);
_menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes);
cm.AddSeparator();
cm.AddButton("Open scripts project", Editor.CodeEditing.OpenSolution);
_menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
cm.AddButton("Recompile scripts", ScriptsBuilder.Compile);
_menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution);
_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("Open project...", OpenProject);
cm.AddSeparator();
@@ -526,27 +541,34 @@ namespace FlaxEditor.Modules
MenuEdit = MainMenu.AddButton("Edit");
cm = MenuEdit.ContextMenu;
cm.VisibleChanged += OnMenuEditShowHide;
_menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo.ToString(), Editor.PerformUndo);
_menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo.ToString(), Editor.PerformRedo);
_menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo, Editor.PerformUndo);
_menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo, Editor.PerformRedo);
cm.AddSeparator();
_menuEditCut = cm.AddButton("Cut", inputOptions.Cut.ToString(), Editor.SceneEditing.Cut);
_menuEditCopy = cm.AddButton("Copy", inputOptions.Copy.ToString(), Editor.SceneEditing.Copy);
_menuEditPaste = cm.AddButton("Paste", inputOptions.Paste.ToString(), Editor.SceneEditing.Paste);
_menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
_menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
_menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
cm.AddSeparator();
_menuEditDelete = cm.AddButton("Delete", inputOptions.Delete.ToString(), Editor.SceneEditing.Delete);
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate.ToString(), Editor.SceneEditing.Duplicate);
_menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
cm.AddSeparator();
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll.ToString(), Editor.SceneEditing.SelectAllScenes);
_menuEditFind = cm.AddButton("Find", inputOptions.Search.ToString(), Editor.Windows.SceneWin.Search);
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
cm.AddSeparator();
cm.AddButton("Game Settings", () =>
{
var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath);
if(item != null)
Editor.ContentEditing.Open(item);
});
// Scene
MenuScene = MainMenu.AddButton("Scene");
cm = MenuScene.ContextMenu;
cm.VisibleChanged += OnMenuSceneShowHide;
_menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", MoveActorToViewport);
_menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", AlignActorWithViewport);
_menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", AlignViewportWithActor);
_menuScenePilotActor = cm.AddButton("Pilot actor", PilotActor);
_menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", inputOptions.MoveActorToViewport, MoveActorToViewport);
_menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", inputOptions.AlignActorWithViewport, AlignActorWithViewport);
_menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", inputOptions.AlignViewportWithActor, AlignViewportWithActor);
_menuScenePilotActor = cm.AddButton("Pilot actor", inputOptions.PilotActor, PilotActor);
cm.AddSeparator();
_menuSceneCreateTerrain = cm.AddButton("Create terrain", CreateTerrain);
@@ -555,39 +577,41 @@ namespace FlaxEditor.Modules
cm = MenuGame.ContextMenu;
cm.VisibleChanged += OnMenuGameShowHide;
_menuGamePlayGame = cm.AddButton("Play Game", Editor.Simulation.RequestPlayGameOrStopPlay);
_menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", Editor.Simulation.RequestPlayScenesOrStopPlay);
_menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay);
_menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay);
_menuGamePlayGame = cm.AddButton("Play Game", inputOptions.Play, Editor.Simulation.RequestPlayGameOrStopPlay);
_menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.PlayCurrentScenes, Editor.Simulation.RequestPlayScenesOrStopPlay);
_menuGameStop = cm.AddButton("Stop Game", inputOptions.Play, Editor.Simulation.RequestStopPlay);
_menuGamePause = cm.AddButton("Pause", inputOptions.Pause, Editor.Simulation.RequestPausePlay);
cm.AddSeparator();
var numberOfClientsMenu = cm.AddChildMenu("Number of game clients");
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
cm.AddSeparator();
cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first.");
_menuGameCookAndRun = cm.AddButton("Cook & Run", inputOptions.CookAndRun, Editor.Windows.GameCookerWin.BuildAndRun);
_menuGameCookAndRun.LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
_menuGameRunCookedGame = cm.AddButton("Run cooked game", inputOptions.RunCookedGame, Editor.Windows.GameCookerWin.RunCooked);
_menuGameRunCookedGame.LinkTooltip("Runs the game build from the last cooking output. Use 'Cook & Run' or Game Cooker first.");
// Tools
MenuTools = MainMenu.AddButton("Tools");
cm = MenuTools.ContextMenu;
cm.VisibleChanged += OnMenuToolsShowHide;
_menuToolsBuildScenes = cm.AddButton("Build scenes data", "Ctrl+F10", Editor.BuildScenesOrCancel);
_menuToolsBuildScenes = cm.AddButton("Build scenes data", inputOptions.BuildScenesData, Editor.BuildScenesOrCancel);
cm.AddSeparator();
_menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", Editor.BakeLightmapsOrCancel);
_menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", Editor.ClearLightmaps);
_menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes);
_menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG);
_menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh);
_menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF);
_menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", inputOptions.BakeLightmaps, Editor.BakeLightmapsOrCancel);
_menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", inputOptions.ClearLightmaps, Editor.ClearLightmaps);
_menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", inputOptions.BakeEnvProbes, Editor.BakeAllEnvProbes);
_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);
cm.AddSeparator();
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
_menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel());
cm.AddSeparator();
cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow);
_menuToolsProfilerWindow = cm.AddButton("Profiler", inputOptions.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow());
cm.AddSeparator();
_menuToolsSetTheCurrentSceneViewAsDefault = cm.AddButton("Set current scene view as project default", SetTheCurrentSceneViewAsDefault);
cm.AddButton("Take screenshot", "F12", Editor.Windows.TakeScreenshot);
_menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot);
cm.AddSeparator();
cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show());
cm.AddButton("Options", () => Editor.Windows.EditorOptionsWin.Show());
@@ -606,7 +630,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Output Log", Editor.Windows.OutputLogWin.FocusOrShow);
cm.AddButton("Graphics Quality", Editor.Windows.GraphicsQualityWin.FocusOrShow);
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow);
cm.AddButton("Profiler", inputOptions.ProfilerWindow, Editor.Windows.ProfilerWin.FocusOrShow);
cm.AddButton("Content Search", Editor.ContentFinding.ShowSearch);
cm.AddButton("Visual Script Debugger", Editor.Windows.VisualScriptDebuggerWin.FocusOrShow);
cm.AddSeparator();
@@ -633,7 +657,12 @@ namespace FlaxEditor.Modules
{
var inputOptions = options.Input;
_menuSaveAll.ShortKeys = inputOptions.Save.ToString();
_menuFileSaveAll.ShortKeys = inputOptions.Save.ToString();
_menuFileSaveScenes.ShortKeys = inputOptions.SaveScenes.ToString();
_menuFileCloseScenes.ShortKeys = inputOptions.CloseScenes.ToString();
_menuFileOpenScriptsProject.ShortKeys = inputOptions.OpenScriptsProject.ToString();
_menuFileGenerateScriptsProjectFiles.ShortKeys = inputOptions.GenerateScriptsProject.ToString();
_menuFileRecompileScripts.ShortKeys = inputOptions.RecompileScripts.ToString();
_menuEditUndo.ShortKeys = inputOptions.Undo.ToString();
_menuEditRedo.ShortKeys = inputOptions.Redo.ToString();
_menuEditCut.ShortKeys = inputOptions.Cut.ToString();
@@ -642,8 +671,21 @@ namespace FlaxEditor.Modules
_menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString();
_menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString();
_menuEditFind.ShortKeys = inputOptions.Search.ToString();
_menuGamePlayCurrentScenes.ShortKeys = inputOptions.Play.ToString();
_menuGamePlayGame.ShortKeys = inputOptions.Play.ToString();
_menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString();
_menuGamePause.ShortKeys = inputOptions.Pause.ToString();
_menuGameStop.ShortKeys = inputOptions.Play.ToString();
_menuGameCookAndRun.ShortKeys = inputOptions.CookAndRun.ToString();
_menuGameRunCookedGame.ShortKeys = inputOptions.RunCookedGame.ToString();
_menuToolsBuildScenes.ShortKeys = inputOptions.BuildScenesData.ToString();
_menuToolsBakeLightmaps.ShortKeys = inputOptions.BakeLightmaps.ToString();
_menuToolsClearLightmaps.ShortKeys = inputOptions.ClearLightmaps.ToString();
_menuToolsBakeAllEnvProbes.ShortKeys = inputOptions.BakeEnvProbes.ToString();
_menuToolsBuildCSGMesh.ShortKeys = inputOptions.BuildCSG.ToString();
_menuToolsBuildNavMesh.ShortKeys = inputOptions.BuildNav.ToString();
_menuToolsBuildAllMeshesSDF.ShortKeys = inputOptions.BuildSDF.ToString();
_menuToolsProfilerWindow.ShortKeys = inputOptions.ProfilerWindow.ToString();
_menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString();
MainMenuShortcutKeysUpdated?.Invoke();
}
@@ -668,10 +710,10 @@ namespace FlaxEditor.Modules
ToolStrip.AddSeparator();
// Cook scenes
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)");
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
// Cook and run
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally");
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator();
@@ -692,7 +734,7 @@ namespace FlaxEditor.Modules
playActionGroup.SelectedChanged = SetPlayAction;
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game({inputOptions.Pause})");
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})");
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
UpdateToolstrip();
@@ -752,7 +794,7 @@ namespace FlaxEditor.Modules
HorizontalAlignment = TextAlignment.Far,
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
Parent = progressPanel,
Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0),
Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0)
};
UpdateStatusBar();
@@ -872,7 +914,7 @@ namespace FlaxEditor.Modules
_menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps";
_menuToolsClearLightmaps.Enabled = canEdit;
_menuToolsBakeAllEnvProbes.Enabled = canEdit;
_menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps;
_menuToolsBuildAllMeshesSDF.Enabled = canEdit && !isBakingLightmaps;
_menuToolsBuildCSGMesh.Enabled = canEdit;
_menuToolsBuildNavMesh.Enabled = canEdit;
_menuToolsCancelBuilding.Enabled = GameCooker.IsRunning;
@@ -911,7 +953,7 @@ namespace FlaxEditor.Modules
Editor.Windows.LoadLayout((string)button.Tag);
}
private void AlignViewportWithActor()
internal void AlignViewportWithActor()
{
var selection = Editor.SceneEditing;
if (selection.HasSthSelected && selection.Selection[0] is ActorNode node)
@@ -922,7 +964,7 @@ namespace FlaxEditor.Modules
}
}
private void MoveActorToViewport()
internal void MoveActorToViewport()
{
var selection = Editor.SceneEditing;
if (selection.HasSthSelected && selection.Selection[0] is ActorNode node)
@@ -936,7 +978,7 @@ namespace FlaxEditor.Modules
}
}
private void AlignActorWithViewport()
internal void AlignActorWithViewport()
{
var selection = Editor.SceneEditing;
if (selection.HasSthSelected && selection.Selection[0] is ActorNode node)
@@ -972,57 +1014,6 @@ namespace FlaxEditor.Modules
new Tools.Terrain.CreateTerrainDialog().Show(Editor.Windows.MainWindow);
}
private void BakeAllEnvProbes()
{
Editor.Scene.ExecuteOnGraph(node =>
{
if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive)
{
((EnvironmentProbe)envProbeNode.Actor).Bake();
node.ParentScene.IsEdited = true;
}
else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene)
{
skyLight.Bake();
node.ParentScene.IsEdited = true;
}
return node.IsActive;
});
}
private void BuildCSG()
{
var scenes = Level.Scenes;
scenes.ToList().ForEach(x => x.BuildCSG(0));
Editor.Scene.MarkSceneEdited(scenes);
}
private void BuildNavMesh()
{
var scenes = Level.Scenes;
scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
Editor.Scene.MarkSceneEdited(scenes);
}
private void BuildAllMeshesSDF()
{
// TODO: async maybe with progress reporting?
Editor.Scene.ExecuteOnGraph(node =>
{
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
{
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
{
Editor.Log("Generating SDF for " + staticModel.Model);
if (!staticModel.Model.GenerateSDF())
staticModel.Model.Save();
}
}
return true;
});
}
private void SetTheCurrentSceneViewAsDefault()
{
var projectInfo = Editor.GameProject;
+1 -1
View File
@@ -721,7 +721,7 @@ namespace FlaxEditor.Modules
// Create main window
var settings = CreateWindowSettings.Default;
settings.Title = "Flax Editor";
settings.Size = Platform.DesktopSize;
settings.Size = Platform.DesktopSize * 0.75f;
settings.StartPosition = WindowStartPosition.CenterScreen;
settings.ShowAfterFirstPaint = true;
+2 -2
View File
@@ -106,7 +106,7 @@ namespace FlaxEditor.Options
[DefaultValue(60.0f), Limit(0, 666)]
[EditorDisplay("General", "Editor FPS"), EditorOrder(110), Tooltip("Limit for the editor draw/update frames per second rate (FPS). Use higher values if you need more responsive interface or lower values to use less device power. Value 0 disables any limits.")]
public float EditorFPS { get; set; } = 60.0f;
/// <summary>
/// Gets or sets The FPS of the editor when the editor window is not focused. Usually set to lower then the editor FPS.
/// </summary>
@@ -203,7 +203,7 @@ namespace FlaxEditor.Options
[DefaultValue(5), Limit(1)]
[EditorDisplay("Auto Save", "Auto Save Frequency"), EditorOrder(801), Tooltip("The interval between auto saves (in minutes)")]
public int AutoSaveFrequency { get; set; } = 5;
/// <summary>
/// Gets or sets a value indicating the time before the auto save that the popup shows (in seconds).
/// </summary>
+73 -25
View File
@@ -134,6 +134,66 @@ namespace FlaxEditor.Options
return false;
}
private bool ProcessModifiers(Control control)
{
return ProcessModifiers(control.Root.GetKey);
}
private bool ProcessModifiers(Window window)
{
return ProcessModifiers(window.GetKey);
}
private bool ProcessModifiers(Func<KeyboardKeys, bool> getKeyFunc)
{
bool ctrlPressed = getKeyFunc(KeyboardKeys.Control);
bool shiftPressed = getKeyFunc(KeyboardKeys.Shift);
bool altPressed = getKeyFunc(KeyboardKeys.Alt);
bool mod1 = false;
if (Modifier1 == KeyboardKeys.None)
mod1 = true;
else if (Modifier1 == KeyboardKeys.Control)
{
mod1 = ctrlPressed;
ctrlPressed = false;
}
else if (Modifier1 == KeyboardKeys.Shift)
{
mod1 = shiftPressed;
shiftPressed = false;
}
else if (Modifier1 == KeyboardKeys.Alt)
{
mod1 = altPressed;
altPressed = false;
}
bool mod2 = false;
if (Modifier2 == KeyboardKeys.None)
mod2 = true;
else if (Modifier2 == KeyboardKeys.Control)
{
mod2 = ctrlPressed;
ctrlPressed = false;
}
else if (Modifier2 == KeyboardKeys.Shift)
{
mod2 = shiftPressed;
shiftPressed = false;
}
else if (Modifier2 == KeyboardKeys.Alt)
{
mod2 = altPressed;
altPressed = false;
}
// Check if any unhandled modifiers are not pressed
if (mod1 && mod2)
return !ctrlPressed && !shiftPressed && !altPressed;
return false;
}
/// <summary>
/// Processes this input binding to check if state matches.
/// </summary>
@@ -142,19 +202,7 @@ namespace FlaxEditor.Options
public bool Process(Control control)
{
var root = control.Root;
if (root.GetKey(Key))
{
if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1))
{
if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2))
{
return true;
}
}
}
return false;
return root.GetKey(Key) && ProcessModifiers(control);
}
/// <summary>
@@ -165,20 +213,20 @@ namespace FlaxEditor.Options
/// <returns>True if input has been processed, otherwise false.</returns>
public bool Process(Control control, KeyboardKeys key)
{
var root = control.Root;
if (key != Key)
return false;
if (key == Key)
{
if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1))
{
if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2))
{
return true;
}
}
}
return ProcessModifiers(control);
}
return false;
/// <summary>
/// Processes this input binding to check if state matches.
/// </summary>
/// <param name="window">The input providing window.</param>
/// <returns>True if input has been processed, otherwise false.</returns>
public bool Process(Window window)
{
return window.GetKey(Key) && ProcessModifiers(window);
}
/// <inheritdoc />
+133 -13
View File
@@ -78,6 +78,30 @@ namespace FlaxEditor.Options
#endregion
#region File
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("File"), EditorOrder(300)]
public InputBinding SaveScenes = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("File"), EditorOrder(310)]
public InputBinding CloseScenes = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("File"), EditorOrder(320)]
public InputBinding OpenScriptsProject = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("File"), EditorOrder(330)]
public InputBinding GenerateScriptsProject = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("File"), EditorOrder(340)]
public InputBinding RecompileScripts = new InputBinding(KeyboardKeys.None);
#endregion
#region Scene
[DefaultValue(typeof(InputBinding), "End")]
@@ -85,35 +109,115 @@ namespace FlaxEditor.Options
public InputBinding SnapToGround = new InputBinding(KeyboardKeys.End);
[DefaultValue(typeof(InputBinding), "F5")]
[EditorDisplay("Scene"), EditorOrder(510)]
[EditorDisplay("Scene", "Play/Stop"), EditorOrder(510)]
public InputBinding Play = new InputBinding(KeyboardKeys.F5);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Scene", "Play Current Scenes/Stop"), EditorOrder(520)]
public InputBinding PlayCurrentScenes = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "F6")]
[EditorDisplay("Scene"), EditorOrder(520)]
[EditorDisplay("Scene"), EditorOrder(530)]
public InputBinding Pause = new InputBinding(KeyboardKeys.F6);
[DefaultValue(typeof(InputBinding), "F11")]
[EditorDisplay("Scene"), EditorOrder(530)]
[EditorDisplay("Scene"), EditorOrder(540)]
public InputBinding StepFrame = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Scene", "Cook & Run"), EditorOrder(550)]
public InputBinding CookAndRun = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Scene", "Run cooked game"), EditorOrder(560)]
public InputBinding RunCookedGame = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Scene", "Move actor to viewport"), EditorOrder(570)]
public InputBinding MoveActorToViewport = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Scene", "Align actor with viewport"), EditorOrder(571)]
public InputBinding AlignActorWithViewport = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Scene", "Align viewport with actor"), EditorOrder(572)]
public InputBinding AlignViewportWithActor = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Scene"), EditorOrder(573)]
public InputBinding PilotActor = new InputBinding(KeyboardKeys.None);
#endregion
#region Tools
[DefaultValue(typeof(InputBinding), "Ctrl+F10")]
[EditorDisplay("Tools", "Build scenes data"), EditorOrder(600)]
public InputBinding BuildScenesData = new InputBinding(KeyboardKeys.F10, KeyboardKeys.Control);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Tools", "Bake lightmaps"), EditorOrder(601)]
public InputBinding BakeLightmaps = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Tools", "Clear lightmaps data"), EditorOrder(602)]
public InputBinding ClearLightmaps = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Tools", "Bake all env probes"), EditorOrder(603)]
public InputBinding BakeEnvProbes = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Tools", "Build CSG mesh"), EditorOrder(604)]
public InputBinding BuildCSG = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Tools", "Build Nav Mesh"), EditorOrder(605)]
public InputBinding BuildNav = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Tools", "Build all meshes SDF"), EditorOrder(606)]
public InputBinding BuildSDF = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "F12")]
[EditorDisplay("Tools", "Take screenshot"), EditorOrder(607)]
public InputBinding TakeScreenshot = new InputBinding(KeyboardKeys.F12);
#endregion
#region Profiler
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Profiler", "Open Profiler Window"), EditorOrder(630)]
public InputBinding ProfilerWindow = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Profiler", "Start/Stop Profiler"), EditorOrder(631)]
public InputBinding ProfilerStartStop = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Profiler", "Clear Profiler data"), EditorOrder(632)]
public InputBinding ProfilerClear = new InputBinding(KeyboardKeys.None);
#endregion
#region Debugger
[DefaultValue(typeof(InputBinding), "F5")]
[EditorDisplay("Debugger", "Continue"), EditorOrder(610)]
[EditorDisplay("Debugger", "Continue"), EditorOrder(810)]
public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5);
[DefaultValue(typeof(InputBinding), "F10")]
[EditorDisplay("Debugger", "Step Over"), EditorOrder(620)]
[EditorDisplay("Debugger", "Step Over"), EditorOrder(820)]
public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10);
[DefaultValue(typeof(InputBinding), "F11")]
[EditorDisplay("Debugger", "Step Into"), EditorOrder(630)]
[EditorDisplay("Debugger", "Step Into"), EditorOrder(830)]
public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "Shift+F11")]
[EditorDisplay("Debugger", "Step Out"), EditorOrder(640)]
[EditorDisplay("Debugger", "Step Out"), EditorOrder(840)]
public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
#endregion
@@ -132,6 +236,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Gizmo"), EditorOrder(1020)]
public InputBinding ScaleMode = new InputBinding(KeyboardKeys.Alpha3);
[DefaultValue(typeof(InputBinding), "Alpha4")]
[EditorDisplay("Gizmo"), EditorOrder(1030)]
public InputBinding ToggleTransformSpace = new InputBinding(KeyboardKeys.Alpha4);
#endregion
#region Viewport
@@ -160,28 +268,40 @@ namespace FlaxEditor.Options
[EditorDisplay("Viewport"), EditorOrder(1550)]
public InputBinding Down = new InputBinding(KeyboardKeys.Q);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)]
public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Viewport", "Increase Camera Move Speed"), EditorOrder(1570)]
public InputBinding CameraIncreaseMoveSpeed = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Viewport", "Decrease Camera Move Speed"), EditorOrder(1571)]
public InputBinding CameraDecreaseMoveSpeed = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "Numpad0")]
[EditorDisplay("Viewport"), EditorOrder(1600)]
[EditorDisplay("Viewport"), EditorOrder(1700)]
public InputBinding ViewpointFront = new InputBinding(KeyboardKeys.Numpad0);
[DefaultValue(typeof(InputBinding), "Numpad5")]
[EditorDisplay("Viewport"), EditorOrder(1610)]
[EditorDisplay("Viewport"), EditorOrder(1710)]
public InputBinding ViewpointBack = new InputBinding(KeyboardKeys.Numpad5);
[DefaultValue(typeof(InputBinding), "Numpad4")]
[EditorDisplay("Viewport"), EditorOrder(1620)]
[EditorDisplay("Viewport"), EditorOrder(1720)]
public InputBinding ViewpointLeft = new InputBinding(KeyboardKeys.Numpad4);
[DefaultValue(typeof(InputBinding), "Numpad6")]
[EditorDisplay("Viewport"), EditorOrder(1630)]
[EditorDisplay("Viewport"), EditorOrder(1730)]
public InputBinding ViewpointRight = new InputBinding(KeyboardKeys.Numpad6);
[DefaultValue(typeof(InputBinding), "Numpad8")]
[EditorDisplay("Viewport"), EditorOrder(1640)]
[EditorDisplay("Viewport"), EditorOrder(1740)]
public InputBinding ViewpointTop = new InputBinding(KeyboardKeys.Numpad8);
[DefaultValue(typeof(InputBinding), "Numpad2")]
[EditorDisplay("Viewport"), EditorOrder(1650)]
[EditorDisplay("Viewport"), EditorOrder(1750)]
public InputBinding ViewpointBottom = new InputBinding(KeyboardKeys.Numpad2);
#endregion
+70 -5
View File
@@ -166,7 +166,19 @@ namespace FlaxEditor.Options
/// Gets or sets the output log text font.
/// </summary>
[EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")]
public FontReference OutputLogTextFont { get; set; } = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
public FontReference OutputLogTextFont
{
get => _outputLogFont;
set
{
if (value == null)
_outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
else if (!value.Font)
_outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);
else
_outputLogFont = value;
}
}
/// <summary>
/// Gets or sets the output log text color.
@@ -225,29 +237,82 @@ namespace FlaxEditor.Options
public int NumberOfGameClientsToLaunch = 1;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
private FontReference _titleFont = new FontReference(DefaultFont, 18);
private FontReference _largeFont = new FontReference(DefaultFont, 14);
private FontReference _mediumFont = new FontReference(DefaultFont, 9);
private FontReference _smallFont = new FontReference(DefaultFont, 9);
private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
/// <summary>
/// Gets or sets the title font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")]
public FontReference TitleFont { get; set; } = new FontReference(DefaultFont, 18);
public FontReference TitleFont
{
get => _titleFont;
set
{
if (value == null)
_titleFont = new FontReference(DefaultFont, 18);
else if (!value.Font)
_titleFont.Font = DefaultFont;
else
_titleFont = value;
}
}
/// <summary>
/// Gets or sets the large font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")]
public FontReference LargeFont { get; set; } = new FontReference(DefaultFont, 14);
public FontReference LargeFont
{
get => _largeFont;
set
{
if (value == null)
_largeFont = new FontReference(DefaultFont, 14);
else if (!value.Font)
_largeFont.Font = DefaultFont;
else
_largeFont = value;
}
}
/// <summary>
/// Gets or sets the medium font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")]
public FontReference MediumFont { get; set; } = new FontReference(DefaultFont, 9);
public FontReference MediumFont
{
get => _mediumFont;
set
{
if (value == null)
_mediumFont = new FontReference(DefaultFont, 9);
else if (!value.Font)
_mediumFont.Font = DefaultFont;
else
_mediumFont = value;
}
}
/// <summary>
/// Gets or sets the small font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")]
public FontReference SmallFont { get; set; } = new FontReference(DefaultFont, 9);
public FontReference SmallFont
{
get => _smallFont;
set
{
if (value == null)
_smallFont = new FontReference(DefaultFont, 9);
else if (!value.Font)
_smallFont.Font = DefaultFont;
else
_smallFont = value;
}
}
}
}
+7
View File
@@ -244,6 +244,13 @@ namespace FlaxEditor.Options
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
ProgressNormal = Color.FromBgra(0xFF0ad328),
Statusbar = new Style.StatusbarStyle()
{
PlayMode = Color.FromBgra(0xFF2F9135),
Failed = Color.FromBgra(0xFF9C2424),
Loading = Color.FromBgra(0xFF2D2D30)
},
// Fonts
FontTitle = options.Interface.TitleFont.GetFont(),
FontLarge = options.Interface.LargeFont.GetFont(),
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
@@ -22,9 +23,9 @@ namespace FlaxEditor.SceneGraph.Actors
}
/// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu)
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
base.OnContextMenu(contextMenu);
base.OnContextMenu(contextMenu, window);
var actor = (AnimatedModel)Actor;
if (actor && actor.SkinnedModel)
@@ -6,6 +6,8 @@ using Real = System.Double;
using Real = System.Single;
#endif
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
@@ -23,6 +25,25 @@ namespace FlaxEditor.SceneGraph.Actors
{
}
/// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
base.OnContextMenu(contextMenu, window);
if (window is not SceneTreeWindow win)
return;
var button = new ContextMenuButton(contextMenu, "Move Camera to View");
button.Parent = contextMenu.ItemsContainer;
contextMenu.ItemsContainer.Children.Remove(button);
contextMenu.ItemsContainer.Children.Insert(4, button);
button.Clicked += () =>
{
var c = Actor as Camera;
var viewport = Editor.Instance.Windows.EditWin.Viewport;
c.Position = viewport.ViewPosition;
c.Orientation = viewport.ViewOrientation;
};
}
/// <inheritdoc />
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
{
+3 -2
View File
@@ -3,6 +3,7 @@
using System.IO;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph.GUI;
using FlaxEditor.Windows;
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
@@ -65,7 +66,7 @@ namespace FlaxEditor.SceneGraph.Actors
public override SceneNode ParentScene => this;
/// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu)
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
contextMenu.AddSeparator();
var path = Scene.Path;
@@ -80,7 +81,7 @@ namespace FlaxEditor.SceneGraph.Actors
if (Level.ScenesCount > 1)
contextMenu.AddButton("Unload all but this scene", OnUnloadAllButSelectedScene).LinkTooltip("Unloads all of the active scenes except for the selected scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene;
base.OnContextMenu(contextMenu);
base.OnContextMenu(contextMenu, window);
}
private void OnSelect()
+33 -16
View File
@@ -9,6 +9,7 @@ using Real = System.Single;
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Modules;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.Json;
using Object = FlaxEngine.Object;
@@ -21,7 +22,7 @@ namespace FlaxEditor.SceneGraph.Actors
[HideInEditor]
public sealed class SplineNode : ActorNode
{
private sealed class SplinePointNode : ActorChildNode<SplineNode>
internal sealed class SplinePointNode : ActorChildNode<SplineNode>
{
public unsafe SplinePointNode(SplineNode node, Guid id, int index)
: base(node, id, index)
@@ -167,10 +168,11 @@ namespace FlaxEditor.SceneGraph.Actors
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
{
normal = -ray.Ray.Direction;
var actor = (Spline)_node.Actor;
var pos = actor.GetSplinePoint(Index);
normal = -ray.Ray.Direction;
return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance);
var nodeSize = NodeSizeByDistance(Transform.Translation, PointNodeSize);
return new BoundingSphere(pos, nodeSize).Intersects(ref ray.Ray, out distance);
}
public override void OnDebugDraw(ViewportDebugDrawData data)
@@ -179,29 +181,32 @@ namespace FlaxEditor.SceneGraph.Actors
var pos = actor.GetSplinePoint(Index);
var tangentIn = actor.GetSplineTangent(Index, true).Translation;
var tangentOut = actor.GetSplineTangent(Index, false).Translation;
var pointSize = NodeSizeByDistance(pos, PointNodeSize);
var tangentInSize = NodeSizeByDistance(tangentIn, TangentNodeSize);
var tangentOutSize = NodeSizeByDistance(tangentOut, TangentNodeSize);
// Draw spline path
ParentNode.OnDebugDraw(data);
// Draw selected point highlight
DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false);
DebugDraw.DrawSphere(new BoundingSphere(pos, pointSize), Color.Yellow, 0, false);
// Draw tangent points
if (tangentIn != pos)
{
DebugDraw.DrawLine(pos, tangentIn, Color.Blue.AlphaMultiplied(0.6f), 0, false);
DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.Blue, 0, false);
DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, tangentInSize), Color.Blue, 0, false);
}
if (tangentOut != pos)
{
DebugDraw.DrawLine(pos, tangentOut, Color.Red.AlphaMultiplied(0.6f), 0, false);
DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.Red, 0, false);
DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, tangentOutSize), Color.Red, 0, false);
}
}
public override void OnContextMenu(ContextMenu contextMenu)
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
ParentNode.OnContextMenu(contextMenu);
ParentNode.OnContextMenu(contextMenu, window);
}
public static SceneGraphNode Create(StateData state)
@@ -219,7 +224,7 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
private sealed class SplinePointTangentNode : ActorChildNode
internal sealed class SplinePointTangentNode : ActorChildNode
{
private SplineNode _node;
private int _index;
@@ -249,10 +254,11 @@ namespace FlaxEditor.SceneGraph.Actors
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
{
normal = -ray.Ray.Direction;
var actor = (Spline)_node.Actor;
var pos = actor.GetSplineTangent(_index, _isIn).Translation;
normal = -ray.Ray.Direction;
return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance);
var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize);
return new BoundingSphere(pos, tangentSize).Intersects(ref ray.Ray, out distance);
}
public override void OnDebugDraw(ViewportDebugDrawData data)
@@ -263,12 +269,13 @@ namespace FlaxEditor.SceneGraph.Actors
// Draw selected tangent highlight
var actor = (Spline)_node.Actor;
var pos = actor.GetSplineTangent(_index, _isIn).Translation;
DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false);
var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize);
DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false);
}
public override void OnContextMenu(ContextMenu contextMenu)
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
ParentNode.OnContextMenu(contextMenu);
ParentNode.OnContextMenu(contextMenu, window);
}
public override void OnDispose()
@@ -279,6 +286,9 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
private const Real PointNodeSize = 1.5f;
private const Real TangentNodeSize = 1.0f;
/// <inheritdoc />
public SplineNode(Actor actor)
: base(actor)
@@ -345,9 +355,9 @@ namespace FlaxEditor.SceneGraph.Actors
}
/// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu)
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
base.OnContextMenu(contextMenu);
base.OnContextMenu(contextMenu, window);
contextMenu.AddButton("Add spline model", OnAddSplineModel);
contextMenu.AddButton("Add spline collider", OnAddSplineCollider);
@@ -398,6 +408,13 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize)
{
var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform;
var distance = Vector3.Distance(cameraTransform.Translation, nodePosition) / 100;
return distance * nodeSize;
}
/// <inheritdoc />
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
{
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
@@ -21,9 +22,9 @@ namespace FlaxEditor.SceneGraph.Actors
}
/// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu)
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
base.OnContextMenu(contextMenu);
base.OnContextMenu(contextMenu, window);
contextMenu.AddButton("Add collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null;
}
@@ -599,7 +599,7 @@ namespace FlaxEditor.SceneGraph.GUI
// Drag scripts
else if (_dragScripts != null && _dragScripts.HasValidDrag)
{
foreach(var script in _dragScripts.Objects)
foreach (var script in _dragScripts.Objects)
{
var customAction = script.HasPrefabLink ? new ReparentAction(script) : null;
using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction))
@@ -616,7 +616,7 @@ namespace FlaxEditor.SceneGraph.GUI
var spawnParent = myActor;
if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below)
spawnParent = newParent;
for (int i = 0; i < _dragAssets.Objects.Count; i++)
{
var item = _dragAssets.Objects[i];
@@ -720,7 +720,7 @@ namespace FlaxEditor.SceneGraph.GUI
for (var i = 0; i < tree.Selection.Count; i++)
{
var e = tree.Selection[i];
// Skip if parent is already selected to keep correct parenting
if (tree.Selection.Contains(e.Parent))
continue;
+2 -1
View File
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Modules;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Windows;
using FlaxEngine;
namespace FlaxEditor.SceneGraph
@@ -339,7 +340,7 @@ namespace FlaxEditor.SceneGraph
/// <summary>
/// Called when scene tree window wants to show the context menu. Allows to add custom options.
/// </summary>
public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu)
public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu, EditorWindow window)
{
}
@@ -24,7 +24,8 @@ namespace
String version;
RiderInstallation(const String& path_, const String& version_)
: path(path_), version(version_)
: path(path_)
, version(version_)
{
}
};
@@ -44,6 +45,10 @@ namespace
if (document.HasParseError())
return;
// Check if this is actually rider and not another jetbrains product
if (document.FindMember("name")->value != "JetBrains Rider")
return;
// Find version
auto versionMember = document.FindMember("version");
if (versionMember == document.MemberEnd())
@@ -141,14 +146,14 @@ bool sortInstallations(RiderInstallation* const& i1, RiderInstallation* const& i
int32 version2[3] = { 0 };
StringUtils::Parse(values1[0].Get(), &version1[0]);
StringUtils::Parse(values1[1].Get(), &version1[1]);
if(values1.Count() > 2)
if (values1.Count() > 2)
StringUtils::Parse(values1[2].Get(), &version1[2]);
StringUtils::Parse(values2[0].Get(), &version2[0]);
StringUtils::Parse(values2[1].Get(), &version2[1]);
if(values2.Count() > 2)
if (values2.Count() > 2)
StringUtils::Parse(values2[2].Get(), &version2[2]);
// Compare by MAJOR.MINOR.BUILD
@@ -174,7 +179,7 @@ void RiderCodeEditor::FindEditors(Array<CodeEditor*>* output)
String localAppDataPath;
FileSystem::GetSpecialFolderPath(SpecialFolder::LocalAppData, localAppDataPath);
#if PLATFORM_WINDOWS
// Lookup from all known registry locations
SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\Rider for Unreal Engine"));
@@ -187,6 +192,7 @@ void RiderCodeEditor::FindEditors(Array<CodeEditor*>* output)
SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\JetBrains Rider"));
// Versions installed via JetBrains Toolbox
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("Programs"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\Rider\\ch-0\\"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\Rider\\ch-1\\")); // Beta versions
#endif
@@ -201,6 +207,7 @@ void RiderCodeEditor::FindEditors(Array<CodeEditor*>* output)
FileSystem::GetChildDirectories(subDirectories, TEXT("/opt/"));
// Versions installed via JetBrains Toolbox
SearchDirectory(&installations, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/rider/"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-0"));
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions
@@ -210,7 +217,24 @@ void RiderCodeEditor::FindEditors(Array<CodeEditor*>* output)
TEXT("flatpak run com.jetbrains.Rider"));
#endif
for (auto directory : subDirectories)
#if PLATFORM_MAC
String applicationSupportFolder;
FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder);
Array<String> subMacDirectories;
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/"));
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/"));
for (const String& directory : subMacDirectories)
{
String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources");
SearchDirectory(&installations, riderAppDirectory);
}
// Check the local installer version
SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"));
#endif
for (const String& directory : subDirectories)
SearchDirectory(&installations, directory);
// Sort found installations by version number
@@ -244,8 +268,16 @@ void RiderCodeEditor::OpenFile(const String& path, int32 line)
// Open file
line = line > 0 ? line : 1;
CreateProcessSettings procSettings;
#if !PLATFORM_MAC
procSettings.FileName = _execPath;
procSettings.Arguments = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _solutionPath, path, line);
#else
// This follows pretty much how all the other engines open rider which deals with cross architecture issues
procSettings.FileName = "/usr/bin/open";
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, _solutionPath, path, line);
#endif
procSettings.HiddenWindow = false;
procSettings.WaitForEnd = false;
procSettings.LogOutput = false;
@@ -263,8 +295,14 @@ void RiderCodeEditor::OpenSolution()
// Open solution
CreateProcessSettings procSettings;
#if !PLATFORM_MAC
procSettings.FileName = _execPath;
procSettings.Arguments = String::Format(TEXT("\"{0}\""), _solutionPath);
#else
// This follows pretty much how all the other engines open rider which deals with cross architecture issues
procSettings.FileName = "/usr/bin/open";
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, _solutionPath);
#endif
procSettings.HiddenWindow = false;
procSettings.WaitForEnd = false;
procSettings.LogOutput = false;
+27
View File
@@ -1393,5 +1393,32 @@ namespace FlaxEditor.Scripting
return _custom.GetMembers(name, MemberTypes.Method, bindingAttr).FirstOrDefault();
return ScriptMemberInfo.Null;
}
/// <summary>
/// Basic check to see if a type could be casted to another type
/// </summary>
/// <param name="from">Source type</param>
/// <param name="to">Target type</param>
/// <returns>True if the type can be casted</returns>
public static bool CanCast(ScriptType from, ScriptType to)
{
if (from == to)
return true;
if (from == Null || to == Null)
return false;
return (from.Type != typeof(void) && from.Type != typeof(FlaxEngine.Object)) &&
(to.Type != typeof(void) && to.Type != typeof(FlaxEngine.Object)) &&
from.IsAssignableFrom(to);
}
/// <summary>
/// Basic check to see if this type could be casted to another type
/// </summary>
/// <param name="to">Target type</param>
/// <returns>True if the type can be casted</returns>
public bool CanCastTo(ScriptType to)
{
return CanCast(this, to);
}
}
}
+3 -6
View File
@@ -246,20 +246,17 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
Log::FileNotFoundException(monoPath).SetLevel(LogType::Fatal);
return true;
}
//const String monoPath = TEXT("mono");
cmdLine.Append(TEXT("\""));
const String monoPath = TEXT("mono");
cmdLine.Append(monoPath);
cmdLine.Append(TEXT("\" "));
cmdLine.Append(TEXT(" "));
// TODO: Set env var for the mono MONO_GC_PARAMS=nursery-size64m to boost build performance -> profile it
#endif
cmdLine.Append(TEXT("\""));
cmdLine.Append(buildToolPath);
cmdLine.Append(TEXT("\" "));
cmdLine.Append(args.Get(), args.Length());
// Call build tool
CreateProcessSettings procSettings;
procSettings.FileName = StringView(*cmdLine, cmdLine.Length());
procSettings.Arguments = args.Get();
procSettings.WorkingDirectory = workingDir;
const int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
+8
View File
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.IO;
using FlaxEngine;
using FlaxEngine.Utilities;
@@ -54,6 +55,13 @@ namespace FlaxEditor.States
}
else if (Editor.Options.Options.General.ForceScriptCompilationOnStartup && !skipCompile)
{
// Generate project files when Cache is missing or was cleared previously
if (!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Intermediate")) ||
!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Projects")))
{
var customArgs = Editor.Instance.CodeEditing.SelectedEditor.GenerateProjectCustomArgs;
ScriptsBuilder.GenerateProject(customArgs);
}
// Compile scripts before loading any scenes, then we load them and can open scenes
ScriptsBuilder.Compile();
}
@@ -744,6 +744,16 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
return inputType.IsVoid;
}
}
private sealed class InvokeMethodNode : SurfaceNode
@@ -1151,6 +1161,54 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
return false;
if (!memberInfo.IsStatic)
{
if (VisjectSurface.FullCastCheck(memberInfo.DeclaringType, outputType, hint))
return true;
}
var parameters = memberInfo.GetParameters();
bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid);
if (outputType.IsVoid)
return !isPure;
foreach (var param in parameters)
{
if (param.IsOut)
continue;
if (VisjectSurface.FullCastCheck(param.Type, outputType, hint))
return true;
}
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
return false;
if (VisjectSurface.FullCastCheck(memberInfo.ValueType, inputType, hint))
return true;
var parameters = memberInfo.GetParameters();
bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid);
if (inputType.IsVoid)
return !isPure;
foreach (var param in memberInfo.GetParameters())
{
if (!param.IsOut)
continue;
if (VisjectSurface.FullCastCheck(param.Type, inputType, hint))
return true;
}
return false;
}
}
private sealed class ReturnNode : SurfaceNode
@@ -1777,6 +1835,16 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
return inputType.IsVoid;
}
}
private abstract class FieldNodeBase : SurfaceNode
@@ -1913,6 +1981,64 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Get " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
UpdateSignature();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
if (scriptType == ScriptType.Null)
return false;
var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
foreach (var member in members)
{
if (!SurfaceUtils.IsValidVisualScriptField(member))
continue;
if (member)
{
if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
return true;
}
else
{
var isStatic = (bool)nodeArch.DefaultValues[3];
if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
return true;
}
break;
}
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
if (scriptType == ScriptType.Null)
return false;
var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
foreach (var member in members)
{
if (!SurfaceUtils.IsValidVisualScriptField(member))
continue;
if (member)
{
if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint))
return true;
}
else
{
var typeName = (string)nodeArch.DefaultValues[2];
if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), inputType, hint))
return true;
}
break;
}
return false;
}
}
private sealed class SetFieldNode : FieldNodeBase
@@ -1966,6 +2092,48 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Set " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
UpdateSignature();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
if (outputType.IsVoid)
return true;
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
if (scriptType == ScriptType.Null)
return false;
var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
foreach (var member in members)
{
if (!SurfaceUtils.IsValidVisualScriptField(member))
continue;
if (member)
{
if (VisjectSurface.FullCastCheck(member.ValueType, outputType, hint))
return true;
if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
return true;
}
else
{
var typeName = (string)nodeArch.DefaultValues[2];
if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), outputType, hint))
return true;
var isStatic = (bool)nodeArch.DefaultValues[3];
if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
return true;
}
break;
}
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
return inputType.IsVoid;
}
}
private abstract class EventBaseNode : SurfaceNode, IFunctionsDependantNode
@@ -2184,6 +2352,43 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
// Event based nodes always have a pulse input, so it's always compatible with void
if (outputType.IsVoid)
return true;
var eventName = (string)nodeArch.DefaultValues[1];
var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
if (member && SurfaceUtils.IsValidVisualScriptEvent(member))
{
if (!member.IsStatic)
{
if (VisjectSurface.FullCastCheck(eventType, outputType, hint))
return true;
}
}
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
// Event based nodes always have a pulse output, so it's always compatible with void
if (inputType.IsVoid)
return true;
var eventName = (string)nodeArch.DefaultValues[1];
var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
if (member && SurfaceUtils.IsValidVisualScriptEvent(member))
{
if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint))
return true;
}
return false;
}
}
private sealed class BindEventNode : EventBaseNode
@@ -2265,6 +2470,8 @@ namespace FlaxEditor.Surface.Archetypes
Title = string.Empty,
Description = "Overrides the base class method with custom implementation",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
IsInputCompatible = MethodOverrideNode.IsInputCompatible,
IsOutputCompatible = MethodOverrideNode.IsOutputCompatible,
Size = new Float2(240, 60),
DefaultValues = new object[]
{
@@ -2277,6 +2484,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 4,
Create = (id, context, arch, groupArch) => new InvokeMethodNode(id, context, arch, groupArch),
IsInputCompatible = InvokeMethodNode.IsInputCompatible,
IsOutputCompatible = InvokeMethodNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(240, 60),
@@ -2317,6 +2526,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 6,
Create = (id, context, arch, groupArch) => new VisualScriptFunctionNode(id, context, arch, groupArch),
IsInputCompatible = VisualScriptFunctionNode.IsInputCompatible,
IsOutputCompatible = VisualScriptFunctionNode.IsOutputCompatible,
Title = "New Function",
Description = "Adds a new function to the script",
Flags = NodeFlags.VisualScriptGraph,
@@ -2330,6 +2541,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 7,
Create = (id, context, arch, groupArch) => new GetFieldNode(id, context, arch, groupArch),
IsInputCompatible = GetFieldNode.IsInputCompatible,
IsOutputCompatible = GetFieldNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(240, 60),
@@ -2345,6 +2558,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 8,
Create = (id, context, arch, groupArch) => new SetFieldNode(id, context, arch, groupArch),
IsInputCompatible = SetFieldNode.IsInputCompatible,
IsOutputCompatible = SetFieldNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(240, 60),
@@ -2361,6 +2576,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 9,
Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch),
IsInputCompatible = EventBaseNode.IsInputCompatible,
IsOutputCompatible = EventBaseNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(260, 60),
@@ -2383,6 +2600,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 10,
Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch),
IsInputCompatible = EventBaseNode.IsInputCompatible,
IsOutputCompatible = EventBaseNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(260, 60),
+1 -1
View File
@@ -126,7 +126,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Clamp",
Description = "Clamps value to the specified range",
Flags = NodeFlags.AllGraphs,
Size = new Float2(110, 60),
Size = new Float2(140, 60),
ConnectionsHints = ConnectionsHint.Numeric,
IndependentBoxes = new[] { 0 },
DependentBoxes = new[] { 1, 2, 3 },
@@ -216,6 +216,35 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch, false)
{
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);
if (type)
{
var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray();
var fieldsLength = fields.Length;
for (var i = 0; i < fieldsLength; i++)
{
if (VisjectSurface.FullCastCheck(fields[i].ValueType, outputType, hint))
return true;
}
}
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);
if (type)
{
if (VisjectSurface.FullCastCheck(type, inputType, hint))
return true;
}
return false;
}
}
private sealed class UnpackStructureNode : StructureNode
@@ -225,6 +254,35 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch, true)
{
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);
if (type)
{
if (VisjectSurface.FullCastCheck(type, outputType, hint))
return true;
}
return false;
}
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
{
var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName);
if (type)
{
var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray();
var fieldsLength = fields.Length;
for (var i = 0; i < fieldsLength; i++)
{
if (VisjectSurface.FullCastCheck(fields[i].ValueType, inputType, hint))
return true;
}
}
return false;
}
}
/// <summary>
@@ -351,6 +409,8 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 26,
Title = "Pack Structure",
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
IsInputCompatible = PackStructureNode.IsInputCompatible,
IsOutputCompatible = PackStructureNode.IsOutputCompatible,
Description = "Makes the structure data to from the components.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(180, 20),
@@ -461,6 +521,8 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 36,
Title = "Unpack Structure",
Create = (id, context, arch, groupArch) => new UnpackStructureNode(id, context, arch, groupArch),
IsInputCompatible = UnpackStructureNode.IsInputCompatible,
IsOutputCompatible = UnpackStructureNode.IsOutputCompatible,
Description = "Breaks the structure data to allow extracting components from it.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(180, 20),
+19 -1
View File
@@ -787,7 +787,7 @@ namespace FlaxEditor.Surface.Archetypes
}
}
private class AsNode : SurfaceNode
internal class AsNode : SurfaceNode
{
private TypePickerControl _picker;
@@ -838,6 +838,15 @@ namespace FlaxEditor.Surface.Archetypes
box.CurrentType = type ? type : ScriptType.FlaxObject;
}
/// <summary>
/// Sets the type of the picker and the type of the output box
/// </summary>
/// <param name="type">Target Type</param>
public void SetPickerValue(ScriptType type)
{
_picker.Value = type;
}
/// <inheritdoc />
public override void OnDestroy()
{
@@ -999,6 +1008,15 @@ namespace FlaxEditor.Surface.Archetypes
GetBox(4).CurrentType = type ? type : _picker.Type;
}
/// <summary>
/// Sets the type of the picker and the type of the output box
/// </summary>
/// <param name="type">Target Type</param>
public void SetPickerValue(ScriptType type)
{
_picker.Value = type;
}
/// <inheritdoc />
public override void OnDestroy()
{
+69 -14
View File
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -40,6 +39,8 @@ namespace FlaxEditor.Surface.ContextMenu
public delegate List<SurfaceParameter> ParameterGetterDelegate();
private readonly List<VisjectCMGroup> _groups = new List<VisjectCMGroup>(16);
private CheckBox _contextSensitiveToggle;
private bool _contextSensitiveSearchEnabled = true;
private readonly TextBox _searchBox;
private bool _waitingForInput;
private VisjectCMGroup _surfaceParametersGroup;
@@ -127,7 +128,7 @@ namespace FlaxEditor.Surface.ContextMenu
_parameterSetNodeArchetype = info.ParameterSetNodeArchetype ?? Archetypes.Parameters.Nodes[3];
// Context menu dimensions
Size = new Float2(320, 248);
Size = new Float2(300, 400);
var headerPanel = new Panel(ScrollBars.None)
{
@@ -139,17 +140,41 @@ namespace FlaxEditor.Surface.ContextMenu
};
// Title bar
var titleFontReference = new FontReference(Style.Current.FontLarge.Asset, 10);
var titleLabel = new Label
{
Width = Width - 8,
Width = Width * 0.5f - 8f,
Height = 20,
X = 4,
Parent = headerPanel,
Text = "Select Node",
HorizontalAlignment = TextAlignment.Center,
Font = new FontReference(Style.Current.FontLarge.Asset, 10),
HorizontalAlignment = TextAlignment.Near,
Font = titleFontReference,
};
// Context sensitive toggle
var contextSensitiveLabel = new Label
{
Width = Width * 0.5f - 28,
Height = 20,
X = Width * 0.5f,
Parent = headerPanel,
Text = "Context Sensitive",
TooltipText = "Should the nodes be filtered to only show those that can be connected in the current context?",
HorizontalAlignment = TextAlignment.Far,
Font = titleFontReference,
};
_contextSensitiveToggle = new CheckBox
{
Width = 20,
Height = 20,
X = Width - 24,
Parent = headerPanel,
Checked = _contextSensitiveSearchEnabled,
};
_contextSensitiveToggle.StateChanged += OnContextSensitiveToggleStateChanged;
// Search box
_searchBox = new SearchBox(false, 2, 22)
{
@@ -288,6 +313,10 @@ namespace FlaxEditor.Surface.ContextMenu
OnSearchFilterChanged();
}
}
else if (_contextSensitiveSearchEnabled)
{
group.EvaluateVisibilityWithBox(_selectedBox);
}
Profiler.EndEvent();
}
@@ -321,6 +350,8 @@ namespace FlaxEditor.Surface.ContextMenu
Parent = group
};
}
if (_contextSensitiveSearchEnabled)
group.EvaluateVisibilityWithBox(_selectedBox);
group.SortChildren();
group.Parent = _groupsPanel;
_groups.Add(group);
@@ -418,8 +449,26 @@ namespace FlaxEditor.Surface.ContextMenu
return;
Profiler.BeginEvent("VisjectCM.OnSearchFilterChanged");
if (string.IsNullOrEmpty(_searchBox.Text))
UpdateFilters();
_searchBox.Focus();
Profiler.EndEvent();
}
private void OnContextSensitiveToggleStateChanged(CheckBox checkBox)
{
// Skip events during setup or init stuff
if (IsLayoutLocked)
return;
Profiler.BeginEvent("VisjectCM.OnContextSensitiveToggleStateChanged");
_contextSensitiveSearchEnabled = checkBox.Checked;
UpdateFilters();
Profiler.EndEvent();
}
private void UpdateFilters()
{
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null)
{
ResetView();
Profiler.EndEvent();
@@ -430,7 +479,7 @@ namespace FlaxEditor.Surface.ContextMenu
LockChildrenRecursive();
for (int i = 0; i < _groups.Count; i++)
{
_groups[i].UpdateFilter(_searchBox.Text);
_groups[i].UpdateFilter(_searchBox.Text, _contextSensitiveSearchEnabled ? _selectedBox : null);
_groups[i].UpdateItemSort(_selectedBox);
}
SortGroups();
@@ -443,9 +492,6 @@ namespace FlaxEditor.Surface.ContextMenu
PerformLayout();
if (SelectedItem != null)
_panel1.ScrollViewTo(SelectedItem);
_searchBox.Focus();
Profiler.EndEvent();
Profiler.EndEvent();
}
@@ -503,7 +549,11 @@ namespace FlaxEditor.Surface.ContextMenu
_searchBox.Clear();
SelectedItem = null;
for (int i = 0; i < _groups.Count; i++)
{
_groups[i].ResetView();
if (_contextSensitiveSearchEnabled)
_groups[i].EvaluateVisibilityWithBox(_selectedBox);
}
UnlockChildrenRecursive();
SortGroups();
@@ -671,9 +721,7 @@ namespace FlaxEditor.Surface.ContextMenu
SelectedItem = previousSelectedItem;
// Scroll into view (without smoothing)
_panel1.VScrollBar.SmoothingScale = 0;
_panel1.ScrollViewTo(SelectedItem);
_panel1.VScrollBar.SmoothingScale = 1;
_panel1.ScrollViewTo(SelectedItem, true);
}
return true;
}
@@ -759,5 +807,12 @@ namespace FlaxEditor.Surface.ContextMenu
{
return GetPreviousSiblings(item).OfType<T>();
}
/// <inheritdoc />
public override void OnDestroy()
{
_contextSensitiveToggle.StateChanged -= OnContextSensitiveToggleStateChanged;
base.OnDestroy();
}
}
}
@@ -66,7 +66,7 @@ namespace FlaxEditor.Surface.ContextMenu
{
if (_children[i] is VisjectCMItem item)
{
item.UpdateFilter(null);
item.UpdateFilter(null, null);
item.UpdateScore(null);
}
}
@@ -84,23 +84,42 @@ namespace FlaxEditor.Surface.ContextMenu
/// Updates the filter.
/// </summary>
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
/// <param name="selectedBox">The optionally selected box to show hints for it.</param>
public void UpdateFilter(string filterText, Box selectedBox)
{
Profiler.BeginEvent("VisjectCMGroup.UpdateFilter");
// Update items
bool isAnyVisible = false;
bool groupHeaderMatches = QueryFilterHelper.Match(filterText, HeaderText);
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is VisjectCMItem item)
{
item.UpdateFilter(filterText);
item.UpdateFilter(filterText, selectedBox, groupHeaderMatches);
isAnyVisible |= item.Visible;
}
}
// Update header title
if (QueryFilterHelper.Match(filterText, HeaderText))
// Update itself
if (isAnyVisible)
{
if (!string.IsNullOrEmpty(filterText))
Open(false);
Visible = true;
}
else
{
// Hide group if none of the items matched the filter
Visible = false;
}
Profiler.EndEvent();
}
internal void EvaluateVisibilityWithBox(Box selectedBox)
{
if (selectedBox == null)
{
for (int i = 0; i < _children.Count; i++)
{
@@ -109,14 +128,25 @@ namespace FlaxEditor.Surface.ContextMenu
item.Visible = true;
}
}
isAnyVisible = true;
Visible = true;
return;
}
Profiler.BeginEvent("VisjectCMGroup.EvaluateVisibilityWithBox");
bool isAnyVisible = false;
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is VisjectCMItem item)
{
item.Visible = item.CanConnectTo(selectedBox);
isAnyVisible |= item.Visible;
}
}
// Update itself
if (isAnyVisible)
{
if (!string.IsNullOrEmpty(filterText))
Open(false);
Visible = true;
}
else
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Utilities;
using FlaxEngine;
@@ -56,7 +57,7 @@ namespace FlaxEditor.Surface.ContextMenu
/// <param name="groupArchetype">The group archetype.</param>
/// <param name="archetype">The archetype.</param>
public VisjectCMItem(VisjectCMGroup group, GroupArchetype groupArchetype, NodeArchetype archetype)
: base(0, 0, 120, 12)
: base(0, 0, 120, 14)
{
Group = group;
_groupArchetype = groupArchetype;
@@ -77,7 +78,7 @@ namespace FlaxEditor.Surface.ContextMenu
if (!Visible)
return;
if (selectedBox != null && CanConnectTo(selectedBox, NodeArchetype))
if (selectedBox != null && CanConnectTo(selectedBox))
SortScore += 1;
if (Data != null)
SortScore += 1;
@@ -92,35 +93,93 @@ namespace FlaxEditor.Surface.ContextMenu
textRect = new Rectangle(22, 0, Width - 24, Height);
}
private bool CanConnectTo(Box startBox, NodeArchetype nodeArchetype)
/// <summary>
/// Checks if this context menu item can be connected to a given box, before a node is actually spawned.
/// </summary>
/// <param name="startBox">The connected box</param>
/// <returns>True if the connected box is compatible with this item</returns>
public bool CanConnectTo(Box startBox)
{
// Is compatible if box is null for reset reasons
if (startBox == null)
return false;
if (!startBox.IsOutput)
return false; // For now, I'm only handing the output box case
return true;
if (nodeArchetype.Elements != null)
if (_archetype == null)
return false;
bool isCompatible = false;
if (startBox.IsOutput && _archetype.IsInputCompatible != null)
{
for (int i = 0; i < nodeArchetype.Elements.Length; i++)
{
if (nodeArchetype.Elements[i].Type == NodeElementType.Input &&
startBox.CanUseType(nodeArchetype.Elements[i].ConnectionsType))
{
return true;
}
}
isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints);
}
return false;
else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null)
{
isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints);
}
else if (_archetype.Elements != null)
{
// Check compatibility based on the defined elements in the archetype. This handles all the default groups and items
isCompatible = CheckElementsCompatibility(startBox);
}
return isCompatible;
}
private bool CheckElementsCompatibility(Box startBox)
{
bool isCompatible = false;
foreach (NodeElementArchetype element in _archetype.Elements)
{
// Ignore all elements that aren't inputs or outputs (e.g. input fields)
if (element.Type != NodeElementType.Output && element.Type != NodeElementType.Input)
continue;
// Ignore elements with the same direction as the box
if ((startBox.IsOutput && element.Type == NodeElementType.Output) || (!startBox.IsOutput && element.Type == NodeElementType.Input))
continue;
ScriptType fromType;
ScriptType toType;
ConnectionsHint hint;
if (startBox.IsOutput)
{
fromType = element.ConnectionsType;
toType = startBox.CurrentType;
hint = _archetype.ConnectionsHints;
}
else
{
fromType = startBox.CurrentType;
toType = element.ConnectionsType;
hint = startBox.ParentNode.Archetype.ConnectionsHints;
}
isCompatible |= VisjectSurface.FullCastCheck(fromType, toType, hint);
}
return isCompatible;
}
/// <summary>
/// Updates the filter.
/// </summary>
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
/// <param name="selectedBox">The optionally selected box to show hints for it.</param>
/// <param name="groupHeaderMatches">True if item's group header got a filter match and item should stay visible.</param>
public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false)
{
if (selectedBox != null)
{
Visible = CanConnectTo(selectedBox);
if (!Visible)
{
_highlights?.Clear();
return;
}
}
_isStartsWithMatch = _isFullMatch = false;
if (filterText == null)
if (string.IsNullOrEmpty(filterText))
{
// Clear filter
_highlights?.Clear();
@@ -184,7 +243,7 @@ namespace FlaxEditor.Surface.ContextMenu
Data = data;
}
else
else if (!groupHeaderMatches)
{
// Hide
_highlights?.Clear();
+63 -70
View File
@@ -91,7 +91,7 @@ namespace FlaxEditor.Surface.Elements
_currentType = value;
// Check if will need to update box connections due to type change
if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !CanCast(prev, _currentType))
if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !prev.CanCastTo(_currentType))
{
// Remove all invalid connections and update those which still can be valid
var connections = Connections.ToArray();
@@ -231,58 +231,8 @@ namespace FlaxEditor.Surface.Elements
}
// Check using connection hints
var connectionsHints = ParentNode.Archetype.ConnectionsHints;
if (Archetype.ConnectionsType == ScriptType.Null && connectionsHints != ConnectionsHint.None)
{
if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything)
return true;
if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void))
return true;
if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum)
return true;
if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray)
return true;
if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary)
return true;
if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector)
{
var t = type.Type;
if (t == typeof(Vector2) ||
t == typeof(Vector3) ||
t == typeof(Vector4) ||
t == typeof(Float2) ||
t == typeof(Float3) ||
t == typeof(Float4) ||
t == typeof(Double2) ||
t == typeof(Double3) ||
t == typeof(Double4) ||
t == typeof(Int2) ||
t == typeof(Int3) ||
t == typeof(Int4) ||
t == typeof(Color))
{
return true;
}
}
if ((connectionsHints & ConnectionsHint.Scalar) == ConnectionsHint.Scalar)
{
var t = type.Type;
if (t == typeof(bool) ||
t == typeof(char) ||
t == typeof(byte) ||
t == typeof(short) ||
t == typeof(ushort) ||
t == typeof(int) ||
t == typeof(uint) ||
t == typeof(long) ||
t == typeof(ulong) ||
t == typeof(float) ||
t == typeof(double))
{
return true;
}
}
}
if (VisjectSurface.IsTypeCompatible(Archetype.ConnectionsType, type, ParentNode.Archetype.ConnectionsHints))
return true;
// Check independent and if there is box with bigger potential because it may block current one from changing type
var parentArch = ParentNode.Archetype;
@@ -296,7 +246,7 @@ namespace FlaxEditor.Surface.Elements
var b = ParentNode.GetBox(boxes[i]);
// Check if its the same and tested type matches the default value type
if (b == this && CanCast(parentArch.DefaultType, type))
if (b == this && parentArch.DefaultType.CanCastTo(type))
{
// Can
return true;
@@ -718,17 +668,6 @@ namespace FlaxEditor.Surface.Elements
}
}
private static bool CanCast(ScriptType oB, ScriptType iB)
{
if (oB == iB)
return true;
if (oB == ScriptType.Null || iB == ScriptType.Null)
return false;
return (oB.Type != typeof(void) && oB.Type != typeof(FlaxEngine.Object)) &&
(iB.Type != typeof(void) && iB.Type != typeof(FlaxEngine.Object)) &&
oB.IsAssignableFrom(iB);
}
/// <inheritdoc />
public bool AreConnected(IConnectionInstigator other)
{
@@ -787,7 +726,7 @@ namespace FlaxEditor.Surface.Elements
{
if (!iB.CanUseType(oB.CurrentType))
{
if (!CanCast(oB.CurrentType, iB.CurrentType))
if (!oB.CurrentType.CanCastTo(iB.CurrentType))
{
// Cannot
return false;
@@ -798,7 +737,7 @@ namespace FlaxEditor.Surface.Elements
{
if (!oB.CanUseType(iB.CurrentType))
{
if (!CanCast(oB.CurrentType, iB.CurrentType))
if (!oB.CurrentType.CanCastTo(iB.CurrentType))
{
// Cannot
return false;
@@ -871,7 +810,7 @@ namespace FlaxEditor.Surface.Elements
bool useCaster = false;
if (!iB.CanUseType(oB.CurrentType))
{
if (CanCast(oB.CurrentType, iB.CurrentType))
if (oB.CurrentType.CanCastTo(iB.CurrentType))
useCaster = true;
else
return;
@@ -881,8 +820,62 @@ namespace FlaxEditor.Surface.Elements
if (useCaster)
{
// Connect via Caster
//AddCaster(oB, iB);
throw new NotImplementedException("AddCaster(..) function");
const float casterXOffset = 250;
if (Surface.Undo != null && Surface.Undo.Enabled)
{
bool undoEnabled = Surface.Undo.Enabled;
Surface.Undo.Enabled = false;
SurfaceNode node = Surface.Context.SpawnNode(7, 22, Float2.Zero); // 22 AsNode, 25 CastNode
Surface.Undo.Enabled = undoEnabled;
if (node is not Archetypes.Tools.AsNode castNode)
throw new Exception("Node is not a casting node!");
// Set the type of the casting node
undoEnabled = castNode.Surface.Undo.Enabled;
castNode.Surface.Undo.Enabled = false;
castNode.SetPickerValue(iB.CurrentType);
castNode.Surface.Undo.Enabled = undoEnabled;
if (node.GetBox(0) is not OutputBox castOutputBox || node.GetBox(1) is not InputBox castInputBox)
throw new NullReferenceException("Casting failed. Cast node is invalid!");
// We set the position of the cast node here to set it relative to the target nodes input box
undoEnabled = castNode.Surface.Undo.Enabled;
castNode.Surface.Undo.Enabled = false;
var wantedOffset = iB.ParentNode.Location - new Float2(casterXOffset, -(iB.LocalY - castOutputBox.LocalY));
castNode.Location = Surface.Root.PointFromParent(ref wantedOffset);
castNode.Surface.Undo.Enabled = undoEnabled;
var spawnNodeAction = new AddRemoveNodeAction(castNode, true);
var connectToCastNodeAction = new ConnectBoxesAction(castInputBox, oB, true);
castInputBox.CreateConnection(oB);
connectToCastNodeAction.End();
var connectCastToTargetNodeAction = new ConnectBoxesAction(iB, castOutputBox, true);
iB.CreateConnection(castOutputBox);
connectCastToTargetNodeAction.End();
Surface.AddBatchedUndoAction(new MultiUndoAction(spawnNodeAction, connectToCastNodeAction, connectCastToTargetNodeAction));
}
else
{
SurfaceNode node = Surface.Context.SpawnNode(7, 22, Float2.Zero); // 22 AsNode, 25 CastNode
if (node is not Archetypes.Tools.AsNode castNode)
throw new Exception("Node is not a casting node!");
// Set the type of the casting node
castNode.SetPickerValue(iB.CurrentType);
if (node.GetBox(0) is not OutputBox castOutputBox || node.GetBox(1) is not InputBox castInputBox)
throw new NullReferenceException("Casting failed. Cast node is invalid!");
// We set the position of the cast node here to set it relative to the target nodes input box
var wantedOffset = iB.ParentNode.Location - new Float2(casterXOffset, -(iB.LocalY - castOutputBox.LocalY));
castNode.Location = Surface.Root.PointFromParent(ref wantedOffset);
castInputBox.CreateConnection(oB);
iB.CreateConnection(castOutputBox);
}
Surface.MarkAsEdited();
}
else
{
+23 -10
View File
@@ -34,9 +34,7 @@ namespace FlaxEditor.Surface.Elements
public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1)
{
// Calculate control points
var dst = (end - start) * new Float2(0.5f, 0.05f);
var control1 = new Float2(start.X + dst.X, start.Y + dst.Y);
var control2 = new Float2(end.X - dst.X, end.Y + dst.Y);
CalculateBezierControlPoints(start, end, out var control1, out var control2);
// Draw line
Render2D.DrawBezier(start, control1, control2, end, color, thickness);
@@ -49,6 +47,23 @@ namespace FlaxEditor.Surface.Elements
*/
}
private static void CalculateBezierControlPoints(Float2 start, Float2 end, out Float2 control1, out Float2 control2)
{
// Control points parameters
const float minControlLength = 100f;
const float maxControlLength = 150f;
var dst = (end - start).Length;
var yDst = Mathf.Abs(start.Y - end.Y);
// Calculate control points
var minControlDst = dst * 0.5f;
var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength);
var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f));
control1 = new Float2(start.X + controlDst, start.Y);
control2 = new Float2(end.X - controlDst, end.Y);
}
/// <summary>
/// Checks if a point intersects a connection
/// </summary>
@@ -71,17 +86,15 @@ namespace FlaxEditor.Surface.Elements
public static bool IntersectsConnection(ref Float2 start, ref Float2 end, ref Float2 point, float distance)
{
// Pretty much a point in rectangle check
if ((point.X - start.X) * (end.X - point.X) < 0) return false;
if ((point.X - start.X) * (end.X - point.X) < 0)
return false;
float offset = Mathf.Sign(end.Y - start.Y) * distance;
if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false;
if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0)
return false;
// Taken from the Render2D.DrawBezier code
float squaredDistance = distance;
var dst = (end - start) * new Float2(0.5f, 0.05f);
var control1 = new Float2(start.X + dst.X, start.Y + dst.Y);
var control2 = new Float2(end.X - dst.X, end.Y + dst.Y);
CalculateBezierControlPoints(start, end, out var control1, out var control2);
var d1 = control1 - start;
var d2 = control2 - control1;
+17
View File
@@ -89,6 +89,11 @@ namespace FlaxEditor.Surface
/// <returns>The created node object.</returns>
public delegate SurfaceNode CreateCustomNodeFunc(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch);
/// <summary>
/// Checks if the given type is compatible with the given node archetype. Used for custom nodes
/// </summary>
public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint);
/// <summary>
/// Unique node type ID within a single group.
/// </summary>
@@ -99,6 +104,16 @@ namespace FlaxEditor.Surface
/// </summary>
public CreateCustomNodeFunc Create;
/// <summary>
/// Function for asynchronously loaded nodes to check if input ports are compatible, for filtering.
/// </summary>
public IsCompatible IsInputCompatible;
/// <summary>
/// Function for asynchronously loaded nodes to check if output ports are compatible, for filtering.
/// </summary>
public IsCompatible IsOutputCompatible;
/// <summary>
/// Default initial size of the node.
/// </summary>
@@ -184,6 +199,8 @@ namespace FlaxEditor.Surface
{
TypeID = TypeID,
Create = Create,
IsInputCompatible = IsInputCompatible,
IsOutputCompatible = IsOutputCompatible,
Size = Size,
Flags = Flags,
Title = Title,
+2 -2
View File
@@ -406,8 +406,8 @@ namespace FlaxEditor.Surface
internal static bool IsValidVisualScriptType(ScriptType scriptType)
{
if (!scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
if (!scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
if (scriptType.IsGenericType)
@@ -136,6 +136,88 @@ namespace FlaxEditor.Surface
return result;
}
/// <summary>
/// Checks if a type is compatible with another type and can be casted by using a connection hint
/// </summary>
/// <param name="from">Source type</param>
/// <param name="to">Type to check compatibility with</param>
/// <param name="hint">Hint to check if casting is possible</param>
/// <returns>True if the source type is compatible with the target type</returns>
public static bool IsTypeCompatible(ScriptType from, ScriptType to, ConnectionsHint hint)
{
if (from == ScriptType.Null && hint != ConnectionsHint.None)
{
if ((hint & ConnectionsHint.Anything) == ConnectionsHint.Anything)
return true;
if ((hint & ConnectionsHint.Value) == ConnectionsHint.Value && to.Type != typeof(void))
return true;
if ((hint & ConnectionsHint.Enum) == ConnectionsHint.Enum && to.IsEnum)
return true;
if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array && to.IsArray)
return true;
if ((hint & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && to.IsDictionary)
return true;
if ((hint & ConnectionsHint.Vector) == ConnectionsHint.Vector)
{
var t = to.Type;
if (t == typeof(Vector2) ||
t == typeof(Vector3) ||
t == typeof(Vector4) ||
t == typeof(Float2) ||
t == typeof(Float3) ||
t == typeof(Float4) ||
t == typeof(Double2) ||
t == typeof(Double3) ||
t == typeof(Double4) ||
t == typeof(Int2) ||
t == typeof(Int3) ||
t == typeof(Int4) ||
t == typeof(Color))
{
return true;
}
}
if ((hint & ConnectionsHint.Scalar) == ConnectionsHint.Scalar)
{
var t = to.Type;
if (t == typeof(bool) ||
t == typeof(char) ||
t == typeof(byte) ||
t == typeof(short) ||
t == typeof(ushort) ||
t == typeof(int) ||
t == typeof(uint) ||
t == typeof(long) ||
t == typeof(ulong) ||
t == typeof(float) ||
t == typeof(double))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks if a type is compatible with another type and can be casted by using a connection hint
/// </summary>
/// <param name="from">Source type</param>
/// <param name="to">Target type</param>
/// <param name="hint">Connection hint</param>
/// <returns>True if any method of casting or compatibility check succeeds</returns>
public static bool FullCastCheck(ScriptType from, ScriptType to, ConnectionsHint hint)
{
// Yes, from and to are switched on purpose
if (CanUseDirectCastStatic(to, from, false))
return true;
if (IsTypeCompatible(from, to, hint))
return true;
// Same here
return to.CanCastTo(from);
}
/// <summary>
/// Checks if can use direct conversion from one type to another.
/// </summary>
+41 -3
View File
@@ -213,6 +213,23 @@ namespace FlaxEditor.Surface
return;
}
if (_middleMouseDown)
{
// Calculate delta
var delta = location - _middleMouseDownPos;
if (delta.LengthSquared > 0.01f)
{
// Move view
_mouseMoveAmount += delta.Length;
_rootControl.Location += delta;
_middleMouseDownPos = location;
Cursor = CursorType.SizeAll;
}
// Handled
return;
}
// Check if user is selecting or moving node(s)
if (_leftMouseDown)
{
@@ -269,6 +286,11 @@ namespace FlaxEditor.Surface
_rightMouseDown = false;
Cursor = CursorType.Default;
}
if (_middleMouseDown)
{
_middleMouseDown = false;
Cursor = CursorType.Default;
}
_isMovingSelection = false;
ConnectingEnd(null);
@@ -291,7 +313,7 @@ namespace FlaxEditor.Surface
if (IsMouseOver && !_leftMouseDown && !IsPrimaryMenuOpened)
{
var nextViewScale = ViewScale + delta * 0.1f;
if (delta > 0 && !_rightMouseDown)
{
// Scale towards mouse when zooming in
@@ -306,7 +328,7 @@ namespace FlaxEditor.Surface
ViewScale = nextViewScale;
ViewCenterPosition = viewCenter;
}
return true;
}
@@ -380,6 +402,7 @@ namespace FlaxEditor.Surface
_isMovingSelection = false;
_rightMouseDown = false;
_leftMouseDown = false;
_middleMouseDown = false;
return true;
}
@@ -399,6 +422,11 @@ namespace FlaxEditor.Surface
_rightMouseDown = true;
_rightMouseDownPos = location;
}
if (button == MouseButton.Middle)
{
_middleMouseDown = true;
_middleMouseDownPos = location;
}
// Check if any node is under the mouse
SurfaceControl controlUnderMouse = GetControlUnderMouse();
@@ -444,7 +472,7 @@ namespace FlaxEditor.Surface
Focus();
return true;
}
if (_rightMouseDown)
if (_rightMouseDown || _middleMouseDown)
{
// Start navigating
StartMouseCapture();
@@ -513,6 +541,13 @@ namespace FlaxEditor.Surface
}
_mouseMoveAmount = 0;
}
if (_middleMouseDown && button == MouseButton.Middle)
{
_middleMouseDown = false;
EndMouseCapture();
Cursor = CursorType.Default;
_mouseMoveAmount = 0;
}
// Base
bool handled = base.OnMouseUp(location, button);
@@ -523,6 +558,7 @@ namespace FlaxEditor.Surface
// Clear flags
_rightMouseDown = false;
_leftMouseDown = false;
_middleMouseDown = false;
return true;
}
@@ -706,6 +742,8 @@ namespace FlaxEditor.Surface
{
if (_inputBrackets.Count == 0)
{
if (currentInputText.StartsWith(' '))
currentInputText = "";
ResetInput();
ShowPrimaryMenu(_mousePos, false, currentInputText);
}
+11 -1
View File
@@ -59,6 +59,11 @@ namespace FlaxEditor.Surface
/// </summary>
protected bool _rightMouseDown;
/// <summary>
/// The middle mouse down flag.
/// </summary>
protected bool _middleMouseDown;
/// <summary>
/// The left mouse down position.
/// </summary>
@@ -69,6 +74,11 @@ namespace FlaxEditor.Surface
/// </summary>
protected Float2 _rightMouseDownPos = Float2.Minimum;
/// <summary>
/// The middle mouse down position.
/// </summary>
protected Float2 _middleMouseDownPos = Float2.Minimum;
/// <summary>
/// The mouse position.
/// </summary>
@@ -902,7 +912,7 @@ namespace FlaxEditor.Surface
{
return _context.FindNode(id);
}
/// <summary>
/// Adds the undo action to be batched (eg. if multiple undo actions is performed in a sequence during single update).
/// </summary>
+10 -1
View File
@@ -137,14 +137,23 @@ namespace FlaxEditor.Tools.Foliage
Offsets = Margin.Zero,
Parent = _noFoliagePanel
};
var buttonText = "Create new foliage";
_createNewFoliage = new Button
{
Text = "Create new foliage",
Text = buttonText,
AnchorPreset = AnchorPresets.MiddleCenter,
Offsets = new Margin(-60, 120, -12, 24),
Parent = _noFoliagePanel,
Enabled = false
};
var textSize = Style.Current.FontMedium.MeasureText(buttonText);
if (_createNewFoliage.Width < textSize.X)
{
_createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2;
_createNewFoliage.Width = textSize.X + 6;
}
_createNewFoliage.Clicked += OnCreateNewFoliageClicked;
}
@@ -375,7 +375,7 @@ namespace FlaxEditor.Tools.Foliage
private void OnModified()
{
Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene);
Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null);
}
private void OnSelectedFoliageChanged()
+1 -1
View File
@@ -218,7 +218,7 @@ namespace FlaxEditor.Tools.Foliage
private void OnModified()
{
Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene);
Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null);
}
private void OnSelectedFoliageChanged()
+10 -1
View File
@@ -95,14 +95,23 @@ namespace FlaxEditor.Tools.Terrain
Offsets = Margin.Zero,
Parent = _noTerrainPanel
};
var buttonText = "Create new terrain";
_createTerrainButton = new Button
{
Text = "Create new terrain",
Text = buttonText,
AnchorPreset = AnchorPresets.MiddleCenter,
Offsets = new Margin(-60, 120, -12, 24),
Parent = _noTerrainPanel,
Enabled = false
};
var textSize = Style.Current.FontMedium.MeasureText(buttonText);
if (_createTerrainButton.Width < textSize.X)
{
_createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2;
_createTerrainButton.Width = textSize.X + 6;
}
_createTerrainButton.Clicked += OnCreateNewTerrainClicked;
}
+1 -1
View File
@@ -544,7 +544,7 @@ namespace FlaxEditor.Tools
public override bool IsControllingMouse => IsPainting;
/// <inheritdoc />
public override BoundingSphere FocusBounds => _selectedModel?.Sphere ?? base.FocusBounds;
public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.Sphere : base.FocusBounds;
/// <inheritdoc />
public override void Update(float dt)
@@ -144,7 +144,7 @@ namespace FlaxEditor.Actions
{
var node = nodeParents[i];
var actor = node.Actor;
var parent = actor?.Parent;
var parent = actor != null ? actor.Parent : null;
if (parent != null)
{
bool IsNameValid(string name)
+6
View File
@@ -14,5 +14,11 @@ namespace FlaxEditor.Utilities
public const string FacebookUrl = "https://facebook.com/FlaxEngine";
public const string YoutubeUrl = "https://youtube.com/c/FlaxEngine";
public const string TwitterUrl = "https://twitter.com/FlaxEngine";
#if PLATFORM_MAC
public const string ShowInExplorer = "Show in Finder";
#else
public const string ShowInExplorer = "Show in explorer";
#endif
}
}
+56 -1
View File
@@ -18,10 +18,10 @@ using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using FlaxEditor.Windows;
namespace FlaxEngine
{
@@ -1235,5 +1235,60 @@ namespace FlaxEditor.Utilities
}
return s;
}
/// <summary>
/// Binds global input actions for the window.
/// </summary>
/// <param name="window">The editor window.</param>
public static void SetupCommonInputActions(EditorWindow window)
{
var inputActions = window.InputActions;
// Setup input actions
inputActions.Add(options => options.Save, Editor.Instance.SaveAll);
inputActions.Add(options => options.Undo, () =>
{
Editor.Instance.PerformUndo();
window.Focus();
});
inputActions.Add(options => options.Redo, () =>
{
Editor.Instance.PerformRedo();
window.Focus();
});
inputActions.Add(options => options.Cut, Editor.Instance.SceneEditing.Cut);
inputActions.Add(options => options.Copy, Editor.Instance.SceneEditing.Copy);
inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste);
inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate);
inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes);
inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete);
inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search());
inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport);
inputActions.Add(options => options.AlignActorWithViewport, Editor.Instance.UI.AlignActorWithViewport);
inputActions.Add(options => options.AlignViewportWithActor, Editor.Instance.UI.AlignViewportWithActor);
inputActions.Add(options => options.PilotActor, Editor.Instance.UI.PilotActor);
inputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor);
inputActions.Add(options => options.PlayCurrentScenes, Editor.Instance.Simulation.RequestPlayScenesOrStopPlay);
inputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause);
inputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame);
inputActions.Add(options => options.CookAndRun, () => Editor.Instance.Windows.GameCookerWin.BuildAndRun());
inputActions.Add(options => options.RunCookedGame, () => Editor.Instance.Windows.GameCookerWin.RunCooked());
inputActions.Add(options => options.BuildScenesData, Editor.Instance.BuildScenesOrCancel);
inputActions.Add(options => options.BakeLightmaps, Editor.Instance.BakeLightmapsOrCancel);
inputActions.Add(options => options.ClearLightmaps, Editor.Instance.ClearLightmaps);
inputActions.Add(options => options.BakeEnvProbes, Editor.Instance.BakeAllEnvProbes);
inputActions.Add(options => options.BuildCSG, Editor.Instance.BuildCSG);
inputActions.Add(options => options.BuildNav, Editor.Instance.BuildNavMesh);
inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF);
inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot);
inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow());
inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); });
inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); });
inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes());
inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes());
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
}
}
}
+154 -38
View File
@@ -2,6 +2,7 @@
using System;
using System.Linq;
using FlaxEditor.Content.Settings;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Options;
@@ -9,6 +10,8 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Widgets;
using FlaxEngine;
using FlaxEngine.GUI;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.Viewport
{
@@ -137,7 +140,7 @@ namespace FlaxEditor.Viewport
// Input
private bool _isControllingMouse, _isViewportControllingMouse;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private int _deltaFilteringStep;
private Float2 _startPos;
private Float2 _mouseDeltaLast;
@@ -441,6 +444,9 @@ namespace FlaxEditor.Viewport
if (useWidgets)
{
var largestText = "Invert Panning";
var textSize = Style.Current.FontMedium.MeasureText(largestText);
var xLocationForExtras = textSize.X + 5;
// Camera speed widget
var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var camSpeedCM = new ContextMenu();
@@ -483,10 +489,63 @@ namespace FlaxEditor.Viewport
}
}
// View Layers
{
var viewLayers = ViewWidgetButtonMenu.AddChildMenu("View Layers").ContextMenu;
viewLayers.AddButton("Copy layers", () => Clipboard.Text = JsonSerializer.Serialize(Task.View.RenderLayersMask));
viewLayers.AddButton("Paste layers", () =>
{
try
{
Task.ViewLayersMask = JsonSerializer.Deserialize<LayersMask>(Clipboard.Text);
}
catch
{
}
});
viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = Editor.Instance.Icons.Rotate32;
viewLayers.AddButton("Disable layers", () => Task.ViewLayersMask = new LayersMask(0)).Icon = Editor.Instance.Icons.Rotate32;
viewLayers.AddSeparator();
var layers = LayersAndTagsSettings.GetCurrentLayers();
if (layers != null && layers.Length > 0)
{
for (int i = 0; i < layers.Length; i++)
{
var layer = layers[i];
var button = viewLayers.AddButton(layer);
button.CloseMenuOnClick = false;
button.Tag = 1 << i;
}
}
viewLayers.ButtonClicked += button =>
{
if (button.Tag != null)
{
int layerIndex = (int)button.Tag;
LayersMask mask = new LayersMask(layerIndex);
Task.ViewLayersMask ^= mask;
button.Icon = (Task.ViewLayersMask & mask) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
};
viewLayers.VisibleChanged += WidgetViewLayersShowHide;
}
// View Flags
{
var viewFlags = ViewWidgetButtonMenu.AddChildMenu("View Flags").ContextMenu;
viewFlags.AddButton("Copy flags", () => Clipboard.Text = JsonSerializer.Serialize(Task.ViewFlags));
viewFlags.AddButton("Paste flags", () =>
{
try
{
Task.ViewFlags = JsonSerializer.Deserialize<ViewFlags>(Clipboard.Text);
}
catch
{
}
});
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddSeparator();
for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++)
{
@@ -501,7 +560,7 @@ namespace FlaxEditor.Viewport
{
var v = (ViewFlags)button.Tag;
Task.ViewFlags ^= v;
button.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
button.Icon = (Task.ViewFlags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
};
viewFlags.VisibleChanged += WidgetViewFlagsShowHide;
@@ -510,6 +569,18 @@ namespace FlaxEditor.Viewport
// Debug View
{
var debugView = ViewWidgetButtonMenu.AddChildMenu("Debug View").ContextMenu;
debugView.AddButton("Copy view", () => Clipboard.Text = JsonSerializer.Serialize(Task.ViewMode));
debugView.AddButton("Paste view", () =>
{
try
{
Task.ViewMode = JsonSerializer.Deserialize<ViewMode>(Clipboard.Text);
}
catch
{
}
});
debugView.AddSeparator();
for (int i = 0; i < EditorViewportViewModeValues.Length; i++)
{
ref var v = ref EditorViewportViewModeValues[i];
@@ -541,7 +612,7 @@ namespace FlaxEditor.Viewport
{
var ortho = ViewWidgetButtonMenu.AddButton("Orthographic");
ortho.CloseMenuOnClick = false;
var orthoValue = new CheckBox(90, 2, _isOrtho)
var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
{
Parent = ortho
};
@@ -581,7 +652,7 @@ namespace FlaxEditor.Viewport
{
var fov = ViewWidgetButtonMenu.AddButton("Field Of View");
fov.CloseMenuOnClick = false;
var fovValue = new FloatValueBox(1, 90, 2, 70.0f, 35.0f, 160.0f, 0.1f)
var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
{
Parent = fov
};
@@ -598,7 +669,7 @@ namespace FlaxEditor.Viewport
{
var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale");
orthoSize.CloseMenuOnClick = false;
var orthoSizeValue = new FloatValueBox(_orthoSize, 90, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
{
Parent = orthoSize
};
@@ -615,7 +686,7 @@ namespace FlaxEditor.Viewport
{
var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane");
nearPlane.CloseMenuOnClick = false;
var nearPlaneValue = new FloatValueBox(2.0f, 90, 2, 70.0f, 0.001f, 1000.0f)
var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
{
Parent = nearPlane
};
@@ -627,7 +698,7 @@ namespace FlaxEditor.Viewport
{
var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane");
farPlane.CloseMenuOnClick = false;
var farPlaneValue = new FloatValueBox(1000, 90, 2, 70.0f, 10.0f)
var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f)
{
Parent = farPlane
};
@@ -639,7 +710,7 @@ namespace FlaxEditor.Viewport
{
var brightness = ViewWidgetButtonMenu.AddButton("Brightness");
brightness.CloseMenuOnClick = false;
var brightnessValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.001f, 10.0f, 0.001f)
var brightnessValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.001f, 10.0f, 0.001f)
{
Parent = brightness
};
@@ -651,7 +722,7 @@ namespace FlaxEditor.Viewport
{
var resolution = ViewWidgetButtonMenu.AddButton("Resolution");
resolution.CloseMenuOnClick = false;
var resolutionValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.1f, 4.0f, 0.001f)
var resolutionValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.1f, 4.0f, 0.001f)
{
Parent = resolution
};
@@ -663,7 +734,7 @@ namespace FlaxEditor.Viewport
{
var invert = ViewWidgetButtonMenu.AddButton("Invert Panning");
invert.CloseMenuOnClick = false;
var invertValue = new CheckBox(90, 2, _invertPanning)
var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
{
Parent = invert
};
@@ -685,6 +756,9 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown);
InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1));
InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1));
// Link for task event
task.Begin += OnRenderBegin;
@@ -722,6 +796,30 @@ namespace FlaxEditor.Viewport
}
}
/// <summary>
/// Increases or decreases the camera movement speed.
/// </summary>
/// <param name="step">The stepping direction for speed adjustment.</param>
protected void AdjustCameraMoveSpeed(int step)
{
int camValueIndex = -1;
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
{
if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
{
camValueIndex = i;
break;
}
}
if (camValueIndex == -1)
return;
if (step > 0)
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
else if (step < 0)
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_mouseSensitivity = options.Viewport.MouseSensitivity;
@@ -1048,6 +1146,15 @@ namespace FlaxEditor.Viewport
// Track controlling mouse state change
bool wasControllingMouse = _prevInput.IsControllingMouse;
_isControllingMouse = _input.IsControllingMouse;
// Simulate holding mouse right down for trackpad users
if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || win.GetKeyDown(KeyboardKeys.Escape))
_isVirtualMouseRightDown = false; // Cancel when mouse right or escape is pressed
if (_wasVirtualMouseRightDown)
wasControllingMouse = true;
if (_isVirtualMouseRightDown)
_isControllingMouse = _isVirtualMouseRightDown;
if (wasControllingMouse != _isControllingMouse)
{
if (_isControllingMouse)
@@ -1061,16 +1168,18 @@ namespace FlaxEditor.Viewport
OnLeftMouseButtonDown();
else if (_prevInput.IsMouseLeftDown && !_input.IsMouseLeftDown)
OnLeftMouseButtonUp();
//
if (!_prevInput.IsMouseRightDown && _input.IsMouseRightDown)
if ((!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) || (!_wasVirtualMouseRightDown && _isVirtualMouseRightDown))
OnRightMouseButtonDown();
else if (_prevInput.IsMouseRightDown && !_input.IsMouseRightDown)
else if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || (_wasVirtualMouseRightDown && !_isVirtualMouseRightDown))
OnRightMouseButtonUp();
//
if (!_prevInput.IsMouseMiddleDown && _input.IsMouseMiddleDown)
OnMiddleMouseButtonDown();
else if (_prevInput.IsMouseMiddleDown && !_input.IsMouseMiddleDown)
OnMiddleMouseButtonUp();
_wasVirtualMouseRightDown = _isVirtualMouseRightDown;
}
// Get clamped delta time (more stable during lags)
@@ -1088,7 +1197,7 @@ namespace FlaxEditor.Viewport
bool isAltDown = _input.IsAltDown;
bool lbDown = _input.IsMouseLeftDown;
bool mbDown = _input.IsMouseMiddleDown;
bool rbDown = _input.IsMouseRightDown;
bool rbDown = _input.IsMouseRightDown || _isVirtualMouseRightDown;
bool wheelInUse = Math.Abs(_input.MouseWheelDelta) > Mathf.Epsilon;
_input.IsPanning = !isAltDown && mbDown && !rbDown;
@@ -1098,32 +1207,20 @@ namespace FlaxEditor.Viewport
_input.IsOrbiting = isAltDown && lbDown && !mbDown && !rbDown;
// Control move speed with RMB+Wheel
rmbWheel = useMovementSpeed && _input.IsMouseRightDown && wheelInUse;
rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse;
if (rmbWheel)
{
float step = 4.0f;
const float step = 4.0f;
_wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
int camValueIndex = -1;
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
if (_wheelMovementChangeDeltaSum >= step)
{
if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
{
camValueIndex = i;
break;
}
_wheelMovementChangeDeltaSum -= step;
AdjustCameraMoveSpeed(1);
}
if (camValueIndex != -1)
else if (_wheelMovementChangeDeltaSum <= -step)
{
if (_wheelMovementChangeDeltaSum >= step)
{
_wheelMovementChangeDeltaSum -= step;
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
}
else if (_wheelMovementChangeDeltaSum <= -step)
{
_wheelMovementChangeDeltaSum += step;
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
}
_wheelMovementChangeDeltaSum += step;
AdjustCameraMoveSpeed(-1);
}
}
}
@@ -1165,7 +1262,7 @@ namespace FlaxEditor.Viewport
// Calculate smooth mouse delta not dependant on viewport size
var offset = _viewMousePos - _startPos;
if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel)
if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown)
{
offset = Float2.Zero;
}
@@ -1213,7 +1310,7 @@ namespace FlaxEditor.Viewport
UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
// Move mouse back to the root position
if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown))
if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown))
{
var center = PointToWindow(_startPos);
win.MousePosition = center;
@@ -1229,7 +1326,7 @@ namespace FlaxEditor.Viewport
}
else
{
if (_input.IsMouseLeftDown || _input.IsMouseRightDown)
if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown)
{
// Calculate smooth mouse delta not dependant on viewport size
var offset = _viewMousePos - _startPos;
@@ -1359,6 +1456,7 @@ namespace FlaxEditor.Viewport
{
OnControlMouseEnd(RootWindow.Window);
_isControllingMouse = false;
_isVirtualMouseRightDown = false;
}
}
@@ -1561,6 +1659,24 @@ namespace FlaxEditor.Viewport
}
}
private void WidgetViewLayersShowHide(Control cm)
{
if (cm.Visible == false)
return;
var ccm = (ContextMenu)cm;
var layersMask = Task.ViewLayersMask;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b && b != null && b.Tag != null)
{
int layerIndex = (int)b.Tag;
LayersMask mask = new LayersMask(layerIndex);
b.Icon = (layersMask & mask) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
}
private float GetGamepadAxis(GamepadAxis axis)
{
var value = FlaxEngine.Input.GetGamepadAxis(InputGamepadIndex.All, axis);
@@ -194,6 +194,7 @@ namespace FlaxEditor.Viewport
{
_editor = editor;
_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem);
var inputOptions = editor.Options.Options.Input;
// Prepare rendering task
Task.ActorsSource = ActorsSources.Scenes;
@@ -250,7 +251,7 @@ namespace FlaxEditor.Viewport
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
{
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = "Gizmo transform space (world or local)",
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
@@ -347,7 +348,7 @@ namespace FlaxEditor.Viewport
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = "Translate gizmo mode",
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
@@ -355,14 +356,14 @@ namespace FlaxEditor.Viewport
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = "Rotate gizmo mode",
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = "Scale gizmo mode",
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
@@ -390,6 +391,7 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; });
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
InputActions.Add(options => options.FocusSelection, FocusSelection);
InputActions.Add(options => options.RotateSelection, RotateSelection);
@@ -84,6 +84,7 @@ namespace FlaxEditor.Viewport
_dragAssets = new DragAssets(ValidateDragItem);
ShowDebugDraw = true;
ShowEditorPrimitives = true;
var inputOptions = window.Editor.Options.Options.Input;
// Prepare rendering task
Task.ActorsSource = ActorsSources.CustomActors;
@@ -113,7 +114,7 @@ namespace FlaxEditor.Viewport
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true)
{
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = "Gizmo transform space (world or local)",
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
@@ -205,7 +206,7 @@ namespace FlaxEditor.Viewport
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = "Translate gizmo mode",
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
@@ -213,14 +214,14 @@ namespace FlaxEditor.Viewport
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = "Rotate gizmo mode",
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = "Scale gizmo mode",
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
@@ -233,6 +234,7 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; });
InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
SetUpdate(ref _update, OnUpdate);
@@ -68,7 +68,7 @@ namespace FlaxEditor.Viewport.Previews
/// </summary>
public bool ShowBounds
{
get => _boundsModel?.IsActive ?? false;
get => _boundsModel != null ? _boundsModel.IsActive : false;
set
{
if (value == ShowBounds)
@@ -110,7 +110,7 @@ namespace FlaxEditor.Viewport.Previews
/// </summary>
public bool ShowOrigin
{
get => _originModel?.IsActive ?? false;
get => _originModel != null ? _originModel.IsActive : false;
set
{
if (value == ShowOrigin)
@@ -500,7 +500,7 @@ namespace FlaxEditor.Viewport.Previews
/// <inheritdoc />
protected override void CalculateTextureRect(out Rectangle rect)
{
CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect);
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
}
/// <inheritdoc />
@@ -549,7 +549,7 @@ namespace FlaxEditor.Viewport.Previews
/// <inheritdoc />
protected override void CalculateTextureRect(out Rectangle rect)
{
CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect);
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
}
/// <inheritdoc />
@@ -604,7 +604,7 @@ namespace FlaxEditor.Viewport.Previews
/// <inheritdoc />
protected override void CalculateTextureRect(out Rectangle rect)
{
CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect);
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
}
/// <inheritdoc />
@@ -659,7 +659,7 @@ namespace FlaxEditor.Viewport.Previews
/// <inheritdoc />
protected override void CalculateTextureRect(out Rectangle rect)
{
CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect);
CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect);
}
/// <inheritdoc />
+4 -2
View File
@@ -52,9 +52,11 @@ namespace FlaxEditor.Windows
VerticalAlignment = TextAlignment.Near,
Parent = this
};
var copyVersionButton = new Button(Width - 104, 6, 100, 20)
var buttonText = "Copy version info";
var fontSize = Style.Current.FontMedium.MeasureText(buttonText);
var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20)
{
Text = "Copy version info",
Text = buttonText,
TooltipText = "Copies the current engine version information to system clipboard.",
Parent = this
};
@@ -116,7 +116,7 @@ namespace FlaxEditor.Windows.Assets
_window = window;
// Try to restore target asset AudioClip import options (useful for fast reimport)
AudioImportSettings.TryRestore(ref ImportSettings, window.Item.Path);
Editor.TryRestoreImportOptions(ref ImportSettings.Settings, window.Item.Path);
// Prepare restore data
PeekState();
@@ -134,6 +134,11 @@ namespace FlaxEditor.Windows.Assets
/// </summary>
public void Reimport()
{
if (_window?._previewSource != null)
{
_window._previewSource.Stop();
_window.UpdateToolstrip();
}
Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)_window.Item, ImportSettings, true);
}
@@ -200,6 +200,7 @@ namespace FlaxEditor.Windows.Assets
ViewportCamera = new FPSCamera(),
Parent = _split.Panel1
};
_preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom;
// Asset properties
_propertiesPresenter = new CustomEditorPresenter(null);
@@ -69,7 +69,7 @@ namespace FlaxEditor.Windows.Assets
[EditorDisplay("General"), Tooltip("The base material used to override it's properties")]
public MaterialBase BaseMaterial
{
get => Window?.Asset?.BaseMaterial;
get => Window?.Asset != null ? Window?.Asset.BaseMaterial : null;
set
{
var asset = Window?.Asset;
@@ -101,10 +101,12 @@ namespace FlaxEditor.Windows.Assets
[HideInEditor]
public object[] Values
{
get => Window?.Asset?.Parameters.Select(x => x.Value).ToArray();
get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value).ToArray() : null;
set
{
var parameters = Window?.Asset?.Parameters;
if (Window?.Asset == null)
return;
var parameters = Window?.Asset.Parameters;
if (value != null && parameters != null)
{
if (value.Length != parameters.Length)
@@ -131,9 +133,11 @@ namespace FlaxEditor.Windows.Assets
[HideInEditor]
public FlaxEngine.Object[] ValuesRef
{
get => Window?.Asset?.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray();
get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray() : null;
set
{
if (Window?.Asset == null)
return;
var parameters = Window?.Asset?.Parameters;
if (value != null && parameters != null)
{
@@ -293,7 +297,7 @@ namespace FlaxEditor.Windows.Assets
var p = (MaterialParameter)e.Tag;
// Try to get default value (from the base material)
var pBase = baseMaterial?.GetParameter(p.Name);
var pBase = baseMaterial != null ? baseMaterial.GetParameter(p.Name) : null;
if (pBase != null && pBase.ParameterType == p.ParameterType)
{
valueContainer.SetDefaultValue(pBase.Value);
+3 -3
View File
@@ -134,7 +134,7 @@ namespace FlaxEditor.Windows.Assets
if (Window._skipEffectsGuiEvents)
return;
Window._isolateIndex = mesh?.MaterialSlotIndex ?? -1;
Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
@@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets
if (Window._skipEffectsGuiEvents)
return;
Window._highlightIndex = mesh?.MaterialSlotIndex ?? -1;
Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1;
Window.UpdateEffectsOnAsset();
UpdateEffectsOnUI();
}
@@ -326,7 +326,7 @@ namespace FlaxEditor.Windows.Assets
[EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)]
public MaterialSlot[] MaterialSlots
{
get => Asset?.MaterialSlots;
get => Asset != null ? Asset.MaterialSlots : null;
set
{
if (Asset != null)
@@ -187,7 +187,7 @@ namespace FlaxEditor.Windows.Assets
base.Initialize(layout);
var emitterTrack = Values[0] as EmitterTrackProxy;
if (emitterTrack?._effect?.Parameters == null)
if (emitterTrack?._effect == null || emitterTrack?._effect.Parameters == null)
return;
var group = layout.Group("Parameters");
@@ -233,7 +233,7 @@ namespace FlaxEditor.Windows.Assets
contextMenu.AddSeparator();
b = contextMenu.AddButton("Create Prefab", () => Editor.Prefabs.CreatePrefab(Selection));
b = contextMenu.AddButton("Create Prefab", () => Editor.Prefabs.CreatePrefab(Selection, this));
b.Enabled = isSingleActorSelected &&
(Selection[0] as ActorNode).CanCreatePrefab &&
Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;
@@ -313,7 +313,7 @@ namespace FlaxEditor.Windows.Assets
}
if (showCustomNodeOptions)
{
Selection[0].OnContextMenu(contextMenu);
Selection[0].OnContextMenu(contextMenu, this);
}
ContextMenuShow?.Invoke(contextMenu);

Some files were not shown because too many files have changed in this diff Show More