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