Merge branch 'master' into add_spline_snap

This commit is contained in:
Mr. Capybara
2023-11-27 14:59:10 -04:00
891 changed files with 37625 additions and 10756 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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.

BIN
Content/Editor/Particles/Smoke.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Particles/Sparks.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -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.

View File

@@ -11,16 +11,6 @@ for %%I in (Source\Logo.png) do if %%~zI LSS 2000 (
call "Development\Scripts\Windows\GetMSBuildPath.bat"
if errorlevel 1 goto Error_NoVisualStudioEnvironment
if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto Compile
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
for %%j in (15.0, Current) do (
if exist "%%i\MSBuild\%%j\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\%%j\Bin\MSBuild.exe"
goto Compile
)
)
)
:Compile
md Cache\Intermediate >nul 2>nul
dir /s /b Source\Tools\Flax.Build\*.cs >Cache\Intermediate\Flax.Build.Files.txt
@@ -44,7 +34,7 @@ goto Exit
echo CallBuildTool ERROR: The script is in invalid directory.
goto Exit
:Error_NoVisualStudioEnvironment
echo CallBuildTool ERROR: Missing Visual Studio 2015 or newer.
echo CallBuildTool ERROR: Missing Visual Studio 2022 or newer.
goto Exit
:Error_CompilationFailed
echo CallBuildTool ERROR: Failed to compile Flax.Build project.

View File

@@ -4,66 +4,26 @@ rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
set MSBUILD_PATH=
rem Look for MSBuild version 17.0 or later
if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto VsWhereNotFound
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe"
goto End
)
)
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe"
goto End
)
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe"
goto End
)
)
:VsWhereNotFound
if exist "%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" (
set MSBUILD_PATH="%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe"
goto End
rem Look for MSBuild version 17.0 or later in pre-release versions
for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do (
if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" (
set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe"
goto End
)
)
call :GetInstallPath Microsoft\VisualStudio\SxS\VS7 15.0 MSBuild\15.0\bin\MSBuild.exe
if not errorlevel 1 goto End
call :GetInstallPath Microsoft\MSBuild\ToolsVersions\14.0 MSBuildToolsPath MSBuild.exe
if not errorlevel 1 goto End
call :GetInstallPath Microsoft\MSBuild\ToolsVersions\12.0 MSBuildToolsPath MSBuild.exe
if not errorlevel 1 goto End
call :GetInstallPath Microsoft\MSBuild\ToolsVersions\4.0 MSBuildToolsPath MSBuild.exe
if not errorlevel 1 goto End
echo GetMSBuildPath ERROR: Could not find MSBuild version 17.0 or later.
exit /B 1
:VsWhereNotFound
echo GetMSBuildPath ERROR: vswhere.exe was not found.
exit /B 1
:End
exit /B 0
:GetInstallPath
for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\%1 /v %2 2^>Nul') do (
if exist "%%B%%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\%1 /v %2 2^>Nul') do (
if exist "%%B%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do (
if exist "%%B%%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do (
if exist "%%B%3" (
set MSBUILD_PATH="%%B%3"
exit /B 0
)
)
exit /B 1
exit /B 0

View File

@@ -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.",

View File

@@ -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>

View File

@@ -1,15 +1,22 @@
@echo off
rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
:: Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
setlocal
pushd
echo Generating Flax Engine project files...
rem Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments)
call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %*
:: Change the path to the script root
cd /D "%~dp0"
:: Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments)
call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %*
if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations
echo Building C# bindings...
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
popd
echo Done!
exit /B 0

View File

@@ -10,3 +10,8 @@ cd "`dirname "$0"`"
# Run Flax.Build to generate project files (also pass the arguments)
bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor

View File

@@ -10,3 +10,8 @@ cd "`dirname "$0"`"
# Run Flax.Build to generate project files (also pass the arguments)
bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor

View File

@@ -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

View File

@@ -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

View File

@@ -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" "$@"

View File

@@ -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" "$@"

View File

@@ -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

View File

@@ -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" "$@"

View File

@@ -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" "$@"

View File

@@ -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`

View 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;
}
}

View File

@@ -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;

View File

@@ -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++)
{

View File

@@ -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
}
}

View File

@@ -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);
}

View File

@@ -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);

View 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
});
}
}
}

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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)
{

View File

@@ -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 />

View File

@@ -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 />

View File

@@ -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--);
}
}

View File

@@ -86,6 +86,7 @@ namespace FlaxEditor.Content
Folder.ParentFolder = parent.Folder;
Parent = parent;
}
IconColor = Style.Current.Foreground;
}
/// <summary>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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));

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View 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();
}
}
}

View 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;
}
}
}

View 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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -175,7 +175,7 @@ namespace FlaxEditor.CustomEditors.GUI
{
// Clear flag
_mouseOverSplitter = false;
if (_cursorChanged)
{
Cursor = CursorType.Default;

View File

@@ -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++)
{

View File

@@ -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);

View File

@@ -36,7 +36,9 @@ namespace FlaxEditor
public static void OnEditorOptionsChanged(Options.EditorOptions options)
{
var param = _highlightMaterial?.GetParameter("Color");
if (!_highlightMaterial)
return;
var param = _highlightMaterial.GetParameter("Color");
if (param != null)
param.Value = options.Visual.HighlightColor;
}

View File

@@ -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

View File

@@ -545,7 +545,7 @@ namespace FlaxEditor.GUI
Render2D.DrawRectangle(clientRect.MakeExpanded(-2.0f), borderColor);
// Check if has selected item
if (_selectedIndices.Count > 0)
if (_selectedIndices != null && _selectedIndices.Count > 0)
{
string text = _selectedIndices.Count == 1 ? _items[_selectedIndices[0]] : "Multiple Values";

View File

@@ -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>

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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;

View File

@@ -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