Merge branch 'master' into add_spline_snap
This commit is contained in:
2
.github/workflows/build_android.yml
vendored
2
.github/workflows/build_android.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/build_ios.yml
vendored
2
.github/workflows/build_ios.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/build_linux.yml
vendored
4
.github/workflows/build_linux.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/build_mac.yml
vendored
4
.github/workflows/build_mac.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/build_windows.yml
vendored
4
.github/workflows/build_windows.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/tests.yml
vendored
10
.github/workflows/tests.yml
vendored
@@ -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: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,35 +2,34 @@
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace %namespace%
|
||||
namespace %namespace%;
|
||||
|
||||
/// <summary>
|
||||
/// %class% Script.
|
||||
/// </summary>
|
||||
public class %class% : Script
|
||||
{
|
||||
/// <summary>
|
||||
/// %class% Script.
|
||||
/// </summary>
|
||||
public class %class% : Script
|
||||
/// <inheritdoc/>
|
||||
public override void OnStart()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void OnStart()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is created, just before the first game update
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnEnable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is enabled (eg. register for events)
|
||||
}
|
||||
// Here you can add code that needs to be called when script is created, just before the first game update
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnEnable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is enabled (eg. register for events)
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnDisable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void OnDisable()
|
||||
{
|
||||
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// Here you can add code that needs to be called every frame
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// Here you can add code that needs to be called every frame
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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.",
|
||||
|
||||
@@ -256,6 +256,8 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deformer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=deformers/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Delaunay/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Defocus/@EntryIndexedValue">True</s:Boolean>
|
||||
@@ -291,6 +293,8 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lightmaps/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Linearize/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lods/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Marshallable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=marshallers/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mclass/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=memcpy/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=metalness/@EntryIndexedValue">True</s:Boolean>
|
||||
@@ -319,6 +323,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycast/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycasting/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=raycasts/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reachability/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=readback/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Reimports/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reimported/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a href="https://flaxengine.com/discord"><img src="https://discordapp.com/api/guilds/437989205315158016/widget.png"/></a>
|
||||
|
||||
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`
|
||||
|
||||
292
Source/Editor/Content/AssetPickerValidator.cs
Normal file
292
Source/Editor/Content/AssetPickerValidator.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.Content;
|
||||
|
||||
/// <summary>
|
||||
/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
|
||||
/// </summary>
|
||||
public class AssetPickerValidator : IContentItemOwner
|
||||
{
|
||||
private Asset _selected;
|
||||
private ContentItem _selectedItem;
|
||||
private ScriptType _type;
|
||||
private string _fileExtension;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected item.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset identifier.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected content item path.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset object.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content items extensions filter. Null if unused.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when selected item gets changed.
|
||||
/// </summary>
|
||||
public event Action SelectedItemChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
|
||||
/// </summary>
|
||||
public Func<ContentItem, bool> CheckValid;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether item is valid.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
|
||||
/// </summary>
|
||||
public AssetPickerValidator()
|
||||
: this(new ScriptType(typeof(Asset)))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assetType">The assets types that this picker accepts.</param>
|
||||
public AssetPickerValidator(ScriptType assetType)
|
||||
{
|
||||
_type = assetType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when selected item gets changed.
|
||||
/// </summary>
|
||||
protected virtual void OnSelectedItemChanged()
|
||||
{
|
||||
SelectedItemChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemDeleted(ContentItem item)
|
||||
{
|
||||
// Deselect item
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemRenamed(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemReimported(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemDispose(ContentItem item)
|
||||
{
|
||||
// Deselect item
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to remove reference from the selected item.
|
||||
/// </summary>
|
||||
public void OnDestroy()
|
||||
{
|
||||
_selectedItem?.RemoveReference(this);
|
||||
_selectedItem = null;
|
||||
_selected = null;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ContentItem>(_selection);
|
||||
for (int i = min; i <= max; i++)
|
||||
{
|
||||
|
||||
@@ -1,144 +1,52 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Interop;
|
||||
using FlaxEngine.Tools;
|
||||
|
||||
namespace FlaxEngine.Tools
|
||||
{
|
||||
partial class AudioTool
|
||||
{
|
||||
partial struct Options
|
||||
{
|
||||
private bool ShowBtiDepth => Format != AudioFormat.Vorbis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="FlaxEngine.Tools.AudioTool.Options"/>.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(FlaxEngine.Tools.AudioTool.Options)), DefaultEditor]
|
||||
public class AudioToolOptionsEditor : GenericEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override List<ItemInfo> GetItemsForType(ScriptType type)
|
||||
{
|
||||
// Show both fields and properties
|
||||
return GetItemsForType(type, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FlaxEditor.Content.Import
|
||||
{
|
||||
/// <summary>
|
||||
/// Proxy object to present audio import settings in <see cref="ImportFilesDialog"/>.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public class AudioImportSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom set of bit depth audio import sizes.
|
||||
/// The settings data.
|
||||
/// </summary>
|
||||
public enum CustomBitDepth
|
||||
{
|
||||
/// <summary>
|
||||
/// The 8.
|
||||
/// </summary>
|
||||
_8 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The 16.
|
||||
/// </summary>
|
||||
_16 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The 24.
|
||||
/// </summary>
|
||||
_24 = 24,
|
||||
|
||||
/// <summary>
|
||||
/// The 32.
|
||||
/// </summary>
|
||||
_32 = 32,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the bit depth to enum.
|
||||
/// </summary>
|
||||
/// <param name="f">The bit depth.</param>
|
||||
/// <returns>The converted enum.</returns>
|
||||
public static CustomBitDepth ConvertBitDepth(int f)
|
||||
{
|
||||
FieldInfo[] fields = typeof(CustomBitDepth).GetFields();
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
if (field.Name.Equals("value__"))
|
||||
continue;
|
||||
|
||||
if (f == (int)field.GetRawConstantValue())
|
||||
return (CustomBitDepth)f;
|
||||
}
|
||||
|
||||
return CustomBitDepth._16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The audio data format to import the audio clip as.
|
||||
/// </summary>
|
||||
[EditorOrder(10), DefaultValue(AudioFormat.Vorbis), Tooltip("The audio data format to import the audio clip as.")]
|
||||
public AudioFormat Format { get; set; } = AudioFormat.Vorbis;
|
||||
|
||||
/// <summary>
|
||||
/// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.
|
||||
/// </summary>
|
||||
[EditorOrder(15), DefaultValue(0.4f), Limit(0, 1, 0.01f), Tooltip("The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.")]
|
||||
public float CompressionQuality { get; set; } = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).
|
||||
/// </summary>
|
||||
[EditorOrder(20), DefaultValue(false), Tooltip("Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).")]
|
||||
public bool DisableStreaming { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.
|
||||
/// </summary>
|
||||
[EditorOrder(30), DefaultValue(false), EditorDisplay(null, "Is 3D"), Tooltip("Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.")]
|
||||
public bool Is3D { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The size of a single sample in bits. The clip will be converted to this bit depth on import.
|
||||
/// </summary>
|
||||
[EditorOrder(40), DefaultValue(CustomBitDepth._16), Tooltip("The size of a single sample in bits. The clip will be converted to this bit depth on import.")]
|
||||
public CustomBitDepth BitDepth { get; set; } = CustomBitDepth._16;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct InternalOptions
|
||||
{
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public AudioFormat Format;
|
||||
public byte DisableStreaming;
|
||||
public byte Is3D;
|
||||
public int BitDepth;
|
||||
public float Quality;
|
||||
}
|
||||
|
||||
internal void ToInternal(out InternalOptions options)
|
||||
{
|
||||
options = new InternalOptions
|
||||
{
|
||||
Format = Format,
|
||||
DisableStreaming = (byte)(DisableStreaming ? 1 : 0),
|
||||
Is3D = (byte)(Is3D ? 1 : 0),
|
||||
Quality = CompressionQuality,
|
||||
BitDepth = (int)BitDepth,
|
||||
};
|
||||
}
|
||||
|
||||
internal void FromInternal(ref InternalOptions options)
|
||||
{
|
||||
Format = options.Format;
|
||||
DisableStreaming = options.DisableStreaming != 0;
|
||||
Is3D = options.Is3D != 0;
|
||||
CompressionQuality = options.Quality;
|
||||
BitDepth = ConvertBitDepth(options.BitDepth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the restore the asset import options from the target resource file.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="assetPath">The asset path.</param>
|
||||
/// <returns>True settings has been restored, otherwise false.</returns>
|
||||
public static bool TryRestore(ref AudioImportSettings options, string assetPath)
|
||||
{
|
||||
if (AudioImportEntry.Internal_GetAudioImportOptions(assetPath, out var internalOptions))
|
||||
{
|
||||
// Restore settings
|
||||
options.FromInternal(ref internalOptions);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
[EditorDisplay(null, EditorDisplayAttribute.InlineStyle)]
|
||||
public AudioTool.Options Settings = AudioTool.Options.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,7 +55,7 @@ namespace FlaxEditor.Content.Import
|
||||
/// <seealso cref="AssetImportEntry" />
|
||||
public partial class AudioImportEntry : AssetImportEntry
|
||||
{
|
||||
private AudioImportSettings _settings = new AudioImportSettings();
|
||||
private AudioImportSettings _settings = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AudioImportEntry"/> class.
|
||||
@@ -157,7 +65,7 @@ namespace FlaxEditor.Content.Import
|
||||
: base(ref request)
|
||||
{
|
||||
// Try to restore target asset Audio import options (useful for fast reimport)
|
||||
AudioImportSettings.TryRestore(ref _settings, ResultUrl);
|
||||
Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -166,27 +74,23 @@ namespace FlaxEditor.Content.Import
|
||||
/// <inheritdoc />
|
||||
public override bool TryOverrideSettings(object settings)
|
||||
{
|
||||
if (settings is AudioImportSettings o)
|
||||
if (settings is AudioImportSettings s)
|
||||
{
|
||||
_settings = o;
|
||||
_settings.Settings = s.Settings;
|
||||
return true;
|
||||
}
|
||||
if (settings is AudioTool.Options o)
|
||||
{
|
||||
_settings.Settings = o;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Import()
|
||||
{
|
||||
return Editor.Import(SourceUrl, ResultUrl, _settings);
|
||||
return Editor.Import(SourceUrl, ResultUrl, _settings.Settings);
|
||||
}
|
||||
|
||||
#region Internal Calls
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "AudioImportEntryInternal_GetAudioImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_GetAudioImportOptions(string path, out AudioImportSettings.InternalOptions result);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace FlaxEditor.Content.Import
|
||||
var menu = new ContextMenu();
|
||||
menu.AddButton("Rename", OnRenameClicked);
|
||||
menu.AddButton("Don't import", OnDontImportClicked);
|
||||
menu.AddButton("Show in Explorer", OnShowInExplorerClicked);
|
||||
menu.AddButton(Utilities.Constants.ShowInExplorer, OnShowInExplorerClicked);
|
||||
menu.Tag = node;
|
||||
menu.Show(node, location);
|
||||
}
|
||||
|
||||
@@ -323,8 +323,6 @@ namespace FlaxEditor.Content
|
||||
/// <param name="value">The new path.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws the item thumbnail.
|
||||
/// </summary>
|
||||
@@ -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);
|
||||
|
||||
66
Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
Normal file
66
Source/Editor/Content/Proxy/BehaviorTreeProxy.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="BehaviorTree"/> asset proxy object.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
|
||||
[ContentContextMenu("New/AI/Behavior Tree")]
|
||||
public class BehaviorTreeProxy : BinaryAssetProxy
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Behavior Tree";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanReimport(ContentItem item)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override EditorWindow Open(Editor editor, ContentItem item)
|
||||
{
|
||||
return new BehaviorTreeWindow(editor, item as BinaryAssetItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Color AccentColor => Color.FromRGB(0x3256A8);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type AssetType => typeof(BehaviorTree);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanCreate(ContentFolder targetLocation)
|
||||
{
|
||||
return targetLocation.CanHaveAssets;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,12 +54,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
return _preview.HasLoadedAssets;
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool CanDrawThumbnail(ThumbnailRequest request)
|
||||
{
|
||||
return _preview.HasLoadedAssets;
|
||||
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -82,12 +82,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -30,6 +30,12 @@ namespace FlaxEditor.Content
|
||||
return item is SceneItem;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool AcceptsAsset(string typeName, string path)
|
||||
{
|
||||
return (typeName == Scene.AssetTypename || typeName == Scene.EditorPickerTypename) && path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanCreate(ContentFolder targetLocation)
|
||||
{
|
||||
|
||||
@@ -54,15 +54,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -57,11 +57,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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<PreviewsCache>(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--);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ namespace FlaxEditor.Content
|
||||
Folder.ParentFolder = parent.Folder;
|
||||
Parent = parent;
|
||||
}
|
||||
IconColor = Style.Current.Foreground;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Game building options. Used as flags.
|
||||
/// </summary>
|
||||
|
||||
@@ -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<String, String>(), 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
|
||||
|
||||
@@ -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<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
|
||||
for (auto& file : files)
|
||||
{
|
||||
if (FileSystem::GetExtension(file).IsEmpty())
|
||||
{
|
||||
executableFile = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <ThirdParty/pugixml/pugixml.hpp>
|
||||
#include <ThirdParty/pugixml/pugixml_extra.hpp>
|
||||
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<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
|
||||
for (auto& file : files)
|
||||
{
|
||||
if (FileSystem::GetExtension(file).IsEmpty())
|
||||
{
|
||||
executableFile = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String> 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,6 +37,23 @@ namespace FlaxEditor.CustomEditors
|
||||
UseDefault = 1 << 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The interface for Editor context that owns the presenter. Can be <see cref="FlaxEditor.Windows.PropertiesWindow"/> or <see cref="FlaxEditor.Windows.Assets.PrefabWindow"/> or other window/panel - custom editor scan use it for more specific features.
|
||||
/// </summary>
|
||||
public interface IPresenterOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the viewport linked with properties presenter (optional, null if unused).
|
||||
/// </summary>
|
||||
public Viewport.EditorViewport PresenterViewport { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Selects the scene objects.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The nodes to select</param>
|
||||
public void Select(List<SceneGraph.SceneGraphNode> nodes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main class for Custom Editors used to present selected objects properties and allow to modify them.
|
||||
/// </summary>
|
||||
@@ -68,8 +85,15 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <summary>
|
||||
/// The Editor context that owns this presenter. Can be <see cref="FlaxEditor.Windows.PropertiesWindow"/> or <see cref="FlaxEditor.Windows.Assets.PrefabWindow"/> or other window/panel - custom editor scan use it for more specific features.
|
||||
/// </summary>
|
||||
public object Owner;
|
||||
public IPresenterOwner Owner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text to show when no object is selected.
|
||||
@@ -270,7 +294,24 @@ namespace FlaxEditor.CustomEditors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value indicating whether properties are read-only.
|
||||
/// </summary>
|
||||
public bool ReadOnly
|
||||
{
|
||||
get => _readOnly;
|
||||
set
|
||||
{
|
||||
if (_readOnly != value)
|
||||
{
|
||||
_readOnly = value;
|
||||
UpdateReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _buildOnUpdate;
|
||||
private bool _readOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomEditorPresenter"/> class.
|
||||
@@ -278,7 +319,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// <param name="undo">The undo. It's optional.</param>
|
||||
/// <param name="noSelectionText">The custom text to display when no object is selected. Default is No selection.</param>
|
||||
/// <param name="owner">The owner of the presenter.</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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)
|
||||
|
||||
97
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
97
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="Cloth"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(Cloth)), DefaultEditor]
|
||||
class ClothEditor : ActorEditor
|
||||
{
|
||||
private ClothPaintingGizmoMode _gizmoMode;
|
||||
private Viewport.Modes.EditorGizmoMode _prevMode;
|
||||
|
||||
/// <inheritdoc />
|
||||
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<float>(cachedPaintValue);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoContinuousPaint", out var cachedContinuousPaint))
|
||||
_gizmoMode.ContinuousPaint = JsonSerializer.Deserialize<bool>(cachedContinuousPaint);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushFalloff", out var cachedBrushFalloff))
|
||||
_gizmoMode.BrushFalloff = JsonSerializer.Deserialize<float>(cachedBrushFalloff);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushSize", out var cachedBrushSize))
|
||||
_gizmoMode.BrushSize = JsonSerializer.Deserialize<float>(cachedBrushSize);
|
||||
if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength))
|
||||
_gizmoMode.BrushStrength = JsonSerializer.Deserialize<float>(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<UniformGridPanel>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
349
Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
Normal file
349
Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="ModelInstanceActor.MeshReference"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
_mousePos = Float2.Minimum;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSubmit()
|
||||
{
|
||||
base.OnSubmit();
|
||||
|
||||
ShowDropDownMenu();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
MeshNames = null;
|
||||
_valueName = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
private ModelInstanceActor _actor;
|
||||
private CustomElement<FlaxObjectRefPickerControl> _actorPicker;
|
||||
private CustomElement<MeshRefPickerControl> _meshPicker;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
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<FlaxObjectRefPickerControl>();
|
||||
_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<MeshRefPickerControl>();
|
||||
_meshPicker.CustomControl.MeshNames = meshNames;
|
||||
_meshPicker.CustomControl.Value = (ModelInstanceActor.MeshReference)Values[0];
|
||||
_meshPicker.CustomControl.ValueChanged += () => SetValue(_meshPicker.CustomControl.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
156
Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
Normal file
156
Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The missing script editor.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(MissingScript)), DefaultEditor]
|
||||
public class MissingScriptEditor : GenericEditor
|
||||
{
|
||||
private DropPanel _dropPanel;
|
||||
private Button _replaceScriptButton;
|
||||
private CheckBox _shouldReplaceAllCheckbox;
|
||||
|
||||
/// <inheritdoc />
|
||||
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<MissingScript> 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<IUndoAction> 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<IUndoAction>(4);
|
||||
|
||||
var missingScripts = new List<MissingScript>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<UniformGridPanel>();
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
private DragHandlers _dragHandlers;
|
||||
private DragScriptItems _dragScripts;
|
||||
private DragAssets _dragAssets;
|
||||
private Button _addScriptsButton;
|
||||
|
||||
/// <summary>
|
||||
/// The parent scripts editor.
|
||||
@@ -40,16 +41,19 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
AutoFocus = false;
|
||||
|
||||
// Add script button
|
||||
float addScriptButtonWidth = 60.0f;
|
||||
var addScriptButton = new Button
|
||||
var buttonText = "Add script";
|
||||
var textSize = Style.Current.FontMedium.MeasureText(buttonText);
|
||||
float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4;
|
||||
var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4;
|
||||
_addScriptsButton = new Button
|
||||
{
|
||||
TooltipText = "Add new scripts to the actor",
|
||||
AnchorPreset = AnchorPresets.MiddleCenter,
|
||||
Text = "Add script",
|
||||
Text = buttonText,
|
||||
Parent = this,
|
||||
Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, 18),
|
||||
Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, buttonHeight),
|
||||
};
|
||||
addScriptButton.ButtonClicked += OnAddScriptButtonClicked;
|
||||
_addScriptsButton.ButtonClicked += OnAddScriptButtonClicked;
|
||||
}
|
||||
|
||||
private void OnAddScriptButtonClicked(Button button)
|
||||
@@ -82,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var size = Size;
|
||||
|
||||
// Info
|
||||
Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, 22, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
||||
Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag)
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Image" />
|
||||
internal class ScriptDragIcon : Image
|
||||
internal class DragImage : Image
|
||||
{
|
||||
private ScriptsEditor _editor;
|
||||
private bool _isMouseDown;
|
||||
private Float2 _mouseDownPos;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target script.
|
||||
/// Action called when drag event should start.
|
||||
/// </summary>
|
||||
public Script Script => (Script)Tag;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptDragIcon"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The script editor.</param>
|
||||
/// <param name="script">The target script.</param>
|
||||
public ScriptDragIcon(ScriptsEditor editor, Script script)
|
||||
{
|
||||
Tag = script;
|
||||
_editor = editor;
|
||||
}
|
||||
public Action<DragImage> Drag;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
@@ -287,11 +279,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <inheritdoc />
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="BehaviorKnowledgeSelector{T}"/> and <see cref="BehaviorKnowledgeSelectorAny"/>.
|
||||
/// </summary>
|
||||
public sealed class BehaviorKnowledgeSelectorEditor : CustomEditor
|
||||
{
|
||||
private ClickableLabel _label;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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 = "<none>",
|
||||
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<TreeNode> before, List<TreeNode> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DragAreaControl>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragHandlers.OnDragLeave();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// Describes object property/field information for custom editors pipeline.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IComparable" />
|
||||
protected class ItemInfo : IComparable
|
||||
public class ItemInfo : IComparable
|
||||
{
|
||||
private Options.GeneralOptions.MembersOrder _membersOrder;
|
||||
|
||||
@@ -247,8 +247,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="useProperties">True if use type properties.</param>
|
||||
/// <param name="useFields">True if use type fields.</param>
|
||||
/// <param name="usePropertiesWithoutSetter">True if use type properties that have only getter method without setter method (aka read-only).</param>
|
||||
/// <returns>The items.</returns>
|
||||
protected List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields)
|
||||
public static List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields, bool usePropertiesWithoutSetter = false)
|
||||
{
|
||||
var items = new List<ItemInfo>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
@@ -20,23 +22,30 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var dropdownElement = layout.Custom<Dropdown>();
|
||||
_dropdown = dropdownElement.CustomControl;
|
||||
var names = new List<LocalizedString>();
|
||||
LinkedLabel.SetupContextMenu += OnSetupContextMenu;
|
||||
var comboBoxElement = layout.ComboBox();
|
||||
_comboBox = comboBoxElement.ComboBox;
|
||||
var names = new List<string>();
|
||||
foreach (var mapping in Input.ActionMappings)
|
||||
{
|
||||
if (!names.Contains(mapping.Name))
|
||||
names.Add(mapping.Name);
|
||||
}
|
||||
_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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
@@ -77,23 +90,30 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var dropdownElement = layout.Custom<Dropdown>();
|
||||
_dropdown = dropdownElement.CustomControl;
|
||||
var names = new List<LocalizedString>();
|
||||
LinkedLabel.SetupContextMenu += OnSetupContextMenu;
|
||||
var comboBoxElement = layout.ComboBox();
|
||||
_comboBox = comboBoxElement.ComboBox;
|
||||
var names = new List<string>();
|
||||
foreach (var mapping in Input.AxisMappings)
|
||||
{
|
||||
if (!names.Contains(mapping.Name))
|
||||
names.Add(mapping.Name);
|
||||
}
|
||||
_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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -623,13 +623,18 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
_label = layout.ClickableLabel(GetText(out _)).CustomControl;
|
||||
_label.RightClick += ShowPicker;
|
||||
var buttonText = "...";
|
||||
var button = new Button
|
||||
{
|
||||
Size = new Float2(16.0f),
|
||||
Text = "...",
|
||||
Text = buttonText,
|
||||
TooltipText = "Edit...",
|
||||
Parent = _label,
|
||||
};
|
||||
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText);
|
||||
if (textSize.Y > button.Width)
|
||||
button.Width = textSize.Y + 2;
|
||||
|
||||
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
|
||||
button.Clicked += ShowPicker;
|
||||
}
|
||||
@@ -674,9 +679,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Values[0] is Tag[])
|
||||
if (Values[0] is Tag[] || Values.Type.Type == typeof(Tag[]))
|
||||
SetValue(value);
|
||||
if (Values[0] is List<Tag>)
|
||||
else if (Values[0] is List<Tag> || Values.Type.Type == typeof(List<Tag>))
|
||||
SetValue(new List<Tag>(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +464,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// </summary>
|
||||
public class TypeNameEditor : TypeEditorBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents spamming log if Value contains missing type to skip research in subsequential Refresh ticks.
|
||||
/// </summary>
|
||||
private string _lastTypeNameError;
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
/// <summary>
|
||||
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
|
||||
/// </summary>
|
||||
[System.Obsolete("Deprecated in 1.4")]
|
||||
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
|
||||
public DoubleValueBox DoubleValue => ValueBox;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
/// <summary>
|
||||
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
|
||||
/// </summary>
|
||||
[System.Obsolete("Deprecated in 1.4, ValueBox instead")]
|
||||
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
|
||||
public FloatValueBox FloatValue => ValueBox;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace FlaxEditor.CustomEditors.GUI
|
||||
{
|
||||
// Clear flag
|
||||
_mouseOverSplitter = false;
|
||||
|
||||
|
||||
if (_cursorChanged)
|
||||
{
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
@@ -38,15 +38,12 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="setter">The value setter (can be null if value is read-only).</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
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
|
||||
/// <param name="valueType">Type of the value.</param>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
/// <param name="getter">The value getter.</param>
|
||||
/// <param name="setter">The value setter.</param>
|
||||
/// <param name="setter">The value setter (can be null if value is read-only).</param>
|
||||
/// <param name="attributes">The custom type attributes used to override the value editor logic or appearance (eg. instance of <see cref="LimitAttribute"/>).</param>
|
||||
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++)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The scripts editing
|
||||
/// The scripts editing.
|
||||
/// </summary>
|
||||
public CodeEditingModule CodeEditing;
|
||||
|
||||
/// <summary>
|
||||
/// The scripts documentation
|
||||
/// The scripts documentation.
|
||||
/// </summary>
|
||||
public CodeDocsModule CodeDocs;
|
||||
|
||||
@@ -177,7 +178,7 @@ namespace FlaxEditor
|
||||
public ProjectCacheModule ProjectCache;
|
||||
|
||||
/// <summary>
|
||||
/// The undo/redo
|
||||
/// The undo/redo.
|
||||
/// </summary>
|
||||
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 <see cref="FlaxEngine.Animation"/>.
|
||||
/// </summary>
|
||||
Animation = 11,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports the audio asset file to the target location.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The source file path.</param>
|
||||
/// <param name="outputPath">The result asset file path.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>True if importing failed, otherwise false.</returns>
|
||||
public static bool Import(string inputPath, string outputPath, AudioImportSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
throw new ArgumentNullException();
|
||||
settings.ToInternal(out var internalOptions);
|
||||
return Internal_ImportAudio(inputPath, outputPath, ref internalOptions);
|
||||
/// <summary>
|
||||
/// The <see cref="FlaxEngine.BehaviorTree"/>.
|
||||
/// </summary>
|
||||
BehaviorTree = 12,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1274,6 +1264,69 @@ namespace FlaxEditor
|
||||
Scene.MarkSceneEdited(scenes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bakes all environmental probes in the scene.
|
||||
/// </summary>
|
||||
public void BakeAllEnvProbes()
|
||||
{
|
||||
Scene.ExecuteOnGraph(node =>
|
||||
{
|
||||
if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive)
|
||||
{
|
||||
((EnvironmentProbe)envProbeNode.Actor).Bake();
|
||||
node.ParentScene.IsEdited = true;
|
||||
}
|
||||
else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene)
|
||||
{
|
||||
skyLight.Bake();
|
||||
node.ParentScene.IsEdited = true;
|
||||
}
|
||||
|
||||
return node.IsActive;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds CSG for all open scenes.
|
||||
/// </summary>
|
||||
public void BuildCSG()
|
||||
{
|
||||
var scenes = Level.Scenes;
|
||||
scenes.ToList().ForEach(x => x.BuildCSG(0));
|
||||
Scene.MarkSceneEdited(scenes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds Nav mesh for all open scenes.
|
||||
/// </summary>
|
||||
public void BuildNavMesh()
|
||||
{
|
||||
var scenes = Level.Scenes;
|
||||
scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
|
||||
Scene.MarkSceneEdited(scenes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds SDF for all static models in the scene.
|
||||
/// </summary>
|
||||
public void BuildAllMeshesSDF()
|
||||
{
|
||||
// TODO: async maybe with progress reporting?
|
||||
Scene.ExecuteOnGraph(node =>
|
||||
{
|
||||
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
|
||||
{
|
||||
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
|
||||
{
|
||||
Log("Generating SDF for " + staticModel.Model);
|
||||
if (!staticModel.Model.GenerateSDF())
|
||||
staticModel.Model.Save();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Calls
|
||||
@@ -1602,10 +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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <seealso cref="Control" />
|
||||
/// <seealso cref="IContentItemOwner" />
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected item.
|
||||
/// The asset validator. Used to ensure only appropriate items can be picked.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset identifier.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected content item path.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected asset object.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use <see cref="ScriptType.Null"/> for generic file picker.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content items extensions filter. Null if unused.
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when selected item gets changed.
|
||||
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPicker"/> class.
|
||||
/// </summary>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemDeleted(ContentItem item)
|
||||
{
|
||||
// Deselect item
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemRenamed(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnItemReimported(ContentItem item)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <inheritdoc />
|
||||
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
|
||||
|
||||
@@ -545,7 +545,7 @@ namespace FlaxEditor.GUI
|
||||
Render2D.DrawRectangle(clientRect.MakeExpanded(-2.0f), borderColor);
|
||||
|
||||
// Check if has selected item
|
||||
if (_selectedIndices.Count > 0)
|
||||
if (_selectedIndices != null && _selectedIndices.Count > 0)
|
||||
{
|
||||
string text = _selectedIndices.Count == 1 ? _items[_selectedIndices[0]] : "Multiple Values";
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -269,6 +270,24 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the button.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="binding">The input binding.</param>
|
||||
/// <param name="clicked">On button clicked event.</param>
|
||||
/// <returns>Created context menu item control.</returns>
|
||||
public ContextMenuButton AddButton(string text, InputBinding binding, Action clicked)
|
||||
{
|
||||
var item = new ContextMenuButton(this, text, binding.ToString())
|
||||
{
|
||||
Parent = _panel
|
||||
};
|
||||
item.Clicked += clicked;
|
||||
SortButtons();
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child menu (with that name).
|
||||
/// </summary>
|
||||
|
||||
@@ -154,6 +154,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
}
|
||||
|
||||
// Unlock and perform controls update
|
||||
Location = Float2.Zero;
|
||||
UnlockChildrenRecursive();
|
||||
PerformLayout();
|
||||
|
||||
@@ -162,7 +163,6 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
var dpiSize = Size * dpiScale;
|
||||
var locationWS = parent.PointToWindow(location);
|
||||
var locationSS = parentWin.PointToScreen(locationWS);
|
||||
Location = Float2.Zero;
|
||||
var monitorBounds = Platform.GetMonitorBounds(locationSS);
|
||||
var rightBottomLocationSS = locationSS + dpiSize;
|
||||
bool isUp = false, isLeft = false;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs
|
||||
/// </summary>
|
||||
public DialogResult Result => _result;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size of the dialog.
|
||||
/// </summary>
|
||||
public Float2 DialogSize => _dialogSize;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Dialog"/> class.
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
if (Parent.Parent is SplitPanel splitter)
|
||||
{
|
||||
// Check if has any child panels
|
||||
var childPanel = new List<DockPanel>(_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.
|
||||
/// </summary>
|
||||
/// <param name="window">The window to insert as a tab.</param>
|
||||
protected virtual void AddTab(DockWindow window)
|
||||
/// <param name="autoSelect">True if auto-select newly added tab.</param>
|
||||
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
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
public class DockPanelProxy : ContainerControl
|
||||
{
|
||||
private DockPanel _panel;
|
||||
private double _dragEnterTime = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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;
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragEnterTime = -1;
|
||||
|
||||
return DragDropEffect.None;
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>
|
||||
/// Gathered objects or empty IEnumerable if cannot get any valid.
|
||||
/// </returns>
|
||||
/// <returns>Gathered objects or empty IEnumerable if cannot get any valid.</returns>
|
||||
public override IEnumerable<Script> FromDragData(DragData data)
|
||||
{
|
||||
if (data is DragDataText dataText)
|
||||
@@ -97,12 +92,9 @@ namespace FlaxEditor.GUI.Drag
|
||||
var results = new List<Script>(ids.Length);
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
// Find element
|
||||
if (Guid.TryParse(ids[i], out Guid id))
|
||||
{
|
||||
var obj = FlaxEngine.Object.Find<Script>(ref id);
|
||||
|
||||
// Check it
|
||||
if (obj != null)
|
||||
results.Add(obj);
|
||||
}
|
||||
@@ -111,11 +103,11 @@ namespace FlaxEditor.GUI.Drag
|
||||
return results.ToArray();
|
||||
}
|
||||
}
|
||||
return new Script[0];
|
||||
return Utils.GetEmptyArray<Script>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the drag data to validate if it has valid scripts darg.
|
||||
/// Tries to parse the drag data to validate if it has valid scripts drag.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>True if drag data has valid scripts, otherwise false.</returns>
|
||||
@@ -138,7 +130,6 @@ namespace FlaxEditor.GUI.Drag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user