diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml
index 568b4f35e..b0d4633a8 100644
--- a/.github/workflows/build_android.yml
+++ b/.github/workflows/build_android.yml
@@ -33,4 +33,4 @@ jobs:
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_ios.yml b/.github/workflows/build_ios.yml
index f4b5d8147..2aec46320 100644
--- a/.github/workflows/build_ios.yml
+++ b/.github/workflows/build_ios.yml
@@ -33,4 +33,4 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -dotnet=7 -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml
index bd1726737..56accba84 100644
--- a/.github/workflows/build_linux.yml
+++ b/.github/workflows/build_linux.yml
@@ -36,7 +36,7 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
# Game
game-linux:
@@ -64,4 +64,4 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml
index 88cb9b7a8..54bdb77b5 100644
--- a/.github/workflows/build_mac.yml
+++ b/.github/workflows/build_mac.yml
@@ -30,7 +30,7 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
# Game
game-mac:
@@ -55,4 +55,4 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml
index 513cbfd08..b6131fb53 100644
--- a/.github/workflows/build_windows.yml
+++ b/.github/workflows/build_windows.yml
@@ -30,7 +30,7 @@ jobs:
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
# Game
game-windows:
@@ -55,4 +55,4 @@ jobs:
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index f9d18bcfc..a524bdca2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -34,8 +34,8 @@ jobs:
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Build
run: |
- ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget
+ ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=7
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget
dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test
@@ -48,7 +48,7 @@ jobs:
dotnet test -f net7.0 Binaries/Tests/FlaxEngine.CSharp.dll
- name: Test UseLargeWorlds
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true
${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests
# Tests on Windows
@@ -72,8 +72,8 @@ jobs:
git lfs pull
- name: Build
run: |
- .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
+ .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=7
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test
run: |
diff --git a/.gitignore b/.gitignore
index 54907892f..b7e11e554 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ Source/*.csproj
/Package_*/
!Source/Engine/Debug
/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
+PackageEditor_Cert.command
PackageEditor_Cert.bat
PackagePlatforms_Cert.bat
diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax
index 8e810f7d4..cc4a16fe3 100644
--- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax
+++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:57066ba805fd3f21a1d48048c7d3a0ec4e4c66c5cdd1ca97553605987f43b460
-size 41028
+oid sha256:5d0dc041c6b8712c2f892ac3185c1f9e0b3608b774db5590bcaad3ad0775dc93
+size 41042
diff --git a/Content/Editor/DebugMaterials/SingleColor/Particle.flax b/Content/Editor/DebugMaterials/SingleColor/Particle.flax
index 72e956e7a..e22b6c1fa 100644
--- a/Content/Editor/DebugMaterials/SingleColor/Particle.flax
+++ b/Content/Editor/DebugMaterials/SingleColor/Particle.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e737c911cffc1e1ad5cad3d7d4e13e24cdba7d08da4b488bf7bb41f098cb1638
-size 31713
+oid sha256:a54c7442f97baa1ab835485f906911372847808e2865790109b0524614663210
+size 31722
diff --git a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
index 2fffdd0eb..790690cd8 100644
--- a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
+++ b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7933e14f937b148d6b0c4a7fff10aa9b931f01e1bfe42ce3e5ff4575fbeeb463
-size 31873
+oid sha256:4cd7963263033f8f6e09de86da1ce14aa9baee3f1811986d56441cc62164e231
+size 31882
diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax
index 8eb64e89d..f1fa6e3f5 100644
--- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax
+++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:67e5aba4c1eeb15231aef4001c7b0bb25db4243b871b1493774b1d1348310b22
-size 37868
+oid sha256:f87fe1172cc96b6aaef5653b9d7c5ef336b7f5934c620ae13661fd89b636cfcc
+size 37909
diff --git a/Content/Editor/Particles/Constant Burst.flax b/Content/Editor/Particles/Constant Burst.flax
index 16ca82be3..bb90199fe 100644
--- a/Content/Editor/Particles/Constant Burst.flax
+++ b/Content/Editor/Particles/Constant Burst.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5f6a7d30653808828c9f3ece113eb55684a330253366ff34de953085de4f9766
-size 2671
+oid sha256:fc1e46708152005dc92532e940b3ce19ec527b595fb18365b41ebdcd338ad2c4
+size 2705
diff --git a/Content/Editor/Particles/Periodic Burst.flax b/Content/Editor/Particles/Periodic Burst.flax
index 80a4c306d..784d456c5 100644
--- a/Content/Editor/Particles/Periodic Burst.flax
+++ b/Content/Editor/Particles/Periodic Burst.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:eeedc5055dc5a8218fd229a7a433ede79ce036eaf07326a9cee5f988b9b67a06
-size 3621
+oid sha256:94bf88983e3cf9fe555141a867af3c20e5bd58d13a527a9b4e02d4d1be157be9
+size 3664
diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax
index 1335a84f4..a682db6ee 100644
--- a/Content/Editor/Particles/Smoke.flax
+++ b/Content/Editor/Particles/Smoke.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c3dc51e7805056006ca6cbb481ba202583a9b2287c152fc04e28e1d07747d6ce
-size 14706
+oid sha256:dc72b3152b85137a22b7dc72209ece02e8c2fff6674ddd395eaa4a4b089a51da
+size 14662
diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax
index 7977e231b..227d9e381 100644
--- a/Content/Editor/Particles/Sparks.flax
+++ b/Content/Editor/Particles/Sparks.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:77d902ab5f79426cc66dc5f19a3b8280136a58aa3c6fd317554d1a032357c65a
-size 15275
+oid sha256:d38b4ed6a68e0c327e7d6dda2f47c6665611c4ae9c7f8b1ba6148eb26abb205f
+size 13650
diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs
index 2a150306d..30fbe9d86 100644
--- a/Content/Editor/Scripting/ScriptTemplate.cs
+++ b/Content/Editor/Scripting/ScriptTemplate.cs
@@ -2,35 +2,34 @@
using System.Collections.Generic;
using FlaxEngine;
-namespace %namespace%
+namespace %namespace%;
+
+///
+/// %class% Script.
+///
+public class %class% : Script
{
- ///
- /// %class% Script.
- ///
- public class %class% : Script
+ ///
+ public override void OnStart()
{
- ///
- public override void OnStart()
- {
- // Here you can add code that needs to be called when script is created, just before the first game update
- }
-
- ///
- 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
+ }
+
+ ///
+ public override void OnEnable()
+ {
+ // Here you can add code that needs to be called when script is enabled (eg. register for events)
+ }
- ///
- public override void OnDisable()
- {
- // Here you can add code that needs to be called when script is disabled (eg. unregister from events)
- }
+ ///
+ public override void OnDisable()
+ {
+ // Here you can add code that needs to be called when script is disabled (eg. unregister from events)
+ }
- ///
- public override void OnUpdate()
- {
- // Here you can add code that needs to be called every frame
- }
+ ///
+ public override void OnUpdate()
+ {
+ // Here you can add code that needs to be called every frame
}
}
diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax
index b014ffb1f..9b21e6c82 100644
--- a/Content/Editor/Terrain/Circle Brush Material.flax
+++ b/Content/Editor/Terrain/Circle Brush Material.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1e73a50fe4296e58eff5942855dd655ba4c2c776ee3acc0451844645dda14247
-size 27150
+oid sha256:a20f7283220500bb17c8c5afbab5a5c1bfdc03703868b8ced7da462657806fd7
+size 27409
diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax
index 18f653f86..0e94c5c06 100644
--- a/Content/Shaders/GlobalSignDistanceField.flax
+++ b/Content/Shaders/GlobalSignDistanceField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2c8aa181a814d69b15ffec6493a71a6f42ae816ce04f7803cff2d5073b4b3c4f
-size 11790
+oid sha256:e075583620e62407503c73f52487c204ddcad421e80fa621aebd55af1cfb08d5
+size 11798
diff --git a/Development/Scripts/Windows/CallBuildTool.bat b/Development/Scripts/Windows/CallBuildTool.bat
index 9f60368c5..22c2d1dd7 100644
--- a/Development/Scripts/Windows/CallBuildTool.bat
+++ b/Development/Scripts/Windows/CallBuildTool.bat
@@ -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.
diff --git a/Development/Scripts/Windows/GetMSBuildPath.bat b/Development/Scripts/Windows/GetMSBuildPath.bat
index b20230118..f44155272 100644
--- a/Development/Scripts/Windows/GetMSBuildPath.bat
+++ b/Development/Scripts/Windows/GetMSBuildPath.bat
@@ -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
\ No newline at end of file
diff --git a/Flax.flaxproj b/Flax.flaxproj
index 50fcaeda3..81a63a1fc 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -2,8 +2,9 @@
"Name": "Flax",
"Version": {
"Major": 1,
- "Minor": 6,
- "Build": 6344
+ "Minor": 7,
+ "Revision": 1,
+ "Build": 6406
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index e8af1461c..ff396d824 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -256,6 +256,8 @@
True
True
True
+ True
+ True
True
True
True
@@ -291,6 +293,8 @@
True
True
True
+ True
+ True
True
True
True
@@ -319,6 +323,7 @@
True
True
True
+ True
True
True
True
diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat
index 1b766457f..28970a203 100644
--- a/GenerateProjectFiles.bat
+++ b/GenerateProjectFiles.bat
@@ -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
+
popd
echo Done!
exit /B 0
diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command
index 6554886bc..a42121252 100755
--- a/GenerateProjectFiles.command
+++ b/GenerateProjectFiles.command
@@ -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
diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh
index fcda1acc1..76d96c7ef 100755
--- a/GenerateProjectFiles.sh
+++ b/GenerateProjectFiles.sh
@@ -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
diff --git a/PackageAll.bat b/PackageAll.bat
index 02ab69f4d..0325f2244 100644
--- a/PackageAll.bat
+++ b/PackageAll.bat
@@ -7,7 +7,7 @@ pushd
echo Performing the full package...
rem Run the build tool.
-call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
+call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed
popd
diff --git a/PackageEditor.bat b/PackageEditor.bat
index 2dc90f7f4..515b81871 100644
--- a/PackageEditor.bat
+++ b/PackageEditor.bat
@@ -7,7 +7,7 @@ pushd
echo Building and packaging Flax Editor...
rem Run the build tool.
-call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
+call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed
popd
diff --git a/PackageEditor.command b/PackageEditor.command
index d7cc909a0..eb62ae1a4 100755
--- a/PackageEditor.command
+++ b/PackageEditor.command
@@ -9,4 +9,4 @@ echo Building and packaging Flax Editor...
cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments)
-bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
+bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/PackageEditor.sh b/PackageEditor.sh
index 0584ab4e7..151147b6a 100755
--- a/PackageEditor.sh
+++ b/PackageEditor.sh
@@ -9,4 +9,4 @@ echo Building and packaging Flax Editor...
cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments)
-bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
+bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/PackagePlatforms.bat b/PackagePlatforms.bat
index d8ebd0980..eb9c42d34 100644
--- a/PackagePlatforms.bat
+++ b/PackagePlatforms.bat
@@ -7,7 +7,7 @@ pushd
echo Building and packaging platforms data...
rem Run the build tool.
-call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
+call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed
popd
diff --git a/PackagePlatforms.command b/PackagePlatforms.command
index 20847f232..e9182a627 100755
--- a/PackagePlatforms.command
+++ b/PackagePlatforms.command
@@ -9,4 +9,4 @@ echo Building and packaging platforms data...
cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments)
-bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
+bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/PackagePlatforms.sh b/PackagePlatforms.sh
index 3f9a9c550..f7e43cc42 100755
--- a/PackagePlatforms.sh
+++ b/PackagePlatforms.sh
@@ -9,4 +9,4 @@ echo Building and packaging platforms data...
cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments)
-bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
+bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/README.md b/README.md
index dc5abb84f..d6688bd03 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Flax Engine is a high quality modern 3D game engine written in C++ and C#.
-From stunning graphics to powerful scripts - Flax can give everything for your games. Designed for fast workflow with many ready to use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
+From stunning graphics to powerful scripts, it's designed for fast workflow with many ready-to-use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
This repository contains full source code of the Flax Engine (excluding NDA-protected platforms support). Anyone is welcome to contribute or use the modified source in Flax-based games.
@@ -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`
diff --git a/Source/Editor/Content/AssetPickerValidator.cs b/Source/Editor/Content/AssetPickerValidator.cs
new file mode 100644
index 000000000..f43ab6a29
--- /dev/null
+++ b/Source/Editor/Content/AssetPickerValidator.cs
@@ -0,0 +1,292 @@
+using System;
+using System.IO;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.Utilities;
+
+namespace FlaxEditor.Content;
+
+///
+/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
+///
+public class AssetPickerValidator : IContentItemOwner
+{
+ private Asset _selected;
+ private ContentItem _selectedItem;
+ private ScriptType _type;
+ private string _fileExtension;
+
+ ///
+ /// Gets or sets the selected item.
+ ///
+ public ContentItem SelectedItem
+ {
+ get => _selectedItem;
+ set
+ {
+ if (_selectedItem == value)
+ return;
+ if (value == null)
+ {
+ if (_selected == null && _selectedItem is SceneItem)
+ {
+ // Deselect scene reference
+ _selectedItem.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ OnSelectedItemChanged();
+ return;
+ }
+
+ // Deselect
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ OnSelectedItemChanged();
+ }
+ else if (value is SceneItem item)
+ {
+ if (_selectedItem == item)
+ return;
+ if (!IsValid(item))
+ item = null;
+
+ // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = item;
+ _selected = null;
+ _selectedItem?.AddReference(this);
+ OnSelectedItemChanged();
+ }
+ else if (value is AssetItem assetItem)
+ {
+ SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
+ }
+ else
+ {
+ // Change value
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = value;
+ _selected = null;
+ OnSelectedItemChanged();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset identifier.
+ ///
+ public Guid SelectedID
+ {
+ get
+ {
+ if (_selected != null)
+ return _selected.ID;
+ if (_selectedItem is AssetItem assetItem)
+ return assetItem.ID;
+ return Guid.Empty;
+ }
+ set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
+ }
+
+ ///
+ /// Gets or sets the selected content item path.
+ ///
+ public string SelectedPath
+ {
+ get
+ {
+ string path = _selectedItem?.Path ?? _selected?.Path;
+ if (path != null)
+ {
+ // Convert into path relative to the project (cross-platform)
+ var projectFolder = Globals.ProjectFolder;
+ if (path.StartsWith(projectFolder))
+ path = path.Substring(projectFolder.Length + 1);
+ }
+ return path;
+ }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ SelectedItem = null;
+ }
+ else
+ {
+ var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
+ SelectedItem = Editor.Instance.ContentDatabase.Find(path);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset object.
+ ///
+ public Asset SelectedAsset
+ {
+ get => _selected;
+ set
+ {
+ // Check if value won't change
+ if (value == _selected)
+ return;
+
+ // Find item from content database and check it
+ var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
+ if (item != null && !IsValid(item))
+ item = null;
+
+ // Change value
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = item;
+ _selected = value;
+ _selectedItem?.AddReference(this);
+ OnSelectedItemChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
+ ///
+ public ScriptType AssetType
+ {
+ get => _type;
+ set
+ {
+ if (_type != value)
+ {
+ _type = value;
+
+ // Auto deselect if the current value is invalid
+ if (_selectedItem != null && !IsValid(_selectedItem))
+ SelectedItem = null;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the content items extensions filter. Null if unused.
+ ///
+ public string FileExtension
+ {
+ get => _fileExtension;
+ set
+ {
+ if (_fileExtension != value)
+ {
+ _fileExtension = value;
+
+ // Auto deselect if the current value is invalid
+ if (_selectedItem != null && !IsValid(_selectedItem))
+ SelectedItem = null;
+ }
+ }
+ }
+
+ ///
+ /// Occurs when selected item gets changed.
+ ///
+ public event Action SelectedItemChanged;
+
+ ///
+ /// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
+ ///
+ public Func CheckValid;
+
+ ///
+ /// Returns whether item is valid.
+ ///
+ ///
+ ///
+ public bool IsValid(ContentItem item)
+ {
+ if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
+ return false;
+ if (CheckValid != null && !CheckValid(item))
+ return false;
+ if (_type == ScriptType.Null)
+ return true;
+
+ if (item is AssetItem assetItem)
+ {
+ // Faster path for binary items (in-built)
+ if (assetItem is BinaryAssetItem binaryItem)
+ return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
+
+ // Type filter
+ var type = TypeUtils.GetType(assetItem.TypeName);
+ if (_type.IsAssignableFrom(type))
+ return true;
+
+ // Json assets can contain any type of the object defined by the C# type (data oriented design)
+ if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
+ return true;
+
+ // Special case for scene asset references
+ if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AssetPickerValidator()
+ : this(new ScriptType(typeof(Asset)))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The assets types that this picker accepts.
+ public AssetPickerValidator(ScriptType assetType)
+ {
+ _type = assetType;
+ }
+
+ ///
+ /// Called when selected item gets changed.
+ ///
+ protected virtual void OnSelectedItemChanged()
+ {
+ SelectedItemChanged?.Invoke();
+ }
+
+ ///
+ public void OnItemDeleted(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ public void OnItemRenamed(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemReimported(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemDispose(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ /// Call to remove reference from the selected item.
+ ///
+ public void OnDestroy()
+ {
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ }
+}
diff --git a/Source/Editor/Content/GUI/ContentView.DragDrop.cs b/Source/Editor/Content/GUI/ContentView.DragDrop.cs
index 348e2b443..ffce81e2d 100644
--- a/Source/Editor/Content/GUI/ContentView.DragDrop.cs
+++ b/Source/Editor/Content/GUI/ContentView.DragDrop.cs
@@ -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;
diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs
index 5e054e5c6..259be104b 100644
--- a/Source/Editor/Content/GUI/ContentView.cs
+++ b/Source/Editor/Content/GUI/ContentView.cs
@@ -220,8 +220,9 @@ namespace FlaxEditor.Content.GUI
// Remove references and unlink items
for (int i = 0; i < _items.Count; i++)
{
- _items[i].Parent = null;
- _items[i].RemoveReference(this);
+ var item = _items[i];
+ item.Parent = null;
+ item.RemoveReference(this);
}
_items.Clear();
@@ -263,11 +264,12 @@ namespace FlaxEditor.Content.GUI
// Add references and link items
for (int i = 0; i < items.Count; i++)
{
- if (items[i].Visible)
+ var item = items[i];
+ if (item.Visible && !_items.Contains(item))
{
- items[i].Parent = this;
- items[i].AddReference(this);
- _items.Add(items[i]);
+ item.Parent = this;
+ item.AddReference(this);
+ _items.Add(item);
}
}
if (selection != null)
@@ -279,6 +281,8 @@ namespace FlaxEditor.Content.GUI
// Sort items depending on sortMethod parameter
_children.Sort(((control, control1) =>
{
+ if (control == null || control1 == null)
+ return 0;
if (sortType == SortType.AlphabeticReverse)
{
if (control.CompareTo(control1) > 0)
@@ -520,8 +524,8 @@ namespace FlaxEditor.Content.GUI
{
int min = _selection.Min(x => x.IndexInParent);
int max = _selection.Max(x => x.IndexInParent);
- min = Mathf.Min(min, item.IndexInParent);
- max = Mathf.Max(max, item.IndexInParent);
+ min = Mathf.Max(Mathf.Min(min, item.IndexInParent), 0);
+ max = Mathf.Min(Mathf.Max(max, item.IndexInParent), _children.Count - 1);
var selection = new List(_selection);
for (int i = min; i <= max; i++)
{
diff --git a/Source/Editor/Content/Import/AudioImportSettings.cs b/Source/Editor/Content/Import/AudioImportSettings.cs
index 2fc0a1795..e0bfb6e20 100644
--- a/Source/Editor/Content/Import/AudioImportSettings.cs
+++ b/Source/Editor/Content/Import/AudioImportSettings.cs
@@ -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
+{
+ ///
+ /// Custom editor for .
+ ///
+ [CustomEditor(typeof(FlaxEngine.Tools.AudioTool.Options)), DefaultEditor]
+ public class AudioToolOptionsEditor : GenericEditor
+ {
+ ///
+ protected override List GetItemsForType(ScriptType type)
+ {
+ // Show both fields and properties
+ return GetItemsForType(type, true, true);
+ }
+ }
+}
namespace FlaxEditor.Content.Import
{
///
/// Proxy object to present audio import settings in .
///
+ [HideInEditor]
public class AudioImportSettings
{
///
- /// A custom set of bit depth audio import sizes.
+ /// The settings data.
///
- public enum CustomBitDepth
- {
- ///
- /// The 8.
- ///
- _8 = 8,
-
- ///
- /// The 16.
- ///
- _16 = 16,
-
- ///
- /// The 24.
- ///
- _24 = 24,
-
- ///
- /// The 32.
- ///
- _32 = 32,
- }
-
- ///
- /// Converts the bit depth to enum.
- ///
- /// The bit depth.
- /// The converted enum.
- 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;
- }
-
- ///
- /// The audio data format to import the audio clip as.
- ///
- [EditorOrder(10), DefaultValue(AudioFormat.Vorbis), Tooltip("The audio data format to import the audio clip as.")]
- public AudioFormat Format { get; set; } = AudioFormat.Vorbis;
-
- ///
- /// 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.
- ///
- [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;
-
- ///
- /// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).
- ///
- [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;
-
- ///
- /// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.
- ///
- [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;
-
- ///
- /// The size of a single sample in bits. The clip will be converted to this bit depth on import.
- ///
- [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);
- }
-
- ///
- /// Tries the restore the asset import options from the target resource file.
- ///
- /// The options.
- /// The asset path.
- /// True settings has been restored, otherwise false.
- 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;
}
///
@@ -147,7 +55,7 @@ namespace FlaxEditor.Content.Import
///
public partial class AudioImportEntry : AssetImportEntry
{
- private AudioImportSettings _settings = new AudioImportSettings();
+ private AudioImportSettings _settings = new();
///
/// Initializes a new instance of the 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);
}
///
@@ -166,27 +74,23 @@ namespace FlaxEditor.Content.Import
///
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;
}
///
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
}
}
diff --git a/Source/Editor/Content/Import/ImportFilesDialog.cs b/Source/Editor/Content/Import/ImportFilesDialog.cs
index ab2c7be15..967583cf6 100644
--- a/Source/Editor/Content/Import/ImportFilesDialog.cs
+++ b/Source/Editor/Content/Import/ImportFilesDialog.cs
@@ -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);
}
diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs
index 94db3a5b9..604caa704 100644
--- a/Source/Editor/Content/Items/ContentItem.cs
+++ b/Source/Editor/Content/Items/ContentItem.cs
@@ -323,8 +323,6 @@ namespace FlaxEditor.Content
/// The new path.
internal virtual void UpdatePath(string value)
{
- Assert.AreNotEqual(Path, value);
-
// Set path
Path = StringUtils.NormalizePath(value);
FileName = System.IO.Path.GetFileName(value);
@@ -486,7 +484,7 @@ namespace FlaxEditor.Content
else
Render2D.FillRectangle(rectangle, Color.Black);
}
-
+
///
/// Draws the item thumbnail.
///
@@ -684,7 +682,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 +690,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 +704,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 +731,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);
diff --git a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
new file mode 100644
index 000000000..33ad0862f
--- /dev/null
+++ b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
@@ -0,0 +1,66 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.IO;
+using FlaxEditor.Content.Thumbnails;
+using FlaxEditor.Windows;
+using FlaxEditor.Windows.Assets;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.Content
+{
+ ///
+ /// A asset proxy object.
+ ///
+ ///
+ [ContentContextMenu("New/AI/Behavior Tree")]
+ public class BehaviorTreeProxy : BinaryAssetProxy
+ {
+ ///
+ public override string Name => "Behavior Tree";
+
+ ///
+ public override bool CanReimport(ContentItem item)
+ {
+ return true;
+ }
+
+ ///
+ public override EditorWindow Open(Editor editor, ContentItem item)
+ {
+ return new BehaviorTreeWindow(editor, item as BinaryAssetItem);
+ }
+
+ ///
+ public override Color AccentColor => Color.FromRGB(0x3256A8);
+
+ ///
+ public override Type AssetType => typeof(BehaviorTree);
+
+ ///
+ public override bool CanCreate(ContentFolder targetLocation)
+ {
+ return targetLocation.CanHaveAssets;
+ }
+
+ ///
+ public override void Create(string outputPath, object arg)
+ {
+ if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath))
+ throw new Exception("Failed to create new asset.");
+ }
+
+ ///
+ public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
+ {
+ guiRoot.AddChild(new Label
+ {
+ Text = Path.GetFileNameWithoutExtension(request.Asset.Path),
+ Offsets = Margin.Zero,
+ AnchorPreset = AnchorPresets.StretchAll,
+ Wrapping = TextWrapping.WrapWords
+ });
+ }
+ }
+}
diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs
index 691e4ac53..2dccc9e47 100644
--- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs
+++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs
@@ -54,12 +54,7 @@ namespace FlaxEditor.Content
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- if (!_preview.HasLoadedAssets)
- return false;
-
- // Check if all mip maps are streamed
- var asset = (CubeTexture)request.Asset;
- return asset.ResidentMipLevels >= Mathf.Max(1, (int)(asset.MipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
+ return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
index dcc3e3ec4..331ff81c3 100644
--- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
@@ -62,7 +62,7 @@ namespace FlaxEditor.Content
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- return _preview.HasLoadedAssets;
+ return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs
index cf7c8b77e..a7fcfecc8 100644
--- a/Source/Editor/Content/Proxy/MaterialProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialProxy.cs
@@ -106,7 +106,7 @@ namespace FlaxEditor.Content
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- return _preview.HasLoadedAssets;
+ return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs
index 845cbc80b..0cf16850d 100644
--- a/Source/Editor/Content/Proxy/ModelProxy.cs
+++ b/Source/Editor/Content/Proxy/ModelProxy.cs
@@ -82,12 +82,7 @@ namespace FlaxEditor.Content
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- if (!_preview.HasLoadedAssets)
- return false;
-
- // Check if asset is streamed enough
- var asset = (Model)request.Asset;
- return asset.LoadedLODs >= Mathf.Max(1, (int)(asset.LODs.Length * ThumbnailsModule.MinimumRequiredResourcesQuality));
+ return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Model)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs
index 78d88b440..004c2aed7 100644
--- a/Source/Editor/Content/Proxy/SceneProxy.cs
+++ b/Source/Editor/Content/Proxy/SceneProxy.cs
@@ -30,6 +30,12 @@ namespace FlaxEditor.Content
return item is SceneItem;
}
+ ///
+ public override bool AcceptsAsset(string typeName, string path)
+ {
+ return (typeName == Scene.AssetTypename || typeName == Scene.EditorPickerTypename) && path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
+ }
+
///
public override bool CanCreate(ContentFolder targetLocation)
{
diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
index f0193d0d0..6e228aa86 100644
--- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
+++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
@@ -54,15 +54,7 @@ namespace FlaxEditor.Content
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- if (!_preview.HasLoadedAssets)
- return false;
-
- // Check if asset is streamed enough
- var asset = (SkinnedModel)request.Asset;
- var lods = asset.LODs.Length;
- if (asset.IsLoaded && lods == 0)
- return true; // Skeleton-only model
- return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * ThumbnailsModule.MinimumRequiredResourcesQuality));
+ return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/TextureProxy.cs b/Source/Editor/Content/Proxy/TextureProxy.cs
index 48bfcc9e9..709ca4dbf 100644
--- a/Source/Editor/Content/Proxy/TextureProxy.cs
+++ b/Source/Editor/Content/Proxy/TextureProxy.cs
@@ -57,11 +57,7 @@ namespace FlaxEditor.Content
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- // Check if asset is streamed enough
- var asset = (Texture)request.Asset;
- var mipLevels = asset.MipLevels;
- var minMipLevels = Mathf.Min(mipLevels, 7);
- return asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
+ return ThumbnailsModule.HasMinimumQuality((Texture)request.Asset);
}
///
diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
index a6996310e..1282e4daa 100644
--- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
+++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
@@ -125,6 +125,74 @@ namespace FlaxEditor.Content.Thumbnails
}
}
+ internal static bool HasMinimumQuality(TextureBase asset)
+ {
+ var mipLevels = asset.MipLevels;
+ var minMipLevels = Mathf.Min(mipLevels, 7);
+ return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality));
+ }
+
+ internal static bool HasMinimumQuality(Model asset)
+ {
+ if (!asset.IsLoaded)
+ return false;
+ var lods = asset.LODs.Length;
+ var slots = asset.MaterialSlots;
+ foreach (var slot in slots)
+ {
+ if (slot.Material && !HasMinimumQuality(slot.Material))
+ return false;
+ }
+ return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
+ }
+
+ internal static bool HasMinimumQuality(SkinnedModel asset)
+ {
+ var lods = asset.LODs.Length;
+ if (asset.IsLoaded && lods == 0)
+ return true; // Skeleton-only model
+ var slots = asset.MaterialSlots;
+ foreach (var slot in slots)
+ {
+ if (slot.Material && !HasMinimumQuality(slot.Material))
+ return false;
+ }
+ return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
+ }
+
+ internal static bool HasMinimumQuality(MaterialBase asset)
+ {
+ if (asset is MaterialInstance asInstance)
+ return HasMinimumQuality(asInstance);
+ return HasMinimumQualityInternal(asset);
+ }
+
+ internal static bool HasMinimumQuality(Material asset)
+ {
+ return HasMinimumQualityInternal(asset);
+ }
+
+ internal static bool HasMinimumQuality(MaterialInstance asset)
+ {
+ if (!HasMinimumQualityInternal(asset))
+ return false;
+ var baseMaterial = asset.BaseMaterial;
+ return baseMaterial == null || HasMinimumQualityInternal(baseMaterial);
+ }
+
+ private static bool HasMinimumQualityInternal(MaterialBase asset)
+ {
+ if (!asset.IsLoaded)
+ return false;
+ var parameters = asset.Parameters;
+ foreach (var parameter in parameters)
+ {
+ if (parameter.Value is TextureBase asTexture && !HasMinimumQuality(asTexture))
+ return false;
+ }
+ return true;
+ }
+
#region IContentItemOwner
///
@@ -338,18 +406,16 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < maxChecks; i++)
{
var request = _requests[i];
-
try
{
if (request.IsReady)
- {
return request;
- }
}
catch (Exception ex)
{
- Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
+ Editor.LogWarning(ex);
+ _requests.RemoveAt(i--);
}
}
@@ -368,7 +434,6 @@ namespace FlaxEditor.Content.Thumbnails
// Create atlas
if (PreviewsCache.Create(path))
{
- // Error
Editor.LogError("Failed to create thumbnails atlas.");
return null;
}
@@ -377,7 +442,6 @@ namespace FlaxEditor.Content.Thumbnails
var atlas = FlaxEngine.Content.LoadAsync(path);
if (atlas == null)
{
- // Error
Editor.LogError("Failed to load thumbnails atlas.");
return null;
}
@@ -449,7 +513,6 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < checks; i++)
{
var request = _requests[i];
-
try
{
if (request.IsReady)
@@ -463,8 +526,9 @@ namespace FlaxEditor.Content.Thumbnails
}
catch (Exception ex)
{
- Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
+ Editor.LogWarning(ex);
+ _requests.RemoveAt(i--);
}
}
diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs
index 2f6651b14..0c0fc6a51 100644
--- a/Source/Editor/Content/Tree/ContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/ContentTreeNode.cs
@@ -86,6 +86,7 @@ namespace FlaxEditor.Content
Folder.ParentFolder = parent.Folder;
Parent = parent;
}
+ IconColor = Style.Current.Foreground;
}
///
diff --git a/Source/Editor/Content/Tree/MainContentTreeNode.cs b/Source/Editor/Content/Tree/MainContentTreeNode.cs
index 43b36986d..def873622 100644
--- a/Source/Editor/Content/Tree/MainContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/MainContentTreeNode.cs
@@ -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)
diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h
index 51a3fcd79..1924b0c56 100644
--- a/Source/Editor/Cooker/CookingData.h
+++ b/Source/Editor/Cooker/CookingData.h
@@ -12,6 +12,13 @@
class GameCooker;
class PlatformTools;
+#if OFFICIAL_BUILD
+// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
+#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=7")
+#else
+#define GAME_BUILD_DOTNET_VER TEXT("")
+#endif
+
///
/// Game building options. Used as flags.
///
diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
index 7d05409e5..995f19fce 100644
--- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
@@ -280,17 +280,25 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const Char* gradlew = TEXT("gradlew");
#endif
#if PLATFORM_LINUX
- Platform::RunProcess(String::Format(TEXT("chmod +x \"{0}/gradlew\""), data.OriginalOutputPath), data.OriginalOutputPath, Dictionary(), true);
+ {
+ CreateProcessSettings procSettings;
+ procSettings.FileName = String::Format(TEXT("chmod +x \"{0}/gradlew\""), data.OriginalOutputPath);
+ procSettings.WorkingDirectory = data.OriginalOutputPath;
+ procSettings.HiddenWindow = true;
+ Platform::CreateProcess(procSettings);
+ }
#endif
const bool distributionPackage = buildSettings->ForDistribution;
- CreateProcessSettings procSettings;
- procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
- procSettings.WorkingDirectory = data.OriginalOutputPath;
- const int32 result = Platform::CreateProcess(procSettings);
- if (result != 0)
{
- data.Error(String::Format(TEXT("Failed to build Gradle project into package (result code: {0}). See log for more info."), result));
- return true;
+ CreateProcessSettings procSettings;
+ procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
+ procSettings.WorkingDirectory = data.OriginalOutputPath;
+ const int32 result = Platform::CreateProcess(procSettings);
+ if (result != 0)
+ {
+ data.Error(String::Format(TEXT("Failed to build Gradle project into package (result code: {0}). See log for more info."), result));
+ return true;
+ }
}
// Copy result package
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
index ec7b8e7e1..42ef6fcdf 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
@@ -104,4 +104,19 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
return false;
}
+void LinuxPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
index 562b38962..432240d00 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
@@ -20,6 +20,7 @@ public:
ArchitectureType GetArchitecture() const override;
bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
index c059acba9..aa56a6b95 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
@@ -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,7 @@
#include "Editor/ProjectInfo.h"
#include "Editor/Cooker/GameCooker.h"
#include "Editor/Utilities/EditorUtilities.h"
-#include
+#include
using namespace pugi;
IMPLEMENT_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
@@ -124,17 +125,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 +170,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 +194,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,18 +229,39 @@ 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;
}
+void MacPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
index 21d9141e3..efdd0b733 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
@@ -27,6 +27,7 @@ public:
bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp
index 8b0cf06a8..85a7f99fa 100644
--- a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp
@@ -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));
diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
index 361e5f937..befc41640 100644
--- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
@@ -188,8 +188,8 @@ bool CompileScriptsStep::Perform(CookingData& data)
LOG(Info, "Starting scripts compilation for game...");
const String logFile = data.CacheDirectory / TEXT("CompileLog.txt");
auto args = String::Format(
- TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5}"),
- target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()));
+ TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5} {6}"),
+ target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), GAME_BUILD_DOTNET_VER);
#if PLATFORM_WINDOWS
if (data.Platform == BuildPlatform::LinuxX64)
#elif PLATFORM_LINUX
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index c9bbc016e..c1179c77a 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -87,7 +87,7 @@ bool DeployDataStep::Perform(CookingData& data)
{
// Ask Flax.Build to provide .Net SDK location for the current platform
String sdks;
- bool failed = ScriptsBuilder::RunBuildTool(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs"), data.CacheDirectory);
+ bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
int32 idx = sdks.Find(TEXT("DotNetSdk, "), StringSearchCase::CaseSensitive);
if (idx != -1)
@@ -131,7 +131,8 @@ bool DeployDataStep::Perform(CookingData& data)
if (FileSystem::DirectoryExists(dstDotnet))
{
String cachedData;
- File::ReadAllText(dotnetCacheFilePath, cachedData);
+ if (FileSystem::FileExists(dotnetCacheFilePath))
+ File::ReadAllText(dotnetCacheFilePath, cachedData);
if (cachedData != dotnetCachedValue)
{
FileSystem::DeleteDirectory(dstDotnet);
@@ -167,7 +168,7 @@ bool DeployDataStep::Perform(CookingData& data)
String sdks;
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
- String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={}"), platformName, archName);
+ String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, GAME_BUILD_DOTNET_VER);
bool failed = ScriptsBuilder::RunBuildTool(args, data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
Array parts;
@@ -268,8 +269,8 @@ bool DeployDataStep::Perform(CookingData& data)
LOG(Info, "Optimizing .NET class library size to include only used assemblies");
const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt");
String args = String::Format(
- TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\""),
- logFile, data.DataOutputPath);
+ TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\" {}"),
+ logFile, data.DataOutputPath, GAME_BUILD_DOTNET_VER);
for (const String& define : data.CustomDefines)
{
args += TEXT(" -D");
@@ -360,7 +361,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME);
data.AddRootEngineAsset(SMAA_AREA_TEX);
data.AddRootEngineAsset(SMAA_SEARCH_TEX);
- if (data.Configuration != BuildConfiguration::Release)
+ if (!buildSettings.SkipDefaultFonts)
data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular"));
// Register custom assets (eg. plugins)
diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
index deb8f5022..5d566efb5 100644
--- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
+++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
@@ -67,8 +67,8 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
data.GetBuildPlatformName(platform, architecture);
const String logFile = data.CacheDirectory / TEXT("AOTLog.txt");
String args = String::Format(
- TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\""),
- logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath);
+ TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\" {}"),
+ logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, GAME_BUILD_DOTNET_VER);
if (!buildSettings.SkipUnusedDotnetLibsPackaging)
args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs)
for (const String& define : data.CustomDefines)
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 03b21e12b..9de330213 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -157,6 +157,12 @@ namespace FlaxEditor.CustomEditors
var values = _values;
var presenter = _presenter;
var layout = _layout;
+ if (layout.Editors.Count > 1)
+ {
+ // There are more editors using the same layout so rebuild parent editor to prevent removing others editors
+ _parent?.RebuildLayout();
+ return;
+ }
var control = layout.ContainerControl;
var parent = _parent;
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs
index 73e2ede0f..0c4ac4106 100644
--- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs
+++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs
@@ -37,6 +37,23 @@ namespace FlaxEditor.CustomEditors
UseDefault = 1 << 2,
}
+ ///
+ /// The interface for Editor context that owns the presenter. Can be or or other window/panel - custom editor scan use it for more specific features.
+ ///
+ public interface IPresenterOwner
+ {
+ ///
+ /// Gets the viewport linked with properties presenter (optional, null if unused).
+ ///
+ public Viewport.EditorViewport PresenterViewport { get; }
+
+ ///
+ /// Selects the scene objects.
+ ///
+ /// The nodes to select
+ public void Select(List nodes);
+ }
+
///
/// Main class for Custom Editors used to present selected objects properties and allow to modify them.
///
@@ -68,8 +85,15 @@ namespace FlaxEditor.CustomEditors
///
public override void Update(float deltaTime)
{
- // Update editors
- _presenter.Update();
+ try
+ {
+ // Update editors
+ _presenter.Update();
+ }
+ catch (Exception ex)
+ {
+ FlaxEditor.Editor.LogWarning(ex);
+ }
base.Update(deltaTime);
}
@@ -254,7 +278,7 @@ namespace FlaxEditor.CustomEditors
///
/// The Editor context that owns this presenter. Can be or or other window/panel - custom editor scan use it for more specific features.
///
- public object Owner;
+ public IPresenterOwner Owner;
///
/// Gets or sets the text to show when no object is selected.
@@ -270,7 +294,24 @@ namespace FlaxEditor.CustomEditors
}
}
+ ///
+ /// Gets or sets the value indicating whether properties are read-only.
+ ///
+ public bool ReadOnly
+ {
+ get => _readOnly;
+ set
+ {
+ if (_readOnly != value)
+ {
+ _readOnly = value;
+ UpdateReadOnly();
+ }
+ }
+ }
+
private bool _buildOnUpdate;
+ private bool _readOnly;
///
/// Initializes a new instance of the class.
@@ -278,7 +319,7 @@ namespace FlaxEditor.CustomEditors
/// The undo. It's optional.
/// The custom text to display when no object is selected. Default is No selection.
/// The owner of the presenter.
- public CustomEditorPresenter(Undo undo, string noSelectionText = null, object owner = null)
+ public CustomEditorPresenter(Undo undo, string noSelectionText = null, IPresenterOwner owner = null)
{
Undo = undo;
Owner = owner;
@@ -364,6 +405,8 @@ namespace FlaxEditor.CustomEditors
// Restore scroll value
if (parentScrollV > -1)
panel.VScrollBar.Value = parentScrollV;
+ if (_readOnly)
+ UpdateReadOnly();
}
///
@@ -374,6 +417,16 @@ namespace FlaxEditor.CustomEditors
_buildOnUpdate = true;
}
+ private void UpdateReadOnly()
+ {
+ // Only scrollbars are enabled
+ foreach (var child in Panel.Children)
+ {
+ if (!(child is ScrollBar))
+ child.Enabled = !_readOnly;
+ }
+ }
+
private void ExpandGroups(LayoutElementsContainer c, bool open)
{
if (c is Elements.GroupElement group)
diff --git a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
new file mode 100644
index 000000000..4053c6e16
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
@@ -0,0 +1,97 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Gizmo;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using FlaxEngine.Json;
+using FlaxEngine.Tools;
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for .
+ ///
+ ///
+ [CustomEditor(typeof(Cloth)), DefaultEditor]
+ class ClothEditor : ActorEditor
+ {
+ private ClothPaintingGizmoMode _gizmoMode;
+ private Viewport.Modes.EditorGizmoMode _prevMode;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ base.Initialize(layout);
+
+ if (Values.Count != 1)
+ return;
+
+ // Add gizmo painting mode to the viewport
+ var owner = Presenter.Owner;
+ if (owner == null)
+ return;
+ var gizmoOwner = owner as IGizmoOwner ?? owner.PresenterViewport as IGizmoOwner;
+ if (gizmoOwner == null)
+ return;
+ var gizmos = gizmoOwner.Gizmos;
+ _gizmoMode = new ClothPaintingGizmoMode();
+
+ var projectCache = Editor.Instance.ProjectCache;
+ if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue))
+ _gizmoMode.PaintValue = JsonSerializer.Deserialize(cachedPaintValue);
+ if (projectCache.TryGetCustomData("ClothGizmoContinuousPaint", out var cachedContinuousPaint))
+ _gizmoMode.ContinuousPaint = JsonSerializer.Deserialize(cachedContinuousPaint);
+ if (projectCache.TryGetCustomData("ClothGizmoBrushFalloff", out var cachedBrushFalloff))
+ _gizmoMode.BrushFalloff = JsonSerializer.Deserialize(cachedBrushFalloff);
+ if (projectCache.TryGetCustomData("ClothGizmoBrushSize", out var cachedBrushSize))
+ _gizmoMode.BrushSize = JsonSerializer.Deserialize(cachedBrushSize);
+ if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength))
+ _gizmoMode.BrushStrength = JsonSerializer.Deserialize(cachedBrushStrength);
+
+ gizmos.AddMode(_gizmoMode);
+ _prevMode = gizmos.ActiveMode;
+ gizmos.ActiveMode = _gizmoMode;
+ _gizmoMode.Gizmo.SetPaintCloth((Cloth)Values[0]);
+
+ // Insert gizmo mode options to properties editing
+ var paintGroup = layout.Group("Cloth Painting");
+ var paintValue = new ReadOnlyValueContainer(new ScriptType(typeof(ClothPaintingGizmoMode)), _gizmoMode);
+ paintGroup.Object(paintValue);
+ {
+ var grid = paintGroup.CustomContainer();
+ var gridControl = grid.CustomControl;
+ gridControl.ClipChildren = false;
+ gridControl.Height = Button.DefaultHeight;
+ gridControl.SlotsHorizontally = 2;
+ gridControl.SlotsVertically = 1;
+ grid.Button("Fill", "Fills the cloth particles with given paint value.").Button.Clicked += _gizmoMode.Gizmo.Fill;
+ grid.Button("Reset", "Clears the cloth particles paint.").Button.Clicked += _gizmoMode.Gizmo.Reset;
+ }
+ }
+
+ ///
+ protected override void Deinitialize()
+ {
+ // Cleanup gizmos
+ if (_gizmoMode != null)
+ {
+ var gizmos = _gizmoMode.Owner.Gizmos;
+ if (gizmos.ActiveMode == _gizmoMode)
+ gizmos.ActiveMode = _prevMode;
+ gizmos.RemoveMode(_gizmoMode);
+ var projectCache = Editor.Instance.ProjectCache;
+ projectCache.SetCustomData("ClothGizmoPaintValue", JsonSerializer.Serialize(_gizmoMode.PaintValue, typeof(float)));
+ projectCache.SetCustomData("ClothGizmoContinuousPaint", JsonSerializer.Serialize(_gizmoMode.ContinuousPaint, typeof(bool)));
+ projectCache.SetCustomData("ClothGizmoBrushFalloff", JsonSerializer.Serialize(_gizmoMode.BrushFalloff, typeof(float)));
+ projectCache.SetCustomData("ClothGizmoBrushSize", JsonSerializer.Serialize(_gizmoMode.BrushSize, typeof(float)));
+ projectCache.SetCustomData("ClothGizmoBrushStrength", JsonSerializer.Serialize(_gizmoMode.BrushStrength, typeof(float)));
+ _gizmoMode.Dispose();
+ _gizmoMode = null;
+ }
+ _prevMode = null;
+
+ base.Deinitialize();
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
new file mode 100644
index 000000000..4099e5aee
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
@@ -0,0 +1,349 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.IO;
+using System.Linq;
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.GUI;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for .
+ ///
+ ///
+ [CustomEditor(typeof(ModelInstanceActor.MeshReference)), DefaultEditor]
+ public class MeshReferenceEditor : CustomEditor
+ {
+ private class MeshRefPickerControl : Control
+ {
+ private ModelInstanceActor.MeshReference _value = new ModelInstanceActor.MeshReference { LODIndex = -1, MeshIndex = -1 };
+ private string _valueName;
+ private Float2 _mousePos;
+
+ public string[][] MeshNames;
+ public event Action ValueChanged;
+
+ public ModelInstanceActor.MeshReference Value
+ {
+ get => _value;
+ set
+ {
+ if (_value.LODIndex == value.LODIndex && _value.MeshIndex == value.MeshIndex)
+ return;
+ _value = value;
+ if (value.LODIndex == -1 || value.MeshIndex == -1)
+ _valueName = null;
+ else if (MeshNames.Length == 1)
+ _valueName = MeshNames[value.LODIndex][value.MeshIndex];
+ else
+ _valueName = $"LOD{value.LODIndex} - {MeshNames[value.LODIndex][value.MeshIndex]}";
+ ValueChanged?.Invoke();
+ }
+ }
+
+ public MeshRefPickerControl()
+ : base(0, 0, 50, 16)
+ {
+ }
+
+ private void ShowDropDownMenu()
+ {
+ // Show context menu with tree structure of LODs and meshes
+ Focus();
+ var cm = new ItemsListContextMenu(200);
+ var meshNames = MeshNames;
+ var actor = _value.Actor;
+ for (int lodIndex = 0; lodIndex < meshNames.Length; lodIndex++)
+ {
+ var item = new ItemsListContextMenu.Item
+ {
+ Name = "LOD" + lodIndex,
+ Tag = new ModelInstanceActor.MeshReference { Actor = actor, LODIndex = lodIndex, MeshIndex = 0 },
+ TintColor = new Color(0.8f, 0.8f, 1.0f, 0.8f),
+ };
+ cm.AddItem(item);
+
+ for (int meshIndex = 0; meshIndex < meshNames[lodIndex].Length; meshIndex++)
+ {
+ item = new ItemsListContextMenu.Item
+ {
+ Name = " " + meshNames[lodIndex][meshIndex],
+ Tag = new ModelInstanceActor.MeshReference { Actor = actor, LODIndex = lodIndex, MeshIndex = meshIndex },
+ };
+ if (_value.LODIndex == lodIndex && _value.MeshIndex == meshIndex)
+ item.BackgroundColor = FlaxEngine.GUI.Style.Current.BackgroundSelected;
+ cm.AddItem(item);
+ }
+ }
+ cm.ItemClicked += item => Value = (ModelInstanceActor.MeshReference)item.Tag;
+ cm.Show(Parent, BottomLeft);
+ }
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+
+ // Cache data
+ var style = FlaxEngine.GUI.Style.Current;
+ bool isSelected = _valueName != null;
+ bool isEnabled = EnabledInHierarchy;
+ var frameRect = new Rectangle(0, 0, Width, 16);
+ if (isSelected)
+ frameRect.Width -= 16;
+ frameRect.Width -= 16;
+ var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
+ var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
+ var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
+
+ // Draw frame
+ Render2D.DrawRectangle(frameRect, isEnabled && (IsMouseOver || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal);
+
+ // Check if has item selected
+ if (isSelected)
+ {
+ // Draw name
+ Render2D.PushClip(nameRect);
+ Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
+ Render2D.PopClip();
+
+ // Draw deselect button
+ Render2D.DrawSprite(style.Cross, button1Rect, isEnabled && button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
+ }
+ else
+ {
+ // Draw info
+ Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center);
+ }
+
+ // Draw picker button
+ var pickerRect = isSelected ? button2Rect : button1Rect;
+ Render2D.DrawSprite(style.ArrowDown, pickerRect, isEnabled && pickerRect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
+ }
+
+ ///
+ public override void OnMouseEnter(Float2 location)
+ {
+ _mousePos = location;
+
+ base.OnMouseEnter(location);
+ }
+
+ ///
+ public override void OnMouseLeave()
+ {
+ _mousePos = Float2.Minimum;
+
+ base.OnMouseLeave();
+ }
+
+ ///
+ public override void OnMouseMove(Float2 location)
+ {
+ _mousePos = location;
+
+ base.OnMouseMove(location);
+ }
+
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ // Cache data
+ bool isSelected = _valueName != null;
+ var frameRect = new Rectangle(0, 0, Width, 16);
+ if (isSelected)
+ frameRect.Width -= 16;
+ frameRect.Width -= 16;
+ var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14);
+ var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14);
+ var button2Rect = new Rectangle(button1Rect.Right + 2, 1, 14, 14);
+
+ // Deselect
+ if (isSelected && button1Rect.Contains(ref location))
+ Value = new ModelInstanceActor.MeshReference { Actor = null, LODIndex = -1, MeshIndex = -1 };
+
+ // Picker dropdown menu
+ if ((isSelected ? button2Rect : button1Rect).Contains(ref location))
+ ShowDropDownMenu();
+
+ return base.OnMouseUp(location, button);
+ }
+
+ ///
+ public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
+ {
+ Focus();
+
+ // Open model editor window
+ if (_value.Actor is StaticModel staticModel)
+ Editor.Instance.ContentEditing.Open(staticModel.Model);
+ else if (_value.Actor is AnimatedModel animatedModel)
+ Editor.Instance.ContentEditing.Open(animatedModel.SkinnedModel);
+
+ return base.OnMouseDoubleClick(location, button);
+ }
+
+ ///
+ public override void OnSubmit()
+ {
+ base.OnSubmit();
+
+ ShowDropDownMenu();
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ MeshNames = null;
+ _valueName = null;
+
+ base.OnDestroy();
+ }
+ }
+
+ private ModelInstanceActor _actor;
+ private CustomElement _actorPicker;
+ private CustomElement _meshPicker;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ // Get the context actor to pick the mesh from it
+ if (GetActor(out var actor))
+ {
+ // TODO: support editing multiple values
+ layout.Label("Different values");
+ return;
+ }
+ _actor = actor;
+
+ if (ParentEditor.Values.Any(x => x is Cloth))
+ {
+ // Cloth always picks the parent model mesh
+ if (actor == null)
+ {
+ layout.Label("Cloth needs to be added as a child to model actor.");
+ }
+ }
+ else
+ {
+ // Actor reference picker
+ _actorPicker = layout.Custom();
+ _actorPicker.CustomControl.Type = new ScriptType(typeof(ModelInstanceActor));
+ _actorPicker.CustomControl.ValueChanged += () => SetValue(new ModelInstanceActor.MeshReference { Actor = (ModelInstanceActor)_actorPicker.CustomControl.Value });
+ }
+
+ if (actor != null)
+ {
+ // Get mesh names hierarchy
+ string[][] meshNames;
+ if (actor is StaticModel staticModel)
+ {
+ var model = staticModel.Model;
+ if (model == null || model.WaitForLoaded())
+ {
+ layout.Label("No model.");
+ return;
+ }
+ var materials = model.MaterialSlots;
+ var lods = model.LODs;
+ meshNames = new string[lods.Length][];
+ for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++)
+ {
+ var lodMeshes = lods[lodIndex].Meshes;
+ meshNames[lodIndex] = new string[lodMeshes.Length];
+ for (int meshIndex = 0; meshIndex < lodMeshes.Length; meshIndex++)
+ {
+ var mesh = lodMeshes[meshIndex];
+ var materialName = materials[mesh.MaterialSlotIndex].Name;
+ if (string.IsNullOrEmpty(materialName) && materials[mesh.MaterialSlotIndex].Material)
+ materialName = Path.GetFileNameWithoutExtension(materials[mesh.MaterialSlotIndex].Material.Path);
+ if (string.IsNullOrEmpty(materialName))
+ meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex}";
+ else
+ meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex} ({materialName})";
+ }
+ }
+ }
+ else if (actor is AnimatedModel animatedModel)
+ {
+ var skinnedModel = animatedModel.SkinnedModel;
+ if (skinnedModel == null || skinnedModel.WaitForLoaded())
+ {
+ layout.Label("No model.");
+ return;
+ }
+ var materials = skinnedModel.MaterialSlots;
+ var lods = skinnedModel.LODs;
+ meshNames = new string[lods.Length][];
+ for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++)
+ {
+ var lodMeshes = lods[lodIndex].Meshes;
+ meshNames[lodIndex] = new string[lodMeshes.Length];
+ for (int meshIndex = 0; meshIndex < lodMeshes.Length; meshIndex++)
+ {
+ var mesh = lodMeshes[meshIndex];
+ var materialName = materials[mesh.MaterialSlotIndex].Name;
+ if (string.IsNullOrEmpty(materialName) && materials[mesh.MaterialSlotIndex].Material)
+ materialName = Path.GetFileNameWithoutExtension(materials[mesh.MaterialSlotIndex].Material.Path);
+ if (string.IsNullOrEmpty(materialName))
+ meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex}";
+ else
+ meshNames[lodIndex][meshIndex] = $"Mesh {meshIndex} ({materialName})";
+ }
+ }
+ }
+ else
+ return; // Not supported model type
+
+ // Mesh reference picker
+ _meshPicker = layout.Custom();
+ _meshPicker.CustomControl.MeshNames = meshNames;
+ _meshPicker.CustomControl.Value = (ModelInstanceActor.MeshReference)Values[0];
+ _meshPicker.CustomControl.ValueChanged += () => SetValue(_meshPicker.CustomControl.Value);
+ }
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ if (_actorPicker != null)
+ {
+ GetActor(out var actor);
+ _actorPicker.CustomControl.Value = actor;
+ if (actor != _actor)
+ {
+ RebuildLayout();
+ return;
+ }
+ }
+ if (_meshPicker != null)
+ {
+ _meshPicker.CustomControl.Value = (ModelInstanceActor.MeshReference)Values[0];
+ }
+ }
+
+ private bool GetActor(out ModelInstanceActor actor)
+ {
+ actor = null;
+ foreach (ModelInstanceActor.MeshReference value in Values)
+ {
+ if (actor == null)
+ actor = value.Actor;
+ else if (actor != value.Actor)
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
new file mode 100644
index 000000000..7e0c6f38c
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
@@ -0,0 +1,156 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Actions;
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using System.Collections.Generic;
+
+namespace FlaxEditor.CustomEditors.Dedicated;
+
+///
+/// The missing script editor.
+///
+[CustomEditor(typeof(MissingScript)), DefaultEditor]
+public class MissingScriptEditor : GenericEditor
+{
+ private DropPanel _dropPanel;
+ private Button _replaceScriptButton;
+ private CheckBox _shouldReplaceAllCheckbox;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ if (layout.ContainerControl is not DropPanel dropPanel)
+ {
+ base.Initialize(layout);
+ return;
+ }
+ _dropPanel = dropPanel;
+ _dropPanel.HeaderTextColor = Color.OrangeRed;
+
+ var replaceScriptPanel = new Panel
+ {
+ Parent = _dropPanel,
+ Height = 64,
+ };
+ _replaceScriptButton = new Button
+ {
+ Text = "Replace Script",
+ TooltipText = "Replaces the missing script with a given script type",
+ AnchorPreset = AnchorPresets.TopCenter,
+ Bounds = new Rectangle(-120, 0, 240, 24),
+ Parent = replaceScriptPanel,
+ };
+ _replaceScriptButton.Clicked += OnReplaceScriptButtonClicked;
+ var replaceAllLabel = new Label
+ {
+ Text = "Replace all matching missing scripts",
+ TooltipText = "Whether or not to apply this script change to all scripts missing the same type.",
+ AnchorPreset = AnchorPresets.BottomCenter,
+ Y = -38,
+ Parent = replaceScriptPanel,
+ };
+ _shouldReplaceAllCheckbox = new CheckBox
+ {
+ TooltipText = replaceAllLabel.TooltipText,
+ AnchorPreset = AnchorPresets.BottomCenter,
+ Y = -38,
+ Parent = replaceScriptPanel,
+ };
+ _shouldReplaceAllCheckbox.X -= _replaceScriptButton.Width * 0.5f + 0.5f;
+ replaceAllLabel.X -= 52;
+
+ base.Initialize(layout);
+ }
+
+ private void FindActorsWithMatchingMissingScript(List missingScripts)
+ {
+ foreach (Actor actor in Level.GetActors(typeof(Actor)))
+ {
+ for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++)
+ {
+ Script actorScript = actor.Scripts[scriptIndex];
+ if (actorScript is not MissingScript missingActorScript)
+ continue;
+
+ MissingScript currentMissing = Values[0] as MissingScript;
+ if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName)
+ continue;
+
+ missingScripts.Add(missingActorScript);
+ }
+ }
+ }
+
+ private void RunReplacementMultiCast(List actions)
+ {
+ if (actions.Count == 0)
+ {
+ Editor.LogWarning("Failed to replace scripts!");
+ return;
+ }
+
+ var multiAction = new MultiUndoAction(actions);
+ multiAction.Do();
+ var presenter = ParentEditor.Presenter;
+ if (presenter != null)
+ {
+ presenter.Undo.AddAction(multiAction);
+ presenter.Control.Focus();
+ }
+ }
+
+ private void ReplaceScript(ScriptType script, bool replaceAllInScene)
+ {
+ var actions = new List(4);
+
+ var missingScripts = new List();
+ if (!replaceAllInScene)
+ missingScripts.Add((MissingScript)Values[0]);
+ else
+ FindActorsWithMatchingMissingScript(missingScripts);
+
+ foreach (var missingScript in missingScripts)
+ actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
+ RunReplacementMultiCast(actions);
+
+ for (int actionIdx = 0; actionIdx < actions.Count; actionIdx++)
+ {
+ AddRemoveScript addRemoveScriptAction = (AddRemoveScript)actions[actionIdx];
+ int orderInParent = addRemoveScriptAction.GetOrderInParent();
+
+ Script newScript = missingScripts[actionIdx].Actor.Scripts[orderInParent];
+ missingScripts[actionIdx].ReferenceScript = newScript;
+ }
+ actions.Clear();
+
+ foreach (var missingScript in missingScripts)
+ actions.Add(AddRemoveScript.Remove(missingScript));
+ RunReplacementMultiCast(actions);
+ }
+
+ private void OnReplaceScriptButtonClicked()
+ {
+ var scripts = Editor.Instance.CodeEditing.Scripts.Get();
+ if (scripts.Count == 0)
+ {
+ // No scripts
+ var cm1 = new ContextMenu();
+ cm1.AddButton("No scripts in project");
+ cm1.Show(_dropPanel, _replaceScriptButton.BottomLeft);
+ return;
+ }
+
+ // Show context menu with list of scripts to add
+ var cm = new ItemsListContextMenu(180);
+ for (int i = 0; i < scripts.Count; i++)
+ cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
+ cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked);
+ cm.SortItems();
+ cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0));
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs
index ec71348cf..5b2cfc144 100644
--- a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs
@@ -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;
}
}
diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs
index 5a10b9a52..c4b334b3a 100644
--- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs
@@ -50,7 +50,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
grid.Button("Remove bone").Button.ButtonClicked += OnRemoveBone;
}
- if (Presenter.Owner is Windows.PropertiesWindow || Presenter.Owner is Windows.Assets.PrefabWindow)
+ if (Presenter.Owner != null)
{
// Selection
var grid = editorGroup.CustomContainer();
@@ -309,10 +309,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (node != null)
selection.Add(node);
}
- if (Presenter.Owner is Windows.PropertiesWindow propertiesWindow)
- propertiesWindow.Editor.SceneEditing.Select(selection);
- else if (Presenter.Owner is Windows.Assets.PrefabWindow prefabWindow)
- prefabWindow.Select(selection);
+ Presenter.Owner.Select(selection);
}
}
}
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index bf82e19da..d7bfbbad7 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
private DragHandlers _dragHandlers;
private DragScriptItems _dragScripts;
private DragAssets _dragAssets;
+ private Button _addScriptsButton;
///
/// 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)
@@ -254,27 +258,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts.
///
///
- internal class ScriptDragIcon : Image
+ internal class DragImage : Image
{
- private ScriptsEditor _editor;
private bool _isMouseDown;
private Float2 _mouseDownPos;
///
- /// Gets the target script.
+ /// Action called when drag event should start.
///
- public Script Script => (Script)Tag;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The script editor.
- /// The target script.
- public ScriptDragIcon(ScriptsEditor editor, Script script)
- {
- Tag = script;
- _editor = editor;
- }
+ public Action Drag;
///
public override void OnMouseEnter(Float2 location)
@@ -287,11 +279,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override void OnMouseLeave()
{
- // Check if start drag drop
if (_isMouseDown)
{
- DoDrag();
_isMouseDown = false;
+ Drag(this);
}
base.OnMouseLeave();
@@ -300,11 +291,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override void OnMouseMove(Float2 location)
{
- // Check if start drag drop
if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f)
{
- DoDrag();
_isMouseDown = false;
+ Drag(this);
}
base.OnMouseMove(location);
@@ -315,8 +305,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (button == MouseButton.Left)
{
- // Clear flag
_isMouseDown = false;
+ return true;
}
return base.OnMouseUp(location, button);
@@ -327,21 +317,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (button == MouseButton.Left)
{
- // Set flag
_isMouseDown = true;
_mouseDownPos = location;
+ return true;
}
return base.OnMouseDown(location, button);
}
-
- private void DoDrag()
- {
- var script = Script;
- _editor.OnScriptDragChange(true, script);
- DoDragDrop(DragScripts.GetDragData(script));
- _editor.OnScriptDragChange(false, script);
- }
}
internal class ScriptArrangeBar : Control
@@ -576,8 +558,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;
}
@@ -639,7 +621,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
_scriptToggles[i] = scriptToggle;
// Add drag button to the group
- var scriptDrag = new ScriptDragIcon(this, script)
+ var scriptDrag = new DragImage
{
TooltipText = "Script reference",
AutoFocus = true,
@@ -650,6 +632,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
Margin = new Margin(1),
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
Tag = script,
+ Drag = img =>
+ {
+ var s = (Script)img.Tag;
+ OnScriptDragChange(true, s);
+ img.DoDragDrop(DragScripts.GetDragData(s));
+ OnScriptDragChange(false, s);
+ }
};
// Add settings button to the group
diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
index 03d58ef33..7b9b65c5c 100644
--- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
@@ -348,7 +348,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (!CanEditTangent())
return;
-
+
var index = _lastPointSelected.Index;
var currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation;
var currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation;
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index fde4967e8..296560507 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -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),
};
@@ -693,7 +695,41 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void SetType(ref ScriptType controlType, UIControl uiControl)
{
string previousName = uiControl.Control?.GetType().Name ?? nameof(UIControl);
- uiControl.Control = (Control)controlType.CreateInstance();
+
+ var oldControlType = (Control)uiControl.Control;
+ var newControlType = (Control)controlType.CreateInstance();
+
+ // copy old control data to new control
+ if (oldControlType != null)
+ {
+ newControlType.Visible = oldControlType.Visible;
+ newControlType.Enabled = oldControlType.Enabled;
+ newControlType.AutoFocus = oldControlType.AutoFocus;
+
+ newControlType.AnchorMin = oldControlType.AnchorMin;
+ newControlType.AnchorMax = oldControlType.AnchorMax;
+ newControlType.Offsets = oldControlType.Offsets;
+
+ newControlType.LocalLocation = oldControlType.LocalLocation;
+ newControlType.Scale = oldControlType.Scale;
+ newControlType.Bounds = oldControlType.Bounds;
+ newControlType.Width = oldControlType.Width;
+ newControlType.Height = oldControlType.Height;
+ newControlType.Center = oldControlType.Center;
+ newControlType.PivotRelative = oldControlType.PivotRelative;
+
+ newControlType.Pivot = oldControlType.Pivot;
+ newControlType.Shear = oldControlType.Shear;
+ newControlType.Rotation = oldControlType.Rotation;
+ }
+ if (oldControlType is ContainerControl oldContainer && newControlType is ContainerControl newContainer)
+ {
+ newContainer.CullChildren = oldContainer.CullChildren;
+ newContainer.ClipChildren = oldContainer.ClipChildren;
+ }
+
+ uiControl.Control = newControlType;
+
if (uiControl.Name.StartsWith(previousName))
{
string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
index bf087feda..4829bd91a 100644
--- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
@@ -34,7 +34,7 @@ namespace FlaxEditor.CustomEditors.Editors
value = 0;
// If selected is single actor that has children, ask if apply layer to the sub objects as well
- if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
+ if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren && !Editor.IsPlayMode)
{
var valueText = comboBox.SelectedItem;
diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
index cb4e7b9c1..4c153e759 100644
--- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
@@ -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();
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index cfba940c2..1f3359fd5 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
- Picker.FileExtension = assetReference.TypeName;
+ Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
@@ -85,7 +85,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
- Picker.AssetType = assetType;
+ Picker.Validator.AssetType = assetType;
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
@@ -95,15 +95,15 @@ namespace FlaxEditor.CustomEditors.Editors
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
- SetValue(Picker.SelectedItem);
+ SetValue(Picker.Validator.SelectedItem);
else if (_valueType.Type == typeof(Guid))
- SetValue(Picker.SelectedID);
+ SetValue(Picker.Validator.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
- SetValue(new SceneReference(Picker.SelectedID));
+ SetValue(new SceneReference(Picker.Validator.SelectedID));
else if (_valueType.Type == typeof(string))
- SetValue(Picker.SelectedPath);
+ SetValue(Picker.Validator.SelectedPath);
else
- SetValue(Picker.SelectedAsset);
+ SetValue(Picker.Validator.SelectedAsset);
}
///
@@ -115,15 +115,15 @@ namespace FlaxEditor.CustomEditors.Editors
{
_isRefreshing = true;
if (Values[0] is AssetItem assetItem)
- Picker.SelectedItem = assetItem;
+ Picker.Validator.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
- Picker.SelectedID = guid;
+ Picker.Validator.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
- Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
+ Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
- Picker.SelectedPath = path;
+ Picker.Validator.SelectedPath = path;
else
- Picker.SelectedAsset = Values[0] as Asset;
+ Picker.Validator.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
}
}
diff --git a/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs
new file mode 100644
index 000000000..b3c5792ac
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs
@@ -0,0 +1,198 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FlaxEditor.GUI;
+using FlaxEditor.GUI.Tree;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
+
+namespace FlaxEditor.CustomEditors.Editors
+{
+ ///
+ /// Custom editor for and .
+ ///
+ public sealed class BehaviorKnowledgeSelectorEditor : CustomEditor
+ {
+ private ClickableLabel _label;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ _label = layout.ClickableLabel(Path).CustomControl;
+ _label.RightClick += ShowPicker;
+ var button = new Button
+ {
+ Size = new Float2(16.0f),
+ Text = "...",
+ TooltipText = "Edit...",
+ Parent = _label,
+ };
+ button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
+ button.Clicked += ShowPicker;
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ // Update label
+ _label.Text = _label.TooltipText = Path;
+ }
+
+ private string Path
+ {
+ get
+ {
+ var v = Values[0];
+ if (v is BehaviorKnowledgeSelectorAny any)
+ return any.Path;
+ if (v is string str)
+ return str;
+ var pathField = v.GetType().GetField("Path");
+ return pathField.GetValue(v) as string;
+ }
+ set
+ {
+ if (string.Equals(Path, value, StringComparison.Ordinal))
+ return;
+ var v = Values[0];
+ if (v is BehaviorKnowledgeSelectorAny)
+ v = new BehaviorKnowledgeSelectorAny(value);
+ else if (v is string)
+ v = value;
+ else
+ {
+ var pathField = v.GetType().GetField("Path");
+ pathField.SetValue(v, value);
+ }
+ SetValue(v);
+ }
+ }
+
+ private void ShowPicker()
+ {
+ // Get Behavior Knowledge to select from
+ var behaviorTreeWindow = Presenter.Owner as Windows.Assets.BehaviorTreeWindow;
+ var rootNode = behaviorTreeWindow?.RootNode;
+ if (rootNode == null)
+ return;
+ var typed = ScriptType.Null;
+ var valueType = Values[0].GetType();
+ if (valueType.Name == "BehaviorKnowledgeSelector`1")
+ {
+ // Get typed selector type to show only assignable items
+ typed = new ScriptType(valueType.GenericTypeArguments[0]);
+ }
+
+ // Get customization options
+ var attributes = Values.GetAttributes();
+ var attribute = (BehaviorKnowledgeSelectorAttribute)attributes?.FirstOrDefault(x => x is BehaviorKnowledgeSelectorAttribute);
+ bool isGoalSelector = false;
+ if (attribute != null)
+ {
+ isGoalSelector = attribute.IsGoalSelector;
+ }
+
+ // Create menu with tree-like structure and search box
+ var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree, 0, true);
+ var selected = Path;
+
+ // Empty
+ var noneNode = new TreeNode
+ {
+ Text = "",
+ TooltipText = "Deselect value",
+ Parent = tree,
+ };
+ if (string.IsNullOrEmpty(selected))
+ tree.Select(noneNode);
+
+ if (!isGoalSelector)
+ {
+ // Blackboard
+ SetupPickerTypeItems(tree, typed, selected, "Blackboard", "Blackboard/", rootNode.BlackboardType);
+ }
+
+ // Goals
+ var goalTypes = rootNode.GoalTypes;
+ if (goalTypes?.Length != 0)
+ {
+ var goalsNode = new TreeNode
+ {
+ Text = "Goal",
+ TooltipText = "List of goal types defined in Blackboard Tree",
+ Parent = tree,
+ };
+ foreach (var goalTypeName in goalTypes)
+ {
+ var goalType = TypeUtils.GetType(goalTypeName);
+ if (goalType == null)
+ continue;
+ var goalTypeNode = SetupPickerTypeItems(tree, typed, selected, goalType.Name, "Goal/" + goalTypeName + "/", goalTypeName, !isGoalSelector);
+ goalTypeNode.Parent = goalsNode;
+ }
+ goalsNode.ExpandAll(true);
+ }
+
+ tree.SelectedChanged += delegate(List before, List after)
+ {
+ if (after.Count == 1)
+ {
+ menu.Hide();
+ Path = after[0].Tag as string;
+ }
+ };
+ menu.Show(_label, new Float2(0, _label.Height));
+ }
+
+ private TreeNode SetupPickerTypeItems(Tree tree, ScriptType typed, string selected, string text, string typePath, string typeName, bool addItems = true)
+ {
+ var type = TypeUtils.GetType(typeName);
+ if (type == null)
+ return null;
+ var typeNode = new TreeNode
+ {
+ Text = text,
+ TooltipText = type.TypeName,
+ Tag = typePath, // Ability to select whole item type data (eg. whole blackboard value)
+ Parent = tree,
+ };
+ if (typed && !typed.IsAssignableFrom(type))
+ typeNode.Tag = null;
+ if (string.Equals(selected, (string)typeNode.Tag, StringComparison.Ordinal))
+ tree.Select(typeNode);
+ if (addItems)
+ {
+ var items = GenericEditor.GetItemsForType(type, type.IsClass, true, true);
+ foreach (var item in items)
+ {
+ if (typed && !typed.IsAssignableFrom(item.Info.ValueType))
+ continue;
+ if (item.Info.DeclaringType.Type == typeof(FlaxEngine.Object))
+ continue; // Skip engine internals
+ var itemPath = typePath + item.Info.Name;
+ var node = new TreeNode
+ {
+ Text = item.DisplayName,
+ TooltipText = item.TooltipText,
+ Tag = itemPath,
+ Parent = typeNode,
+ };
+ if (string.Equals(selected, itemPath, StringComparison.Ordinal))
+ tree.Select(node);
+ // TODO: add support for nested items (eg. field from blackboard structure field)
+ }
+ typeNode.Expand(true);
+ }
+ return typeNode;
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs
index f5edafed8..803b7b4dd 100644
--- a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs
@@ -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;
}
}
}
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 8922e2d25..6f623fb23 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -3,9 +3,12 @@
using System;
using System.Collections;
using System.Linq;
+using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Drag;
+using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -110,7 +113,7 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
// No support for different collections for now
- if (HasDifferentValues || HasDifferentTypes)
+ if (HasDifferentTypes)
return;
var size = Count;
@@ -135,14 +138,43 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing;
}
+ var dragArea = layout.CustomContainer();
+ dragArea.CustomControl.Editor = this;
+ dragArea.CustomControl.ElementType = ElementType;
+
+ // Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter
+ // which scripts can be dragged over and dropped on this collection editor.
+ var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
+ if (assetReference != null)
+ {
+ if (string.IsNullOrEmpty(assetReference.TypeName))
+ {
+ }
+ else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
+ {
+ dragArea.CustomControl.ElementType = ScriptType.Null;
+ dragArea.CustomControl.FileExtension = assetReference.TypeName;
+ }
+ else
+ {
+ var customType = TypeUtils.GetType(assetReference.TypeName);
+ if (customType != ScriptType.Null)
+ dragArea.CustomControl.ElementType = customType;
+ else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
+ Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for drag and drop filter.", assetReference.TypeName));
+ else
+ dragArea.CustomControl.ElementType = ScriptType.Void;
+ }
+ }
+
// Size
if (_readOnly || (NotNullItems && size == 0))
{
- layout.Label("Size", size.ToString());
+ dragArea.Label("Size", size.ToString());
}
else
{
- _size = layout.IntegerValue("Size");
+ _size = dragArea.IntegerValue("Size");
_size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size;
@@ -152,7 +184,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements
if (size > 0)
{
- var panel = layout.VerticalPanel();
+ var panel = dragArea.VerticalPanel();
panel.Panel.BackgroundColor = _background;
var elementType = ElementType;
@@ -212,37 +244,33 @@ namespace FlaxEditor.CustomEditors.Editors
// Add/Remove buttons
if (!_readOnly)
{
- var area = layout.Space(20);
- var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
- {
- Text = "+",
- TooltipText = "Add new item",
- AnchorPreset = AnchorPresets.TopRight,
- Parent = area.ContainerControl,
- Enabled = !NotNullItems || size > 0,
- };
- addButton.Clicked += () =>
- {
- if (IsSetBlocked)
- return;
+ var panel = dragArea.HorizontalPanel();
+ panel.Panel.Size = new Float2(0, 20);
+ panel.Panel.Margin = new Margin(2);
- Resize(Count + 1);
- };
- var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
- {
- Text = "-",
- TooltipText = "Remove last item",
- AnchorPreset = AnchorPresets.TopRight,
- Parent = area.ContainerControl,
- Enabled = size > 0,
- };
- removeButton.Clicked += () =>
+ var removeButton = panel.Button("-", "Remove last item");
+ removeButton.Button.Size = new Float2(16, 16);
+ removeButton.Button.Enabled = size > 0;
+ removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
+ removeButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count - 1);
};
+
+ var addButton = panel.Button("+", "Add new item");
+ addButton.Button.Size = new Float2(16, 16);
+ addButton.Button.Enabled = !NotNullItems || size > 0;
+ addButton.Button.AnchorPreset = AnchorPresets.TopRight;
+ addButton.Button.Clicked += () =>
+ {
+ if (IsSetBlocked)
+ return;
+
+ Resize(Count + 1);
+ };
}
}
@@ -369,5 +397,232 @@ namespace FlaxEditor.CustomEditors.Editors
}
return base.OnDirty(editor, value, token);
}
+
+ private class DragAreaControl : VerticalPanel
+ {
+ private DragItems _dragItems;
+ private DragActors _dragActors;
+ private DragHandlers _dragHandlers;
+ private AssetPickerValidator _pickerValidator;
+
+ public ScriptType ElementType
+ {
+ get => _pickerValidator?.AssetType ?? ScriptType.Null;
+ set => _pickerValidator = new AssetPickerValidator(value);
+ }
+
+ public CollectionEditor Editor { get; set; }
+
+ public string FileExtension
+ {
+ set => _pickerValidator.FileExtension = value;
+ }
+
+ ///
+ public override void Draw()
+ {
+ if (_dragHandlers is { HasValidDrag: true })
+ {
+ var area = new Rectangle(Float2.Zero, Size);
+ Render2D.FillRectangle(area, Color.Orange * 0.5f);
+ Render2D.DrawRectangle(area, Color.Black);
+ }
+
+ base.Draw();
+ }
+
+ public override void OnDestroy()
+ {
+ _pickerValidator.OnDestroy();
+ }
+
+ private bool ValidateActors(ActorNode node)
+ {
+ return node.Actor.GetScript(ElementType.Type) || ElementType.Type.IsAssignableTo(typeof(Actor));
+ }
+
+ ///
+ public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragEnter(ref location, data);
+ if (result != DragDropEffect.None)
+ return result;
+
+ if (_dragHandlers == null)
+ {
+ _dragItems = new DragItems(_pickerValidator.IsValid);
+ _dragActors = new DragActors(ValidateActors);
+ _dragHandlers = new DragHandlers
+ {
+ _dragActors,
+ _dragItems
+ };
+ }
+ return _dragHandlers.OnDragEnter(data);
+ }
+
+ ///
+ public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragMove(ref location, data);
+ if (result != DragDropEffect.None)
+ return result;
+
+ return _dragHandlers.Effect;
+ }
+
+ ///
+ public override void OnDragLeave()
+ {
+ _dragHandlers.OnDragLeave();
+
+ base.OnDragLeave();
+ }
+
+ ///
+ public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragDrop(ref location, data);
+ if (result != DragDropEffect.None)
+ {
+ _dragHandlers.OnDragDrop(null);
+ return result;
+ }
+
+ if (_dragHandlers.HasValidDrag)
+ {
+ if (_dragItems.HasValidDrag)
+ {
+ var list = Editor.CloneValues();
+ if (list == null)
+ {
+ if (Editor.Values.Type.IsArray)
+ {
+ list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
+ }
+ else
+ {
+ list = Editor.Values.Type.CreateInstance() as IList;
+ }
+ }
+ if (list.IsFixedSize)
+ {
+ var oldSize = list.Count;
+ var newSize = list.Count + _dragItems.Objects.Count;
+ var type = Editor.Values.Type.GetElementType();
+ var array = TypeUtils.CreateArrayInstance(type, newSize);
+ list.CopyTo(array, 0);
+
+ for (var i = oldSize; i < newSize; i++)
+ {
+ var validator = new AssetPickerValidator
+ {
+ FileExtension = _pickerValidator.FileExtension,
+ AssetType = _pickerValidator.AssetType,
+ SelectedItem = _dragItems.Objects[i - oldSize],
+ };
+
+ if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
+ array.SetValue(validator.SelectedItem, i);
+ else if (ElementType.Type == typeof(Guid))
+ array.SetValue(validator.SelectedID, i);
+ else if (ElementType.Type == typeof(SceneReference))
+ array.SetValue(new SceneReference(validator.SelectedID), i);
+ else if (ElementType.Type == typeof(string))
+ array.SetValue(validator.SelectedPath, i);
+ else
+ array.SetValue(validator.SelectedAsset, i);
+
+ validator.OnDestroy();
+ }
+ Editor.SetValue(array);
+ }
+ else
+ {
+ foreach (var item in _dragItems.Objects)
+ {
+ var validator = new AssetPickerValidator
+ {
+ FileExtension = _pickerValidator.FileExtension,
+ AssetType = _pickerValidator.AssetType,
+ SelectedItem = item,
+ };
+
+ if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
+ list.Add(validator.SelectedItem);
+ else if (ElementType.Type == typeof(Guid))
+ list.Add(validator.SelectedID);
+ else if (ElementType.Type == typeof(SceneReference))
+ list.Add(new SceneReference(validator.SelectedID));
+ else if (ElementType.Type == typeof(string))
+ list.Add(validator.SelectedPath);
+ else
+ list.Add(validator.SelectedAsset);
+
+ validator.OnDestroy();
+ }
+ Editor.SetValue(list);
+ }
+ }
+ else if (_dragActors.HasValidDrag)
+ {
+ var list = Editor.CloneValues();
+ if (list == null)
+ {
+ if (Editor.Values.Type.IsArray)
+ {
+ list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
+ }
+ else
+ {
+ list = Editor.Values.Type.CreateInstance() as IList;
+ }
+ }
+
+ if (list.IsFixedSize)
+ {
+ var oldSize = list.Count;
+ var newSize = list.Count + _dragActors.Objects.Count;
+ var type = Editor.Values.Type.GetElementType();
+ var array = TypeUtils.CreateArrayInstance(type, newSize);
+ list.CopyTo(array, 0);
+
+ for (var i = oldSize; i < newSize; i++)
+ {
+ var actor = _dragActors.Objects[i - oldSize].Actor;
+ if (ElementType.Type.IsAssignableTo(typeof(Actor)))
+ {
+ array.SetValue(actor, i);
+ }
+ else
+ {
+ array.SetValue(actor.GetScript(ElementType.Type), i);
+ }
+ }
+ Editor.SetValue(array);
+ }
+ else
+ {
+ foreach (var actorNode in _dragActors.Objects)
+ {
+ if (ElementType.Type.IsAssignableTo(typeof(Actor)))
+ {
+ list.Add(actorNode.Actor);
+ }
+ else
+ {
+ list.Add(actorNode.Actor.GetScript(ElementType.Type));
+ }
+ }
+ Editor.SetValue(list);
+ }
+ }
+
+ _dragHandlers.OnDragDrop(null);
+ }
+
+ return result;
+ }
+ }
}
}
diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
index cba2b5a5a..68c24a675 100644
--- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
@@ -26,7 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors
/// Describes object property/field information for custom editors pipeline.
///
///
- protected class ItemInfo : IComparable
+ public class ItemInfo : IComparable
{
private Options.GeneralOptions.MembersOrder _membersOrder;
@@ -247,8 +247,9 @@ namespace FlaxEditor.CustomEditors.Editors
/// The type.
/// True if use type properties.
/// True if use type fields.
+ /// True if use type properties that have only getter method without setter method (aka read-only).
/// The items.
- protected List GetItemsForType(ScriptType type, bool useProperties, bool useFields)
+ public static List GetItemsForType(ScriptType type, bool useProperties, bool useFields, bool usePropertiesWithoutSetter = false)
{
var items = new List();
@@ -264,7 +265,7 @@ namespace FlaxEditor.CustomEditors.Editors
var showInEditor = attributes.Any(x => x is ShowInEditorAttribute);
// Skip properties without getter or setter
- if (!p.HasGet || (!p.HasSet && !showInEditor))
+ if (!p.HasGet || (!p.HasSet && !showInEditor && !usePropertiesWithoutSetter))
continue;
// Skip hidden fields, handle special attributes
diff --git a/Source/Editor/CustomEditors/Editors/InputEditor.cs b/Source/Editor/CustomEditors/Editors/InputEditor.cs
index 416626e81..7b2682a84 100644
--- a/Source/Editor/CustomEditors/Editors/InputEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/InputEditor.cs
@@ -1,8 +1,10 @@
// 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;
-using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
@@ -12,7 +14,7 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(InputEvent)), DefaultEditor]
public class InputEventEditor : CustomEditor
{
- private Dropdown _dropdown;
+ private ComboBox _comboBox;
///
public override DisplayStyle Style => DisplayStyle.Inline;
@@ -20,23 +22,30 @@ namespace FlaxEditor.CustomEditors.Editors
///
public override void Initialize(LayoutElementsContainer layout)
{
- var dropdownElement = layout.Custom();
- _dropdown = dropdownElement.CustomControl;
- var names = new List();
+ LinkedLabel.SetupContextMenu += OnSetupContextMenu;
+ var comboBoxElement = layout.ComboBox();
+ _comboBox = comboBoxElement.ComboBox;
+ var names = new List();
foreach (var mapping in Input.ActionMappings)
{
if (!names.Contains(mapping.Name))
names.Add(mapping.Name);
}
- _dropdown.Items = names;
+ _comboBox.Items = names;
if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name))
- _dropdown.SelectedItem = inputEvent.Name;
- _dropdown.SelectedIndexChanged += OnSelectedIndexChanged;
+ _comboBox.SelectedItem = inputEvent.Name;
+ _comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
- private void OnSelectedIndexChanged(Dropdown dropdown)
+ private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
{
- SetValue(new InputEvent(dropdown.SelectedItem));
+ 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));
}
///
@@ -49,17 +58,21 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
- if (Values[0] is InputEvent inputEvent && _dropdown.Items.Contains(inputEvent.Name))
- _dropdown.SelectedItem = inputEvent.Name;
+ if (Values[0] is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name))
+ _comboBox.SelectedItem = inputEvent.Name;
+ else
+ _comboBox.SelectedItem = null;
}
}
///
protected override void Deinitialize()
{
- if (_dropdown != null)
- _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged;
- _dropdown = null;
+ if (LinkedLabel != null)
+ LinkedLabel.SetupContextMenu -= OnSetupContextMenu;
+ if (_comboBox != null)
+ _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged;
+ _comboBox = null;
}
}
@@ -69,7 +82,7 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(InputAxis)), DefaultEditor]
public class InputAxisEditor : CustomEditor
{
- private Dropdown _dropdown;
+ private ComboBox _comboBox;
///
public override DisplayStyle Style => DisplayStyle.Inline;
@@ -77,23 +90,30 @@ namespace FlaxEditor.CustomEditors.Editors
///
public override void Initialize(LayoutElementsContainer layout)
{
- var dropdownElement = layout.Custom();
- _dropdown = dropdownElement.CustomControl;
- var names = new List();
+ LinkedLabel.SetupContextMenu += OnSetupContextMenu;
+ var comboBoxElement = layout.ComboBox();
+ _comboBox = comboBoxElement.ComboBox;
+ var names = new List();
foreach (var mapping in Input.AxisMappings)
{
if (!names.Contains(mapping.Name))
names.Add(mapping.Name);
}
- _dropdown.Items = names;
+ _comboBox.Items = names;
if (Values[0] is InputAxis inputAxis && names.Contains(inputAxis.Name))
- _dropdown.SelectedItem = inputAxis.Name;
- _dropdown.SelectedIndexChanged += OnSelectedIndexChanged;
+ _comboBox.SelectedItem = inputAxis.Name;
+ _comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
- private void OnSelectedIndexChanged(Dropdown dropdown)
+ private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
{
- SetValue(new InputAxis(dropdown.SelectedItem));
+ 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));
}
///
@@ -106,17 +126,21 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
- if (Values[0] is InputAxis inputAxis && _dropdown.Items.Contains(inputAxis.Name))
- _dropdown.SelectedItem = inputAxis.Name;
+ if (Values[0] is InputAxis inputAxis && _comboBox.Items.Contains(inputAxis.Name))
+ _comboBox.SelectedItem = inputAxis.Name;
+ else
+ _comboBox.SelectedItem = null;
}
}
///
protected override void Deinitialize()
{
- if (_dropdown != null)
- _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged;
- _dropdown = null;
+ if (LinkedLabel != null)
+ LinkedLabel.SetupContextMenu -= OnSetupContextMenu;
+ if (_comboBox != null)
+ _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged;
+ _comboBox = null;
}
}
}
diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
index 9607680f2..f901b20d9 100644
--- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
@@ -28,14 +28,16 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry");
_group = group;
- if (ParentEditor == null)
+ if (ParentEditor == null || HasDifferentTypes)
return;
var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering.";
- if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
+ var parentEditorValues = ParentEditor.ParentEditor?.Values;
+ if (parentEditorValues?[0] is ModelInstanceActor modelInstance)
{
+ // TODO: store _modelInstance and _material in array for each selected model instance actor
_entryIndex = entryIndex;
_modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots;
@@ -56,6 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
// Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
+ for (var i = 1; i < parentEditorValues.Count; i++)
+ materialValue.Add(_material);
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue();
@@ -72,14 +76,14 @@ namespace FlaxEditor.CustomEditors.Editors
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
- var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
+ var material = _materialEditor.Picker.Validator.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
- _materialEditor.Picker.SelectedAsset = defaultMaterial;
+ _materialEditor.Picker.Validator.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)
diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
index 4d0c0f662..0369d679d 100644
--- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
@@ -125,7 +125,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
// Value
- var values = new CustomValueContainer(type, (instance, index) => instance, (instance, index, value) => { });
+ var values = new CustomValueContainer(type, (instance, index) => instance);
values.AddRange(Values);
var editor = CustomEditorsUtil.CreateEditor(type);
var style = editor.Style;
@@ -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();
diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs
index 3d2dd86aa..dbd5d124c 100644
--- a/Source/Editor/CustomEditors/Editors/TagEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs
@@ -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)
+ else if (Values[0] is List || Values.Type.Type == typeof(List))
SetValue(new List(value));
}
}
diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs
index ab17c4ae1..38800a738 100644
--- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs
@@ -464,6 +464,11 @@ namespace FlaxEditor.CustomEditors.Editors
///
public class TypeNameEditor : TypeEditorBase
{
+ ///
+ /// Prevents spamming log if Value contains missing type to skip research in subsequential Refresh ticks.
+ ///
+ private string _lastTypeNameError;
+
///
public override void Initialize(LayoutElementsContainer layout)
{
@@ -484,8 +489,19 @@ namespace FlaxEditor.CustomEditors.Editors
{
base.Refresh();
- if (!HasDifferentValues && Values[0] is string asTypename)
- _element.CustomControl.Value = TypeUtils.GetType(asTypename);
+ if (!HasDifferentValues && Values[0] is string asTypename &&
+ !string.Equals(asTypename, _lastTypeNameError, StringComparison.Ordinal))
+ {
+ try
+ {
+ _element.CustomControl.Value = TypeUtils.GetType(asTypename);
+ }
+ finally
+ {
+ if (_element.CustomControl.Value == null && asTypename.Length != 0)
+ _lastTypeNameError = asTypename;
+ }
+ }
}
}
}
diff --git a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
index e46040a42..07af5e991 100644
--- a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
+++ b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
///
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
///
- [System.Obsolete("Deprecated in 1.4")]
+ [System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public DoubleValueBox DoubleValue => ValueBox;
///
diff --git a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs
index 552e9d125..789d8966e 100644
--- a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs
+++ b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
///
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
///
- [System.Obsolete("Deprecated in 1.4, ValueBox instead")]
+ [System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public FloatValueBox FloatValue => ValueBox;
///
diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
index 2b2f0a3d3..93aacbd34 100644
--- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs
+++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
@@ -175,7 +175,7 @@ namespace FlaxEditor.CustomEditors.GUI
{
// Clear flag
_mouseOverSplitter = false;
-
+
if (_cursorChanged)
{
Cursor = CursorType.Default;
diff --git a/Source/Editor/CustomEditors/Values/CustomValueContainer.cs b/Source/Editor/CustomEditors/Values/CustomValueContainer.cs
index 5be61399b..9a09c4cc2 100644
--- a/Source/Editor/CustomEditors/Values/CustomValueContainer.cs
+++ b/Source/Editor/CustomEditors/Values/CustomValueContainer.cs
@@ -38,15 +38,12 @@ namespace FlaxEditor.CustomEditors
///
/// Type of the value.
/// The value getter.
- /// The value setter.
+ /// The value setter (can be null if value is read-only).
/// The custom type attributes used to override the value editor logic or appearance (eg. instance of ).
- public CustomValueContainer(ScriptType valueType, GetDelegate getter, SetDelegate setter, object[] attributes = null)
+ public CustomValueContainer(ScriptType valueType, GetDelegate getter, SetDelegate setter = null, object[] attributes = null)
: base(ScriptMemberInfo.Null, valueType)
{
- if (getter == null || setter == null)
- throw new ArgumentNullException();
-
- _getter = getter;
+ _getter = getter ?? throw new ArgumentNullException();
_setter = setter;
_attributes = attributes;
}
@@ -57,9 +54,9 @@ namespace FlaxEditor.CustomEditors
/// Type of the value.
/// The initial value.
/// The value getter.
- /// The value setter.
+ /// The value setter (can be null if value is read-only).
/// The custom type attributes used to override the value editor logic or appearance (eg. instance of ).
- public CustomValueContainer(ScriptType valueType, object initialValue, GetDelegate getter, SetDelegate setter, object[] attributes = null)
+ public CustomValueContainer(ScriptType valueType, object initialValue, GetDelegate getter, SetDelegate setter = null, object[] attributes = null)
: this(valueType, getter, setter, attributes)
{
Add(initialValue);
@@ -89,6 +86,8 @@ namespace FlaxEditor.CustomEditors
{
if (instanceValues == null || instanceValues.Count != Count)
throw new ArgumentException();
+ if (_setter == null)
+ return;
for (int i = 0; i < Count; i++)
{
@@ -105,6 +104,8 @@ namespace FlaxEditor.CustomEditors
throw new ArgumentException();
if (values == null || values.Count != Count)
throw new ArgumentException();
+ if (_setter == null)
+ return;
for (int i = 0; i < Count; i++)
{
@@ -120,6 +121,8 @@ namespace FlaxEditor.CustomEditors
{
if (instanceValues == null || instanceValues.Count != Count)
throw new ArgumentException();
+ if (_setter == null)
+ return;
for (int i = 0; i < Count; i++)
{
diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index 7f0359331..b384b6515 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -3,16 +3,17 @@
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;
using FlaxEditor.Content;
-using FlaxEditor.Content.Import;
using FlaxEditor.Content.Settings;
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;
@@ -152,12 +153,12 @@ namespace FlaxEditor
public ContentFindingModule ContentFinding;
///
- /// The scripts editing
+ /// The scripts editing.
///
public CodeEditingModule CodeEditing;
///
- /// The scripts documentation
+ /// The scripts documentation.
///
public CodeDocsModule CodeDocs;
@@ -177,7 +178,7 @@ namespace FlaxEditor
public ProjectCacheModule ProjectCache;
///
- /// The undo/redo
+ /// The undo/redo.
///
public EditorUndo Undo;
@@ -363,7 +364,7 @@ namespace FlaxEditor
{
foreach (var preview in activePreviews)
{
- if (preview == loadingPreview ||
+ if (preview == loadingPreview ||
(preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control))))
{
// Link it to the prefab preview to see it in the editor
@@ -724,8 +725,8 @@ namespace FlaxEditor
// Cleanup
Undo.Dispose();
- Surface.VisualScriptSurface.NodesCache.Clear();
- Surface.AnimGraphSurface.NodesCache.Clear();
+ foreach (var cache in Surface.VisjectSurface.NodesCache.Caches.ToArray())
+ cache.Clear();
Instance = null;
// Invoke new instance if need to open a project
@@ -795,7 +796,6 @@ namespace FlaxEditor
{
if (projectFilePath == null || !File.Exists(projectFilePath))
{
- // Error
MessageBox.Show("Missing project");
return;
}
@@ -931,21 +931,11 @@ namespace FlaxEditor
/// The .
///
Animation = 11,
- }
- ///
- /// Imports the audio asset file to the target location.
- ///
- /// The source file path.
- /// The result asset file path.
- /// The settings.
- /// True if importing failed, otherwise false.
- 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);
+ ///
+ /// The .
+ ///
+ BehaviorTree = 12,
}
///
@@ -1274,6 +1264,69 @@ namespace FlaxEditor
Scene.MarkSceneEdited(scenes);
}
+ ///
+ /// Bakes all environmental probes in the scene.
+ ///
+ 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;
+ });
+ }
+
+ ///
+ /// Builds CSG for all open scenes.
+ ///
+ public void BuildCSG()
+ {
+ var scenes = Level.Scenes;
+ scenes.ToList().ForEach(x => x.BuildCSG(0));
+ Scene.MarkSceneEdited(scenes);
+ }
+
+ ///
+ /// Builds Nav mesh for all open scenes.
+ ///
+ public void BuildNavMesh()
+ {
+ var scenes = Level.Scenes;
+ scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
+ Scene.MarkSceneEdited(scenes);
+ }
+
+ ///
+ /// Builds SDF for all static models in the scene.
+ ///
+ 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 +1655,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);
diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs
index 7fa920ca6..eb2f21356 100644
--- a/Source/Editor/EditorAssets.cs
+++ b/Source/Editor/EditorAssets.cs
@@ -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;
}
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index 3e5d22eb0..84f58daf1 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -5,6 +5,7 @@ using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
+using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -17,189 +18,21 @@ namespace FlaxEditor.GUI
///
///
[HideInEditor]
- public class AssetPicker : Control, IContentItemOwner
+ public class AssetPicker : Control
{
private const float DefaultIconSize = 64;
private const float ButtonsOffset = 2;
private const float ButtonsSize = 12;
- private Asset _selected;
- private ContentItem _selectedItem;
- private ScriptType _type;
- private string _fileExtension;
-
private bool _isMouseDown;
private Float2 _mouseDownPos;
private Float2 _mousePos;
private DragItems _dragOverElement;
///
- /// Gets or sets the selected item.
+ /// The asset validator. Used to ensure only appropriate items can be picked.
///
- public ContentItem SelectedItem
- {
- get => _selectedItem;
- set
- {
- if (_selectedItem == value)
- return;
- if (value == null)
- {
- if (_selected == null && _selectedItem is SceneItem)
- {
- // Deselect scene reference
- _selectedItem.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
- OnSelectedItemChanged();
- return;
- }
-
- // Deselect
- _selectedItem?.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
- OnSelectedItemChanged();
- }
- else if (value is SceneItem item)
- {
- if (_selectedItem == item)
- return;
- if (!IsValid(item))
- item = null;
-
- // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
- _selectedItem?.RemoveReference(this);
- _selectedItem = item;
- _selected = null;
- _selectedItem?.AddReference(this);
- OnSelectedItemChanged();
- }
- else if (value is AssetItem assetItem)
- {
- SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
- }
- else
- {
- // Change value
- _selectedItem?.RemoveReference(this);
- _selectedItem = value;
- _selected = null;
- OnSelectedItemChanged();
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset identifier.
- ///
- public Guid SelectedID
- {
- get
- {
- if (_selected != null)
- return _selected.ID;
- if (_selectedItem is AssetItem assetItem)
- return assetItem.ID;
- return Guid.Empty;
- }
- set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
- }
-
- ///
- /// Gets or sets the selected content item path.
- ///
- public string SelectedPath
- {
- get
- {
- string path = _selectedItem?.Path ?? _selected?.Path;
- if (path != null)
- {
- // Convert into path relative to the project (cross-platform)
- var projectFolder = Globals.ProjectFolder;
- if (path.StartsWith(projectFolder))
- path = path.Substring(projectFolder.Length + 1);
- }
- return path;
- }
- set
- {
- if (string.IsNullOrEmpty(value))
- {
- SelectedItem = null;
- }
- else
- {
- var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
- SelectedItem = Editor.Instance.ContentDatabase.Find(path);
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset object.
- ///
- public Asset SelectedAsset
- {
- get => _selected;
- set
- {
- // Check if value won't change
- if (value == _selected)
- return;
-
- // Find item from content database and check it
- var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
- if (item != null && !IsValid(item))
- item = null;
-
- // Change value
- _selectedItem?.RemoveReference(this);
- _selectedItem = item;
- _selected = value;
- _selectedItem?.AddReference(this);
- OnSelectedItemChanged();
- }
- }
-
- ///
- /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
- ///
- public ScriptType AssetType
- {
- get => _type;
- set
- {
- if (_type != value)
- {
- _type = value;
-
- // Auto deselect if the current value is invalid
- if (_selectedItem != null && !IsValid(_selectedItem))
- SelectedItem = null;
- }
- }
- }
-
- ///
- /// Gets or sets the content items extensions filter. Null if unused.
- ///
- public string FileExtension
- {
- get => _fileExtension;
- set
- {
- if (_fileExtension != value)
- {
- _fileExtension = value;
-
- // Auto deselect if the current value is invalid
- if (_selectedItem != null && !IsValid(_selectedItem))
- SelectedItem = null;
- }
- }
- }
+ public AssetPickerValidator Validator { get; }
///
/// Occurs when selected item gets changed.
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
///
public bool CanEdit = true;
- private bool IsValid(ContentItem item)
- {
- if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
- return false;
- if (CheckValid != null && !CheckValid(item))
- return false;
- if (_type == ScriptType.Null)
- return true;
-
- if (item is AssetItem assetItem)
- {
- // Faster path for binary items (in-built)
- if (assetItem is BinaryAssetItem binaryItem)
- return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
-
- // Type filter
- var type = TypeUtils.GetType(assetItem.TypeName);
- if (_type.IsAssignableFrom(type))
- return true;
-
- // Json assets can contain any type of the object defined by the C# type (data oriented design)
- if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
- return true;
-
- // Special case for scene asset references
- if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
- return true;
- }
-
- return false;
- }
-
///
/// Initializes a new instance of the class.
///
@@ -264,7 +65,8 @@ namespace FlaxEditor.GUI
public AssetPicker(ScriptType assetType, Float2 location)
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
{
- _type = assetType;
+ Validator = new AssetPickerValidator(assetType);
+ Validator.SelectedItemChanged += OnSelectedItemChanged;
_mousePos = Float2.Minimum;
}
@@ -275,10 +77,10 @@ namespace FlaxEditor.GUI
{
// Update tooltip
string tooltip;
- if (_selectedItem is AssetItem assetItem)
+ if (Validator.SelectedItem is AssetItem assetItem)
tooltip = assetItem.NamePath;
else
- tooltip = SelectedPath;
+ tooltip = Validator.SelectedPath;
TooltipText = tooltip;
SelectedItemChanged?.Invoke();
@@ -289,37 +91,13 @@ namespace FlaxEditor.GUI
// Do the drag drop operation if has selected element
if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos))
{
- if (_selected != null)
- DoDragDrop(DragAssets.GetDragData(_selected));
- else if (_selectedItem != null)
- DoDragDrop(DragItems.GetDragData(_selectedItem));
+ if (Validator.SelectedAsset != null)
+ DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset));
+ else if (Validator.SelectedItem != null)
+ DoDragDrop(DragItems.GetDragData(Validator.SelectedItem));
}
}
- ///
- public void OnItemDeleted(ContentItem item)
- {
- // Deselect item
- SelectedItem = null;
- }
-
- ///
- public void OnItemRenamed(ContentItem item)
- {
- }
-
- ///
- public void OnItemReimported(ContentItem item)
- {
- }
-
- ///
- public void OnItemDispose(ContentItem item)
- {
- // Deselect item
- SelectedItem = null;
- }
-
private Rectangle IconRect => new Rectangle(0, 0, Height, Height);
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize);
@@ -341,10 +119,10 @@ namespace FlaxEditor.GUI
if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
- if (_selectedItem != null)
+ if (Validator.SelectedItem != null)
{
// Draw item preview
- _selectedItem.DrawThumbnail(ref iconRect);
+ Validator.SelectedItem.DrawThumbnail(ref iconRect);
// Draw buttons
if (CanEdit)
@@ -363,7 +141,7 @@ namespace FlaxEditor.GUI
{
Render2D.DrawText(
style.FontSmall,
- _selectedItem.ShortName,
+ Validator.SelectedItem.ShortName,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground,
TextAlignment.Near,
@@ -371,7 +149,7 @@ namespace FlaxEditor.GUI
}
}
// Check if has no item but has an asset (eg. virtual asset)
- else if (_selected)
+ else if (Validator.SelectedAsset)
{
// Draw remove button
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
@@ -380,8 +158,8 @@ namespace FlaxEditor.GUI
float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30)
{
- var name = _selected.GetType().Name;
- if (_selected.IsVirtual)
+ var name = Validator.SelectedAsset.GetType().Name;
+ if (Validator.SelectedAsset.IsVirtual)
name += " (virtual)";
Render2D.DrawText(
style.FontSmall,
@@ -395,8 +173,8 @@ namespace FlaxEditor.GUI
else
{
// No element selected
- Render2D.FillRectangle(iconRect, new Color(0.2f));
- Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Wheat, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
+ Render2D.FillRectangle(iconRect, style.BackgroundNormal);
+ Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
}
// Check if drag is over
@@ -407,9 +185,7 @@ namespace FlaxEditor.GUI
///
public override void OnDestroy()
{
- _selectedItem?.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
+ Validator.OnDestroy();
base.OnDestroy();
}
@@ -463,57 +239,57 @@ namespace FlaxEditor.GUI
// Buttons logic
if (!CanEdit)
{
- if (Button1Rect.Contains(location) && _selectedItem != null)
+ if (Button1Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
- Editor.Instance.Windows.ContentWin.Select(_selectedItem);
+ Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
}
else if (Button1Rect.Contains(location))
{
Focus();
- if (_type != ScriptType.Null)
+ if (Validator.AssetType != ScriptType.Null)
{
// Show asset picker popup
- var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
- SelectedItem = item;
+ Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
- if (_selected != null)
- {
- var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
+ if (Validator.SelectedAsset != null)
+ {
+ var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
- var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
- SelectedItem = item;
+ Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
- if (_selectedItem != null)
+ if (Validator.SelectedItem != null)
{
- popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
+ popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
}
}
}
- else if (_selected != null || _selectedItem != null)
+ else if (Validator.SelectedAsset != null || Validator.SelectedItem != null)
{
- if (Button2Rect.Contains(location) && _selectedItem != null)
+ if (Button2Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
- Editor.Instance.Windows.ContentWin.Select(_selectedItem);
+ Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
else if (Button3Rect.Contains(location))
{
// Deselect asset
Focus();
- SelectedItem = null;
+ Validator.SelectedItem = null;
}
}
}
@@ -540,10 +316,10 @@ namespace FlaxEditor.GUI
{
Focus();
- if (_selectedItem != null && IconRect.Contains(location))
+ if (Validator.SelectedItem != null && IconRect.Contains(location))
{
// Open it
- Editor.Instance.ContentEditing.Open(_selectedItem);
+ Editor.Instance.ContentEditing.Open(Validator.SelectedItem);
}
// Handled
@@ -557,7 +333,7 @@ namespace FlaxEditor.GUI
// Check if drop asset
if (_dragOverElement == null)
- _dragOverElement = new DragItems(IsValid);
+ _dragOverElement = new DragItems(Validator.IsValid);
if (CanEdit && _dragOverElement.OnDragEnter(data))
{
}
@@ -590,7 +366,7 @@ namespace FlaxEditor.GUI
if (CanEdit && _dragOverElement.HasValidDrag)
{
// Select element
- SelectedItem = _dragOverElement.Objects[0];
+ Validator.SelectedItem = _dragOverElement.Objects[0];
}
// Clear cache
diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs
index da2106450..0417cc7e3 100644
--- a/Source/Editor/GUI/ComboBox.cs
+++ b/Source/Editor/GUI/ComboBox.cs
@@ -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";
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
index f2e5d30e3..25f45a1f8 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
@@ -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;
}
+ ///
+ /// Adds the button.
+ ///
+ /// The text.
+ /// The input binding.
+ /// On button clicked event.
+ /// Created context menu item control.
+ 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;
+ }
+
///
/// Gets the child menu (with that name).
///
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
index 7ecd197eb..628af18e1 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
@@ -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;
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs
index 7af36fae0..49a60a04e 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs
@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.ContextMenu
// Hide parent CM popups and set itself as child
parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0)));
}
-
+
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
index 161b3f4ae..27878a763 100644
--- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
+++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
@@ -319,7 +319,9 @@ namespace FlaxEditor.GUI.Dialogs
protected override void OnShow()
{
// Auto cancel on lost focus
+#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnCancel;
+#endif
base.OnShow();
}
diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs
index 8910cfe49..86e24e3b3 100644
--- a/Source/Editor/GUI/Dialogs/Dialog.cs
+++ b/Source/Editor/GUI/Dialogs/Dialog.cs
@@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs
///
public DialogResult Result => _result;
+ ///
+ /// Returns the size of the dialog.
+ ///
+ public Float2 DialogSize => _dialogSize;
+
///
/// Initializes a new instance of the class.
///
@@ -290,7 +295,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;
diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs
index 6dc700731..6e2353441 100644
--- a/Source/Editor/GUI/Docking/DockHintWindow.cs
+++ b/Source/Editor/GUI/Docking/DockHintWindow.cs
@@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking
var mousePos = window.MousePosition;
var previousSize = window.Size;
window.Restore();
- window.Position = FlaxEngine.Input.MouseScreenPosition - mousePos * window.Size / previousSize;
+ window.Position = Platform.MousePosition - mousePos * window.Size / previousSize;
}
// Calculate dragging offset and move window to the destination position
- var mouseScreenPosition = FlaxEngine.Input.MouseScreenPosition;
+ var mouseScreenPosition = Platform.MousePosition;
// If the _toMove window was not focused when initializing this window, the result vector only contains zeros
// and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler.
@@ -83,6 +83,7 @@ namespace FlaxEditor.GUI.Docking
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
+ Proxy.Window.Focus();
}
///
@@ -113,7 +114,7 @@ namespace FlaxEditor.GUI.Docking
var window = _toMove.Window?.Window;
if (window == null)
return;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
// Move base window
window.Position = mouse - _dragOffset;
@@ -193,7 +194,7 @@ namespace FlaxEditor.GUI.Docking
// Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel
@@ -244,7 +245,7 @@ namespace FlaxEditor.GUI.Docking
private void UpdateRects()
{
// Cache mouse position
- _mouse = FlaxEngine.Input.MouseScreenPosition;
+ _mouse = Platform.MousePosition;
// Check intersection with any dock panel
var uiMouse = _mouse;
@@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking
// Cache dock rectangles
var size = _rectDock.Size;
var offset = _rectDock.Location;
- float BorderMargin = 4.0f;
- float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f;
- float centerX = size.X * 0.5f;
- float centerY = size.Y * 0.5f;
- _rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
+ var borderMargin = 4.0f;
+ var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
+ var hintWindowsSize2 = hintWindowsSize * 0.5f;
+ var centerX = size.X * 0.5f;
+ var centerY = size.Y * 0.5f;
+ _rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test
DockState toSet = DockState.Float;
@@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking
{
if (Window == null)
{
- // Create proxy window
var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window";
settings.Size = initSize;
@@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
- settings.ShowAfterFirstPaint = true;
+ settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings);
-
- // Set opacity and background color
Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.DragWindow;
}
@@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
var settings = CreateWindowSettings.Default;
settings.Title = name;
- settings.Size = new Float2(HintWindowsSize);
+ settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
@@ -476,9 +475,9 @@ namespace FlaxEditor.GUI.Docking
settings.ShowInTaskbar = false;
settings.ActivateWhenFirstShown = false;
settings.IsTopmost = true;
+ settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
-
win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.DragWindow;
}
diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs
index e544285a5..b04aad08c 100644
--- a/Source/Editor/GUI/Docking/DockPanel.cs
+++ b/Source/Editor/GUI/Docking/DockPanel.cs
@@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking
{
if (Parent.Parent is SplitPanel splitter)
{
- // Check if has any child panels
- var childPanel = new List(_childPanels);
- for (int i = 0; i < childPanel.Count; i++)
+ // Check if there is another nested dock panel inside this dock panel and extract it here
+ var childPanels = _childPanels.ToArray();
+ if (childPanels.Length != 0)
{
- // Undock all tabs
- var panel = childPanel[i];
- int count = panel.TabsCount;
- while (count-- > 0)
+ // Move tabs from child panels into this one
+ DockWindow selectedTab = null;
+ foreach (var childPanel in childPanels)
{
- panel.GetTab(0).Close();
+ var childPanelTabs = childPanel.Tabs.ToArray();
+ for (var i = 0; i < childPanelTabs.Length; i++)
+ {
+ var childPanelTab = childPanelTabs[i];
+ if (selectedTab == null && childPanelTab.IsSelected)
+ selectedTab = childPanelTab;
+ childPanel.UndockWindow(childPanelTab);
+ AddTab(childPanelTab, false);
+ }
}
+ if (selectedTab != null)
+ SelectTab(selectedTab);
}
-
- // Unlink splitter
- var splitterParent = splitter.Parent;
- Assert.IsNotNull(splitterParent);
- splitter.Parent = null;
-
- // Move controls from second split panel to the split panel parent
- var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
- var srcPanelChildrenCount = scrPanel.ChildrenCount;
- for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
+ else
{
- scrPanel.GetChild(i).Parent = splitterParent;
- }
- Assert.IsTrue(scrPanel.ChildrenCount == 0);
- Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
+ // Unlink splitter
+ var splitterParent = splitter.Parent;
+ Assert.IsNotNull(splitterParent);
+ splitter.Parent = null;
- // Delete
- splitter.Dispose();
+ // Move controls from second split panel to the split panel parent
+ var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
+ var srcPanelChildrenCount = scrPanel.ChildrenCount;
+ for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
+ {
+ scrPanel.GetChild(i).Parent = splitterParent;
+ }
+ Assert.IsTrue(scrPanel.ChildrenCount == 0);
+ Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
+
+ // Delete
+ splitter.Dispose();
+ }
}
else if (!IsMaster)
{
@@ -582,19 +593,17 @@ namespace FlaxEditor.GUI.Docking
/// Adds the tab.
///
/// The window to insert as a tab.
- protected virtual void AddTab(DockWindow window)
+ /// True if auto-select newly added tab.
+ protected virtual void AddTab(DockWindow window, bool autoSelect = true)
{
- // Dock
_tabs.Add(window);
window.ParentDockPanel = this;
-
- // Select tab
- SelectTab(window);
+ if (autoSelect)
+ SelectTab(window);
}
private void CreateTabsProxy()
{
- // Check if has no tabs proxy created
if (_tabsProxy == null)
{
// Create proxy and make set simple full dock
diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs
index 15ff2cad0..e6e57de8e 100644
--- a/Source/Editor/GUI/Docking/DockPanelProxy.cs
+++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs
@@ -13,6 +13,7 @@ namespace FlaxEditor.GUI.Docking
public class DockPanelProxy : ContainerControl
{
private DockPanel _panel;
+ private double _dragEnterTime = -1;
///
/// The is mouse down flag (left button).
@@ -256,8 +257,8 @@ namespace FlaxEditor.GUI.Docking
else
{
tabColor = style.BackgroundHighlighted;
- Render2D.DrawLine(tabRect.BottomLeft - new Float2(0 , 1), tabRect.UpperLeft, tabColor);
- Render2D.DrawLine(tabRect.BottomRight - new Float2(0 , 1), tabRect.UpperRight, tabColor);
+ Render2D.DrawLine(tabRect.BottomLeft - new Float2(0, 1), tabRect.UpperLeft, tabColor);
+ Render2D.DrawLine(tabRect.BottomRight - new Float2(0, 1), tabRect.UpperRight, tabColor);
}
if (tab.Icon.IsValid)
@@ -477,11 +478,7 @@ namespace FlaxEditor.GUI.Docking
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- if (TrySelectTabUnderLocation(ref location))
- return DragDropEffect.Move;
-
- return DragDropEffect.None;
+ return TrySelectTabUnderLocation(ref location);
}
///
@@ -490,11 +487,15 @@ namespace FlaxEditor.GUI.Docking
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
+ return TrySelectTabUnderLocation(ref location);
+ }
- if (TrySelectTabUnderLocation(ref location))
- return DragDropEffect.Move;
+ ///
+ public override void OnDragLeave()
+ {
+ _dragEnterTime = -1;
- return DragDropEffect.None;
+ base.OnDragLeave();
}
///
@@ -503,17 +504,25 @@ namespace FlaxEditor.GUI.Docking
rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight);
}
- private bool TrySelectTabUnderLocation(ref Float2 location)
+ private DragDropEffect TrySelectTabUnderLocation(ref Float2 location)
{
var tab = GetTabAtPos(location, out _);
if (tab != null)
{
+ // Auto-select tab only if drag takes some time
+ var time = Platform.TimeSeconds;
+ if (_dragEnterTime < 0)
+ _dragEnterTime = time;
+ if (time - _dragEnterTime < 0.3f)
+ return DragDropEffect.Link;
+ _dragEnterTime = -1;
+
_panel.SelectTab(tab);
Update(0); // Fake update
- return true;
+ return DragDropEffect.Move;
}
-
- return false;
+ _dragEnterTime = -1;
+ return DragDropEffect.None;
}
private void ShowContextMenu(DockWindow tab, ref Float2 location)
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index f8ffe62ad..7bb85751a 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.Docking
settings.Size = size;
settings.Position = location;
settings.MinimumSize = new Float2(1);
- settings.MaximumSize = new Float2(4096);
+ settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
settings.SupportsTransparency = false;
diff --git a/Source/Editor/GUI/Drag/DragScripts.cs b/Source/Editor/GUI/Drag/DragScripts.cs
index e72d28479..875875ef3 100644
--- a/Source/Editor/GUI/Drag/DragScripts.cs
+++ b/Source/Editor/GUI/Drag/DragScripts.cs
@@ -58,7 +58,6 @@ namespace FlaxEditor.GUI.Drag
{
if (item == null)
throw new ArgumentNullException();
-
return new DragDataText(DragPrefix + item.ID.ToString("N"));
}
@@ -71,11 +70,9 @@ namespace FlaxEditor.GUI.Drag
{
if (items == null)
throw new ArgumentNullException();
-
string text = DragPrefix;
foreach (var item in items)
text += item.ID.ToString("N") + '\n';
-
return new DragDataText(text);
}
@@ -83,9 +80,7 @@ namespace FlaxEditor.GUI.Drag
/// Tries to parse the drag data.
///
/// The data.
- ///
- /// Gathered objects or empty IEnumerable if cannot get any valid.
- ///
+ /// Gathered objects or empty IEnumerable if cannot get any valid.
public override IEnumerable