Merge remote-tracking branch 'upstream/master'

This commit is contained in:
rkrahn
2024-03-29 13:38:38 -07:00
329 changed files with 7133 additions and 3928 deletions

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Setup .NET Workload - name: Setup .NET Workload
run: | run: |
dotnet workload install android dotnet workload install android
@@ -33,4 +33,4 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Setup .NET Workload - name: Setup .NET Workload
run: | run: |
dotnet workload install ios dotnet workload install ios
@@ -33,4 +33,4 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
./Development/Scripts/Mac/CallBuildTool.sh -build -log -dotnet=7 -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame ./Development/Scripts/Mac/CallBuildTool.sh -build -log -dotnet=8 -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -36,7 +36,7 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
# Game # Game
game-linux: game-linux:
@@ -53,7 +53,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -64,4 +64,4 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -30,7 +30,7 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
# Game # Game
game-mac: game-mac:
@@ -44,7 +44,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -55,4 +55,4 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -30,7 +30,7 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
# Game # Game
game-windows: game-windows:
@@ -44,7 +44,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -55,4 +55,4 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -59,7 +59,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -95,7 +95,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -129,7 +129,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -159,7 +159,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -187,7 +187,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info

View File

@@ -17,7 +17,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -34,21 +34,21 @@ 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 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 - name: Build
run: | run: |
./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=7 ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=8
./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=8 -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
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 - name: Test
run: | run: |
${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests ${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests
dotnet test -f net7.0 Binaries/Tests/Flax.Build.Tests.dll dotnet test -f net8.0 Binaries/Tests/Flax.Build.Tests.dll
cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.dll Binaries/Tests cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.dll Binaries/Tests
cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.runtimeconfig.json Binaries/Tests cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.runtimeconfig.json Binaries/Tests
cp Binaries/Editor/Linux/Development/Newtonsoft.Json.dll Binaries/Tests cp Binaries/Editor/Linux/Development/Newtonsoft.Json.dll Binaries/Tests
dotnet test -f net7.0 Binaries/Tests/FlaxEngine.CSharp.dll dotnet test -f net8.0 Binaries/Tests/FlaxEngine.CSharp.dll
- name: Test UseLargeWorlds - name: Test UseLargeWorlds
run: | run: |
./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=8 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true
${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests ${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests
# Tests on Windows # Tests on Windows
@@ -61,7 +61,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Print .NET info - name: Print .NET info
run: | run: |
dotnet --info dotnet --info
@@ -72,14 +72,14 @@ jobs:
git lfs pull git lfs pull
- name: Build - name: Build
run: | run: |
.\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=7 .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=8
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget .\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -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 dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test - name: Test
run: | run: |
.\Binaries\Editor\Win64\Development\FlaxTests.exe .\Binaries\Editor\Win64\Development\FlaxTests.exe
dotnet test -f net7.0 Binaries\Tests\Flax.Build.Tests.dll dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll
xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests
xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests
xcopy /y Binaries\Editor\Win64\Development\Newtonsoft.Json.dll Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\Newtonsoft.Json.dll Binaries\Tests
dotnet test -f net7.0 Binaries\Tests\FlaxEngine.CSharp.dll dotnet test -f net8.0 Binaries\Tests\FlaxEngine.CSharp.dll

BIN
Content/Shaders/DebugDraw.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Shaders/TAA.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -2,9 +2,9 @@
"Name": "Flax", "Name": "Flax",
"Version": { "Version": {
"Major": 1, "Major": 1,
"Minor": 7, "Minor": 8,
"Revision": 2, "Revision": 0,
"Build": 6408 "Build": 6510
}, },
"Company": "Flax", "Company": "Flax",
"Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.", "Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.",

View File

@@ -7,7 +7,7 @@ pushd
echo Performing the full package... echo Performing the full package...
rem Run the build tool. rem Run the build tool.
call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -dotnet=8 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed if errorlevel 1 goto BuildToolFailed
popd popd

View File

@@ -7,7 +7,7 @@ pushd
echo Building and packaging Flax Editor... echo Building and packaging Flax Editor...
rem Run the build tool. rem Run the build tool.
call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -dotnet=8 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed if errorlevel 1 goto BuildToolFailed
popd popd

View File

@@ -9,4 +9,4 @@ echo Building and packaging Flax Editor...
cd "`dirname "$0"`" cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments) # Run Flax.Build (also pass the arguments)
bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"

View File

@@ -9,4 +9,4 @@ echo Building and packaging Flax Editor...
cd "`dirname "$0"`" cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments) # Run Flax.Build (also pass the arguments)
bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"

View File

@@ -7,7 +7,7 @@ pushd
echo Building and packaging platforms data... echo Building and packaging platforms data...
rem Run the build tool. rem Run the build tool.
call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -dotnet=8 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed if errorlevel 1 goto BuildToolFailed
popd popd

View File

@@ -9,4 +9,4 @@ echo Building and packaging platforms data...
cd "`dirname "$0"`" cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments) # Run Flax.Build (also pass the arguments)
bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"

View File

@@ -9,4 +9,4 @@ echo Building and packaging platforms data...
cd "`dirname "$0"`" cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments) # Run Flax.Build (also pass the arguments)
bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"

View File

@@ -31,7 +31,7 @@ Follow the instructions below to compile and run the engine from source.
* Install Visual Studio 2022 or newer * Install Visual Studio 2022 or newer
* Install Windows 8.1 SDK or newer (via Visual Studio Installer) * 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 Microsoft Visual C++ 2015 v140 toolset or newer (via Visual Studio Installer)
* 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 .NET 8 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/8.0))
* Install Git with LFS * Install Git with LFS
* Clone repo (with LFS) * Clone repo (with LFS)
* Run **GenerateProjectFiles.bat** * Run **GenerateProjectFiles.bat**
@@ -44,8 +44,8 @@ Follow the instructions below to compile and run the engine from source.
## Linux ## Linux
* Install Visual Studio Code * 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 8 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0))
* Ubuntu: `sudo apt install dotnet-sdk-7.0` * Ubuntu: `sudo apt install dotnet-sdk-8.0`
* Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
* Ubuntu: `sudo apt install vulkan-sdk` * Ubuntu: `sudo apt install vulkan-sdk`
* Arch: `sudo pacman -S spirv-tools vulkan-headers vulkan-tools vulkan-validation-layers` * Arch: `sudo pacman -S spirv-tools vulkan-headers vulkan-tools vulkan-validation-layers`
@@ -67,7 +67,7 @@ Follow the instructions below to compile and run the engine from source.
## Mac ## Mac
* Install XCode * 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 8 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0))
* Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
* Clone repo (with LFS) * Clone repo (with LFS)
* Run `GenerateProjectFiles.command` * Run `GenerateProjectFiles.command`
@@ -80,9 +80,9 @@ Follow the instructions below to compile and run the engine from source.
Restart PC - ensure DotNet is added to PATH for command line tools execution. Restart PC - ensure DotNet is added to PATH for command line tools execution.
* `Microsoft.NET.TargetFrameworkInference.targets(141,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 7.0. Either target .NET 5.0 or lower, or use a version of the .NET SDK that supports .NET 7.0` * `Microsoft.NET.TargetFrameworkInference.targets(141,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 8.0. Either target .NET 5.0 or lower, or use a version of the .NET SDK that supports .NET 8.0`
Use Visual Studio 2022, older versions are not supported by .NET SDK 7. Use Visual Studio 2022, older versions are not supported by .NET SDK 8.
* `Building for Windows without Vulkan rendering backend (Vulkan SDK is missing)` * `Building for Windows without Vulkan rendering backend (Vulkan SDK is missing)`

View File

@@ -646,7 +646,9 @@ namespace FlaxEditor.Content.GUI
_rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0); _rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0);
_isRubberBandSpanning = true; _isRubberBandSpanning = true;
StartMouseCapture(); StartMouseCapture();
return true;
} }
return AutoFocus && Focus(this); return AutoFocus && Focus(this);
} }

View File

@@ -213,7 +213,8 @@ namespace FlaxEditor.Content
if (_attributes == null) if (_attributes == null)
{ {
var data = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.AttributeMetaTypeID); var data = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.AttributeMetaTypeID);
_attributes = Surface.SurfaceMeta.GetAttributes(data); var dataOld = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.OldAttributeMetaTypeID);
_attributes = Surface.SurfaceMeta.GetAttributes(data, dataOld);
} }
return _attributes; return _attributes;
} }
@@ -290,13 +291,11 @@ namespace FlaxEditor.Content
_methods = Utils.GetEmptyArray<ScriptMemberInfo>(); _methods = Utils.GetEmptyArray<ScriptMemberInfo>();
// Cache Visual Script attributes // Cache Visual Script attributes
var attributesData = _asset.GetMetaData(Surface.SurfaceMeta.AttributeMetaTypeID);
if (attributesData != null && attributesData.Length != 0)
{ {
_attributes = Surface.SurfaceMeta.GetAttributes(attributesData); var data = _asset.GetMetaData(Surface.SurfaceMeta.AttributeMetaTypeID);
var dataOld = _asset.GetMetaData(Surface.SurfaceMeta.OldAttributeMetaTypeID);
_attributes = Surface.SurfaceMeta.GetAttributes(data, dataOld);
} }
else
_attributes = Utils.GetEmptyArray<object>();
} }
private void OnAssetReloading(Asset asset) private void OnAssetReloading(Asset asset)

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.IO;
using FlaxEditor.Content.Thumbnails; using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows; using FlaxEditor.Windows;
@@ -194,4 +195,64 @@ namespace FlaxEditor.Content
base.Dispose(); base.Dispose();
} }
} }
/// <summary>
/// Content proxy for quick UI Control prefab creation as widget.
/// </summary>
[ContentContextMenu("New/Widget")]
internal sealed class WidgetProxy : AssetProxy
{
/// <inheritdoc />
public override string Name => "UI Widget";
/// <inheritdoc />
public override bool IsProxyFor(ContentItem item)
{
return false;
}
/// <inheritdoc />
public override string FileExtension => PrefabProxy.Extension;
/// <inheritdoc />
public override EditorWindow Open(Editor editor, ContentItem item)
{
return null;
}
/// <inheritdoc />
public override Color AccentColor => Color.Transparent;
/// <inheritdoc />
public override string TypeName => PrefabProxy.AssetTypename;
/// <inheritdoc />
public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
{
return null;
}
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{
return targetLocation.CanHaveAssets;
}
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
// Create prefab with UI Control
var actor = new UIControl
{
Name = Path.GetFileNameWithoutExtension(outputPath),
StaticFlags = StaticFlags.None,
};
actor.Control = new Button
{
Text = "Button",
};
PrefabManager.CreatePrefab(actor, outputPath, false);
Object.Destroy(actor, 20.0f);
}
}
} }

View File

@@ -14,7 +14,7 @@ class PlatformTools;
#if OFFICIAL_BUILD #if OFFICIAL_BUILD
// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it) // Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=7") #define GAME_BUILD_DOTNET_VER TEXT("-dotnet=8")
#else #else
#define GAME_BUILD_DOTNET_VER TEXT("") #define GAME_BUILD_DOTNET_VER TEXT("")
#endif #endif

View File

@@ -22,6 +22,11 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform);
namespace namespace
{ {
struct AndroidPlatformCache
{
AndroidPlatformSettings::TextureQuality TexturesQuality;
};
void DeployIcon(const CookingData& data, const TextureData& iconData, const Char* subDir, int32 iconSize, int32 adaptiveIconSize) void DeployIcon(const CookingData& data, const TextureData& iconData, const Char* subDir, int32 iconSize, int32 adaptiveIconSize)
{ {
const String mipmapPath = data.OriginalOutputPath / TEXT("app/src/main/res") / subDir; const String mipmapPath = data.OriginalOutputPath / TEXT("app/src/main/res") / subDir;
@@ -30,6 +35,24 @@ namespace
FileSystem::CreateDirectory(mipmapPath); FileSystem::CreateDirectory(mipmapPath);
EditorUtilities::ExportApplicationImage(iconData, iconSize, iconSize, PixelFormat::B8G8R8A8_UNorm, iconPath); EditorUtilities::ExportApplicationImage(iconData, iconSize, iconSize, PixelFormat::B8G8R8A8_UNorm, iconPath);
} }
PixelFormat GetQualityTextureFormat(bool sRGB, PixelFormat format)
{
const auto platformSettings = AndroidPlatformSettings::Get();
switch (platformSettings->TexturesQuality)
{
case AndroidPlatformSettings::TextureQuality::Uncompressed:
return PixelFormatExtensions::FindUncompressedFormat(format);
case AndroidPlatformSettings::TextureQuality::ASTC_High:
return sRGB ? PixelFormat::ASTC_4x4_UNorm_sRGB : PixelFormat::ASTC_4x4_UNorm;
case AndroidPlatformSettings::TextureQuality::ASTC_Medium:
return sRGB ? PixelFormat::ASTC_6x6_UNorm_sRGB : PixelFormat::ASTC_6x6_UNorm;
case AndroidPlatformSettings::TextureQuality::ASTC_Low:
return sRGB ? PixelFormat::ASTC_8x8_UNorm_sRGB : PixelFormat::ASTC_8x8_UNorm;
default:
return format;
}
}
} }
const Char* AndroidPlatformTools::GetDisplayName() const const Char* AndroidPlatformTools::GetDisplayName() const
@@ -54,62 +77,67 @@ ArchitectureType AndroidPlatformTools::GetArchitecture() const
PixelFormat AndroidPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) PixelFormat AndroidPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format)
{ {
// TODO: add ETC compression support for Android
// TODO: add ASTC compression support for Android
// BC formats are not widely supported on Android
if (PixelFormatExtensions::IsCompressedBC(format))
{
switch (format)
{
case PixelFormat::BC1_Typeless:
case PixelFormat::BC2_Typeless:
case PixelFormat::BC3_Typeless:
return PixelFormat::R8G8B8A8_Typeless;
case PixelFormat::BC1_UNorm:
case PixelFormat::BC2_UNorm:
case PixelFormat::BC3_UNorm:
return PixelFormat::R8G8B8A8_UNorm;
case PixelFormat::BC1_UNorm_sRGB:
case PixelFormat::BC2_UNorm_sRGB:
case PixelFormat::BC3_UNorm_sRGB:
return PixelFormat::R8G8B8A8_UNorm_sRGB;
case PixelFormat::BC4_Typeless:
return PixelFormat::R8_Typeless;
case PixelFormat::BC4_UNorm:
return PixelFormat::R8_UNorm;
case PixelFormat::BC4_SNorm:
return PixelFormat::R8_SNorm;
case PixelFormat::BC5_Typeless:
return PixelFormat::R16G16_Typeless;
case PixelFormat::BC5_UNorm:
return PixelFormat::R16G16_UNorm;
case PixelFormat::BC5_SNorm:
return PixelFormat::R16G16_SNorm;
case PixelFormat::BC7_Typeless:
case PixelFormat::BC6H_Typeless:
return PixelFormat::R16G16B16A16_Typeless;
case PixelFormat::BC7_UNorm:
case PixelFormat::BC6H_Uf16:
case PixelFormat::BC6H_Sf16:
return PixelFormat::R16G16B16A16_Float;
case PixelFormat::BC7_UNorm_sRGB:
return PixelFormat::R16G16B16A16_UNorm;
default:
return format;
}
}
switch (format) switch (format)
{ {
// Not all Android devices support R11G11B10 textures (eg. M6 Note)
case PixelFormat::R11G11B10_Float: case PixelFormat::R11G11B10_Float:
// Not all Android devices support R11G11B10 textures (eg. M6 Note)
return PixelFormat::R16G16B16A16_UNorm; return PixelFormat::R16G16B16A16_UNorm;
case PixelFormat::BC1_Typeless:
case PixelFormat::BC2_Typeless:
case PixelFormat::BC3_Typeless:
case PixelFormat::BC4_Typeless:
case PixelFormat::BC5_Typeless:
case PixelFormat::BC1_UNorm:
case PixelFormat::BC2_UNorm:
case PixelFormat::BC3_UNorm:
case PixelFormat::BC4_UNorm:
case PixelFormat::BC5_UNorm:
return GetQualityTextureFormat(false, format);
case PixelFormat::BC1_UNorm_sRGB:
case PixelFormat::BC2_UNorm_sRGB:
case PixelFormat::BC3_UNorm_sRGB:
case PixelFormat::BC7_UNorm_sRGB:
return GetQualityTextureFormat(true, format);
case PixelFormat::BC4_SNorm:
return PixelFormat::R8_SNorm;
case PixelFormat::BC5_SNorm:
return PixelFormat::R16G16_SNorm;
case PixelFormat::BC6H_Typeless:
case PixelFormat::BC6H_Uf16:
case PixelFormat::BC6H_Sf16:
case PixelFormat::BC7_Typeless:
case PixelFormat::BC7_UNorm:
return PixelFormat::R16G16B16A16_Float; // TODO: ASTC HDR
default: default:
return format; return format;
} }
} }
void AndroidPlatformTools::LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes)
{
const auto platformSettings = AndroidPlatformSettings::Get();
bool invalidTextures = true;
if (bytes.Length() == sizeof(AndroidPlatformCache))
{
auto* platformCache = (AndroidPlatformCache*)bytes.Get();
invalidTextures = platformCache->TexturesQuality != platformSettings->TexturesQuality;
}
if (invalidTextures)
{
LOG(Info, "{0} option has been modified.", TEXT("TexturesQuality"));
cache->InvalidateCacheTextures();
}
}
Array<byte> AndroidPlatformTools::SaveCache(CookingData& data, IBuildCache* cache)
{
const auto platformSettings = AndroidPlatformSettings::Get();
AndroidPlatformCache platformCache;
platformCache.TexturesQuality = platformSettings->TexturesQuality;
Array<byte> result;
result.Add((const byte*)&platformCache, sizeof(platformCache));
return result;
}
void AndroidPlatformTools::OnBuildStarted(CookingData& data) void AndroidPlatformTools::OnBuildStarted(CookingData& data)
{ {
// Adjust the cooking output folder to be located inside the Gradle assets directory // Adjust the cooking output folder to be located inside the Gradle assets directory
@@ -328,7 +356,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
// Copy result package // Copy result package
const String apk = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/apk/release/app-release-unsigned.apk") : TEXT("app/build/outputs/apk/debug/app-debug.apk")); const String apk = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/apk/release/app-release-unsigned.apk") : TEXT("app/build/outputs/apk/debug/app-debug.apk"));
const String outputApk = data.OriginalOutputPath / gameSettings->ProductName + TEXT(".apk"); const String outputApk = data.OriginalOutputPath / EditorUtilities::GetOutputName() + TEXT(".apk");
if (FileSystem::CopyFile(outputApk, apk)) if (FileSystem::CopyFile(outputApk, apk))
{ {
LOG(Error, "Failed to copy package from {0} to {1}", apk, outputApk); LOG(Error, "Failed to copy package from {0} to {1}", apk, outputApk);

View File

@@ -30,6 +30,8 @@ public:
PlatformType GetPlatform() const override; PlatformType GetPlatform() const override;
ArchitectureType GetArchitecture() const override; ArchitectureType GetArchitecture() const override;
PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override; PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override;
void LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes) override;
Array<byte> SaveCache(CookingData& data, IBuildCache* cache) override;
void OnBuildStarted(CookingData& data) override; void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override; bool OnPostProcess(CookingData& data) override;
}; };

View File

@@ -494,11 +494,11 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
{ {
const auto platformSettings = WindowsPlatformSettings::Get(); const auto platformSettings = WindowsPlatformSettings::Get();
// Apply executable icon
Array<String> files; Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly); FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly);
if (files.HasItems()) if (files.HasItems())
{ {
// Apply executable icon
TextureData iconData; TextureData iconData;
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData)) if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
{ {
@@ -508,11 +508,31 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
return true; return true;
} }
} }
// Rename app
const String newName = EditorUtilities::GetOutputName();
if (newName != StringUtils::GetFileNameWithoutExtension(files[0]))
{
if (FileSystem::MoveFile(data.NativeCodeOutputPath / newName + TEXT(".exe"), files[0], true))
{
data.Error(TEXT("Failed to change output executable name."));
return true;
}
}
} }
return false; return false;
} }
void WindowsPlatformTools::OnBuildStarted(CookingData& data)
{
// Remove old executable
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly);
for (auto& file : files)
FileSystem::DeleteFile(file);
}
void WindowsPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) void WindowsPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
{ {
// Pick the first executable file // Pick the first executable file

View File

@@ -31,6 +31,7 @@ public:
ArchitectureType GetArchitecture() const override; ArchitectureType GetArchitecture() const override;
bool UseSystemDotnet() const override; bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override; bool OnDeployBinaries(CookingData& data) override;
void OnBuildStarted(CookingData& data) override;
void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override; void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
}; };

View File

@@ -24,6 +24,11 @@ IMPLEMENT_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform);
namespace namespace
{ {
struct iOSPlatformCache
{
iOSPlatformSettings::TextureQuality TexturesQuality;
};
String GetAppName() String GetAppName()
{ {
const auto gameSettings = GameSettings::Get(); const auto gameSettings = GameSettings::Get();
@@ -60,6 +65,24 @@ namespace
result = result.TrimTrailing(); result = result.TrimTrailing();
return result; return result;
} }
PixelFormat GetQualityTextureFormat(bool sRGB, PixelFormat format)
{
const auto platformSettings = iOSPlatformSettings::Get();
switch (platformSettings->TexturesQuality)
{
case iOSPlatformSettings::TextureQuality::Uncompressed:
return PixelFormatExtensions::FindUncompressedFormat(format);
case iOSPlatformSettings::TextureQuality::ASTC_High:
return sRGB ? PixelFormat::ASTC_4x4_UNorm_sRGB : PixelFormat::ASTC_4x4_UNorm;
case iOSPlatformSettings::TextureQuality::ASTC_Medium:
return sRGB ? PixelFormat::ASTC_6x6_UNorm_sRGB : PixelFormat::ASTC_6x6_UNorm;
case iOSPlatformSettings::TextureQuality::ASTC_Low:
return sRGB ? PixelFormat::ASTC_8x8_UNorm_sRGB : PixelFormat::ASTC_8x8_UNorm;
default:
return format;
}
}
} }
const Char* iOSPlatformTools::GetDisplayName() const const Char* iOSPlatformTools::GetDisplayName() const
@@ -89,51 +112,37 @@ DotNetAOTModes iOSPlatformTools::UseAOT() const
PixelFormat iOSPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) PixelFormat iOSPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format)
{ {
// TODO: add ETC compression support for iOS switch (format)
// TODO: add ASTC compression support for iOS
if (PixelFormatExtensions::IsCompressedBC(format))
{ {
switch (format) case PixelFormat::BC1_Typeless:
{ case PixelFormat::BC2_Typeless:
case PixelFormat::BC1_Typeless: case PixelFormat::BC3_Typeless:
case PixelFormat::BC2_Typeless: case PixelFormat::BC4_Typeless:
case PixelFormat::BC3_Typeless: case PixelFormat::BC5_Typeless:
return PixelFormat::R8G8B8A8_Typeless; case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm: case PixelFormat::BC2_UNorm:
case PixelFormat::BC2_UNorm: case PixelFormat::BC3_UNorm:
case PixelFormat::BC3_UNorm: case PixelFormat::BC4_UNorm:
return PixelFormat::R8G8B8A8_UNorm; case PixelFormat::BC5_UNorm:
case PixelFormat::BC1_UNorm_sRGB: return GetQualityTextureFormat(false, format);
case PixelFormat::BC2_UNorm_sRGB: case PixelFormat::BC1_UNorm_sRGB:
case PixelFormat::BC3_UNorm_sRGB: case PixelFormat::BC2_UNorm_sRGB:
return PixelFormat::R8G8B8A8_UNorm_sRGB; case PixelFormat::BC3_UNorm_sRGB:
case PixelFormat::BC4_Typeless: case PixelFormat::BC7_UNorm_sRGB:
return PixelFormat::R8_Typeless; return GetQualityTextureFormat(true, format);
case PixelFormat::BC4_UNorm: case PixelFormat::BC4_SNorm:
return PixelFormat::R8_UNorm; return PixelFormat::R8_SNorm;
case PixelFormat::BC4_SNorm: case PixelFormat::BC5_SNorm:
return PixelFormat::R8_SNorm; return PixelFormat::R16G16_SNorm;
case PixelFormat::BC5_Typeless: case PixelFormat::BC6H_Typeless:
return PixelFormat::R16G16_Typeless; case PixelFormat::BC6H_Uf16:
case PixelFormat::BC5_UNorm: case PixelFormat::BC6H_Sf16:
return PixelFormat::R16G16_UNorm; case PixelFormat::BC7_Typeless:
case PixelFormat::BC5_SNorm: case PixelFormat::BC7_UNorm:
return PixelFormat::R16G16_SNorm; return PixelFormat::R16G16B16A16_Float; // TODO: ASTC HDR
case PixelFormat::BC7_Typeless: default:
case PixelFormat::BC6H_Typeless: return format;
return PixelFormat::R16G16B16A16_Typeless;
case PixelFormat::BC7_UNorm:
case PixelFormat::BC6H_Uf16:
case PixelFormat::BC6H_Sf16:
return PixelFormat::R16G16B16A16_Float;
case PixelFormat::BC7_UNorm_sRGB:
return PixelFormat::R16G16B16A16_UNorm;
default:
return format;
}
} }
return format; return format;
} }
@@ -143,6 +152,32 @@ bool iOSPlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
return extension.IsEmpty() || extension == TEXT("dylib"); return extension.IsEmpty() || extension == TEXT("dylib");
} }
void iOSPlatformTools::LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes)
{
const auto platformSettings = iOSPlatformSettings::Get();
bool invalidTextures = true;
if (bytes.Length() == sizeof(iOSPlatformCache))
{
auto* platformCache = (iOSPlatformCache*)bytes.Get();
invalidTextures = platformCache->TexturesQuality != platformSettings->TexturesQuality;
}
if (invalidTextures)
{
LOG(Info, "{0} option has been modified.", TEXT("TexturesQuality"));
cache->InvalidateCacheTextures();
}
}
Array<byte> iOSPlatformTools::SaveCache(CookingData& data, IBuildCache* cache)
{
const auto platformSettings = iOSPlatformSettings::Get();
iOSPlatformCache platformCache;
platformCache.TexturesQuality = platformSettings->TexturesQuality;
Array<byte> result;
result.Add((const byte*)&platformCache, sizeof(platformCache));
return result;
}
void iOSPlatformTools::OnBuildStarted(CookingData& data) void iOSPlatformTools::OnBuildStarted(CookingData& data)
{ {
// Adjust the cooking output folders for packaging app // Adjust the cooking output folders for packaging app
@@ -167,13 +202,25 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
if (EditorUtilities::FormatAppPackageName(appIdentifier)) if (EditorUtilities::FormatAppPackageName(appIdentifier))
return true; return true;
// Copy fresh Gradle project template // Copy fresh XCode project template
if (FileSystem::CopyDirectory(data.OriginalOutputPath, platformDataPath / TEXT("Project"), true)) if (FileSystem::CopyDirectory(data.OriginalOutputPath, platformDataPath / TEXT("Project"), true))
{ {
LOG(Error, "Failed to deploy XCode project to {0} from {1}", data.OriginalOutputPath, platformDataPath); LOG(Error, "Failed to deploy XCode project to {0} from {1}", data.OriginalOutputPath, platformDataPath);
return true; return true;
} }
// Fix MoltenVK lib (copied from VulkanSDK xcframework)
FileSystem::MoveFile(data.DataOutputPath / TEXT("libMoltenVK.dylib"), data.DataOutputPath / TEXT("MoltenVK"), true);
{
// Fix rpath to point into dynamic library (rather than framework location)
CreateProcessSettings procSettings;
procSettings.HiddenWindow = true;
procSettings.WorkingDirectory = data.DataOutputPath;
procSettings.FileName = TEXT("/usr/bin/install_name_tool");
procSettings.Arguments = TEXT("-id \"@rpath/libMoltenVK.dylib\" libMoltenVK.dylib");
Platform::CreateProcess(procSettings);
}
// Format project template files // Format project template files
Dictionary<String, String> configReplaceMap; Dictionary<String, String> configReplaceMap;
configReplaceMap[TEXT("${AppName}")] = appName; configReplaceMap[TEXT("${AppName}")] = appName;

View File

@@ -19,6 +19,8 @@ public:
ArchitectureType GetArchitecture() const override; ArchitectureType GetArchitecture() const override;
DotNetAOTModes UseAOT() const override; DotNetAOTModes UseAOT() const override;
PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override; PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override;
void LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes) override;
Array<byte> SaveCache(CookingData& data, IBuildCache* cache) override;
bool IsNativeCodeFile(CookingData& data, const String& file) override; bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override; void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override; bool OnPostProcess(CookingData& data) override;

View File

@@ -8,6 +8,37 @@
class TextureBase; class TextureBase;
/// <summary>
/// The game cooker cache interface.
/// </summary>
class FLAXENGINE_API IBuildCache
{
public:
/// <summary>
/// Removes all cached entries for assets that contain a given asset type. This forces rebuild for them.
/// </summary>
virtual void InvalidateCachePerType(const StringView& typeName) = 0;
/// <summary>
/// Removes all cached entries for assets that contain a given asset type. This forces rebuild for them.
/// </summary>
template<typename T>
FORCE_INLINE void InvalidateCachePerType()
{
InvalidateCachePerType(T::TypeName);
}
/// <summary>
/// Removes all cached entries for assets that contain a shader. This forces rebuild for them.
/// </summary>
void InvalidateCacheShaders();
/// <summary>
/// Removes all cached entries for assets that contain a texture. This forces rebuild for them.
/// </summary>
void InvalidateCacheTextures();
};
/// <summary> /// <summary>
/// The platform support tools base interface. /// The platform support tools base interface.
/// </summary> /// </summary>
@@ -76,6 +107,27 @@ public:
virtual bool IsNativeCodeFile(CookingData& data, const String& file); virtual bool IsNativeCodeFile(CookingData& data, const String& file);
public: public:
/// <summary>
/// Loads the build cache. Allows to invalidate any cached asset types based on the build settings for incremental builds (eg. invalidate textures/shaders).
/// </summary>
/// <param name="data">The cooking data.</param>
/// <param name="data">The build cache interface.</param>
/// <param name="data">The loaded cache data. Can be empty when starting a fresh build.</param>
virtual void LoadCache(CookingData& data, IBuildCache* cache, const Span<byte>& bytes)
{
}
/// <summary>
/// Saves the build cache. Allows to store any build settings to be used for cache invalidation on incremental builds.
/// </summary>
/// <param name="data">The cooking data.</param>
/// <param name="data">The build cache interface.</param>
/// <returns>Data to cache, will be restored during next incremental build.<returns>
virtual Array<byte> SaveCache(CookingData& data, IBuildCache* cache)
{
return Array<byte>();
}
/// <summary> /// <summary>
/// Called when game building starts. /// Called when game building starts.
/// </summary> /// </summary>

View File

@@ -31,10 +31,12 @@
#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Materials/MaterialShader.h" #include "Engine/Graphics/Materials/MaterialShader.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h"
#include "Engine/Engine/Base/GameBase.h" #include "Engine/Engine/Base/GameBase.h"
#include "Engine/Engine/Globals.h" #include "Engine/Engine/Globals.h"
#include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Enums.h" #include "Engine/Scripting/Enums.h"
#if PLATFORM_TOOLS_WINDOWS #if PLATFORM_TOOLS_WINDOWS
#include "Engine/Platform/Windows/WindowsPlatformSettings.h" #include "Engine/Platform/Windows/WindowsPlatformSettings.h"
@@ -49,6 +51,20 @@
Dictionary<String, CookAssetsStep::ProcessAssetFunc> CookAssetsStep::AssetProcessors; Dictionary<String, CookAssetsStep::ProcessAssetFunc> CookAssetsStep::AssetProcessors;
void IBuildCache::InvalidateCacheShaders()
{
InvalidateCachePerType<Shader>();
InvalidateCachePerType<Material>();
InvalidateCachePerType<ParticleEmitter>();
}
void IBuildCache::InvalidateCacheTextures()
{
InvalidateCachePerType<Texture>();
InvalidateCachePerType<CubeTexture>();
InvalidateCachePerType<SpriteAtlas>();
}
bool CookAssetsStep::CacheEntry::IsValid(bool withDependencies) bool CookAssetsStep::CacheEntry::IsValid(bool withDependencies)
{ {
AssetInfo assetInfo; AssetInfo assetInfo;
@@ -113,15 +129,13 @@ void CookAssetsStep::CacheData::InvalidateCachePerType(const StringView& typeNam
void CookAssetsStep::CacheData::Load(CookingData& data) void CookAssetsStep::CacheData::Load(CookingData& data)
{ {
PROFILE_CPU();
HeaderFilePath = data.CacheDirectory / String::Format(TEXT("CookedHeader_{0}.bin"), FLAXENGINE_VERSION_BUILD); HeaderFilePath = data.CacheDirectory / String::Format(TEXT("CookedHeader_{0}.bin"), FLAXENGINE_VERSION_BUILD);
CacheFolder = data.CacheDirectory / TEXT("Cooked"); CacheFolder = data.CacheDirectory / TEXT("Cooked");
Entries.Clear(); Entries.Clear();
if (!FileSystem::DirectoryExists(CacheFolder)) if (!FileSystem::DirectoryExists(CacheFolder))
{
FileSystem::CreateDirectory(CacheFolder); FileSystem::CreateDirectory(CacheFolder);
}
if (!FileSystem::FileExists(HeaderFilePath)) if (!FileSystem::FileExists(HeaderFilePath))
{ {
LOG(Warning, "Missing incremental build cooking assets cache."); LOG(Warning, "Missing incremental build cooking assets cache.");
@@ -143,9 +157,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
return; return;
LOG(Info, "Loading incremental build cooking cache (entries count: {0})", entriesCount); LOG(Info, "Loading incremental build cooking cache (entries count: {0})", entriesCount);
file->ReadBytes(&Settings, sizeof(Settings)); file->ReadBytes(&Settings, sizeof(Settings));
Entries.EnsureCapacity(Math::RoundUpToPowerOf2(static_cast<int32>(entriesCount * 3.0f))); Entries.EnsureCapacity(Math::RoundUpToPowerOf2(static_cast<int32>(entriesCount * 3.0f)));
Array<Pair<String, DateTime>> fileDependencies; Array<Pair<String, DateTime>> fileDependencies;
@@ -179,6 +191,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
e.FileDependencies = fileDependencies; e.FileDependencies = fileDependencies;
} }
Array<byte> platformCache;
file->Read(platformCache);
int32 checkChar; int32 checkChar;
file->ReadInt32(&checkChar); file->ReadInt32(&checkChar);
if (checkChar != 13) if (checkChar != 13)
@@ -187,6 +202,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
Entries.Clear(); Entries.Clear();
} }
// Per-platform custom data loading (eg. to invalidate textures/shaders options)
data.Tools->LoadCache(data, this, ToSpan(platformCache));
const auto buildSettings = BuildSettings::Get(); const auto buildSettings = BuildSettings::Get();
const auto gameSettings = GameSettings::Get(); const auto gameSettings = GameSettings::Get();
@@ -200,12 +218,12 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
if (MATERIAL_GRAPH_VERSION != Settings.Global.MaterialGraphVersion) if (MATERIAL_GRAPH_VERSION != Settings.Global.MaterialGraphVersion)
{ {
LOG(Info, "{0} option has been modified.", TEXT("MaterialGraphVersion")); LOG(Info, "{0} option has been modified.", TEXT("MaterialGraphVersion"));
InvalidateCachePerType(Material::TypeName); InvalidateCachePerType<Material>();
} }
if (PARTICLE_GPU_GRAPH_VERSION != Settings.Global.ParticleGraphVersion) if (PARTICLE_GPU_GRAPH_VERSION != Settings.Global.ParticleGraphVersion)
{ {
LOG(Info, "{0} option has been modified.", TEXT("ParticleGraphVersion")); LOG(Info, "{0} option has been modified.", TEXT("ParticleGraphVersion"));
InvalidateCachePerType(ParticleEmitter::TypeName); InvalidateCachePerType<ParticleEmitter>();
} }
if (buildSettings->ShadersNoOptimize != Settings.Global.ShadersNoOptimize) if (buildSettings->ShadersNoOptimize != Settings.Global.ShadersNoOptimize)
{ {
@@ -262,24 +280,24 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
#endif #endif
if (invalidateShaders) if (invalidateShaders)
{ {
InvalidateCachePerType(Shader::TypeName); InvalidateCachePerType<Shader>();
InvalidateCachePerType(Material::TypeName); InvalidateCachePerType<Material>();
InvalidateCachePerType(ParticleEmitter::TypeName); InvalidateCachePerType<ParticleEmitter>();
} }
// Invalidate textures if streaming settings gets modified // Invalidate textures if streaming settings gets modified
if (Settings.Global.StreamingSettingsAssetId != gameSettings->Streaming || (Entries.ContainsKey(gameSettings->Streaming) && !Entries[gameSettings->Streaming].IsValid())) if (Settings.Global.StreamingSettingsAssetId != gameSettings->Streaming || (Entries.ContainsKey(gameSettings->Streaming) && !Entries[gameSettings->Streaming].IsValid()))
{ {
InvalidateCachePerType(Texture::TypeName); InvalidateCachePerType<Texture>();
InvalidateCachePerType(CubeTexture::TypeName); InvalidateCachePerType<CubeTexture>();
InvalidateCachePerType(SpriteAtlas::TypeName); InvalidateCachePerType<SpriteAtlas>();
} }
} }
void CookAssetsStep::CacheData::Save() void CookAssetsStep::CacheData::Save(CookingData& data)
{ {
PROFILE_CPU();
LOG(Info, "Saving incremental build cooking cache (entries count: {0})", Entries.Count()); LOG(Info, "Saving incremental build cooking cache (entries count: {0})", Entries.Count());
auto file = FileWriteStream::Open(HeaderFilePath); auto file = FileWriteStream::Open(HeaderFilePath);
if (file == nullptr) if (file == nullptr)
return; return;
@@ -302,6 +320,7 @@ void CookAssetsStep::CacheData::Save()
file->Write(f.Second); file->Write(f.Second);
} }
} }
file->Write(data.Tools->SaveCache(data, this));
file->WriteInt32(13); file->WriteInt32(13);
} }
@@ -624,7 +643,8 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data)
const auto asset = static_cast<TextureBase*>(data.Asset); const auto asset = static_cast<TextureBase*>(data.Asset);
const auto& assetHeader = asset->StreamingTexture()->GetHeader(); const auto& assetHeader = asset->StreamingTexture()->GetHeader();
const auto format = asset->Format(); const auto format = asset->Format();
const auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format); auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format);
CHECK_RETURN(!PixelFormatExtensions::IsTypeless(targetFormat), true);
const auto streamingSettings = StreamingSettings::Get(); const auto streamingSettings = StreamingSettings::Get();
int32 mipLevelsMax = GPU_MAX_TEXTURE_MIP_LEVELS; int32 mipLevelsMax = GPU_MAX_TEXTURE_MIP_LEVELS;
if (assetHeader->TextureGroup >= 0 && assetHeader->TextureGroup < streamingSettings->TextureGroups.Count()) if (assetHeader->TextureGroup >= 0 && assetHeader->TextureGroup < streamingSettings->TextureGroups.Count())
@@ -634,6 +654,11 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data)
group.MipLevelsMaxPerPlatform.TryGet(data.Data.Tools->GetPlatform(), mipLevelsMax); group.MipLevelsMaxPerPlatform.TryGet(data.Data.Tools->GetPlatform(), mipLevelsMax);
} }
// If texture is smaller than the block size of the target format (eg. 4x4 texture using ASTC_6x6) then fallback to uncompressed
int32 blockSize = PixelFormatExtensions::ComputeBlockSize(targetFormat);
if (assetHeader->Width < blockSize || assetHeader->Height < blockSize || (blockSize != 1 && mipLevelsMax < 4))
targetFormat = PixelFormatExtensions::FindUncompressedFormat(format);
// Faster path if don't need to modify texture for the target platform // Faster path if don't need to modify texture for the target platform
if (format == targetFormat && assetHeader->MipLevels <= mipLevelsMax) if (format == targetFormat && assetHeader->MipLevels <= mipLevelsMax)
{ {
@@ -961,6 +986,7 @@ public:
const int32 count = addedEntries.Count(); const int32 count = addedEntries.Count();
if (count == 0) if (count == 0)
return false; return false;
PROFILE_CPU();
// Get assets init data and load all chunks // Get assets init data and load all chunks
Array<AssetInitData> assetsData; Array<AssetInitData> assetsData;
@@ -1143,7 +1169,7 @@ bool CookAssetsStep::Perform(CookingData& data)
// Cook asset // Cook asset
if (Process(data, cache, assetRef.Get())) if (Process(data, cache, assetRef.Get()))
{ {
cache.Save(); cache.Save(data);
return true; return true;
} }
data.Stats.CookedAssets++; data.Stats.CookedAssets++;
@@ -1151,12 +1177,12 @@ bool CookAssetsStep::Perform(CookingData& data)
// Auto save build cache after every few cooked assets (reduces next build time if cooking fails later) // Auto save build cache after every few cooked assets (reduces next build time if cooking fails later)
if (data.Stats.CookedAssets % 50 == 0) if (data.Stats.CookedAssets % 50 == 0)
{ {
cache.Save(); cache.Save(data);
} }
} }
// Save build cache header // Save build cache header
cache.Save(); cache.Save(data);
// Create build game header // Create build game header
{ {
@@ -1173,7 +1199,6 @@ bool CookAssetsStep::Perform(CookingData& data)
} }
stream->WriteInt32(('x' + 'D') * 131); // think about it as '131 times xD' stream->WriteInt32(('x' + 'D') * 131); // think about it as '131 times xD'
stream->WriteInt32(FLAXENGINE_VERSION_BUILD); stream->WriteInt32(FLAXENGINE_VERSION_BUILD);
Array<byte> bytes; Array<byte> bytes;

View File

@@ -3,6 +3,7 @@
#pragma once #pragma once
#include "Editor/Cooker/GameCooker.h" #include "Editor/Cooker/GameCooker.h"
#include "Editor/Cooker/PlatformTools.h"
#include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/Pair.h"
#include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/Dictionary.h"
@@ -56,7 +57,7 @@ public:
/// <summary> /// <summary>
/// Assets cooking cache data (incremental building feature). /// Assets cooking cache data (incremental building feature).
/// </summary> /// </summary>
struct FLAXENGINE_API CacheData struct FLAXENGINE_API CacheData : public IBuildCache
{ {
/// <summary> /// <summary>
/// The cache header file path. /// The cache header file path.
@@ -136,16 +137,6 @@ public:
/// <returns>The added entry reference.</returns> /// <returns>The added entry reference.</returns>
CacheEntry& CreateEntry(const Asset* asset, String& cachedFilePath); CacheEntry& CreateEntry(const Asset* asset, String& cachedFilePath);
/// <summary>
/// Removes all cached entries for assets that contain a shader. This forces rebuild for them.
/// </summary>
void InvalidateShaders();
/// <summary>
/// Removes all cached entries for assets that contain a texture. This forces rebuild for them.
/// </summary>
void InvalidateCachePerType(const StringView& typeName);
/// <summary> /// <summary>
/// Loads the cache for the given cooking data. /// Loads the cache for the given cooking data.
/// </summary> /// </summary>
@@ -155,7 +146,11 @@ public:
/// <summary> /// <summary>
/// Saves this cache (header file). /// Saves this cache (header file).
/// </summary> /// </summary>
void Save(); /// <param name="data">The data.</param>
void Save(CookingData& data);
using IBuildCache::InvalidateCachePerType;
void InvalidateCachePerType(const StringView& typeName) override;
}; };
struct FLAXENGINE_API AssetCookData struct FLAXENGINE_API AssetCookData

View File

@@ -116,7 +116,7 @@ bool DeployDataStep::Perform(CookingData& data)
for (String& version : versions) for (String& version : versions)
{ {
version = String(StringUtils::GetFileName(version)); version = String(StringUtils::GetFileName(version));
if (!version.StartsWith(TEXT("7.")) && !version.StartsWith(TEXT("8."))) // .NET 7 or .NET 8 if (!version.StartsWith(TEXT("8."))) // Check for major part of 8.0
version.Clear(); version.Clear();
} }
Sorting::QuickSort(versions); Sorting::QuickSort(versions);
@@ -204,14 +204,14 @@ bool DeployDataStep::Perform(CookingData& data)
{ {
// AOT runtime files inside Engine Platform folder // AOT runtime files inside Engine Platform folder
packFolder /= TEXT("Dotnet"); packFolder /= TEXT("Dotnet");
dstDotnetLibs /= TEXT("lib/net7.0"); dstDotnetLibs /= TEXT("lib/net8.0");
srcDotnetLibs = packFolder / TEXT("lib/net7.0"); srcDotnetLibs = packFolder / TEXT("lib/net8.0");
} }
else else
{ {
// Runtime files inside Dotnet SDK folder but placed for AOT // Runtime files inside Dotnet SDK folder but placed for AOT
dstDotnetLibs /= TEXT("lib/net7.0"); dstDotnetLibs /= TEXT("lib/net8.0");
srcDotnetLibs /= TEXT("../lib/net7.0"); srcDotnetLibs /= TEXT("../lib/net8.0");
} }
} }
else else
@@ -219,16 +219,18 @@ bool DeployDataStep::Perform(CookingData& data)
if (srcDotnetFromEngine) if (srcDotnetFromEngine)
{ {
// Runtime files inside Engine Platform folder // Runtime files inside Engine Platform folder
dstDotnetLibs /= TEXT("lib/net7.0"); dstDotnetLibs /= TEXT("lib/net8.0");
srcDotnetLibs /= TEXT("lib/net7.0"); srcDotnetLibs /= TEXT("lib/net8.0");
} }
else else
{ {
// Runtime files inside Dotnet SDK folder // Runtime files inside Dotnet SDK folder
dstDotnetLibs /= TEXT("shared/Microsoft.NETCore.App"); dstDotnetLibs /= TEXT("shared/Microsoft.NETCore.App");
srcDotnetLibs /= TEXT("../lib/net7.0"); srcDotnetLibs /= TEXT("../lib/net8.0");
} }
} }
LOG(Info, "Copying .NET files from {} to {}", packFolder, dstDotnet);
LOG(Info, "Copying .NET files from {} to {}", srcDotnetLibs, dstDotnetLibs);
FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.txt")); FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.txt"));
FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.TXT")); FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.TXT"));
FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("ThirdPartyNotices.txt")); FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("ThirdPartyNotices.txt"));

View File

@@ -35,7 +35,8 @@ void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
if (cachedData != aotModeCacheValue) if (cachedData != aotModeCacheValue)
{ {
LOG(Info, "AOT cache invalidation"); LOG(Info, "AOT cache invalidation");
FileSystem::DeleteDirectory(data.ManagedCodeOutputPath); FileSystem::DeleteDirectory(data.ManagedCodeOutputPath); // Remove AOT cache
FileSystem::DeleteDirectory(data.DataOutputPath / TEXT("Dotnet")); // Remove deployed Dotnet libs (be sure to remove any leftovers from previous build)
} }
} }
if (!FileSystem::DirectoryExists(data.ManagedCodeOutputPath)) if (!FileSystem::DirectoryExists(data.ManagedCodeOutputPath))

View File

@@ -385,6 +385,15 @@ namespace FlaxEditor.CustomEditors
LinkedLabel = label; LinkedLabel = label;
} }
internal bool CanEditValue
{
get
{
var readOnly = Values.Info.GetAttribute<ReadOnlyAttribute>();
return readOnly == null;
}
}
/// <summary> /// <summary>
/// If true, the value reverting to default/reference will be handled via iteration over children editors, instead of for a whole object at once. /// If true, the value reverting to default/reference will be handled via iteration over children editors, instead of for a whole object at once.
/// </summary> /// </summary>
@@ -413,7 +422,7 @@ namespace FlaxEditor.CustomEditors
{ {
if (!Values.IsDefaultValueModified) if (!Values.IsDefaultValueModified)
return false; return false;
return true; return CanEditValue;
} }
} }
@@ -422,7 +431,7 @@ namespace FlaxEditor.CustomEditors
/// </summary> /// </summary>
public void RevertToDefaultValue() public void RevertToDefaultValue()
{ {
if (!Values.HasDefaultValue) if (!Values.HasDefaultValue || !CanEditValue)
return; return;
RevertDiffToDefault(); RevertDiffToDefault();
} }
@@ -471,7 +480,7 @@ namespace FlaxEditor.CustomEditors
} }
else else
{ {
if (Values.IsReferenceValueModified) if (CanRevertReferenceValue)
SetValueToReference(); SetValueToReference();
} }
} }
@@ -485,7 +494,7 @@ namespace FlaxEditor.CustomEditors
{ {
if (!Values.IsReferenceValueModified) if (!Values.IsReferenceValueModified)
return false; return false;
return true; return CanEditValue;
} }
} }
@@ -494,7 +503,7 @@ namespace FlaxEditor.CustomEditors
/// </summary> /// </summary>
public void RevertToReferenceValue() public void RevertToReferenceValue()
{ {
if (!Values.HasReferenceValue) if (!Values.HasReferenceValue || !CanEditValue)
return; return;
RevertDiffToReference(); RevertDiffToReference();
} }

View File

@@ -4,6 +4,7 @@
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Types/Stopwatch.h"
#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Engine/EngineService.h" #include "Engine/Engine/EngineService.h"
#include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Scripting.h"
@@ -81,7 +82,7 @@ bool CustomEditorsUtilService::Init()
void OnAssemblyLoaded(MAssembly* assembly) void OnAssemblyLoaded(MAssembly* assembly)
{ {
const auto startTime = DateTime::NowUTC(); Stopwatch stopwatch;
// Prepare FlaxEngine // Prepare FlaxEngine
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
@@ -162,8 +163,8 @@ void OnAssemblyLoaded(MAssembly* assembly)
} }
} }
const auto endTime = DateTime::NowUTC(); stopwatch.Stop();
LOG(Info, "Assembly \'{0}\' scanned for custom editors in {1} ms", assembly->ToString(), (int32)(endTime - startTime).GetTotalMilliseconds()); LOG(Info, "Assembly \'{0}\' scanned for custom editors in {1} ms", assembly->ToString(), stopwatch.GetMilliseconds());
} }
void OnAssemblyUnloading(MAssembly* assembly) void OnAssemblyUnloading(MAssembly* assembly)

View File

@@ -589,25 +589,27 @@ namespace FlaxEditor.CustomEditors.Dedicated
LayoutElementsContainer yEl; LayoutElementsContainer yEl;
LayoutElementsContainer hEl; LayoutElementsContainer hEl;
LayoutElementsContainer vEl; LayoutElementsContainer vEl;
Color axisColorX = ActorTransformEditor.AxisColorX;
Color axisColorY = ActorTransformEditor.AxisColorY;
if (xEq) if (xEq)
{ {
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values)); xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values)); vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX);
} }
else else
{ {
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values)); xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values)); vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX);
} }
if (yEq) if (yEq)
{ {
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values)); yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values)); hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY);
} }
else else
{ {
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values)); yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values)); hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY);
} }
xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y); xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y);
xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y); xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y);
@@ -624,28 +626,34 @@ namespace FlaxEditor.CustomEditors.Dedicated
private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont) private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont)
{ {
var horUp = cont.VerticalPanel(); var panel = cont.VerticalPanel();
horUp.Panel.Margin = Margin.Zero; panel.Panel.Margin = Margin.Zero;
return horUp; return panel;
} }
private CustomElementsContainer<UniformGridPanel> UniformGridTwoByOne(LayoutElementsContainer cont) private CustomElementsContainer<UniformGridPanel> UniformGridTwoByOne(LayoutElementsContainer cont)
{ {
var horUp = cont.CustomContainer<UniformGridPanel>(); var grid = cont.CustomContainer<UniformGridPanel>();
horUp.CustomControl.SlotsHorizontally = 2; grid.CustomControl.SlotsHorizontally = 2;
horUp.CustomControl.SlotsVertically = 1; grid.CustomControl.SlotsVertically = 1;
horUp.CustomControl.SlotPadding = Margin.Zero; grid.CustomControl.SlotPadding = Margin.Zero;
horUp.CustomControl.ClipChildren = false; grid.CustomControl.ClipChildren = false;
return horUp; return grid;
} }
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values) private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor)
{ {
CustomElementsContainer<UniformGridPanel> hor = UniformGridTwoByOne(el); var grid = UniformGridTwoByOne(el);
hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0); grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1);
LabelElement lab = hor.Label(text); var label = grid.Label(text);
hor.Object(values); var editor = grid.Object(values);
return hor; if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement)
{
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
floatEditorElement.ValueBox.BorderSelectedColor = borderColor;
}
return grid;
} }
private bool _cachedXEq; private bool _cachedXEq;

View File

@@ -26,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors
/// </summary> /// </summary>
public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f); public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f);
/// <summary>
/// The axes colors grey out scale when input field is not focused.
/// </summary>
public static float AxisGreyOutFactor = 0.6f;
/// <summary> /// <summary>
/// Custom editor for actor position property. /// Custom editor for actor position property.
/// </summary> /// </summary>
@@ -39,13 +44,15 @@ namespace FlaxEditor.CustomEditors.Editors
// Override colors // Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f; XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX; XElement.ValueBox.BorderSelectedColor = AxisColorX;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); XElement.ValueBox.Category = Utils.ValueCategory.Distance;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY; YElement.ValueBox.BorderSelectedColor = AxisColorY;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); YElement.ValueBox.Category = Utils.ValueCategory.Distance;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ; ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Distance;
} }
} }
@@ -62,13 +69,15 @@ namespace FlaxEditor.CustomEditors.Editors
// Override colors // Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f; XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX; XElement.ValueBox.BorderSelectedColor = AxisColorX;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); XElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY; YElement.ValueBox.BorderSelectedColor = AxisColorY;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); YElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ; ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
} }
} }
@@ -102,14 +111,17 @@ namespace FlaxEditor.CustomEditors.Editors
SetLinkStyle(); SetLinkStyle();
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value);
_linkButton.LocalX += textSize.X + 10; _linkButton.LocalX += textSize.X + 10;
LinkedLabel.SetupContextMenu += (label, menu, editor) => if (LinkedLabel != null)
{ {
menu.AddSeparator(); LinkedLabel.SetupContextMenu += (label, menu, editor) =>
if (LinkValues) {
menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling"); menu.AddSeparator();
else if (LinkValues)
menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling"); menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling");
}; else
menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling");
};
}
// Override colors // Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;

View File

@@ -21,32 +21,31 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
_element = null; var doubleValue = layout.DoubleValue();
doubleValue.ValueBox.ValueChanged += OnValueChanged;
// Try get limit attribute for value min/max range setting and slider speed doubleValue.ValueBox.SlidingEnd += ClearToken;
_element = doubleValue;
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
if (attributes != null) if (attributes != null)
{ {
var limit = attributes.FirstOrDefault(x => x is LimitAttribute); var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
if (limit != null) doubleValue.SetLimits(limit);
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
if (valueCategory != Utils.ValueCategory.None)
{ {
// Use double value editor with limit doubleValue.SetCategory(valueCategory);
var doubleValue = layout.DoubleValue(); if (LinkedLabel != null)
doubleValue.SetLimits((LimitAttribute)limit); {
doubleValue.ValueBox.ValueChanged += OnValueChanged; LinkedLabel.SetupContextMenu += (label, menu, editor) =>
doubleValue.ValueBox.SlidingEnd += ClearToken; {
_element = doubleValue; menu.AddSeparator();
return; var mb = menu.AddButton("Show formatted", bt => { doubleValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
mb.AutoCheck = true;
mb.Checked = doubleValue.ValueBox.Category != Utils.ValueCategory.None;
};
}
} }
} }
if (_element == null)
{
// Use double value editor
var doubleValue = layout.DoubleValue();
doubleValue.ValueBox.ValueChanged += OnValueChanged;
doubleValue.ValueBox.SlidingEnd += ClearToken;
_element = doubleValue;
}
} }
private void OnValueChanged() private void OnValueChanged()

View File

@@ -4,6 +4,7 @@ using System;
using System.Linq; using System.Linq;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEngine; using FlaxEngine;
using Utils = FlaxEngine.Utils;
namespace FlaxEditor.CustomEditors.Editors namespace FlaxEditor.CustomEditors.Editors
{ {
@@ -27,41 +28,42 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
_element = null; _element = null;
// Try get limit attribute for value min/max range setting and slider speed
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
var range = (RangeAttribute)attributes?.FirstOrDefault(x => x is RangeAttribute);
if (range != null)
{
// Use slider
var slider = layout.Slider();
slider.Slider.SetLimits(range);
slider.Slider.ValueChanged += OnValueChanged;
slider.Slider.SlidingEnd += ClearToken;
_element = slider;
return;
}
var floatValue = layout.FloatValue();
floatValue.ValueBox.ValueChanged += OnValueChanged;
floatValue.ValueBox.SlidingEnd += ClearToken;
_element = floatValue;
if (attributes != null) if (attributes != null)
{ {
var range = attributes.FirstOrDefault(x => x is RangeAttribute); var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
if (range != null) floatValue.SetLimits(limit);
var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None;
if (valueCategory != Utils.ValueCategory.None)
{ {
// Use slider floatValue.SetCategory(valueCategory);
var slider = layout.Slider(); if (LinkedLabel != null)
slider.SetLimits((RangeAttribute)range); {
slider.Slider.ValueChanged += OnValueChanged; LinkedLabel.SetupContextMenu += (label, menu, editor) =>
slider.Slider.SlidingEnd += ClearToken; {
_element = slider; menu.AddSeparator();
return; var mb = menu.AddButton("Show formatted", bt => { floatValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); });
mb.AutoCheck = true;
mb.Checked = floatValue.ValueBox.Category != Utils.ValueCategory.None;
};
}
} }
var limit = attributes.FirstOrDefault(x => x is LimitAttribute);
if (limit != null)
{
// Use float value editor with limit
var floatValue = layout.FloatValue();
floatValue.SetLimits((LimitAttribute)limit);
floatValue.ValueBox.ValueChanged += OnValueChanged;
floatValue.ValueBox.SlidingEnd += ClearToken;
_element = floatValue;
return;
}
}
if (_element == null)
{
// Use float value editor
var floatValue = layout.FloatValue();
floatValue.ValueBox.ValueChanged += OnValueChanged;
floatValue.ValueBox.SlidingEnd += ClearToken;
_element = floatValue;
} }
} }

View File

@@ -22,7 +22,8 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
LinkedLabel.SetupContextMenu += OnSetupContextMenu; if (LinkedLabel != null)
LinkedLabel.SetupContextMenu += OnSetupContextMenu;
var comboBoxElement = layout.ComboBox(); var comboBoxElement = layout.ComboBox();
_comboBox = comboBoxElement.ComboBox; _comboBox = comboBoxElement.ComboBox;
var names = new List<string>(); var names = new List<string>();

View File

@@ -45,23 +45,29 @@ namespace FlaxEditor.CustomEditors.Editors
gridControl.SlotsVertically = 1; gridControl.SlotsVertically = 1;
XElement = grid.FloatValue(); XElement = grid.FloatValue();
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.ValueChanged += OnValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += ClearToken;
YElement = grid.FloatValue(); YElement = grid.FloatValue();
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.ValueChanged += OnValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += ClearToken;
ZElement = grid.FloatValue(); ZElement = grid.FloatValue();
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.ValueChanged += OnValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += ClearToken;
LinkedLabel.SetupContextMenu += (label, menu, editor) => if (LinkedLabel != null)
{ {
menu.AddSeparator(); LinkedLabel.SetupContextMenu += (label, menu, editor) =>
var value = ((Quaternion)Values[0]).EulerAngles; {
menu.AddButton("Copy Euler", () => { Clipboard.Text = JsonSerializer.Serialize(value); }).TooltipText = "Copy the Euler Angles in Degrees"; menu.AddSeparator();
}; var value = ((Quaternion)Values[0]).EulerAngles;
menu.AddButton("Copy Euler", () => { Clipboard.Text = JsonSerializer.Serialize(value); }).TooltipText = "Copy the Euler Angles in Degrees";
};
}
} }
private void OnValueChanged() private void OnValueChanged()

View File

@@ -70,25 +70,48 @@ namespace FlaxEditor.CustomEditors.Editors
LimitAttribute limit = null; LimitAttribute limit = null;
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
var category = Utils.ValueCategory.None;
if (attributes != null) if (attributes != null)
{ {
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
if (categoryAttribute != null)
category = categoryAttribute.Category;
} }
XElement = grid.FloatValue(); XElement = grid.FloatValue();
XElement.SetLimits(limit); XElement.SetLimits(limit);
XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnXValueChanged; XElement.ValueBox.ValueChanged += OnXValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += ClearToken;
YElement = grid.FloatValue(); YElement = grid.FloatValue();
YElement.SetLimits(limit); YElement.SetLimits(limit);
YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnYValueChanged; YElement.ValueBox.ValueChanged += OnYValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += ClearToken;
ZElement = grid.FloatValue(); ZElement = grid.FloatValue();
ZElement.SetLimits(limit); ZElement.SetLimits(limit);
ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnZValueChanged; ZElement.ValueBox.ValueChanged += OnZValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += ClearToken;
if (LinkedLabel != null)
{
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{
menu.AddSeparator();
var mb = menu.AddButton("Show formatted", bt =>
{
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
});
mb.AutoCheck = true;
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
};
}
} }
private void OnXValueChanged() private void OnXValueChanged()
@@ -248,26 +271,49 @@ namespace FlaxEditor.CustomEditors.Editors
gridControl.SlotsVertically = 1; gridControl.SlotsVertically = 1;
LimitAttribute limit = null; LimitAttribute limit = null;
Utils.ValueCategory category = Utils.ValueCategory.None;
var attributes = Values.GetAttributes(); var attributes = Values.GetAttributes();
if (attributes != null) if (attributes != null)
{ {
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute);
if (categoryAttribute != null)
category = categoryAttribute.Category;
} }
XElement = grid.DoubleValue(); XElement = grid.DoubleValue();
XElement.SetLimits(limit); XElement.SetLimits(limit);
XElement.SetCategory(category);
XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.ValueChanged += OnValueChanged;
XElement.ValueBox.SlidingEnd += ClearToken; XElement.ValueBox.SlidingEnd += ClearToken;
YElement = grid.DoubleValue(); YElement = grid.DoubleValue();
YElement.SetLimits(limit); YElement.SetLimits(limit);
YElement.SetCategory(category);
YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.ValueChanged += OnValueChanged;
YElement.ValueBox.SlidingEnd += ClearToken; YElement.ValueBox.SlidingEnd += ClearToken;
ZElement = grid.DoubleValue(); ZElement = grid.DoubleValue();
ZElement.SetLimits(limit); ZElement.SetLimits(limit);
ZElement.SetCategory(category);
ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.ValueChanged += OnValueChanged;
ZElement.ValueBox.SlidingEnd += ClearToken; ZElement.ValueBox.SlidingEnd += ClearToken;
if (LinkedLabel != null)
{
LinkedLabel.SetupContextMenu += (label, menu, editor) =>
{
menu.AddSeparator();
var mb = menu.AddButton("Show formatted", bt =>
{
XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None);
});
mb.AutoCheck = true;
mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None;
};
}
} }
private void OnValueChanged() private void OnValueChanged()

View File

@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
} }
} }
/// <summary>
/// Sets the editor value category.
/// </summary>
/// <param name="category">The category.</param>
public void SetCategory(Utils.ValueCategory category)
{
ValueBox.Category = category;
}
/// <summary> /// <summary>
/// Sets the editor limits from member <see cref="LimitAttribute"/>. /// Sets the editor limits from member <see cref="LimitAttribute"/>.
/// </summary> /// </summary>

View File

@@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements
} }
} }
/// <summary>
/// Sets the editor value category.
/// </summary>
/// <param name="category">The category.</param>
public void SetCategory(Utils.ValueCategory category)
{
ValueBox.Category = category;
}
/// <summary> /// <summary>
/// Sets the editor limits from member <see cref="LimitAttribute"/>. /// Sets the editor limits from member <see cref="LimitAttribute"/>.
/// </summary> /// </summary>

View File

@@ -315,9 +315,9 @@ namespace FlaxEditor.CustomEditors
internal LabelElement Header(HeaderAttribute header) internal LabelElement Header(HeaderAttribute header)
{ {
var element = Header(header.Text); var element = Header(header.Text);
if (header.FontSize != -1) if (header.FontSize > 0)
element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize); element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize);
if (header.Color != 0) if (header.Color > 0)
element.Label.TextColor = Color.FromRGBA(header.Color); element.Label.TextColor = Color.FromRGBA(header.Color);
return element; return element;
} }

View File

@@ -407,12 +407,26 @@ int32 Editor::LoadProduct()
{ {
Array<String> projectFiles; Array<String> projectFiles;
FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly); FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly);
if (projectFiles.Count() == 1) if (projectFiles.Count() > 1)
{ {
// Skip creating new project if it already exists Platform::Fatal(TEXT("Too many project files."));
LOG(Info, "Skip creatinng new project because it already exists"); return -2;
}
else if (projectFiles.Count() == 1)
{
LOG(Info, "Skip creating new project because it already exists");
CommandLine::Options.NewProject.Reset(); CommandLine::Options.NewProject.Reset();
} }
else
{
Array<String> files;
FileSystem::DirectoryGetFiles(files, projectPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
if (files.Count() > 0)
{
Platform::Fatal(String::Format(TEXT("Target project folder '{0}' is not empty."), projectPath));
return -1;
}
}
} }
if (CommandLine::Options.NewProject) if (CommandLine::Options.NewProject)
{ {

View File

@@ -290,7 +290,6 @@ namespace FlaxEditor
StateMachine = new EditorStateMachine(this); StateMachine = new EditorStateMachine(this);
Undo = new EditorUndo(this); Undo = new EditorUndo(this);
UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet;
if (newProject) if (newProject)
InitProject(); InitProject();
@@ -355,27 +354,6 @@ namespace FlaxEditor
StateMachine.LoadingState.StartInitEnding(skipCompile); StateMachine.LoadingState.StartInitEnding(skipCompile);
} }
private ContainerControl OnUIControlFallbackParentGet(UIControl control)
{
// Check if prefab root control is this UIControl
var loadingPreview = Viewport.Previews.PrefabPreview.LoadingPreview;
var activePreviews = Viewport.Previews.PrefabPreview.ActivePreviews;
if (activePreviews != null)
{
foreach (var preview in activePreviews)
{
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
preview.customControlLinked = control;
return preview;
}
}
}
return null;
}
internal void RegisterModule(EditorModule module) internal void RegisterModule(EditorModule module)
{ {
Log("Register Editor module " + module); Log("Register Editor module " + module);

View File

@@ -431,7 +431,6 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
protected CurveEditor() protected CurveEditor()
{ {
_tickStrengths = new float[TickSteps.Length];
Accessor.GetDefaultValue(out DefaultValue); Accessor.GetDefaultValue(out DefaultValue);
var style = Style.Current; var style = Style.Current;
@@ -780,75 +779,31 @@ namespace FlaxEditor.GUI
return _mainPanel.PointToParent(point); return _mainPanel.PointToParent(point);
} }
private void DrawAxis(Float2 axis, ref Rectangle viewRect, float min, float max, float pixelRange) private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
{ {
int minDistanceBetweenTicks = 20; Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
int maxDistanceBetweenTicks = 60;
var range = max - min;
// Find the strength for each modulo number tick marker
int smallestTick = 0;
int biggestTick = TickSteps.Length - 1;
for (int i = TickSteps.Length - 1; i >= 0; i--)
{ {
// Calculate how far apart these modulo tick steps are spaced var p = PointFromKeyframes(axis * tick, ref viewRect);
float tickSpacing = TickSteps[i] * pixelRange / range;
// Calculate the strength of the tick markers based on the spacing // Draw line
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); var lineRect = new Rectangle
(
viewRect.Location + (p - 0.5f) * axis,
Float2.Lerp(viewRect.Size, Float2.One, axis)
);
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
// Beyond threshold the ticks don't get any bigger or fatter // Draw label
if (_tickStrengths[i] >= 1) string label = tick.ToString(CultureInfo.InvariantCulture);
biggestTick = i; var labelRect = new Rectangle
(
// Do not show small tick markers viewRect.X + 4.0f + (p.X * axis.X),
if (tickSpacing <= minDistanceBetweenTicks) viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
{ 50,
smallestTick = i; LabelsSize
break; );
} Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
} }, TickSteps, ref _tickStrengths, min, max, pixelRange);
// Draw all tick levels
int tickLevels = biggestTick - smallestTick + 1;
for (int level = 0; level < tickLevels; level++)
{
float strength = _tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon)
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, TickSteps.Length - 1);
int startTick = Mathf.FloorToInt(min / TickSteps[l]);
int endTick = Mathf.CeilToInt(max / TickSteps[l]);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(TickSteps[l + 1] / TickSteps[l]) == 0))
continue;
var tick = i * TickSteps[l];
var p = PointFromKeyframes(axis * tick, ref viewRect);
// Draw line
var lineRect = new Rectangle
(
viewRect.Location + (p - 0.5f) * axis,
Float2.Lerp(viewRect.Size, Float2.One, axis)
);
Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));
// Draw label
string label = tick.ToString(CultureInfo.InvariantCulture);
var labelRect = new Rectangle
(
viewRect.X + 4.0f + (p.X * axis.X),
viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
50,
LabelsSize
);
Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}
}
} }
/// <summary> /// <summary>
@@ -890,9 +845,9 @@ namespace FlaxEditor.GUI
Render2D.PushClip(ref viewRect); Render2D.PushClip(ref viewRect);
if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical) if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical)
DrawAxis(Float2.UnitX, ref viewRect, min.X, max.X, pixelRange.X); DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal) if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal)
DrawAxis(Float2.UnitY, ref viewRect, min.Y, max.Y, pixelRange.Y); DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
Render2D.PopClip(); Render2D.PopClip();
} }

View File

@@ -3,6 +3,7 @@
using System; using System;
using FlaxEditor.Utilities; using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using Utils = FlaxEngine.Utils;
namespace FlaxEditor.GUI.Input namespace FlaxEditor.GUI.Input
{ {
@@ -13,6 +14,8 @@ namespace FlaxEditor.GUI.Input
[HideInEditor] [HideInEditor]
public class DoubleValueBox : ValueBox<double> public class DoubleValueBox : ValueBox<double>
{ {
private Utils.ValueCategory _category = Utils.ValueCategory.None;
/// <inheritdoc /> /// <inheritdoc />
public override double Value public override double Value
{ {
@@ -129,10 +132,25 @@ namespace FlaxEditor.GUI.Input
Value = Value; Value = Value;
} }
/// <summary>
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
/// </summary>
public Utils.ValueCategory Category
{
get => _category;
set
{
if (_category == value)
return;
_category = value;
UpdateText();
}
}
/// <inheritdoc /> /// <inheritdoc />
protected sealed override void UpdateText() protected sealed override void UpdateText()
{ {
SetText(Utilities.Utils.FormatFloat(_value)); SetText(Utilities.Utils.FormatFloat(_value, Category));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.Globalization;
using FlaxEditor.Utilities; using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using Utils = FlaxEngine.Utils;
namespace FlaxEditor.GUI.Input namespace FlaxEditor.GUI.Input
{ {
@@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input
[HideInEditor] [HideInEditor]
public class FloatValueBox : ValueBox<float> public class FloatValueBox : ValueBox<float>
{ {
private Utils.ValueCategory _category = Utils.ValueCategory.None;
/// <inheritdoc /> /// <inheritdoc />
public override float Value public override float Value
{ {
@@ -137,10 +139,25 @@ namespace FlaxEditor.GUI.Input
Value = Value; Value = Value;
} }
/// <summary>
/// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance.
/// </summary>
public Utils.ValueCategory Category
{
get => _category;
set
{
if (_category == value)
return;
_category = value;
UpdateText();
}
}
/// <inheritdoc /> /// <inheritdoc />
protected sealed override void UpdateText() protected sealed override void UpdateText()
{ {
SetText(Utilities.Utils.FormatFloat(_value)); SetText(Utilities.Utils.FormatFloat(_value, Category));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -28,7 +28,6 @@ namespace FlaxEditor.GUI.Timeline.GUI
{ {
_timeline = timeline; _timeline = timeline;
_tickSteps = Utilities.Utils.CurveTickSteps; _tickSteps = Utilities.Utils.CurveTickSteps;
_tickStrengths = new float[_tickSteps.Length];
} }
private void UpdateSelectionRectangle() private void UpdateSelectionRectangle()
@@ -173,55 +172,20 @@ namespace FlaxEditor.GUI.Timeline.GUI
var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond;
var min = leftFrame; var min = leftFrame;
var max = rightFrame; var max = rightFrame;
int smallestTick = 0;
int biggestTick = _tickSteps.Length - 1;
for (int i = _tickSteps.Length - 1; i >= 0; i--)
{
// Calculate how far apart these modulo tick steps are spaced
float tickSpacing = _tickSteps[i] * _timeline.Zoom;
// Calculate the strength of the tick markers based on the spacing
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
// Beyond threshold the ticks don't get any bigger or fatter
if (_tickStrengths[i] >= 1)
biggestTick = i;
// Do not show small tick markers
if (tickSpacing <= minDistanceBetweenTicks)
{
smallestTick = i;
break;
}
}
int tickLevels = biggestTick - smallestTick + 1;
// Draw vertical lines for time axis // Draw vertical lines for time axis
for (int level = 0; level < tickLevels; level++) var pixelsInRange = _timeline.Zoom;
var pixelRange = pixelsInRange * (max - min);
var tickRange = Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
{ {
float strength = _tickStrengths[smallestTick + level]; var time = tick / _timeline.FramesPerSecond;
if (strength <= Mathf.Epsilon) var x = time * zoom + Timeline.StartOffset;
continue; var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
// Draw all ticks }, _tickSteps, ref _tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks);
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); var smallestTick = tickRange.X;
var lStep = _tickSteps[l]; var biggestTick = tickRange.Y;
var lNextStep = _tickSteps[l + 1]; var tickLevels = biggestTick - smallestTick + 1;
int startTick = Mathf.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep);
Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = i * lStep;
var time = tick / _timeline.FramesPerSecond;
var x = time * zoom + Timeline.StartOffset;
// Draw line
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
}
}
// Draw selection rectangle // Draw selection rectangle
if (_isSelecting) if (_isSelecting)

View File

@@ -904,7 +904,7 @@ namespace FlaxEditor.GUI
var k = new Keyframe var k = new Keyframe
{ {
Time = keyframesPos.X, Time = keyframesPos.X,
Value = DefaultValue, Value = Utilities.Utils.CloneValue(DefaultValue),
}; };
OnEditingStart(); OnEditingStart();
AddKeyframe(k); AddKeyframe(k);

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Globalization;
using FlaxEditor.GUI.Timeline.Undo; using FlaxEditor.GUI.Timeline.Undo;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -44,6 +46,25 @@ namespace FlaxEditor.GUI.Timeline.GUI
var thickness = 2.0f; var thickness = 2.0f;
var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal); var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal);
Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor); Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor);
if (_canEdit && _isMoving)
{
// TODO: handle start
string labelText;
switch (_timeline.TimeShowMode)
{
case Timeline.TimeShowModes.Frames:
labelText = _timeline.DurationFrames.ToString("###0", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Seconds:
labelText = _timeline.Duration.ToString("###0.##'s'", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Time:
labelText = TimeSpan.FromSeconds(_timeline.DurationFrames / _timeline.FramesPerSecond).ToString("g");
break;
default: throw new ArgumentOutOfRangeException();
}
Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2((Width - thickness) * 0.5f + 4, timeAxisHeaderOffset));
}
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -90,13 +111,26 @@ namespace FlaxEditor.GUI.Timeline.GUI
{ {
_timeline.MarkAsEdited(); _timeline.MarkAsEdited();
} }
Cursor = CursorType.SizeWE;
}
else if (IsMouseOver && _canEdit)
{
Cursor = CursorType.SizeWE;
} }
else else
{ {
Cursor = CursorType.Default;
base.OnMouseMove(location); base.OnMouseMove(location);
} }
} }
/// <inheritdoc />
public override void OnMouseLeave()
{
Cursor = CursorType.Default;
base.OnMouseLeave();
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {
@@ -127,6 +161,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
{ {
EndMoving(); EndMoving();
} }
Cursor = CursorType.Default;
base.OnLostFocus(); base.OnLostFocus();
} }

View File

@@ -1141,17 +1141,19 @@ namespace FlaxEditor.GUI.Timeline
{ {
foreach (var e in _playbackNavigation) foreach (var e in _playbackNavigation)
{ {
e.Enabled = false; e.Enabled = true;
e.Visible = false; e.Visible = true;
} }
} }
if (_playbackStop != null) if (_playbackStop != null)
{ {
_playbackStop.Visible = false; _playbackStop.Visible = true;
_playbackStop.Enabled = false;
} }
if (_playbackPlay != null) if (_playbackPlay != null)
{ {
_playbackPlay.Visible = false; _playbackPlay.Visible = true;
_playbackPlay.Enabled = false;
} }
if (_positionHandle != null) if (_positionHandle != null)
{ {

View File

@@ -204,7 +204,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor); b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor);
} }
} }
menu.AddButton("Select...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track"; menu.AddButton("Retarget...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track";
} }
/// <summary> /// <summary>

View File

@@ -7,6 +7,8 @@ using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using FlaxEditor.GUI.Timeline.Undo; using FlaxEditor.GUI.Timeline.Undo;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
@@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
throw new Exception("Invalid track data."); throw new Exception("Invalid track data.");
var keyframes = new KeyframesEditor.Keyframe[keyframesCount]; var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
var dataBuffer = new byte[e.ValueSize];
var propertyType = TypeUtils.GetManagedType(e.MemberTypeName); var propertyType = TypeUtils.GetManagedType(e.MemberTypeName);
if (propertyType == null) if (propertyType == null)
{ {
@@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return; return;
} }
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned); if (e.ValueSize != 0)
for (int i = 0; i < keyframesCount; i++)
{ {
var time = stream.ReadSingle(); // POD value type - use raw memory
stream.Read(dataBuffer, 0, e.ValueSize); var dataBuffer = new byte[e.ValueSize];
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType); GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
for (int i = 0; i < keyframesCount; i++)
keyframes[i] = new KeyframesEditor.Keyframe
{ {
Time = time, var time = stream.ReadSingle();
Value = value, stream.Read(dataBuffer, 0, e.ValueSize);
}; var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
keyframes[i] = new KeyframesEditor.Keyframe
{
Time = time,
Value = value,
};
}
handle.Free();
}
else
{
// Generic value - use Json storage (as UTF-8)
for (int i = 0; i < keyframesCount; i++)
{
var time = stream.ReadSingle();
var len = stream.ReadInt32();
var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null;
keyframes[i] = new KeyframesEditor.Keyframe
{
Time = time,
Value = value,
};
}
} }
handle.Free();
e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType); e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType);
e.Keyframes.SetKeyframes(keyframes); e.Keyframes.SetKeyframes(keyframes);
@@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks
stream.Write(propertyTypeNameData); stream.Write(propertyTypeNameData);
stream.Write('\0'); stream.Write('\0');
var dataBuffer = new byte[e.ValueSize]; if (e.ValueSize != 0)
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
for (int i = 0; i < keyframes.Count; i++)
{ {
var keyframe = keyframes[i]; // POD value type - use raw memory
Marshal.StructureToPtr(keyframe.Value, ptr, true); var dataBuffer = new byte[e.ValueSize];
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize); IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
stream.Write(keyframe.Time); for (int i = 0; i < keyframes.Count; i++)
stream.Write(dataBuffer); {
var keyframe = keyframes[i];
Marshal.StructureToPtr(keyframe.Value, ptr, true);
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
stream.Write(keyframe.Time);
stream.Write(dataBuffer);
}
Marshal.FreeHGlobal(ptr);
}
else
{
// Generic value - use Json storage (as UTF-8)
for (int i = 0; i < keyframes.Count; i++)
{
var keyframe = keyframes[i];
stream.Write(keyframe.Time);
var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null;
var len = json?.Length ?? 0;
stream.Write(len);
if (len > 0)
stream.Write(Encoding.UTF8.GetBytes(json));
}
} }
Marshal.FreeHGlobal(ptr);
} }
private byte[] _keyframesEditingStartData; private byte[] _keyframesEditingStartData;
@@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnKeyframesEditingEnd() private void OnKeyframesEditingEnd()
{ {
var after = EditTrackAction.CaptureData(this); var after = EditTrackAction.CaptureData(this);
if (!Utils.ArraysEqual(_keyframesEditingStartData, after)) if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after))
Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after)); Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after));
_keyframesEditingStartData = null; _keyframesEditingStartData = null;
} }
@@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <returns>The default value.</returns> /// <returns>The default value.</returns>
protected virtual object GetDefaultValue(Type propertyType) protected virtual object GetDefaultValue(Type propertyType)
{ {
return Activator.CreateInstance(propertyType); var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType));
if (value == null)
value = Activator.CreateInstance(propertyType);
return value;
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ typeof(Guid), KeyframesPropertyTrack.GetArchetype() }, { typeof(Guid), KeyframesPropertyTrack.GetArchetype() },
{ typeof(DateTime), KeyframesPropertyTrack.GetArchetype() }, { typeof(DateTime), KeyframesPropertyTrack.GetArchetype() },
{ typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() }, { typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() },
{ typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() },
{ typeof(string), StringPropertyTrack.GetArchetype() }, { typeof(string), StringPropertyTrack.GetArchetype() },
}; };
} }

View File

@@ -161,5 +161,20 @@ namespace FlaxEditor.Gizmo
} }
throw new ArgumentException("Not added mode to activate."); throw new ArgumentException("Not added mode to activate.");
} }
/// <summary>
/// Gets the gizmo of a given type or returns null if not added.
/// </summary>
/// <typeparam name="T">Type of the gizmo.</typeparam>
/// <returns>Found gizmo or null.</returns>
public T Get<T>() where T : GizmoBase
{
foreach (var e in this)
{
if (e is T asT)
return asT;
}
return null;
}
} }
} }

View File

@@ -46,7 +46,8 @@ namespace FlaxEditor.Gizmo
var plane = new Plane(Vector3.Zero, Vector3.UnitY); var plane = new Plane(Vector3.Zero, Vector3.UnitY);
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos); var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size; var options = Editor.Instance.Options.Options;
float space = options.Viewport.ViewportGridScale, size;
if (dst <= 500.0f) if (dst <= 500.0f)
{ {
size = 8000; size = 8000;
@@ -62,8 +63,12 @@ namespace FlaxEditor.Gizmo
size = 100000; size = 100000;
} }
Color color = Color.Gray * 0.7f; float bigLineIntensity = 0.8f;
Color bigColor = Color.Gray * bigLineIntensity;
Color color = bigColor * 0.8f;
int count = (int)(size / space); int count = (int)(size / space);
int midLine = count / 2;
int bigLinesMod = count / 8;
Vector3 start = new Vector3(0, 0, size * -0.5f); Vector3 start = new Vector3(0, 0, size * -0.5f);
Vector3 end = new Vector3(0, 0, size * 0.5f); Vector3 end = new Vector3(0, 0, size * 0.5f);
@@ -71,7 +76,12 @@ namespace FlaxEditor.Gizmo
for (int i = 0; i <= count; i++) for (int i = 0; i <= count; i++)
{ {
start.X = end.X = i * space + start.Z; start.X = end.X = i * space + start.Z;
DebugDraw.DrawLine(start, end, color); Color lineColor = color;
if (i == midLine)
lineColor = Color.Blue * bigLineIntensity;
else if (i % bigLinesMod == 0)
lineColor = bigColor;
DebugDraw.DrawLine(start, end, lineColor);
} }
start = new Vector3(size * -0.5f, 0, 0); start = new Vector3(size * -0.5f, 0, 0);
@@ -80,7 +90,12 @@ namespace FlaxEditor.Gizmo
for (int i = 0; i <= count; i++) for (int i = 0; i <= count; i++)
{ {
start.Z = end.Z = i * space + start.X; start.Z = end.Z = i * space + start.X;
DebugDraw.DrawLine(start, end, color); Color lineColor = color;
if (i == midLine)
lineColor = Color.Red * bigLineIntensity;
else if (i % bigLinesMod == 0)
lineColor = bigColor;
DebugDraw.DrawLine(start, end, lineColor);
} }
DebugDraw.Draw(ref renderContext, input.View(), null, true); DebugDraw.Draw(ref renderContext, input.View(), null, true);

View File

@@ -117,5 +117,10 @@ namespace FlaxEditor.Gizmo
/// </summary> /// </summary>
/// <param name="actor">The new actor to spawn.</param> /// <param name="actor">The new actor to spawn.</param>
void Spawn(Actor actor); void Spawn(Actor actor);
/// <summary>
/// Opens the context menu at the current mouse location (using current selection).
/// </summary>
void OpenContextMenu();
} }
} }

View File

@@ -42,6 +42,11 @@ namespace FlaxEditor.Gizmo
/// </summary> /// </summary>
public Action Duplicate; public Action Duplicate;
/// <summary>
/// Gets the array of selected objects.
/// </summary>
public List<SceneGraphNode> Selection => _selection;
/// <summary> /// <summary>
/// Gets the array of selected parent objects (as actors). /// Gets the array of selected parent objects (as actors).
/// </summary> /// </summary>

View File

@@ -0,0 +1,891 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Viewport.Cameras;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor
{
/// <summary>
/// UI editor camera.
/// </summary>
[HideInEditor]
internal sealed class UIEditorCamera : ViewportCamera
{
public UIEditorRoot UIEditor;
public void ShowActors(IEnumerable<Actor> actors)
{
var root = UIEditor.UIRoot;
if (root == null)
return;
// Calculate bounds of all selected objects
var areaRect = Rectangle.Empty;
foreach (var actor in actors)
{
Rectangle bounds;
if (actor is UIControl uiControl && uiControl.HasControl && uiControl.IsActive)
{
var control = uiControl.Control;
bounds = control.EditorBounds;
var ul = control.PointToParent(root, bounds.UpperLeft);
var ur = control.PointToParent(root, bounds.UpperRight);
var bl = control.PointToParent(root, bounds.BottomLeft);
var br = control.PointToParent(root, bounds.BottomRight);
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
}
else if (actor is UICanvas uiCanvas && uiCanvas.IsActive && uiCanvas.GUI.Parent == root)
{
bounds = uiCanvas.GUI.Bounds;
}
else
continue;
if (areaRect == Rectangle.Empty)
areaRect = bounds;
else
areaRect = Rectangle.Union(areaRect, bounds);
}
if (areaRect == Rectangle.Empty)
return;
// Add margin
areaRect = areaRect.MakeExpanded(100.0f);
// Show bounds
UIEditor.ViewScale = (UIEditor.Size / areaRect.Size).MinValue * 0.95f;
UIEditor.ViewCenterPosition = areaRect.Center;
}
public override void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
{
ShowActors(gizmos.Get<TransformGizmo>().Selection, ref orientation);
}
public override void ShowActor(Actor actor)
{
ShowActors(new[] { actor });
}
public override void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
{
ShowActors(selection.ConvertAll(x => (Actor)x.EditableObject));
}
public override void UpdateView(float dt, ref Vector3 moveDelta, ref Float2 mouseDelta, out bool centerMouse)
{
centerMouse = false;
}
}
/// <summary>
/// Root control for UI Controls presentation in the game/prefab viewport.
/// </summary>
[HideInEditor]
internal class UIEditorRoot : InputsPassThrough
{
/// <summary>
/// View for the UI structure to be linked in for camera zoom and panning operations.
/// </summary>
private sealed class View : ContainerControl
{
public View(UIEditorRoot parent)
{
AutoFocus = false;
ClipChildren = false;
CullChildren = false;
Pivot = Float2.Zero;
Size = new Float2(1920, 1080);
Parent = parent;
}
public override bool RayCast(ref Float2 location, out Control hit)
{
// Ignore self
return RayCastChildren(ref location, out hit);
}
public override bool IntersectsContent(ref Float2 locationParent, out Float2 location)
{
location = PointFromParent(ref locationParent);
return true;
}
public override void DrawSelf()
{
var uiRoot = (UIEditorRoot)Parent;
if (!uiRoot.EnableBackground)
return;
// Draw canvas area
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, new Color(0, 0, 0, 0.2f));
}
}
/// <summary>
/// Cached placement of the widget used to size/edit control
/// </summary>
private struct Widget
{
public UIControl UIControl;
public Rectangle Bounds;
public Float2 ResizeAxis;
public CursorType Cursor;
}
private bool _mouseMovesControl, _mouseMovesView, _mouseMovesWidget;
private Float2 _mouseMovesPos, _moveSnapDelta;
private float _mouseMoveSum;
private UndoMultiBlock _undoBlock;
private View _view;
private float[] _gridTickSteps = Utilities.Utils.CurveTickSteps, _gridTickStrengths;
private List<Widget> _widgets;
private Widget _activeWidget;
/// <summary>
/// True if enable displaying UI editing background and grid elements.
/// </summary>
public virtual bool EnableBackground => false;
/// <summary>
/// True if enable selecting controls with mouse button.
/// </summary>
public virtual bool EnableSelecting => false;
/// <summary>
/// True if enable panning and zooming the view.
/// </summary>
public bool EnableCamera => _view != null && EnableBackground;
/// <summary>
/// Transform gizmo to use sync with (selection, snapping, transformation settings).
/// </summary>
public virtual TransformGizmo TransformGizmo => null;
/// <summary>
/// The root control for controls to be linked in.
/// </summary>
public readonly ContainerControl UIRoot;
internal Float2 ViewPosition
{
get => _view.Location / -ViewScale;
set => _view.Location = value * -ViewScale;
}
internal Float2 ViewCenterPosition
{
get => (_view.Location - Size * 0.5f) / -ViewScale;
set => _view.Location = Size * 0.5f + value * -ViewScale;
}
internal float ViewScale
{
get => _view?.Scale.X ?? 1;
set
{
if (_view == null)
return;
value = Mathf.Clamp(value, 0.1f, 4.0f);
_view.Scale = new Float2(value);
}
}
public UIEditorRoot(bool enableCamera = false)
{
AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero;
AutoFocus = false;
UIRoot = this;
CullChildren = false;
ClipChildren = true;
if (enableCamera)
{
_view = new View(this);
UIRoot = _view;
}
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
var transformGizmo = TransformGizmo;
var owner = transformGizmo?.Owner;
if (_widgets != null && _widgets.Count != 0 && button == MouseButton.Left)
{
foreach (var widget in _widgets)
{
if (widget.Bounds.Contains(ref location))
{
// Initialize widget movement
_activeWidget = widget;
_mouseMovesWidget = true;
_mouseMovesPos = location;
Cursor = widget.Cursor;
StartUndo();
Focus();
StartMouseCapture();
return true;
}
}
}
if (EnableSelecting && owner != null && !_mouseMovesControl && button == MouseButton.Left)
{
// Raycast the control under the mouse
var mousePos = PointFromWindow(RootWindow.MousePosition);
if (RayCastControl(ref mousePos, out var hitControl))
{
var uiControlNode = FindUIControlNode(hitControl);
if (uiControlNode != null)
{
// Select node (with additive mode)
var selection = new List<SceneGraphNode>();
if (Root.GetKey(KeyboardKeys.Control))
{
// Add/remove from selection
selection.AddRange(transformGizmo.Selection);
if (transformGizmo.Selection.Contains(uiControlNode))
selection.Remove(uiControlNode);
else
selection.Add(uiControlNode);
}
else
{
// Select
selection.Add(uiControlNode);
}
owner.Select(selection);
// Initialize control movement
_mouseMovesControl = true;
_mouseMovesPos = location;
_mouseMoveSum = 0.0f;
_moveSnapDelta = Float2.Zero;
Focus();
StartMouseCapture();
return true;
}
}
// Allow deselecting if user clicks on nothing
else
{
owner.Select(null);
}
}
if (EnableCamera && (button == MouseButton.Right || button == MouseButton.Middle))
{
// Initialize surface movement
_mouseMovesView = true;
_mouseMovesPos = location;
_mouseMoveSum = 0.0f;
Focus();
StartMouseCapture();
return true;
}
return Focus(this);
}
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
// Change cursor if mouse is over active control widget
bool cursorChanged = false;
if (_widgets != null && _widgets.Count != 0 && !_mouseMovesControl && !_mouseMovesWidget && !_mouseMovesView)
{
foreach (var widget in _widgets)
{
if (widget.Bounds.Contains(ref location))
{
Cursor = widget.Cursor;
cursorChanged = true;
}
else if (Cursor != CursorType.Default && !cursorChanged)
{
Cursor = CursorType.Default;
}
}
}
var transformGizmo = TransformGizmo;
if (_mouseMovesControl && transformGizmo != null)
{
// Calculate transform delta
var delta = location - _mouseMovesPos;
if (transformGizmo.TranslationSnapEnable || transformGizmo.Owner.UseSnapping)
{
_moveSnapDelta += delta;
delta = Float2.SnapToGrid(_moveSnapDelta, new Float2(transformGizmo.TranslationSnapValue * ViewScale));
_moveSnapDelta -= delta;
}
// Move selected controls
if (delta.LengthSquared > 0.0f)
{
StartUndo();
var moved = false;
var moveLocation = _mouseMovesPos + delta;
var selection = transformGizmo.Selection;
for (var i = 0; i < selection.Count; i++)
{
if (IsValidControl(selection[i], out var uiControl))
{
// Move control (handle any control transformations by moving in editor's local-space)
var control = uiControl.Control;
var localLocation = control.LocalLocation;
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
control.LocalLocation = localLocation + uiControlDelta;
// Don't move if layout doesn't allow it
if (control.Parent != null)
control.Parent.PerformLayout();
else
control.PerformLayout();
// Check if control was moved (parent container could block it)
if (localLocation != control.LocalLocation)
moved = true;
}
}
_mouseMovesPos = location;
_mouseMoveSum += delta.Length;
if (moved)
Cursor = CursorType.SizeAll;
}
}
if (_mouseMovesWidget && _activeWidget.UIControl)
{
// Calculate transform delta
var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute;
var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var delta = location - _mouseMovesPos;
// TODO: scale/size snapping?
delta *= resizeAxisAbs;
// Resize control via widget
var moveLocation = _mouseMovesPos + delta;
var control = _activeWidget.UIControl.Control;
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
control.LocalLocation += uiControlDelta * resizeAxisNeg;
control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
// Don't move if layout doesn't allow it
if (control.Parent != null)
control.Parent.PerformLayout();
else
control.PerformLayout();
_mouseMovesPos = location;
}
if (_mouseMovesView)
{
// Move view
var delta = location - _mouseMovesPos;
if (delta.LengthSquared > 4.0f)
{
_mouseMovesPos = location;
_mouseMoveSum += delta.Length;
_view.Location += delta;
Cursor = CursorType.SizeAll;
}
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
EndMovingControls();
EndMovingWidget();
if (_mouseMovesView)
{
EndMovingView();
if (button == MouseButton.Right && _mouseMoveSum < 2.0f)
TransformGizmo.Owner.OpenContextMenu();
}
return base.OnMouseUp(location, button);
}
public override void OnMouseLeave()
{
EndMovingControls();
EndMovingView();
EndMovingWidget();
base.OnMouseLeave();
}
public override void OnLostFocus()
{
EndMovingControls();
EndMovingView();
EndMovingWidget();
base.OnLostFocus();
}
public override bool OnMouseWheel(Float2 location, float delta)
{
if (base.OnMouseWheel(location, delta))
return true;
if (EnableCamera && !_mouseMovesControl)
{
// Zoom view
var nextViewScale = ViewScale + delta * 0.1f;
if (delta > 0 && !_mouseMovesControl)
{
// Scale towards mouse when zooming in
var nextCenterPosition = ViewPosition + location / ViewScale;
ViewScale = nextViewScale;
ViewPosition = nextCenterPosition - (location / ViewScale);
}
else
{
// Scale while keeping center position when zooming out or when dragging view
var viewCenter = ViewCenterPosition;
ViewScale = nextViewScale;
ViewCenterPosition = viewCenter;
}
return true;
}
return false;
}
public override void Draw()
{
if (EnableBackground && _view != null)
{
// Draw background
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
// Draw grid
var viewRect = GetClientArea();
var upperLeft = _view.PointFromParent(viewRect.Location);
var bottomRight = _view.PointFromParent(viewRect.Size);
var min = Float2.Min(upperLeft, bottomRight);
var max = Float2.Max(upperLeft, bottomRight);
var pixelRange = (max - min) * ViewScale;
Render2D.PushClip(ref viewRect);
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
Render2D.PopClip();
}
base.Draw();
if (!_mouseMovesWidget)
{
// Clear widgets to collect them during drawing
_widgets?.Clear();
}
bool drawAnySelectedControl = false;
var transformGizmo = TransformGizmo;
var mousePos = PointFromWindow(RootWindow.MousePosition);
if (transformGizmo != null)
{
// Selected UI controls outline
var selection = transformGizmo.Selection;
for (var i = 0; i < selection.Count; i++)
{
if (IsValidControl(selection[i], out var controlActor))
{
DrawControl(controlActor, controlActor.Control, true, ref mousePos, ref drawAnySelectedControl, EnableSelecting);
}
}
}
if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver)
{
// Highlight control under mouse for easier selecting (except if already selected)
if (RayCastControl(ref mousePos, out var hitControl) &&
(transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl)))
{
DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl);
}
}
if (drawAnySelectedControl)
Render2D.PopTransform();
if (EnableBackground)
{
// Draw border
if (ContainsFocus)
{
Render2D.DrawRectangle(new Rectangle(1, 1, Width - 2, Height - 2), Editor.IsPlayMode ? Color.OrangeRed : Style.Current.BackgroundSelected);
}
}
}
public override void OnDestroy()
{
if (IsDisposing)
return;
EndMovingControls();
EndMovingView();
EndMovingWidget();
base.OnDestroy();
}
private Float2 GetControlDelta(Control control, ref Float2 start, ref Float2 end)
{
var pointOrigin = control.Parent ?? control;
var startPos = pointOrigin.PointFromParent(this, start);
var endPos = pointOrigin.PointFromParent(this, end);
return endPos - startPos;
}
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
{
var style = Style.Current;
var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f);
var labelsColor = style.ForegroundDisabled;
var labelsSize = 10.0f;
Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
{
var p = _view.PointToParent(axis * tick);
// Draw line
var lineRect = new Rectangle
(
viewRect.Location + (p - 0.5f) * axis,
Float2.Lerp(viewRect.Size, Float2.One, axis)
);
Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength));
// Draw label
string label = tick.ToString(System.Globalization.CultureInfo.InvariantCulture);
var labelRect = new Rectangle
(
viewRect.X + 4.0f + (p.X * axis.X),
viewRect.Y - labelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
50,
labelsSize
);
Render2D.DrawText(style.FontSmall, label, labelRect, labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
}, _gridTickSteps, ref _gridTickStrengths, min, max, pixelRange);
}
private void DrawControl(UIControl uiControl, Control control, bool selection, ref Float2 mousePos, ref bool drawAnySelectedControl, bool withWidgets = false)
{
if (!drawAnySelectedControl)
{
drawAnySelectedControl = true;
Render2D.PushTransform(ref _cachedTransform);
}
var options = Editor.Instance.Options.Options.Visual;
// Draw bounds
var bounds = control.EditorBounds;
var ul = control.PointToParent(this, bounds.UpperLeft);
var ur = control.PointToParent(this, bounds.UpperRight);
var bl = control.PointToParent(this, bounds.BottomLeft);
var br = control.PointToParent(this, bounds.BottomRight);
var color = selection ? options.SelectionOutlineColor0 : Style.Current.SelectionBorder;
#if false
// AABB
var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br));
var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br));
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
Render2D.DrawRectangle(bounds, color, options.UISelectionOutlineSize);
#else
// OBB
Render2D.DrawLine(ul, ur, color, options.UISelectionOutlineSize);
Render2D.DrawLine(ur, br, color, options.UISelectionOutlineSize);
Render2D.DrawLine(br, bl, color, options.UISelectionOutlineSize);
Render2D.DrawLine(bl, ul, color, options.UISelectionOutlineSize);
#endif
if (withWidgets)
{
// Draw sizing widgets
if (_widgets == null)
_widgets = new List<Widget>();
var widgetSize = 8.0f;
var viewScale = ViewScale;
if (viewScale < 0.7f)
widgetSize *= viewScale;
var controlSize = control.Size.Absolute.MinValue / 50.0f;
if (controlSize < 1.0f)
widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f);
var cornerSize = new Float2(widgetSize);
DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE);
DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW);
DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE);
var edgeSizeV = new Float2(widgetSize * 2, widgetSize);
var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X);
Float2.Lerp(ref ul, ref bl, 0.5f, out var el);
Float2.Lerp(ref ur, ref br, 0.5f, out var er);
Float2.Lerp(ref ul, ref ur, 0.5f, out var eu);
Float2.Lerp(ref bl, ref br, 0.5f, out var eb);
DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE);
DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS);
DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS);
// TODO: draw anchors
}
}
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor)
{
var style = Style.Current;
var rect = new Rectangle(pos - size * 0.5f, size);
if (rect.Contains(ref mousePos))
{
Render2D.FillRectangle(rect, style.Foreground);
}
else
{
Render2D.FillRectangle(rect, style.ForegroundGrey);
Render2D.DrawRectangle(rect, style.Foreground);
}
if (!_mouseMovesWidget && uiControl != null)
{
// Collect widget
_widgets.Add(new Widget
{
UIControl = uiControl,
Bounds = rect,
ResizeAxis = resizeAxis,
Cursor = cursor,
});
}
}
private bool IsValidControl(SceneGraphNode node, out UIControl uiControl)
{
uiControl = null;
if (node.EditableObject is UIControl controlActor)
uiControl = controlActor;
return uiControl != null &&
uiControl.Control != null &&
uiControl.Control.VisibleInHierarchy &&
uiControl.Control.RootWindow != null;
}
private bool RayCastControl(ref Float2 location, out Control hit)
{
#if false
// Raycast only controls with content (eg. skips transparent panels)
return RayCastChildren(ref location, out hit);
#else
// Find any control under mouse (hierarchical)
hit = GetChildAtRecursive(location);
if (hit is View || hit is CanvasContainer)
hit = null;
return hit != null;
#endif
}
private UIControlNode FindUIControlNode(Control control)
{
return FindUIControlNode(TransformGizmo.Owner.SceneGraphRoot, control);
}
private UIControlNode FindUIControlNode(SceneGraphNode node, Control control)
{
var result = node as UIControlNode;
if (result != null && ((UIControl)result.Actor).Control == control)
return result;
foreach (var e in node.ChildNodes)
{
result = FindUIControlNode(e, control);
if (result != null)
return result;
}
return null;
}
private void StartUndo()
{
var undo = TransformGizmo?.Owner?.Undo;
if (undo == null || _undoBlock != null)
return;
_undoBlock = new UndoMultiBlock(undo, TransformGizmo.Selection.ConvertAll(x => x.EditableObject), "Edit control");
}
private void EndUndo()
{
if (_undoBlock == null)
return;
_undoBlock.Dispose();
_undoBlock = null;
}
private void EndMovingControls()
{
if (!_mouseMovesControl)
return;
_mouseMovesControl = false;
EndMouseCapture();
Cursor = CursorType.Default;
EndUndo();
}
private void EndMovingView()
{
if (!_mouseMovesView)
return;
_mouseMovesView = false;
EndMouseCapture();
Cursor = CursorType.Default;
}
private void EndMovingWidget()
{
if (!_mouseMovesWidget)
return;
_mouseMovesWidget = false;
_activeWidget = new Widget();
EndMouseCapture();
Cursor = CursorType.Default;
EndUndo();
}
}
/// <summary>
/// Control that can optionally disable inputs to the children.
/// </summary>
[HideInEditor]
internal class InputsPassThrough : ContainerControl
{
private bool _isMouseOver;
/// <summary>
/// True if enable input events passing to the UI.
/// </summary>
public virtual bool EnableInputs => true;
public override bool RayCast(ref Float2 location, out Control hit)
{
return RayCastChildren(ref location, out hit);
}
public override bool ContainsPoint(ref Float2 location, bool precise = false)
{
if (precise)
return false;
return base.ContainsPoint(ref location, precise);
}
public override bool OnCharInput(char c)
{
if (!EnableInputs)
return false;
return base.OnCharInput(c);
}
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
if (!EnableInputs)
return DragDropEffect.None;
return base.OnDragDrop(ref location, data);
}
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
if (!EnableInputs)
return DragDropEffect.None;
return base.OnDragEnter(ref location, data);
}
public override void OnDragLeave()
{
if (!EnableInputs)
return;
base.OnDragLeave();
}
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
if (!EnableInputs)
return DragDropEffect.None;
return base.OnDragMove(ref location, data);
}
public override bool OnKeyDown(KeyboardKeys key)
{
if (!EnableInputs)
return false;
return base.OnKeyDown(key);
}
public override void OnKeyUp(KeyboardKeys key)
{
if (!EnableInputs)
return;
base.OnKeyUp(key);
}
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (!EnableInputs)
return false;
return base.OnMouseDoubleClick(location, button);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (!EnableInputs)
return false;
return base.OnMouseDown(location, button);
}
public override bool IsMouseOver => _isMouseOver;
public override void OnMouseEnter(Float2 location)
{
_isMouseOver = true;
if (!EnableInputs)
return;
base.OnMouseEnter(location);
}
public override void OnMouseLeave()
{
_isMouseOver = false;
if (!EnableInputs)
return;
base.OnMouseLeave();
}
public override void OnMouseMove(Float2 location)
{
if (!EnableInputs)
return;
base.OnMouseMove(location);
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (!EnableInputs)
return false;
return base.OnMouseUp(location, button);
}
public override bool OnMouseWheel(Float2 location, float delta)
{
if (!EnableInputs)
return false;
return base.OnMouseWheel(location, delta);
}
}
}

View File

@@ -263,22 +263,22 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_GetShaderAssetSourceCode(BinaryAss
INTERNAL_CALL_CHECK_RETURN(obj, nullptr); INTERNAL_CALL_CHECK_RETURN(obj, nullptr);
if (obj->WaitForLoaded()) if (obj->WaitForLoaded())
DebugLog::ThrowNullReference(); DebugLog::ThrowNullReference();
auto lock = obj->Storage->Lock(); auto lock = obj->Storage->Lock();
if (obj->LoadChunk(SHADER_FILE_CHUNK_SOURCE)) if (obj->LoadChunk(SHADER_FILE_CHUNK_SOURCE))
return nullptr; return nullptr;
// Decrypt source code
BytesContainer data; BytesContainer data;
obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data); obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data);
auto source = data.Get<char>();
auto sourceLength = data.Length();
Encryption::DecryptBytes(data.Get(), data.Length());
source[sourceLength - 1] = 0;
Encryption::DecryptBytes((byte*)data.Get(), data.Length()); // Get source and encrypt it back
const StringAnsiView srcData((const char*)data.Get(), data.Length()); const StringAnsiView srcData((const char*)data.Get(), data.Length());
const String source(srcData); const auto str = MUtils::ToString(srcData);
const auto str = MUtils::ToString(source); Encryption::EncryptBytes(data.Get(), data.Length());
Encryption::EncryptBytes((byte*)data.Get(), data.Length());
return str; return str;
} }

View File

@@ -1106,6 +1106,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new VisualScriptProxy()); Proxy.Add(new VisualScriptProxy());
Proxy.Add(new BehaviorTreeProxy()); Proxy.Add(new BehaviorTreeProxy());
Proxy.Add(new LocalizedStringTableProxy()); Proxy.Add(new LocalizedStringTableProxy());
Proxy.Add(new WidgetProxy());
Proxy.Add(new FileProxy()); Proxy.Add(new FileProxy());
Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>()); Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>());

View File

@@ -101,7 +101,10 @@ namespace FlaxEditor.Modules
public void Select(List<SceneGraphNode> selection, bool additive = false) public void Select(List<SceneGraphNode> selection, bool additive = false)
{ {
if (selection == null) if (selection == null)
throw new ArgumentNullException(); {
Deselect();
return;
}
// Prevent from selecting null nodes // Prevent from selecting null nodes
selection.RemoveAll(x => x == null); selection.RemoveAll(x => x == null);

View File

@@ -6,6 +6,7 @@ using System.IO;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors; using FlaxEditor.SceneGraph.Actors;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Modules namespace FlaxEditor.Modules
@@ -454,6 +455,41 @@ namespace FlaxEditor.Modules
Profiler.EndEvent(); Profiler.EndEvent();
} }
private Dictionary<ContainerControl, Float2> _uiRootSizes;
internal void OnSaveStart(ContainerControl uiRoot)
{
// Force viewport UI to have fixed size during scene/prefabs saving to result in stable data (less mess in version control diffs)
if (_uiRootSizes == null)
_uiRootSizes = new Dictionary<ContainerControl, Float2>();
_uiRootSizes[uiRoot] = uiRoot.Size;
uiRoot.Size = new Float2(1920, 1080);
}
internal void OnSaveEnd(ContainerControl uiRoot)
{
// Restore cached size of the UI root container
if (_uiRootSizes != null && _uiRootSizes.Remove(uiRoot, out var size))
{
uiRoot.Size = size;
}
}
private void OnSceneSaving(Scene scene, Guid sceneId)
{
OnSaveStart(RootControl.GameRoot);
}
private void OnSceneSaved(Scene scene, Guid sceneId)
{
OnSaveEnd(RootControl.GameRoot);
}
private void OnSceneSaveError(Scene scene, Guid sceneId)
{
OnSaveEnd(RootControl.GameRoot);
}
private void OnSceneLoaded(Scene scene, Guid sceneId) private void OnSceneLoaded(Scene scene, Guid sceneId)
{ {
var startTime = DateTime.UtcNow; var startTime = DateTime.UtcNow;
@@ -659,6 +695,9 @@ namespace FlaxEditor.Modules
Root = new ScenesRootNode(); Root = new ScenesRootNode();
// Bind events // Bind events
Level.SceneSaving += OnSceneSaving;
Level.SceneSaved += OnSceneSaved;
Level.SceneSaveError += OnSceneSaveError;
Level.SceneLoaded += OnSceneLoaded; Level.SceneLoaded += OnSceneLoaded;
Level.SceneUnloading += OnSceneUnloading; Level.SceneUnloading += OnSceneUnloading;
Level.ActorSpawned += OnActorSpawned; Level.ActorSpawned += OnActorSpawned;
@@ -673,6 +712,9 @@ namespace FlaxEditor.Modules
public override void OnExit() public override void OnExit()
{ {
// Unbind events // Unbind events
Level.SceneSaving -= OnSceneSaving;
Level.SceneSaved -= OnSceneSaved;
Level.SceneSaveError -= OnSceneSaveError;
Level.SceneLoaded -= OnSceneLoaded; Level.SceneLoaded -= OnSceneLoaded;
Level.SceneUnloading -= OnSceneUnloading; Level.SceneUnloading -= OnSceneUnloading;
Level.ActorSpawned -= OnActorSpawned; Level.ActorSpawned -= OnActorSpawned;

View File

@@ -201,8 +201,8 @@ namespace FlaxEditor.Options
/// <returns>True if input has been processed, otherwise false.</returns> /// <returns>True if input has been processed, otherwise false.</returns>
public bool Process(Control control) public bool Process(Control control)
{ {
var root = control.Root; var root = control?.Root;
return root.GetKey(Key) && ProcessModifiers(control); return root != null && root.GetKey(Key) && ProcessModifiers(control);
} }
/// <summary> /// <summary>

View File

@@ -2,6 +2,7 @@
using System.ComponentModel; using System.ComponentModel;
using FlaxEditor.GUI.Docking; using FlaxEditor.GUI.Docking;
using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Options namespace FlaxEditor.Options
@@ -116,6 +117,27 @@ namespace FlaxEditor.Options
BorderlessWindow, BorderlessWindow,
} }
/// <summary>
/// Options for formatting numerical values.
/// </summary>
public enum ValueFormattingType
{
/// <summary>
/// No formatting.
/// </summary>
None,
/// <summary>
/// Format using the base SI unit.
/// </summary>
BaseUnit,
/// <summary>
/// Format using a unit that matches the value best.
/// </summary>
AutoUnit,
}
/// <summary> /// <summary>
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required. /// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
/// </summary> /// </summary>
@@ -174,6 +196,20 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")] [EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")]
public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal; public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal;
/// <summary>
/// Gets or sets the formatting option for numeric values in the editor.
/// </summary>
[DefaultValue(ValueFormattingType.None)]
[EditorDisplay("Interface"), EditorOrder(300)]
public ValueFormattingType ValueFormatting { get; set; }
/// <summary>
/// Gets or sets the option to put a space between numbers and units for unit formatting.
/// </summary>
[DefaultValue(false)]
[EditorDisplay("Interface"), EditorOrder(310)]
public bool SeparateValueAndUnit { get; set; }
/// <summary> /// <summary>
/// Gets or sets the timestamps prefix mode for output log messages. /// Gets or sets the timestamps prefix mode for output log messages.
/// </summary> /// </summary>

View File

@@ -200,6 +200,27 @@ namespace FlaxEditor.Options
EditorAssets.Cache.OnEditorOptionsChanged(Options); EditorAssets.Cache.OnEditorOptionsChanged(Options);
// Units formatting options
bool useUnitsFormatting = Options.Interface.ValueFormatting != InterfaceOptions.ValueFormattingType.None;
bool automaticUnitsFormatting = Options.Interface.ValueFormatting == InterfaceOptions.ValueFormattingType.AutoUnit;
bool separateValueAndUnit = Options.Interface.SeparateValueAndUnit;
if (useUnitsFormatting != Utilities.Units.UseUnitsFormatting ||
automaticUnitsFormatting != Utilities.Units.AutomaticUnitsFormatting ||
separateValueAndUnit != Utilities.Units.SeparateValueAndUnit)
{
Utilities.Units.UseUnitsFormatting = useUnitsFormatting;
Utilities.Units.AutomaticUnitsFormatting = automaticUnitsFormatting;
Utilities.Units.SeparateValueAndUnit = separateValueAndUnit;
// Refresh UI in property panels
Editor.Windows.PropertiesWin?.Presenter.BuildLayoutOnUpdate();
foreach (var window in Editor.Windows.Windows)
{
if (window is Windows.Assets.PrefabWindow prefabWindow)
prefabWindow.Presenter.BuildLayoutOnUpdate();
}
}
// Send event // Send event
OptionsChanged?.Invoke(Options); OptionsChanged?.Invoke(Options);
} }

View File

@@ -308,11 +308,14 @@ namespace FlaxEditor.SceneGraph.Actors
var selection = Editor.Instance.SceneEditing.Selection; var selection = Editor.Instance.SceneEditing.Selection;
if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this) if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this)
{ {
if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) var mouse = Input.Mouse;
var keyboard = Input.Keyboard;
if (keyboard.GetKey(KeyboardKeys.Shift))
EditSplineWithSnap(selectedPoint); EditSplineWithSnap(selectedPoint);
var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero; var canAddSplinePoint = mouse.PositionDelta == Float2.Zero && mouse.Position != Float2.Zero;
var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right); var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && mouse.GetButtonDown(MouseButton.Right);
if (requestAddSplinePoint && canAddSplinePoint) if (requestAddSplinePoint && canAddSplinePoint)
AddSplinePoint(selectedPoint); AddSplinePoint(selectedPoint);
} }

View File

@@ -11,6 +11,7 @@ using System.Collections.Generic;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors namespace FlaxEditor.SceneGraph.Actors
@@ -84,76 +85,109 @@ namespace FlaxEditor.SceneGraph.Actors
{ {
base.OnContextMenu(contextMenu, window); base.OnContextMenu(contextMenu, window);
contextMenu.AddButton("Add collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null; contextMenu.AddButton("Add collider", () => OnAddMeshCollider(window)).Enabled = ((StaticModel)Actor).Model != null;
} }
private void OnAddMeshCollider() private void OnAddMeshCollider(EditorWindow window)
{ {
var model = ((StaticModel)Actor).Model; // Allow collider to be added to evey static model selection
if (!model) SceneGraphNode[] selection = Array.Empty<SceneGraphNode>();
return; if (window is SceneTreeWindow)
// Special case for in-built Editor models that can use analytical collision
var modelPath = model.Path;
if (modelPath.EndsWith("/Primitives/Cube.flax", StringComparison.Ordinal))
{ {
var actor = new BoxCollider selection = Editor.Instance.SceneEditing.Selection.ToArray();
{
StaticFlags = Actor.StaticFlags,
Transform = Actor.Transform,
};
Root.Spawn(actor, Actor);
return;
} }
if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal)) else if (window is PrefabWindow prefabWindow)
{ {
var actor = new SphereCollider selection = prefabWindow.Selection.ToArray();
{
StaticFlags = Actor.StaticFlags,
Transform = Actor.Transform,
};
Root.Spawn(actor, Actor);
return;
}
if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal))
{
var actor = new BoxCollider
{
StaticFlags = Actor.StaticFlags,
Transform = Actor.Transform,
Size = new Float3(100.0f, 100.0f, 1.0f),
};
Root.Spawn(actor, Actor);
return;
}
if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal))
{
var actor = new CapsuleCollider
{
StaticFlags = Actor.StaticFlags,
Transform = Actor.Transform,
Radius = 25.0f,
Height = 50.0f,
};
Editor.Instance.SceneEditing.Spawn(actor, Actor);
actor.LocalPosition = new Vector3(0, 50.0f, 0);
actor.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
return;
} }
// Create collision data (or reuse) and add collision actor var createdNodes = new List<SceneGraphNode>();
Action<CollisionData> created = collisionData => foreach (var node in selection)
{ {
var actor = new MeshCollider if (node is not StaticModelNode staticModelNode)
continue;
var model = ((StaticModel)staticModelNode.Actor).Model;
if (!model)
continue;
// Special case for in-built Editor models that can use analytical collision
var modelPath = model.Path;
if (modelPath.EndsWith("/Primitives/Cube.flax", StringComparison.Ordinal))
{ {
StaticFlags = Actor.StaticFlags, var actor = new BoxCollider
Transform = Actor.Transform, {
CollisionData = collisionData, StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
};
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
continue;
}
if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal))
{
var actor = new SphereCollider
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
};
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
continue;
}
if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal))
{
var actor = new BoxCollider
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
Size = new Float3(100.0f, 100.0f, 1.0f),
};
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
continue;
}
if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal))
{
var actor = new CapsuleCollider
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
Radius = 25.0f,
Height = 50.0f,
};
Editor.Instance.SceneEditing.Spawn(actor, staticModelNode.Actor);
actor.LocalPosition = new Vector3(0, 50.0f, 0);
actor.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
continue;
}
// Create collision data (or reuse) and add collision actor
Action<CollisionData> created = collisionData =>
{
var actor = new MeshCollider
{
StaticFlags = staticModelNode.Actor.StaticFlags,
Transform = staticModelNode.Actor.Transform,
CollisionData = collisionData,
};
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
}; };
Root.Spawn(actor, Actor); var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>();
}; collisionDataProxy.CreateCollisionDataFromModel(model, created, selection.Length == 1);
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>(); }
collisionDataProxy.CreateCollisionDataFromModel(model, created);
// Select all created nodes
if (window is SceneTreeWindow)
{
Editor.Instance.SceneEditing.Select(createdNodes);
}
else if (window is PrefabWindow pWindow)
{
pWindow.Select(createdNodes);
}
} }
} }
} }

View File

@@ -617,8 +617,9 @@ namespace FlaxEditor.Surface.Archetypes
public override void SetLocation(int index, Float2 location) public override void SetLocation(int index, Float2 location)
{ {
var dataA = (Float4)_node.Values[4 + index * 2]; var dataA = (Float4)_node.Values[4 + index * 2];
var ranges = (Float4)_node.Values[0];
dataA.X = location.X; dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
_node.Values[4 + index * 2] = dataA; _node.Values[4 + index * 2] = dataA;
_node.Surface.MarkAsEdited(); _node.Surface.MarkAsEdited();
@@ -750,9 +751,10 @@ namespace FlaxEditor.Surface.Archetypes
public override void SetLocation(int index, Float2 location) public override void SetLocation(int index, Float2 location)
{ {
var dataA = (Float4)_node.Values[4 + index * 2]; var dataA = (Float4)_node.Values[4 + index * 2];
var ranges = (Float4)_node.Values[0];
dataA.X = location.X; dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y);
dataA.Y = location.Y; dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W);
_node.Values[4 + index * 2] = dataA; _node.Values[4 + index * 2] = dataA;
_node.Surface.MarkAsEdited(); _node.Surface.MarkAsEdited();

View File

@@ -117,17 +117,9 @@ namespace FlaxEditor.Surface
editor.Panel.Tag = attributeType; editor.Panel.Tag = attributeType;
_presenter = editor; _presenter = editor;
using (var stream = new MemoryStream()) // Cache 'previous' state to check if attributes were edited after operation
{ _oldData = SurfaceMeta.GetAttributesData(attributes);
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
var formatter = new BinaryFormatter();
#pragma warning disable SYSLIB0011
formatter.Serialize(stream, attributes);
#pragma warning restore SYSLIB0011
_oldData = stream.ToArray();
}
editor.Select(new Proxy editor.Select(new Proxy
{ {
Value = attributes, Value = attributes,
@@ -145,20 +137,11 @@ namespace FlaxEditor.Surface
return; return;
} }
} }
using (var stream = new MemoryStream())
{
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
var formatter = new BinaryFormatter(); var newData = SurfaceMeta.GetAttributesData(newValue);
#pragma warning disable SYSLIB0011 if (!_oldData.SequenceEqual(newData))
formatter.Serialize(stream, newValue); {
#pragma warning restore SYSLIB0011 Edited?.Invoke(newValue);
var newData = stream.ToArray();
if (!_oldData.SequenceEqual(newData))
{
Edited?.Invoke(newValue);
}
} }
Hide(); Hide();

View File

@@ -4,9 +4,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Surface namespace FlaxEditor.Surface
@@ -39,28 +39,48 @@ namespace FlaxEditor.Surface
public readonly List<Entry> Entries = new List<Entry>(); public readonly List<Entry> Entries = new List<Entry>();
/// <summary> /// <summary>
/// The attribute meta type identifier. /// The attribute meta type identifier. Uses byte[] as storage for Attribute[] serialized with BinaryFormatter (deprecated in .NET 5).
/// [Deprecated on 8.12.2023, expires on 8.12.2025]
/// </summary> /// </summary>
public const int AttributeMetaTypeID = 12; public const int OldAttributeMetaTypeID = 12;
/// <summary>
/// The attribute meta type identifier. Uses byte[] as storage for Attribute[] serialized with JsonSerializer.
/// </summary>
public const int AttributeMetaTypeID = 13;
/// <summary> /// <summary>
/// Gets the attributes collection from the data. /// Gets the attributes collection from the data.
/// </summary> /// </summary>
/// <param name="data">The graph metadata.</param> /// <param name="data">The graph metadata serialized with JsonSerializer.</param>
/// <param name="oldData">The graph metadata serialized with BinaryFormatter.</param>
/// <returns>The attributes collection.</returns> /// <returns>The attributes collection.</returns>
public static Attribute[] GetAttributes(byte[] data) public static Attribute[] GetAttributes(byte[] data, byte[] oldData)
{ {
if (data != null && data.Length != 0) if (data != null && data.Length != 0)
{ {
using (var stream = new MemoryStream(data)) try
{
var json = Encoding.Unicode.GetString(data);
return FlaxEngine.Json.JsonSerializer.Deserialize<Attribute[]>(json);
}
catch (Exception ex)
{
Editor.LogError("Failed to deserialize Visject attributes array.");
Editor.LogWarning(ex);
}
}
if (oldData != null && oldData.Length != 0)
{
// [Deprecated on 8.12.2023, expires on 8.12.2025]
using (var stream = new MemoryStream(oldData))
{ {
try try
{ {
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041) // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly); using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
var formatter = new BinaryFormatter();
#pragma warning disable SYSLIB0011 #pragma warning disable SYSLIB0011
var formatter = new BinaryFormatter();
return (Attribute[])formatter.Deserialize(stream); return (Attribute[])formatter.Deserialize(stream);
#pragma warning restore SYSLIB0011 #pragma warning restore SYSLIB0011
} }
@@ -74,6 +94,21 @@ namespace FlaxEditor.Surface
return Utils.GetEmptyArray<Attribute>(); return Utils.GetEmptyArray<Attribute>();
} }
/// <summary>
/// Serializes surface attributes into byte[] data using the current format.
/// </summary>
/// <param name="attributes">The input attributes.</param>
/// <returns>The result array with bytes. Can be empty but not null.</returns>
internal static byte[] GetAttributesData(Attribute[] attributes)
{
if (attributes != null && attributes.Length != 0)
{
var json = FlaxEngine.Json.JsonSerializer.Serialize(attributes);
return Encoding.Unicode.GetBytes(json);
}
return Utils.GetEmptyArray<byte>();
}
/// <summary> /// <summary>
/// Determines whether the specified attribute was defined for this member. /// Determines whether the specified attribute was defined for this member.
/// </summary> /// </summary>
@@ -93,7 +128,8 @@ namespace FlaxEditor.Surface
public static Attribute[] GetAttributes(GraphParameter parameter) public static Attribute[] GetAttributes(GraphParameter parameter)
{ {
var data = parameter.GetMetaData(AttributeMetaTypeID); var data = parameter.GetMetaData(AttributeMetaTypeID);
return GetAttributes(data); var dataOld = parameter.GetMetaData(OldAttributeMetaTypeID);
return GetAttributes(data, dataOld);
} }
/// <summary> /// <summary>
@@ -102,12 +138,7 @@ namespace FlaxEditor.Surface
/// <returns>The attributes collection.</returns> /// <returns>The attributes collection.</returns>
public Attribute[] GetAttributes() public Attribute[] GetAttributes()
{ {
for (int i = 0; i < Entries.Count; i++) return GetAttributes(GetEntry(AttributeMetaTypeID).Data, GetEntry(OldAttributeMetaTypeID).Data);
{
if (Entries[i].TypeID == AttributeMetaTypeID)
return GetAttributes(Entries[i].Data);
}
return Utils.GetEmptyArray<Attribute>();
} }
/// <summary> /// <summary>
@@ -119,25 +150,12 @@ namespace FlaxEditor.Surface
if (attributes == null || attributes.Length == 0) if (attributes == null || attributes.Length == 0)
{ {
RemoveEntry(AttributeMetaTypeID); RemoveEntry(AttributeMetaTypeID);
RemoveEntry(OldAttributeMetaTypeID);
} }
else else
{ {
for (int i = 0; i < attributes.Length; i++) AddEntry(AttributeMetaTypeID, GetAttributesData(attributes));
{ RemoveEntry(OldAttributeMetaTypeID);
if (attributes[i] == null)
throw new NullReferenceException("One of the Visject attributes is null.");
}
using (var stream = new MemoryStream())
{
// Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
var formatter = new BinaryFormatter();
#pragma warning disable SYSLIB0011
formatter.Serialize(stream, attributes);
#pragma warning restore SYSLIB0011
AddEntry(AttributeMetaTypeID, stream.ToArray());
}
} }
} }
@@ -180,11 +198,11 @@ namespace FlaxEditor.Surface
/// <returns>True if cannot save data</returns> /// <returns>True if cannot save data</returns>
public void Save(BinaryWriter stream) public void Save(BinaryWriter stream)
{ {
stream.Write(Entries.Count); var entries = Entries;
stream.Write(entries.Count);
for (int i = 0; i < Entries.Count; i++) for (int i = 0; i < entries.Count; i++)
{ {
Entry e = Entries[i]; Entry e = entries[i];
stream.Write(e.TypeID); stream.Write(e.TypeID);
stream.Write((long)0); stream.Write((long)0);
@@ -214,14 +232,12 @@ namespace FlaxEditor.Surface
/// <returns>Entry</returns> /// <returns>Entry</returns>
public Entry GetEntry(int typeID) public Entry GetEntry(int typeID)
{ {
for (int i = 0; i < Entries.Count; i++) var entries = Entries;
for (int i = 0; i < entries.Count; i++)
{ {
if (Entries[i].TypeID == typeID) if (entries[i].TypeID == typeID)
{ return entries[i];
return Entries[i];
}
} }
return new Entry(); return new Entry();
} }

View File

@@ -64,7 +64,11 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
protected virtual void DrawBackground() protected virtual void DrawBackground()
{ {
var background = Style.Background; DrawBackgroundDefault(Style.Background, Width, Height);
}
internal static void DrawBackgroundDefault(Texture background, float width, float height)
{
if (background && background.ResidentMipLevels > 0) if (background && background.ResidentMipLevels > 0)
{ {
var bSize = background.Size; var bSize = background.Size;
@@ -77,8 +81,8 @@ namespace FlaxEditor.Surface
if (pos.Y > 0) if (pos.Y > 0)
pos.Y -= bh; pos.Y -= bh;
int maxI = Mathf.CeilToInt(Width / bw + 1.0f); int maxI = Mathf.CeilToInt(width / bw + 1.0f);
int maxJ = Mathf.CeilToInt(Height / bh + 1.0f); int maxJ = Mathf.CeilToInt(height / bh + 1.0f);
for (int i = 0; i < maxI; i++) for (int i = 0; i < maxI; i++)
{ {

View File

@@ -10,6 +10,7 @@
#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/GameSettings.h"
#include "Engine/Core/Config/BuildSettings.h"
#include "Engine/Content/Content.h" #include "Engine/Content/Content.h"
#include "Engine/Content/AssetReference.h" #include "Engine/Content/AssetReference.h"
#include "Engine/Content/Assets/Texture.h" #include "Engine/Content/Assets/Texture.h"
@@ -17,7 +18,18 @@
#if PLATFORM_MAC #if PLATFORM_MAC
#include "Engine/Platform/Apple/ApplePlatformSettings.h" #include "Engine/Platform/Apple/ApplePlatformSettings.h"
#endif #endif
#include <fstream>
String EditorUtilities::GetOutputName()
{
const auto gameSettings = GameSettings::Get();
const auto buildSettings = BuildSettings::Get();
String outputName = buildSettings->OutputName;
outputName.Replace(TEXT("${PROJECT_NAME}"), *gameSettings->ProductName, StringSearchCase::IgnoreCase);
outputName.Replace(TEXT("${COMPANY_NAME}"), *gameSettings->CompanyName, StringSearchCase::IgnoreCase);
if (outputName.IsEmpty())
outputName = TEXT("FlaxGame");
return outputName;
}
bool EditorUtilities::FormatAppPackageName(String& packageName) bool EditorUtilities::FormatAppPackageName(String& packageName)
{ {

View File

@@ -22,6 +22,7 @@ public:
SplashScreen, SplashScreen,
}; };
static String GetOutputName();
static bool FormatAppPackageName(String& packageName); static bool FormatAppPackageName(String& packageName);
static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon); static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon);
static bool GetTexture(const Guid& textureId, TextureData& textureData); static bool GetTexture(const Guid& textureId, TextureData& textureData);

View File

@@ -121,6 +121,37 @@ namespace FlaxEditor.Utilities
["e"] = Math.E, ["e"] = Math.E,
["infinity"] = double.MaxValue, ["infinity"] = double.MaxValue,
["-infinity"] = -double.MaxValue, ["-infinity"] = -double.MaxValue,
["m"] = Units.Meters2Units,
["cm"] = Units.Meters2Units / 100,
["km"] = Units.Meters2Units * 1000,
["s"] = 1,
["ms"] = 0.001,
["min"] = 60,
["h"] = 3600,
["cm²"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
["cm³"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
["dm²"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["dm³"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["l"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["m²"] = Units.Meters2Units * Units.Meters2Units,
["m³"] = Units.Meters2Units * Units.Meters2Units * Units.Meters2Units,
["kg"] = 1,
["g"] = 0.001,
["n"] = Units.Meters2Units
};
/// <summary>
/// List known units which cannot be handled as a variable easily because they contain operator symbols (mostly a forward slash). The value is the factor to calculate game units.
/// </summary>
private static readonly IDictionary<string, double> UnitSymbols = new Dictionary<string, double>
{
["cm/s"] = Units.Meters2Units / 100,
["cm/s²"] = Units.Meters2Units / 100,
["m/s"] = Units.Meters2Units,
["m/s²"] = Units.Meters2Units,
["km/h"] = 1 / 3.6 * Units.Meters2Units,
// Nm is here because these values are compared case-sensitive, and we don't want to confuse nanometers and Newtonmeters
["Nm"] = Units.Meters2Units * Units.Meters2Units,
}; };
/// <summary> /// <summary>
@@ -156,7 +187,7 @@ namespace FlaxEditor.Utilities
if (Operators.ContainsKey(str)) if (Operators.ContainsKey(str))
return TokenType.Operator; return TokenType.Operator;
if (char.IsLetter(c)) if (char.IsLetter(c) || c == '²' || c == '³')
return TokenType.Variable; return TokenType.Variable;
throw new ParsingException("wrong character"); throw new ParsingException("wrong character");
@@ -170,7 +201,24 @@ namespace FlaxEditor.Utilities
public static IEnumerable<Token> Tokenize(string text) public static IEnumerable<Token> Tokenize(string text)
{ {
// Prepare text // Prepare text
text = text.Replace(',', '.'); text = text.Replace(',', '.').Replace("°", "");
foreach (var kv in UnitSymbols)
{
int idx;
do
{
idx = text.IndexOf(kv.Key, StringComparison.InvariantCulture);
if (idx > 0)
{
if (DetermineType(text[idx - 1]) != TokenType.Number)
throw new ParsingException($"unit found without a number: {kv.Key} at {idx} in {text}");
if (Mathf.Abs(kv.Value - 1) < Mathf.Epsilon)
text = text.Remove(idx, kv.Key.Length);
else
text = text.Replace(kv.Key, "*" + kv.Value);
}
} while (idx > 0);
}
// Necessary to correctly parse negative numbers // Necessary to correctly parse negative numbers
var previous = TokenType.WhiteSpace; var previous = TokenType.WhiteSpace;
@@ -240,6 +288,11 @@ namespace FlaxEditor.Utilities
} }
else if (type == TokenType.Variable) else if (type == TokenType.Variable)
{ {
if (previous == TokenType.Number)
{
previous = TokenType.Operator;
yield return new Token(TokenType.Operator, "*");
}
// Continue till the end of the variable // Continue till the end of the variable
while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable) while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable)
{ {
@@ -335,7 +388,7 @@ namespace FlaxEditor.Utilities
} }
else else
{ {
throw new ParsingException("unknown variable"); throw new ParsingException($"unknown variable : {token.Value}");
} }
} }
else else
@@ -372,6 +425,15 @@ namespace FlaxEditor.Utilities
} }
} }
// if stack has more than one item we're not finished with evaluating
// we assume the remaining values are all factors to be multiplied
if (stack.Count > 1)
{
var v1 = stack.Pop();
while (stack.Count > 0)
v1 *= stack.Pop();
return v1;
}
return stack.Pop(); return stack.Pop();
} }

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
namespace FlaxEditor.Utilities;
/// <summary>
/// Units display utilities for Editor.
/// </summary>
public class Units
{
/// <summary>
/// Factor of units per meter.
/// </summary>
public static readonly float Meters2Units = 100f;
/// <summary>
/// False to always show game units without any postfix.
/// </summary>
public static bool UseUnitsFormatting = true;
/// <summary>
/// Add a space between numbers and units for readability.
/// </summary>
public static bool SeparateValueAndUnit = true;
/// <summary>
/// If set to true, the distance unit is chosen on the magnitude, otherwise it's meters.
/// </summary>
public static bool AutomaticUnitsFormatting = true;
/// <summary>
/// Return the unit according to user settings.
/// </summary>
/// <param name="unit">The unit name.</param>
/// <returns>The formatted text.</returns>
public static string Unit(string unit)
{
if (SeparateValueAndUnit)
return $" {unit}";
return unit;
}
}

View File

@@ -243,6 +243,63 @@ namespace FlaxEditor.Utilities
500000, 1000000, 5000000, 10000000, 100000000 500000, 1000000, 5000000, 10000000, 100000000
}; };
internal delegate void DrawCurveTick(float tick, float strength);
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{
if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
tickStrengths = new float[tickSteps.Length];
// Find the strength for each modulo number tick marker
var pixelsInRange = pixelRange / (max - min);
var smallestTick = 0;
var biggestTick = tickSteps.Length - 1;
for (int i = tickSteps.Length - 1; i >= 0; i--)
{
// Calculate how far apart these modulo tick steps are spaced
float tickSpacing = tickSteps[i] * pixelsInRange;
// Calculate the strength of the tick markers based on the spacing
tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
// Beyond threshold the ticks don't get any bigger or fatter
if (tickStrengths[i] >= 1)
biggestTick = i;
// Do not show small tick markers
if (tickSpacing <= minDistanceBetweenTicks)
{
smallestTick = i;
break;
}
}
var tickLevels = biggestTick - smallestTick + 1;
// Draw all tick levels
for (int level = 0; level < tickLevels; level++)
{
float strength = tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon)
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1);
var lStep = tickSteps[l];
var lNextStep = tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = i * lStep;
drawTick(tick, strength);
}
}
return new Int2(smallestTick, biggestTick);
}
/// <summary> /// <summary>
/// Determines whether the specified path string contains any invalid character. /// Determines whether the specified path string contains any invalid character.
/// </summary> /// </summary>
@@ -1187,6 +1244,71 @@ namespace FlaxEditor.Utilities
return StringUtils.GetPathWithoutExtension(path); return StringUtils.GetPathWithoutExtension(path);
} }
private static string InternalFormat(double value, string format, FlaxEngine.Utils.ValueCategory category)
{
switch (category)
{
case FlaxEngine.Utils.ValueCategory.Distance:
if (!Units.AutomaticUnitsFormatting)
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
var absValue = Mathf.Abs(value);
// in case a unit != cm this would be (value / Meters2Units * 100)
if (absValue < Units.Meters2Units)
return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("cm");
if (absValue < Units.Meters2Units * 1000)
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
return (value / 1000 / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("km");
case FlaxEngine.Utils.ValueCategory.Angle: return value.ToString(format, CultureInfo.InvariantCulture) + "°";
case FlaxEngine.Utils.ValueCategory.Time: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("s");
// some fonts have a symbol for that: "\u33A7"
case FlaxEngine.Utils.ValueCategory.Speed: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s");
case FlaxEngine.Utils.ValueCategory.Acceleration: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s²");
case FlaxEngine.Utils.ValueCategory.Area: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m²");
case FlaxEngine.Utils.ValueCategory.Volume: return (value / Units.Meters2Units / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m³");
case FlaxEngine.Utils.ValueCategory.Mass: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("kg");
case FlaxEngine.Utils.ValueCategory.Force: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("N");
case FlaxEngine.Utils.ValueCategory.Torque: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("Nm");
case FlaxEngine.Utils.ValueCategory.None:
default: return FormatFloat(value);
}
}
/// <summary>
/// Format a float value either as-is, with a distance unit or with a degree sign.
/// </summary>
/// <param name="value">The value to format.</param>
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
/// <returns>The formatted string.</returns>
public static string FormatFloat(float value, FlaxEngine.Utils.ValueCategory category)
{
if (float.IsPositiveInfinity(value) || value == float.MaxValue)
return "Infinity";
if (float.IsNegativeInfinity(value) || value == float.MinValue)
return "-Infinity";
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
return FormatFloat(value);
const string format = "G7";
return InternalFormat(value, format, category);
}
/// <summary>
/// Format a double value either as-is, with a distance unit or with a degree sign
/// </summary>
/// <param name="value">The value to format.</param>
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
/// <returns>The formatted string.</returns>
public static string FormatFloat(double value, FlaxEngine.Utils.ValueCategory category)
{
if (double.IsPositiveInfinity(value) || value == double.MaxValue)
return "Infinity";
if (double.IsNegativeInfinity(value) || value == double.MinValue)
return "-Infinity";
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
return FormatFloat(value);
const string format = "G15";
return InternalFormat(value, format, category);
}
/// <summary> /// <summary>
/// Formats the floating point value (double precision) into the readable text representation. /// Formats the floating point value (double precision) into the readable text representation.
/// </summary> /// </summary>
@@ -1198,7 +1320,7 @@ namespace FlaxEditor.Utilities
return "Infinity"; return "Infinity";
if (float.IsNegativeInfinity(value) || value == float.MinValue) if (float.IsNegativeInfinity(value) || value == float.MinValue)
return "-Infinity"; return "-Infinity";
string str = value.ToString("r", CultureInfo.InvariantCulture); string str = value.ToString("R", CultureInfo.InvariantCulture);
return FormatFloat(str, value < 0); return FormatFloat(str, value < 0);
} }
@@ -1213,7 +1335,7 @@ namespace FlaxEditor.Utilities
return "Infinity"; return "Infinity";
if (double.IsNegativeInfinity(value) || value == double.MinValue) if (double.IsNegativeInfinity(value) || value == double.MinValue)
return "-Infinity"; return "-Infinity";
string str = value.ToString("r", CultureInfo.InvariantCulture); string str = value.ToString("R", CultureInfo.InvariantCulture);
return FormatFloat(str, value < 0); return FormatFloat(str, value < 0);
} }

View File

@@ -187,6 +187,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw) void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw)
{ {
if (!actor || !actor->IsActiveInHierarchy())
return;
auto& view = renderContext.View; auto& view = renderContext.View;
const BoundingFrustum frustum = view.Frustum; const BoundingFrustum frustum = view.Frustum;
Matrix m1, m2, world; Matrix m1, m2, world;
@@ -208,8 +210,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
draw.DrawState = &drawState; draw.DrawState = &drawState;
draw.Deformation = nullptr; draw.Deformation = nullptr;
// Support custom icons through types, but not onces that were added through actors, // Support custom icons through types, but not ones that were added through actors, since they cant register while in prefab view anyway
// since they cant register while in prefab view anyway
if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture)) if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture))
{ {
// Use custom texture // Use custom texture

View File

@@ -6,9 +6,7 @@ using Real = System.Double;
using Real = System.Single; using Real = System.Single;
#endif #endif
using System.Collections.Generic;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Viewport.Cameras namespace FlaxEditor.Viewport.Cameras
@@ -85,86 +83,8 @@ namespace FlaxEditor.Viewport.Cameras
_moveStartTime = Time.UnscaledGameTime; _moveStartTime = Time.UnscaledGameTime;
} }
/// <summary> /// <inheritdoc />
/// Moves the viewport to visualize the actor. public override void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
/// </summary>
/// <param name="actor">The actor to preview.</param>
public void ShowActor(Actor actor)
{
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
ShowSphere(ref sphere);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="actor">The actors to show.</param>
/// <param name="orientation">The used orientation.</param>
public void ShowActor(Actor actor, ref Quaternion orientation)
{
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
ShowSphere(ref sphere, ref orientation);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
public void ShowActors(List<SceneGraphNode> selection)
{
if (selection.Count == 0)
return;
BoundingSphere mergesSphere = BoundingSphere.Empty;
for (int i = 0; i < selection.Count; i++)
{
selection[i].GetEditorSphere(out var sphere);
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
}
if (mergesSphere == BoundingSphere.Empty)
return;
ShowSphere(ref mergesSphere);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
/// <param name="orientation">The used orientation.</param>
public void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
{
if (selection.Count == 0)
return;
BoundingSphere mergesSphere = BoundingSphere.Empty;
for (int i = 0; i < selection.Count; i++)
{
selection[i].GetEditorSphere(out var sphere);
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
}
if (mergesSphere == BoundingSphere.Empty)
return;
ShowSphere(ref mergesSphere, ref orientation);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
public void ShowSphere(ref BoundingSphere sphere)
{
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
ShowSphere(ref sphere, ref q);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
/// <param name="orientation">The camera orientation.</param>
public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
{ {
Vector3 position; Vector3 position;
if (Viewport.UseOrthographicProjection) if (Viewport.UseOrthographicProjection)

View File

@@ -6,6 +6,9 @@ using Real = System.Double;
using Real = System.Single; using Real = System.Single;
#endif #endif
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Viewport.Cameras namespace FlaxEditor.Viewport.Cameras
@@ -33,6 +36,90 @@ namespace FlaxEditor.Viewport.Cameras
/// </summary> /// </summary>
public virtual bool UseMovementSpeed => true; public virtual bool UseMovementSpeed => true;
/// <summary>
/// Focuses the viewport on the current selection of the gizmo.
/// </summary>
/// <param name="gizmos">The gizmo collection (from viewport).</param>
/// <param name="orientation">The target view orientation.</param>
public virtual void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation)
{
var transformGizmo = gizmos.Get<TransformGizmo>();
if (transformGizmo == null || transformGizmo.SelectedParents.Count == 0)
return;
if (gizmos.Active != null)
{
var gizmoBounds = gizmos.Active.FocusBounds;
if (gizmoBounds != BoundingSphere.Empty)
{
ShowSphere(ref gizmoBounds, ref orientation);
return;
}
}
ShowActors(transformGizmo.SelectedParents, ref orientation);
}
/// <summary>
/// Moves the viewport to visualize the actor.
/// </summary>
/// <param name="actor">The actor to preview.</param>
public virtual void ShowActor(Actor actor)
{
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
ShowSphere(ref sphere);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
public void ShowActors(List<SceneGraphNode> selection)
{
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
ShowActors(selection, ref q);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="selection">The actors to show.</param>
/// <param name="orientation">The used orientation.</param>
public virtual void ShowActors(List<SceneGraphNode> selection, ref Quaternion orientation)
{
if (selection.Count == 0)
return;
BoundingSphere mergesSphere = BoundingSphere.Empty;
for (int i = 0; i < selection.Count; i++)
{
selection[i].GetEditorSphere(out var sphere);
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
}
if (mergesSphere == BoundingSphere.Empty)
return;
ShowSphere(ref mergesSphere, ref orientation);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
public void ShowSphere(ref BoundingSphere sphere)
{
var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
ShowSphere(ref sphere, ref q);
}
/// <summary>
/// Moves the camera to visualize given world area defined by the sphere.
/// </summary>
/// <param name="sphere">The sphere.</param>
/// <param name="orientation">The camera orientation.</param>
public virtual void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation)
{
SetArcBallView(orientation, sphere.Center, sphere.Radius);
}
/// <summary> /// <summary>
/// Sets view orientation and position to match the arc ball camera style view for the given target object bounds. /// Sets view orientation and position to match the arc ball camera style view for the given target object bounds.
/// </summary> /// </summary>

View File

@@ -1,9 +1,12 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Widgets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -41,6 +44,7 @@ namespace FlaxEditor.Viewport
Gizmos[i].Update(deltaTime); Gizmos[i].Update(deltaTime);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public EditorViewport Viewport => this; public EditorViewport Viewport => this;
@@ -66,19 +70,19 @@ namespace FlaxEditor.Viewport
public bool IsControlDown => _input.IsControlDown; public bool IsControlDown => _input.IsControlDown;
/// <inheritdoc /> /// <inheritdoc />
public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root); public bool SnapToGround => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToGround.Process(Root);
/// <inheritdoc /> /// <inheritdoc />
public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc /> /// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000; public Float2 MouseDelta => _mouseDelta * 1000;
/// <inheritdoc /> /// <inheritdoc />
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;
/// <inheritdoc /> /// <inheritdoc />
public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift); public bool UseDuplicate => Root?.GetKey(KeyboardKeys.Shift) ?? false;
/// <inheritdoc /> /// <inheritdoc />
public Undo Undo { get; } public Undo Undo { get; }
@@ -92,6 +96,9 @@ namespace FlaxEditor.Viewport
/// <inheritdoc /> /// <inheritdoc />
public abstract void Spawn(Actor actor); public abstract void Spawn(Actor actor);
/// <inheritdoc />
public abstract void OpenContextMenu();
/// <inheritdoc /> /// <inheritdoc />
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
@@ -121,5 +128,284 @@ namespace FlaxEditor.Viewport
base.OnDestroy(); base.OnDestroy();
} }
internal static void AddGizmoViewportWidgets(EditorViewport viewport, TransformGizmo transformGizmo, bool useProjectCache = false)
{
var editor = Editor.Instance;
var inputOptions = editor.Options.Options.Input;
if (useProjectCache)
{
// Initialize snapping enabled from cached values
if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState))
transformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
transformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
transformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
transformGizmo.TranslationSnapValue = float.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
transformGizmo.RotationSnapValue = float.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
transformGizmo.ScaleSnapValue = float.Parse(cachedState);
if (editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
transformGizmo.ActiveTransformSpace = space;
}
// Transform space widget
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
{
Checked = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += _ =>
{
transformGizmo.ToggleTransformSpace();
if (useProjectCache)
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
};
transformSpaceWidget.Parent = viewport;
// Scale snapping widget
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
{
Checked = transformGizmo.ScaleSnapEnabled,
TooltipText = "Enable scale snapping",
Parent = scaleSnappingWidget
};
enableScaleSnapping.Toggled += _ =>
{
transformGizmo.ScaleSnapEnabled = !transformGizmo.ScaleSnapEnabled;
if (useProjectCache)
editor.ProjectCache.SetCustomData("ScaleSnapState", transformGizmo.ScaleSnapEnabled.ToString());
};
var scaleSnappingCM = new ContextMenu();
var scaleSnapping = new ViewportWidgetButton(transformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
{
TooltipText = "Scale snapping values"
};
for (int i = 0; i < ScaleSnapValues.Length; i++)
{
var v = ScaleSnapValues[i];
var button = scaleSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
scaleSnappingCM.ButtonClicked += button =>
{
var v = (float)button.Tag;
transformGizmo.ScaleSnapValue = v;
scaleSnapping.Text = v.ToString();
if (useProjectCache)
editor.ProjectCache.SetCustomData("ScaleSnapValue", transformGizmo.ScaleSnapValue.ToString("N"));
};
scaleSnappingCM.VisibleChanged += control =>
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(transformGizmo.ScaleSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
};
scaleSnapping.Parent = scaleSnappingWidget;
scaleSnappingWidget.Parent = viewport;
// Rotation snapping widget
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
{
Checked = transformGizmo.RotationSnapEnabled,
TooltipText = "Enable rotation snapping",
Parent = rotateSnappingWidget
};
enableRotateSnapping.Toggled += _ =>
{
transformGizmo.RotationSnapEnabled = !transformGizmo.RotationSnapEnabled;
if (useProjectCache)
editor.ProjectCache.SetCustomData("RotationSnapState", transformGizmo.RotationSnapEnabled.ToString());
};
var rotateSnappingCM = new ContextMenu();
var rotateSnapping = new ViewportWidgetButton(transformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
{
TooltipText = "Rotation snapping values"
};
for (int i = 0; i < RotateSnapValues.Length; i++)
{
var v = RotateSnapValues[i];
var button = rotateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
rotateSnappingCM.ButtonClicked += button =>
{
var v = (float)button.Tag;
transformGizmo.RotationSnapValue = v;
rotateSnapping.Text = v.ToString();
if (useProjectCache)
editor.ProjectCache.SetCustomData("RotationSnapValue", transformGizmo.RotationSnapValue.ToString("N"));
};
rotateSnappingCM.VisibleChanged += control =>
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(transformGizmo.RotationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
};
rotateSnapping.Parent = rotateSnappingWidget;
rotateSnappingWidget.Parent = viewport;
// Translation snapping widget
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
{
Checked = transformGizmo.TranslationSnapEnable,
TooltipText = "Enable position snapping",
Parent = translateSnappingWidget
};
enableTranslateSnapping.Toggled += _ =>
{
transformGizmo.TranslationSnapEnable = !transformGizmo.TranslationSnapEnable;
if (useProjectCache)
editor.ProjectCache.SetCustomData("TranslateSnapState", transformGizmo.TranslationSnapEnable.ToString());
};
var translateSnappingCM = new ContextMenu();
var translateSnapping = new ViewportWidgetButton(transformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
{
TooltipText = "Position snapping values"
};
if (transformGizmo.TranslationSnapValue < 0.0f)
translateSnapping.Text = "Bounding Box";
for (int i = 0; i < TranslateSnapValues.Length; i++)
{
var v = TranslateSnapValues[i];
var button = translateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
buttonBB.Tag = -1.0f;
translateSnappingCM.ButtonClicked += button =>
{
var v = (float)button.Tag;
transformGizmo.TranslationSnapValue = v;
if (v < 0.0f)
translateSnapping.Text = "Bounding Box";
else
translateSnapping.Text = v.ToString();
if (useProjectCache)
editor.ProjectCache.SetCustomData("TranslateSnapValue", transformGizmo.TranslationSnapValue.ToString("N"));
};
translateSnappingCM.VisibleChanged += control =>
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
};
translateSnapping.Parent = translateSnappingWidget;
translateSnappingWidget.Parent = viewport;
// Gizmo mode widget
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
gizmoModeTranslate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate;
var gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
gizmoModeRotate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate;
var gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
gizmoModeScale.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale;
gizmoMode.Parent = viewport;
transformGizmo.ModeChanged += () =>
{
var mode = transformGizmo.ActiveMode;
gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
};
// Setup input actions
viewport.InputActions.Add(options => options.TranslateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
viewport.InputActions.Add(options => options.RotateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
viewport.InputActions.Add(options => options.ScaleMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
viewport.InputActions.Add(options => options.ToggleTransformSpace, () =>
{
transformGizmo.ToggleTransformSpace();
if (useProjectCache)
editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString());
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
});
}
internal static readonly float[] TranslateSnapValues =
{
0.1f,
0.5f,
1.0f,
5.0f,
10.0f,
100.0f,
1000.0f,
};
internal static readonly float[] RotateSnapValues =
{
1.0f,
5.0f,
10.0f,
15.0f,
30.0f,
45.0f,
60.0f,
90.0f,
};
internal static readonly float[] ScaleSnapValues =
{
0.05f,
0.1f,
0.25f,
0.5f,
1.0f,
2.0f,
4.0f,
6.0f,
8.0f,
};
} }
} }

View File

@@ -10,7 +10,6 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Widgets; using FlaxEditor.Viewport.Widgets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer; using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.Viewport namespace FlaxEditor.Viewport
@@ -154,6 +153,7 @@ namespace FlaxEditor.Viewport
// Input // Input
internal bool _disableInputUpdate;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private int _deltaFilteringStep; private int _deltaFilteringStep;
private Float2 _startPos; private Float2 _startPos;
@@ -704,9 +704,9 @@ namespace FlaxEditor.Viewport
// Camera Viewpoints // Camera Viewpoints
{ {
var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu; var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++) for (int i = 0; i < CameraViewpointValues.Length; i++)
{ {
var co = EditorViewportCameraViewpointValues[i]; var co = CameraViewpointValues[i];
var button = cameraView.AddButton(co.Name); var button = cameraView.AddButton(co.Name);
button.Tag = co.Orientation; button.Tag = co.Orientation;
} }
@@ -899,9 +899,9 @@ namespace FlaxEditor.Viewport
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddSeparator(); viewFlags.AddSeparator();
for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++) for (int i = 0; i < ViewFlagsValues.Length; i++)
{ {
var v = EditorViewportViewFlagsValues[i]; var v = ViewFlagsValues[i];
var button = viewFlags.AddButton(v.Name); var button = viewFlags.AddButton(v.Name);
button.CloseMenuOnClick = false; button.CloseMenuOnClick = false;
button.Tag = v.Mode; button.Tag = v.Mode;
@@ -933,9 +933,9 @@ namespace FlaxEditor.Viewport
} }
}); });
debugView.AddSeparator(); debugView.AddSeparator();
for (int i = 0; i < EditorViewportViewModeValues.Length; i++) for (int i = 0; i < ViewModeValues.Length; i++)
{ {
ref var v = ref EditorViewportViewModeValues[i]; ref var v = ref ViewModeValues[i];
if (v.Options != null) if (v.Options != null)
{ {
var childMenu = debugView.AddChildMenu(v.Name).ContextMenu; var childMenu = debugView.AddChildMenu(v.Name).ContextMenu;
@@ -989,12 +989,12 @@ namespace FlaxEditor.Viewport
#endregion View mode widget #endregion View mode widget
} }
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation)));
InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Front").Orientation)));
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown);
InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1));
InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1));
@@ -1497,6 +1497,9 @@ namespace FlaxEditor.Viewport
{ {
base.Update(deltaTime); base.Update(deltaTime);
if (_disableInputUpdate)
return;
// Update camera // Update camera
bool useMovementSpeed = false; bool useMovementSpeed = false;
if (_camera != null) if (_camera != null)
@@ -1535,7 +1538,7 @@ namespace FlaxEditor.Viewport
} }
bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height)); bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height));
_prevInput = _input; _prevInput = _input;
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl)); var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
if (canUseInput && ContainsFocus && hit == null) if (canUseInput && ContainsFocus && hit == null)
_input.Gather(win.Window, useMouse); _input.Gather(win.Window, useMouse);
else else
@@ -1868,7 +1871,7 @@ namespace FlaxEditor.Viewport
} }
} }
private readonly CameraViewpoint[] EditorViewportCameraViewpointValues = private readonly CameraViewpoint[] CameraViewpointValues =
{ {
new CameraViewpoint("Front", new Float3(0, 180, 0)), new CameraViewpoint("Front", new Float3(0, 180, 0)),
new CameraViewpoint("Back", new Float3(0, 0, 0)), new CameraViewpoint("Back", new Float3(0, 0, 0)),
@@ -1899,7 +1902,7 @@ namespace FlaxEditor.Viewport
} }
} }
private static readonly ViewModeOptions[] EditorViewportViewModeValues = private static readonly ViewModeOptions[] ViewModeValues =
{ {
new ViewModeOptions(ViewMode.Default, "Default"), new ViewModeOptions(ViewMode.Default, "Default"),
new ViewModeOptions(ViewMode.Unlit, "Unlit"), new ViewModeOptions(ViewMode.Unlit, "Unlit"),
@@ -1971,7 +1974,7 @@ namespace FlaxEditor.Viewport
} }
} }
private static readonly ViewFlagOptions[] EditorViewportViewFlagsValues = private static readonly ViewFlagOptions[] ViewFlagsValues =
{ {
new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"), new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"),
new ViewFlagOptions(ViewFlags.Shadows, "Shadows"), new ViewFlagOptions(ViewFlags.Shadows, "Shadows"),
@@ -2006,16 +2009,13 @@ namespace FlaxEditor.Viewport
{ {
if (cm.Visible == false) if (cm.Visible == false)
return; return;
var ccm = (ContextMenu)cm; var ccm = (ContextMenu)cm;
foreach (var e in ccm.Items) foreach (var e in ccm.Items)
{ {
if (e is ContextMenuButton b && b.Tag != null) if (e is ContextMenuButton b && b.Tag != null)
{ {
var v = (ViewFlags)b.Tag; var v = (ViewFlags)b.Tag;
b.Icon = (Task.View.Flags & v) != 0 b.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
} }
} }
} }
@@ -2024,7 +2024,6 @@ namespace FlaxEditor.Viewport
{ {
if (cm.Visible == false) if (cm.Visible == false)
return; return;
var ccm = (ContextMenu)cm; var ccm = (ContextMenu)cm;
var layersMask = Task.ViewLayersMask; var layersMask = Task.ViewLayersMask;
foreach (var e in ccm.Items) foreach (var e in ccm.Items)

View File

@@ -2,15 +2,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Modes; using FlaxEditor.Viewport.Modes;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -28,13 +25,6 @@ namespace FlaxEditor.Viewport
private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton; private readonly ContextMenuButton _showNavigationButton;
private readonly ViewportWidgetButton _gizmoModeTranslate;
private readonly ViewportWidgetButton _gizmoModeRotate;
private readonly ViewportWidgetButton _gizmoModeScale;
private readonly ViewportWidgetButton _translateSnapping;
private readonly ViewportWidgetButton _rotateSnapping;
private readonly ViewportWidgetButton _scaleSnapping;
private SelectionOutline _customSelectionOutline; private SelectionOutline _customSelectionOutline;
@@ -196,7 +186,6 @@ namespace FlaxEditor.Viewport
{ {
_editor = editor; _editor = editor;
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
var inputOptions = editor.Options.Options.Input;
// Prepare rendering task // Prepare rendering task
Task.ActorsSource = ActorsSources.Scenes; Task.ActorsSource = ActorsSources.Scenes;
@@ -222,8 +211,7 @@ namespace FlaxEditor.Viewport
// Add transformation gizmo // Add transformation gizmo
TransformGizmo = new TransformGizmo(this); TransformGizmo = new TransformGizmo(this);
TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.ApplyTransformation += ApplyTransform;
TransformGizmo.ModeChanged += OnGizmoModeChanged; TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate;
TransformGizmo.Duplicate += Editor.Instance.SceneEditing.Duplicate;
Gizmos.Active = TransformGizmo; Gizmos.Active = TransformGizmo;
// Add grid // Add grid
@@ -232,144 +220,8 @@ namespace FlaxEditor.Viewport
editor.SceneEditing.SelectionChanged += OnSelectionChanged; editor.SceneEditing.SelectionChanged += OnSelectionChanged;
// Initialize snapping enabled from cached values // Gizmo widgets
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState)) AddGizmoViewportWidgets(this, TransformGizmo);
TransformGizmo.TranslationSnapEnable = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState))
TransformGizmo.RotationSnapEnabled = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState))
TransformGizmo.ScaleSnapEnabled = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState))
TransformGizmo.TranslationSnapValue = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState))
TransformGizmo.RotationSnapValue = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState))
TransformGizmo.ScaleSnapValue = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space))
TransformGizmo.ActiveTransformSpace = space;
// Transform space widget
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
{
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
transformSpaceWidget.Parent = this;
// Scale snapping widget
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true)
{
Checked = TransformGizmo.ScaleSnapEnabled,
TooltipText = "Enable scale snapping",
Parent = scaleSnappingWidget
};
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
var scaleSnappingCM = new ContextMenu();
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
{
TooltipText = "Scale snapping values"
};
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
{
var v = EditorViewportScaleSnapValues[i];
var button = scaleSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
_scaleSnapping.Parent = scaleSnappingWidget;
scaleSnappingWidget.Parent = this;
// Rotation snapping widget
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true)
{
Checked = TransformGizmo.RotationSnapEnabled,
TooltipText = "Enable rotation snapping",
Parent = rotateSnappingWidget
};
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
var rotateSnappingCM = new ContextMenu();
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
{
TooltipText = "Rotation snapping values"
};
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
{
var v = EditorViewportRotateSnapValues[i];
var button = rotateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
_rotateSnapping.Parent = rotateSnappingWidget;
rotateSnappingWidget.Parent = this;
// Translation snapping widget
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true)
{
Checked = TransformGizmo.TranslationSnapEnable,
TooltipText = "Enable position snapping",
Parent = translateSnappingWidget
};
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
var translateSnappingCM = new ContextMenu();
_translateSnapping = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
{
TooltipText = "Position snapping values"
};
if (TransformGizmo.TranslationSnapValue < 0.0f)
_translateSnapping.Text = "Bounding Box";
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
{
var v = EditorViewportTranslateSnapValues[i];
var button = translateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume");
buttonBB.Tag = -1.0f;
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
_translateSnapping.Parent = translateSnappingWidget;
translateSnappingWidget.Parent = this;
// Gizmo mode widget
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
gizmoMode.Parent = this;
// Show grid widget // Show grid widget
_showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled); _showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled);
@@ -400,14 +252,6 @@ namespace FlaxEditor.Viewport
} }
// Setup input actions // Setup input actions
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
InputActions.Add(options => options.ToggleTransformSpace, () =>
{
OnTransformSpaceToggle(transformSpaceToggle);
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
});
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection);
InputActions.Add(options => options.RotateSelection, RotateSelection); InputActions.Add(options => options.RotateSelection, RotateSelection);
@@ -486,7 +330,7 @@ namespace FlaxEditor.Viewport
}; };
// Spawn // Spawn
Editor.Instance.SceneEditing.Spawn(actor, parent); _editor.SceneEditing.Spawn(actor, parent);
} }
private void OnBegin(RenderTask task, GPUContext context) private void OnBegin(RenderTask task, GPUContext context)
@@ -552,7 +396,7 @@ namespace FlaxEditor.Viewport
var task = renderContext.Task; var task = renderContext.Task;
// Render editor primitives, gizmo and debug shapes in debug view modes // Render editor primitives, gizmo and debug shapes in debug view modes
// Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers // Note: can use Output buffer as both input and output because EditorPrimitives is using an intermediate buffer
if (EditorPrimitives && EditorPrimitives.CanRender()) if (EditorPrimitives && EditorPrimitives.CanRender())
{ {
EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output); EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output);
@@ -581,161 +425,6 @@ namespace FlaxEditor.Viewport
} }
} }
private void OnGizmoModeToggle(ViewportWidgetButton button)
{
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
}
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
_editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString());
}
private void OnRotateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
_editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString());
}
private void OnScaleSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
_editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString());
}
private void OnTransformSpaceToggle(ViewportWidgetButton button)
{
TransformGizmo.ToggleTransformSpace();
_editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString());
}
private void OnGizmoModeChanged()
{
// Update all viewport widgets status
var mode = TransformGizmo.ActiveMode;
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
}
private static readonly float[] EditorViewportScaleSnapValues =
{
0.05f,
0.1f,
0.25f,
0.5f,
1.0f,
2.0f,
4.0f,
6.0f,
8.0f,
};
private void OnWidgetScaleSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.ScaleSnapValue = v;
_scaleSnapping.Text = v.ToString();
_editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N"));
}
private void OnWidgetScaleSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportRotateSnapValues =
{
1.0f,
5.0f,
10.0f,
15.0f,
30.0f,
45.0f,
60.0f,
90.0f,
};
private void OnWidgetRotateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.RotationSnapValue = v;
_rotateSnapping.Text = v.ToString();
_editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N"));
}
private void OnWidgetRotateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportTranslateSnapValues =
{
0.1f,
0.5f,
1.0f,
5.0f,
10.0f,
100.0f,
1000.0f,
};
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.TranslationSnapValue = v;
if (v < 0.0f)
_translateSnapping.Text = "Bounding Box";
else
_translateSnapping.Text = v.ToString();
_editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N"));
}
private void OnWidgetTranslateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private void OnSelectionChanged() private void OnSelectionChanged()
{ {
var selection = _editor.SceneEditing.Selection; var selection = _editor.SceneEditing.Selection;
@@ -761,7 +450,7 @@ namespace FlaxEditor.Viewport
Vector3 gizmoPosition = TransformGizmo.Position; Vector3 gizmoPosition = TransformGizmo.Position;
// Rotate selected objects // Rotate selected objects
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; bool isPlayMode = _editor.StateMachine.IsPlayMode;
TransformGizmo.StartTransforming(); TransformGizmo.StartTransforming();
for (int i = 0; i < selection.Count; i++) for (int i = 0; i < selection.Count; i++)
{ {
@@ -819,14 +508,7 @@ namespace FlaxEditor.Viewport
/// <param name="orientation">The target view orientation.</param> /// <param name="orientation">The target view orientation.</param>
public void FocusSelection(ref Quaternion orientation) public void FocusSelection(ref Quaternion orientation)
{ {
if (TransformGizmo.SelectedParents.Count == 0) ViewportCamera.FocusSelection(Gizmos, ref orientation);
return;
var gizmoBounds = Gizmos.Active.FocusBounds;
if (gizmoBounds != BoundingSphere.Empty)
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
else
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
} }
/// <summary> /// <summary>
@@ -843,7 +525,7 @@ namespace FlaxEditor.Viewport
Vector3 gizmoPosition = TransformGizmo.Position; Vector3 gizmoPosition = TransformGizmo.Position;
// Transform selected objects // Transform selected objects
bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; bool isPlayMode = _editor.StateMachine.IsPlayMode;
for (int i = 0; i < selection.Count; i++) for (int i = 0; i < selection.Count; i++)
{ {
var obj = selection[i]; var obj = selection[i];
@@ -985,7 +667,14 @@ namespace FlaxEditor.Viewport
{ {
var parent = actor.Parent ?? Level.GetScene(0); var parent = actor.Parent ?? Level.GetScene(0);
actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null); actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
Editor.Instance.SceneEditing.Spawn(actor); _editor.SceneEditing.Spawn(actor);
}
/// <inheritdoc />
public override void OpenContextMenu()
{
var mouse = PointFromWindow(Root.MousePosition);
_editor.Windows.SceneWin.ShowContextMenu(this, mouse);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -5,12 +5,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -30,7 +28,6 @@ namespace FlaxEditor.Viewport
{ {
public PrefabWindowViewport Viewport; public PrefabWindowViewport Viewport;
/// <inheritdoc />
public override bool CanRender() public override bool CanRender()
{ {
return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled; return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled;
@@ -42,19 +39,34 @@ namespace FlaxEditor.Viewport
} }
} }
[HideInEditor]
private sealed class PrefabUIEditorRoot : UIEditorRoot
{
private readonly PrefabWindowViewport _viewport;
private bool UI => _viewport._hasUILinkedCached;
public PrefabUIEditorRoot(PrefabWindowViewport viewport)
: base(true)
{
_viewport = viewport;
Parent = viewport;
}
public override bool EnableInputs => !UI;
public override bool EnableSelecting => UI;
public override bool EnableBackground => UI;
public override TransformGizmo TransformGizmo => _viewport.TransformGizmo;
}
private readonly PrefabWindow _window; private readonly PrefabWindow _window;
private UpdateDelegate _update; private UpdateDelegate _update;
private readonly ViewportWidgetButton _gizmoModeTranslate;
private readonly ViewportWidgetButton _gizmoModeRotate;
private readonly ViewportWidgetButton _gizmoModeScale;
private ViewportWidgetButton _translateSnappng;
private ViewportWidgetButton _rotateSnapping;
private ViewportWidgetButton _scaleSnapping;
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private PrefabSpritesRenderer _spritesRenderer; private PrefabSpritesRenderer _spritesRenderer;
private IntPtr _tempDebugDrawContext;
private bool _hasUILinkedCached;
private PrefabUIEditorRoot _uiRoot;
/// <summary> /// <summary>
/// Drag and drop handlers /// Drag and drop handlers
@@ -107,144 +119,74 @@ namespace FlaxEditor.Viewport
// Add transformation gizmo // Add transformation gizmo
TransformGizmo = new TransformGizmo(this); TransformGizmo = new TransformGizmo(this);
TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.ApplyTransformation += ApplyTransform;
TransformGizmo.ModeChanged += OnGizmoModeChanged;
TransformGizmo.Duplicate += _window.Duplicate; TransformGizmo.Duplicate += _window.Duplicate;
Gizmos.Active = TransformGizmo; Gizmos.Active = TransformGizmo;
// Transform space widget // Use custom root for UI controls
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); _uiRoot = new PrefabUIEditorRoot(this);
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) _uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport
{ _uiParentLink = _uiRoot.UIRoot;
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
transformSpaceWidget.Parent = this;
// Scale snapping widget EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo);
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleSnap32, null, true)
{
Checked = TransformGizmo.ScaleSnapEnabled,
TooltipText = "Enable scale snapping",
Parent = scaleSnappingWidget
};
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
var scaleSnappingCM = new ContextMenu();
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM)
{
TooltipText = "Scale snapping values"
};
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
{
var v = EditorViewportScaleSnapValues[i];
var button = scaleSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
_scaleSnapping.Parent = scaleSnappingWidget;
scaleSnappingWidget.Parent = this;
// Rotation snapping widget
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateSnap32, null, true)
{
Checked = TransformGizmo.RotationSnapEnabled,
TooltipText = "Enable rotation snapping",
Parent = rotateSnappingWidget
};
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
var rotateSnappingCM = new ContextMenu();
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM)
{
TooltipText = "Rotation snapping values"
};
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
{
var v = EditorViewportRotateSnapValues[i];
var button = rotateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
_rotateSnapping.Parent = rotateSnappingWidget;
rotateSnappingWidget.Parent = this;
// Translation snapping widget
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid32, null, true)
{
Checked = TransformGizmo.TranslationSnapEnable,
TooltipText = "Enable position snapping",
Parent = translateSnappingWidget
};
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
var translateSnappingCM = new ContextMenu();
_translateSnappng = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM)
{
TooltipText = "Position snapping values"
};
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
{
var v = EditorViewportTranslateSnapValues[i];
var button = translateSnappingCM.AddButton(v.ToString());
button.Tag = v;
}
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
_translateSnappng.Parent = translateSnappingWidget;
translateSnappingWidget.Parent = this;
// Gizmo mode widget
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
gizmoMode.Parent = this;
// Setup input actions // Setup input actions
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
InputActions.Add(options => options.ToggleTransformSpace, () =>
{
OnTransformSpaceToggle(transformSpaceToggle);
transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
});
InputActions.Add(options => options.FocusSelection, ShowSelectedActors); InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
SetUpdate(ref _update, OnUpdate); SetUpdate(ref _update, OnUpdate);
} }
/// <summary>
/// Updates the viewport's gizmos, especially to toggle between 3D and UI editing modes.
/// </summary>
internal void UpdateGizmoMode()
{
// Skip if gizmo mode was unmodified
if (_hasUILinked == _hasUILinkedCached)
return;
_hasUILinkedCached = _hasUILinked;
if (_hasUILinked)
{
// UI widget
Gizmos.Active = null;
ViewportCamera = new UIEditorCamera { UIEditor = _uiRoot };
// Hide 3D visuals
ShowEditorPrimitives = false;
ShowDefaultSceneActors = false;
ShowDebugDraw = false;
// Show whole UI on startup
var canvas = (CanvasRootControl)_uiParentLink.Children.FirstOrDefault(x => x is CanvasRootControl);
if (canvas != null)
ViewportCamera.ShowActor(canvas.Canvas);
else if (Instance is UIControl)
ViewportCamera.ShowActor(Instance);
}
else
{
// Generic prefab
Gizmos.Active = TransformGizmo;
ViewportCamera = new FPSCamera();
}
// Update default components usage
bool defaultFeatures = !_hasUILinked;
_disableInputUpdate = _hasUILinked;
_spritesRenderer.Enabled = defaultFeatures;
SelectionOutline.Enabled = defaultFeatures;
_showDefaultSceneButton.Visible = defaultFeatures;
_cameraWidget.Visible = defaultFeatures;
_cameraButton.Visible = defaultFeatures;
_orthographicModeButton.Visible = defaultFeatures;
Task.Enabled = defaultFeatures;
UseAutomaticTaskManagement = defaultFeatures;
TintColor = defaultFeatures ? Color.White : Color.Transparent;
}
private void OnUpdate(float deltaTime) private void OnUpdate(float deltaTime)
{ {
UpdateGizmoMode();
for (int i = 0; i < Gizmos.Count; i++) for (int i = 0; i < Gizmos.Count; i++)
{ {
Gizmos[i].Update(deltaTime); Gizmos[i].Update(deltaTime);
@@ -259,11 +201,19 @@ namespace FlaxEditor.Viewport
var selectedParents = TransformGizmo.SelectedParents; var selectedParents = TransformGizmo.SelectedParents;
if (selectedParents.Count > 0) if (selectedParents.Count > 0)
{ {
// Use temporary Debug Draw context to pull any debug shapes drawing in Scene Graph Nodes - those are used in OnDebugDraw down below
if (_tempDebugDrawContext == IntPtr.Zero)
_tempDebugDrawContext = DebugDraw.AllocateContext();
DebugDraw.SetContext(_tempDebugDrawContext);
DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f);
for (int i = 0; i < selectedParents.Count; i++) for (int i = 0; i < selectedParents.Count; i++)
{ {
if (selectedParents[i].IsActiveInHierarchy) if (selectedParents[i].IsActiveInHierarchy)
selectedParents[i].OnDebugDraw(_debugDrawData); selectedParents[i].OnDebugDraw(_debugDrawData);
} }
DebugDraw.SetContext(IntPtr.Zero);
} }
} }
@@ -307,7 +257,7 @@ namespace FlaxEditor.Viewport
public void ShowSelectedActors() public void ShowSelectedActors()
{ {
var orient = ViewOrientation; var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -338,7 +288,7 @@ namespace FlaxEditor.Viewport
public bool SnapToGround => false; public bool SnapToGround => false;
/// <inheritdoc /> /// <inheritdoc />
public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc /> /// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000; public Float2 MouseDelta => _mouseDelta * 1000;
@@ -367,6 +317,13 @@ namespace FlaxEditor.Viewport
_window.Spawn(actor); _window.Spawn(actor);
} }
/// <inheritdoc />
public void OpenContextMenu()
{
var mouse = PointFromWindow(Root.MousePosition);
_window.ShowContextMenu(this, ref mouse);
}
/// <inheritdoc /> /// <inheritdoc />
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
@@ -386,151 +343,6 @@ namespace FlaxEditor.Viewport
root.UpdateCallbacksToRemove.Add(_update); root.UpdateCallbacksToRemove.Add(_update);
} }
private void OnGizmoModeToggle(ViewportWidgetButton button)
{
TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag;
}
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
}
private void OnRotateSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
}
private void OnScaleSnappingToggle(ViewportWidgetButton button)
{
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
}
private void OnTransformSpaceToggle(ViewportWidgetButton button)
{
TransformGizmo.ToggleTransformSpace();
}
private void OnGizmoModeChanged()
{
// Update all viewport widgets status
var mode = TransformGizmo.ActiveMode;
_gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate;
_gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate;
_gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale;
}
private static readonly float[] EditorViewportScaleSnapValues =
{
0.05f,
0.1f,
0.25f,
0.5f,
1.0f,
2.0f,
4.0f,
6.0f,
8.0f,
};
private void OnWidgetScaleSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.ScaleSnapValue = v;
_scaleSnapping.Text = v.ToString();
}
private void OnWidgetScaleSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportRotateSnapValues =
{
1.0f,
5.0f,
10.0f,
15.0f,
30.0f,
45.0f,
60.0f,
90.0f,
};
private void OnWidgetRotateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.RotationSnapValue = v;
_rotateSnapping.Text = v.ToString();
}
private void OnWidgetRotateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private static readonly float[] EditorViewportTranslateSnapValues =
{
0.1f,
0.5f,
1.0f,
5.0f,
10.0f,
100.0f,
1000.0f,
};
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
{
var v = (float)button.Tag;
TransformGizmo.TranslationSnapValue = v;
_translateSnappng.Text = v.ToString();
}
private void OnWidgetTranslateSnapShowHide(Control control)
{
if (control.Visible == false)
return;
var ccm = (ContextMenu)control;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private void OnSelectionChanged() private void OnSelectionChanged()
{ {
Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection)); Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection));
@@ -585,23 +397,6 @@ namespace FlaxEditor.Viewport
} }
} }
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Selected UI controls outline
for (var i = 0; i < _window.Selection.Count; i++)
{
if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
{
var control = controlActor.Control;
var bounds = Rectangle.FromPoints(control.PointToParent(this, Float2.Zero), control.PointToParent(this, control.Size));
Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize);
}
}
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnLeftMouseButtonUp() protected override void OnLeftMouseButtonUp()
{ {
@@ -744,14 +539,7 @@ namespace FlaxEditor.Viewport
/// <param name="orientation">The target view orientation.</param> /// <param name="orientation">The target view orientation.</param>
public void FocusSelection(ref Quaternion orientation) public void FocusSelection(ref Quaternion orientation)
{ {
if (TransformGizmo.SelectedParents.Count == 0) ViewportCamera.FocusSelection(Gizmos, ref orientation);
return;
var gizmoBounds = Gizmos.Active.FocusBounds;
if (gizmoBounds != BoundingSphere.Empty)
((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation);
else
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -776,6 +564,13 @@ namespace FlaxEditor.Viewport
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
if (IsDisposing)
return;
if (_tempDebugDrawContext != IntPtr.Zero)
{
DebugDraw.FreeContext(_tempDebugDrawContext);
_tempDebugDrawContext = IntPtr.Zero;
}
FlaxEngine.Object.Destroy(ref SelectionOutline); FlaxEngine.Object.Destroy(ref SelectionOutline);
FlaxEngine.Object.Destroy(ref _spritesRenderer); FlaxEngine.Object.Destroy(ref _spritesRenderer);
@@ -799,6 +594,15 @@ namespace FlaxEditor.Viewport
{ {
base.OnDebugDraw(context, ref renderContext); base.OnDebugDraw(context, ref renderContext);
// Collect selected objects debug shapes again when DebugDraw is active with a custom context
_debugDrawData.Clear();
var selectedParents = TransformGizmo.SelectedParents;
for (int i = 0; i < selectedParents.Count; i++)
{
if (selectedParents[i].IsActiveInHierarchy)
selectedParents[i].OnDebugDraw(_debugDrawData);
}
unsafe unsafe
{ {
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs) fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)

View File

@@ -21,7 +21,7 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="FlaxEditor.Viewport.EditorViewport" /> /// <seealso cref="FlaxEditor.Viewport.EditorViewport" />
public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner
{ {
private ContextMenuButton _showDefaultSceneButton; internal ContextMenuButton _showDefaultSceneButton;
private IntPtr _debugDrawContext; private IntPtr _debugDrawContext;
private bool _debugDrawEnable; private bool _debugDrawEnable;
private bool _editorPrimitivesEnable; private bool _editorPrimitivesEnable;
@@ -239,6 +239,8 @@ namespace FlaxEditor.Viewport.Previews
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
if (IsDisposing)
return;
Object.Destroy(ref PreviewLight); Object.Destroy(ref PreviewLight);
Object.Destroy(ref EnvProbe); Object.Destroy(ref EnvProbe);
Object.Destroy(ref Sky); Object.Destroy(ref Sky);

View File

@@ -1,8 +1,8 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections.Generic;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews namespace FlaxEditor.Viewport.Previews
@@ -13,19 +13,11 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="AssetPreview" /> /// <seealso cref="AssetPreview" />
public class PrefabPreview : AssetPreview public class PrefabPreview : AssetPreview
{ {
/// <summary>
/// The currently spawned prefab instance owner. Used to link some actors such as UIControl to preview scene and view.
/// </summary>
internal static PrefabPreview LoadingPreview;
/// <summary>
/// The list of active prefab previews. Used to link some actors such as UIControl to preview scene and view.
/// </summary>
internal static List<PrefabPreview> ActivePreviews;
private Prefab _prefab; private Prefab _prefab;
private Actor _instance; private Actor _instance;
internal UIControl customControlLinked; private UIControl _uiControlLinked;
internal bool _hasUILinked;
internal ContainerControl _uiParentLink;
/// <summary> /// <summary>
/// Gets or sets the prefab asset to preview. /// Gets or sets the prefab asset to preview.
@@ -54,13 +46,10 @@ namespace FlaxEditor.Viewport.Previews
_prefab.WaitForLoaded(); _prefab.WaitForLoaded();
// Spawn prefab // Spawn prefab
LoadingPreview = this;
var instance = PrefabManager.SpawnPrefab(_prefab, null); var instance = PrefabManager.SpawnPrefab(_prefab, null);
LoadingPreview = null;
if (instance == null) if (instance == null)
{ {
_prefab = null; _prefab = null;
ActivePreviews.Remove(this);
throw new Exception("Failed to spawn a prefab for the preview."); throw new Exception("Failed to spawn a prefab for the preview.");
} }
@@ -84,11 +73,11 @@ namespace FlaxEditor.Viewport.Previews
if (_instance) if (_instance)
{ {
// Unlink UI control // Unlink UI control
if (customControlLinked) if (_uiControlLinked)
{ {
if (customControlLinked.Control?.Parent == this) if (_uiControlLinked.Control?.Parent == _uiParentLink)
customControlLinked.Control.Parent = null; _uiControlLinked.Control.Parent = null;
customControlLinked = null; _uiControlLinked = null;
} }
// Remove for the preview // Remove for the preview
@@ -96,27 +85,51 @@ namespace FlaxEditor.Viewport.Previews
} }
_instance = value; _instance = value;
_hasUILinked = false;
if (_instance) if (_instance)
{ {
// Add to the preview // Add to the preview
Task.AddCustomActor(_instance); Task.AddCustomActor(_instance);
UpdateLinkage();
// Link UI canvases to the preview
LinkCanvas(_instance);
} }
} }
} }
private void UpdateLinkage()
{
// Clear flag
_hasUILinked = false;
// Link UI canvases to the preview (eg. after canvas added to the prefab)
LinkCanvas(_instance);
// Link UI control to the preview
if (_uiControlLinked == null &&
_instance is UIControl uiControl &&
uiControl.Control != null &&
uiControl.Control.Parent == null)
{
uiControl.Control.Parent = _uiParentLink;
_uiControlLinked = uiControl;
_hasUILinked = true;
}
else if (_uiControlLinked != null)
_hasUILinked = true;
}
private void LinkCanvas(Actor actor) private void LinkCanvas(Actor actor)
{ {
if (actor is UICanvas uiCanvas) if (actor is UICanvas uiCanvas)
uiCanvas.EditorOverride(Task, this); {
uiCanvas.EditorOverride(Task, _uiParentLink);
if (uiCanvas.GUI.Parent == _uiParentLink)
_hasUILinked = true;
}
var children = actor.ChildrenCount; var children = actor.ChildrenCount;
for (int i = 0; i < children; i++) for (int i = 0; i < children; i++)
{
LinkCanvas(actor.GetChild(i)); LinkCanvas(actor.GetChild(i));
}
} }
/// <summary> /// <summary>
@@ -126,9 +139,8 @@ namespace FlaxEditor.Viewport.Previews
public PrefabPreview(bool useWidgets) public PrefabPreview(bool useWidgets)
: base(useWidgets) : base(useWidgets)
{ {
if (ActivePreviews == null) // Link to itself by default
ActivePreviews = new List<PrefabPreview>(); _uiParentLink = this;
ActivePreviews.Add(this);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -138,15 +150,13 @@ namespace FlaxEditor.Viewport.Previews
if (_instance != null) if (_instance != null)
{ {
// Link UI canvases to the preview (eg. after canvas added to the prefab) UpdateLinkage();
LinkCanvas(_instance);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
ActivePreviews.Remove(this);
Prefab = null; Prefab = null;
base.OnDestroy(); base.OnDestroy();

View File

@@ -146,7 +146,7 @@ namespace FlaxEditor.Viewport
var gridPlane = new Plane(Vector3.Zero, Vector3.Up); var gridPlane = new Plane(Vector3.Zero, Vector3.Up);
var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags); hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags);
var girdGizmo = (GridGizmo)_owner.Gizmos.FirstOrDefault(x => x is GridGizmo); var girdGizmo = _owner.Gizmos.Get<GridGizmo>();
if (hit != null) if (hit != null)
{ {
// Use hit location // Use hit location
@@ -180,7 +180,7 @@ namespace FlaxEditor.Viewport
var location = hitLocation + new Vector3(0, bottomToCenter, 0); var location = hitLocation + new Vector3(0, bottomToCenter, 0);
// Apply grid snapping if enabled // Apply grid snapping if enabled
var transformGizmo = (TransformGizmo)_owner.Gizmos.FirstOrDefault(x => x is TransformGizmo); var transformGizmo = _owner.Gizmos.Get<TransformGizmo>();
if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable)) if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable))
{ {
float snapValue = transformGizmo.TranslationSnapValue; float snapValue = transformGizmo.TranslationSnapValue;

View File

@@ -186,6 +186,10 @@ namespace FlaxEditor.Windows.Assets
base.Initialize(layout); base.Initialize(layout);
// Ignore import settings GUI if the type is not animation. This removes the import UI if the animation asset was not created using an import.
if (proxy.ImportSettings.Settings.Type != FlaxEngine.Tools.ModelTool.ModelType.Animation)
return;
// Import Settings // Import Settings
{ {
var group = layout.Group("Import Settings"); var group = layout.Group("Import Settings");

View File

@@ -76,37 +76,37 @@ namespace FlaxEditor.Windows.Assets
// Transparency // Transparency
[EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")] [EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")]
public MaterialTransparentLightingMode TransparentLightingMode; public MaterialTransparentLightingMode TransparentLightingMode;
[EditorOrder(205), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")] [EditorOrder(205), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")]
public bool EnableReflections; public bool EnableReflections;
[VisibleIf(nameof(EnableReflections))] [VisibleIf(nameof(EnableReflections))]
[EditorOrder(210), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")] [EditorOrder(210), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")]
public bool EnableScreenSpaceReflections; public bool EnableScreenSpaceReflections;
[EditorOrder(210), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")] [EditorOrder(210), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")]
public bool EnableFog; public bool EnableFog;
[EditorOrder(220), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")] [EditorOrder(220), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")]
public bool EnableDistortion; public bool EnableDistortion;
[EditorOrder(224), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")] [EditorOrder(224), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")]
public bool EnableGlobalIllumination; public bool EnableGlobalIllumination;
[EditorOrder(225), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")] [EditorOrder(225), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")]
public bool PixelNormalOffsetRefraction; public bool PixelNormalOffsetRefraction;
[EditorOrder(230), DefaultValue(0.12f), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)] [EditorOrder(230), DefaultValue(0.12f), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
public float OpacityThreshold; public float OpacityThreshold;
// Tessellation // Tessellation
[EditorOrder(300), DefaultValue(TessellationMethod.None), EditorDisplay("Tessellation"), Tooltip("Mesh tessellation method.")] [EditorOrder(300), DefaultValue(TessellationMethod.None), VisibleIf(nameof(IsStandard)), EditorDisplay("Tessellation"), Tooltip("Mesh tessellation method.")]
public TessellationMethod TessellationMode; public TessellationMethod TessellationMode;
[EditorOrder(310), DefaultValue(15), EditorDisplay("Tessellation"), Tooltip("Maximum triangle tessellation factor."), Limit(1, 60, 0.01f)] [EditorOrder(310), DefaultValue(15), VisibleIf(nameof(IsStandard)), EditorDisplay("Tessellation"), Tooltip("Maximum triangle tessellation factor."), Limit(1, 60, 0.01f)]
public int MaxTessellationFactor; public int MaxTessellationFactor;
// Misc // Misc
@@ -120,10 +120,10 @@ namespace FlaxEditor.Windows.Assets
[EditorOrder(420), DefaultValue(0.3f), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)] [EditorOrder(420), DefaultValue(0.3f), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)]
public float MaskThreshold; public float MaskThreshold;
[EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")] [EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), VisibleIf(nameof(IsDecal)), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")]
public MaterialDecalBlendingMode DecalBlendingMode; public MaterialDecalBlendingMode DecalBlendingMode;
[EditorOrder(440), DefaultValue(MaterialPostFxLocation.AfterPostProcessingPass), EditorDisplay("Misc"), Tooltip("The post fx material rendering location.")] [EditorOrder(440), DefaultValue(MaterialPostFxLocation.AfterPostProcessingPass), VisibleIf(nameof(IsPostProcess)), EditorDisplay("Misc"), Tooltip("The post fx material rendering location.")]
public MaterialPostFxLocation PostFxLocation; public MaterialPostFxLocation PostFxLocation;
// Parameters // Parameters
@@ -140,6 +140,12 @@ namespace FlaxEditor.Windows.Assets
set => throw new Exception("No setter."); set => throw new Exception("No setter.");
} }
// Visibility conditionals
private bool IsPostProcess => Domain == MaterialDomain.PostProcess;
private bool IsDecal => Domain == MaterialDomain.Decal;
private bool IsStandard => Domain == MaterialDomain.Surface || Domain == MaterialDomain.Terrain || Domain == MaterialDomain.Particle || Domain == MaterialDomain.Deformable;
/// <summary> /// <summary>
/// Gathers parameters from the specified material. /// Gathers parameters from the specified material.
/// </summary> /// </summary>

View File

@@ -360,10 +360,9 @@ namespace FlaxEditor.Windows.Assets
/// </summary> /// </summary>
/// <param name="parent">The parent control.</param> /// <param name="parent">The parent control.</param>
/// <param name="location">The location (within a given control).</param> /// <param name="location">The location (within a given control).</param>
private void ShowContextMenu(Control parent, ref Float2 location) internal void ShowContextMenu(Control parent, ref Float2 location)
{ {
var contextMenu = CreateContextMenu(); var contextMenu = CreateContextMenu();
contextMenu.Show(parent, location); contextMenu.Show(parent, location);
} }

View File

@@ -3,11 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.Tree; using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.GUI; using FlaxEditor.SceneGraph.GUI;
using FlaxEditor.Viewport.Cameras;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Windows.Assets namespace FlaxEditor.Windows.Assets
@@ -64,8 +62,11 @@ namespace FlaxEditor.Windows.Assets
private void OnSelectionUndo(SceneGraphNode[] toSelect) private void OnSelectionUndo(SceneGraphNode[] toSelect)
{ {
Selection.Clear(); Selection.Clear();
Selection.AddRange(toSelect); foreach (var e in toSelect)
{
if (e != null)
Selection.Add(e);
}
OnSelectionChanges(); OnSelectionChanges();
} }
@@ -118,11 +119,13 @@ namespace FlaxEditor.Windows.Assets
/// <param name="nodes">The nodes.</param> /// <param name="nodes">The nodes.</param>
public void Select(List<SceneGraphNode> nodes) public void Select(List<SceneGraphNode> nodes)
{ {
nodes?.RemoveAll(x => x == null);
if (nodes == null || nodes.Count == 0) if (nodes == null || nodes.Count == 0)
{ {
Deselect(); Deselect();
return; return;
} }
if (Utils.ArraysEqual(Selection, nodes)) if (Utils.ArraysEqual(Selection, nodes))
return; return;

View File

@@ -132,7 +132,7 @@ namespace FlaxEditor.Windows.Assets
IsScrollable = false, IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6), Offsets = new Margin(0, 0, 0, 18 + 6),
}; };
_searchBox = new SearchBox() _searchBox = new SearchBox
{ {
AnchorPreset = AnchorPresets.HorizontalStretchMiddle, AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
Parent = headerPanel, Parent = headerPanel,
@@ -140,7 +140,8 @@ namespace FlaxEditor.Windows.Assets
}; };
_searchBox.TextChanged += OnSearchBoxTextChanged; _searchBox.TextChanged += OnSearchBoxTextChanged;
_treePanel = new Panel() // Prefab structure tree
_treePanel = new Panel
{ {
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f), Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f),
@@ -148,8 +149,6 @@ namespace FlaxEditor.Windows.Assets
IsScrollable = true, IsScrollable = true,
Parent = sceneTreePanel, Parent = sceneTreePanel,
}; };
// Prefab structure tree
Graph = new LocalSceneGraph(new CustomRootNode(this)); Graph = new LocalSceneGraph(new CustomRootNode(this));
Graph.Root.TreeNode.Expand(true); Graph.Root.TreeNode.Expand(true);
_tree = new PrefabTree _tree = new PrefabTree
@@ -316,11 +315,7 @@ namespace FlaxEditor.Windows.Assets
return; return;
// Restore // Restore
_viewport.Prefab = _asset; OnPrefabOpened();
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.Expand(true);
_undo.Clear(); _undo.Clear();
ClearEditedFlag(); ClearEditedFlag();
} }
@@ -346,6 +341,16 @@ namespace FlaxEditor.Windows.Assets
} }
} }
private void OnPrefabOpened()
{
_viewport.Prefab = _asset;
_viewport.UpdateGizmoMode();
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.Expand(true);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Save() public override void Save()
{ {
@@ -355,6 +360,8 @@ namespace FlaxEditor.Windows.Assets
try try
{ {
Editor.Scene.OnSaveStart(_viewport._uiParentLink);
// Simply update changes // Simply update changes
Editor.Prefabs.ApplyAll(_viewport.Instance); Editor.Prefabs.ApplyAll(_viewport.Instance);
@@ -371,6 +378,10 @@ namespace FlaxEditor.Windows.Assets
throw; throw;
} }
finally
{
Editor.Scene.OnSaveEnd(_viewport._uiParentLink);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -411,13 +422,8 @@ namespace FlaxEditor.Windows.Assets
return; return;
} }
_viewport.Prefab = _asset; OnPrefabOpened();
Graph.MainActor = _viewport.Instance;
_focusCamera = true; _focusCamera = true;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.Expand(true);
_undo.Clear(); _undo.Clear();
ClearEditedFlag(); ClearEditedFlag();
@@ -462,11 +468,7 @@ namespace FlaxEditor.Windows.Assets
_viewport.Prefab = null; _viewport.Prefab = null;
if (_asset.IsLoaded) if (_asset.IsLoaded)
{ {
_viewport.Prefab = _asset; OnPrefabOpened();
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
Graph.Root.TreeNode.ExpandAll(true);
} }
} }
finally finally
@@ -478,7 +480,6 @@ namespace FlaxEditor.Windows.Assets
if (_focusCamera && _viewport.Task.FrameCount > 1) if (_focusCamera && _viewport.Task.FrameCount > 1)
{ {
_focusCamera = false; _focusCamera = false;
Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds); Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds);
_viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f); _viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f);
} }

View File

@@ -281,6 +281,9 @@ namespace FlaxEditor.Windows
_view.OnDelete += Delete; _view.OnDelete += Delete;
_view.OnDuplicate += Duplicate; _view.OnDuplicate += Duplicate;
_view.OnPaste += Paste; _view.OnPaste += Paste;
_view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
} }
private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox) private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox)

View File

@@ -23,6 +23,7 @@ namespace FlaxEditor.Windows
private Tabs _tabs; private Tabs _tabs;
private EditorOptions _options; private EditorOptions _options;
private ToolStripButton _saveButton; private ToolStripButton _saveButton;
private readonly Undo _undo;
private readonly List<Tab> _customTabs = new List<Tab>(); private readonly List<Tab> _customTabs = new List<Tab>();
/// <summary> /// <summary>
@@ -33,6 +34,12 @@ namespace FlaxEditor.Windows
: base(editor, true, ScrollBars.None) : base(editor, true, ScrollBars.None)
{ {
Title = "Editor Options"; Title = "Editor Options";
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
_undo.RedoDone += OnUndoRedo;
_undo.ActionDone += OnUndoRedo;
var toolstrip = new ToolStrip var toolstrip = new ToolStrip
{ {
@@ -58,9 +65,19 @@ namespace FlaxEditor.Windows
CreateTab("Visual", () => _options.Visual); CreateTab("Visual", () => _options.Visual);
CreateTab("Source Code", () => _options.SourceCode); CreateTab("Source Code", () => _options.SourceCode);
CreateTab("Theme", () => _options.Theme); CreateTab("Theme", () => _options.Theme);
// Setup input actions
InputActions.Add(options => options.Undo, _undo.PerformUndo);
InputActions.Add(options => options.Redo, _undo.PerformRedo);
InputActions.Add(options => options.Save, SaveData);
_tabs.SelectedTabIndex = 0; _tabs.SelectedTabIndex = 0;
} }
private void OnUndoRedo(IUndoAction action)
{
MarkAsEdited();
}
private Tab CreateTab(string name, Func<object> getValue) private Tab CreateTab(string name, Func<object> getValue)
{ {
@@ -73,7 +90,7 @@ namespace FlaxEditor.Windows
Parent = tab Parent = tab
}; };
var settings = new CustomEditorPresenter(null); var settings = new CustomEditorPresenter(_undo);
settings.Panel.Parent = panel; settings.Panel.Parent = panel;
settings.Panel.Tag = getValue; settings.Panel.Tag = getValue;
settings.Modified += MarkAsEdited; settings.Modified += MarkAsEdited;

View File

@@ -38,6 +38,7 @@ namespace FlaxEditor.Windows
protected EditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars) protected EditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars)
: base(editor.UI.MasterPanel, hideOnClose, scrollBars) : base(editor.UI.MasterPanel, hideOnClose, scrollBars)
{ {
AutoFocus = true;
Editor = editor; Editor = editor;
InputActions.Add(options => options.ContentFinder, () => InputActions.Add(options => options.ContentFinder, () =>

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml; using System.Xml;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEditor.Options; using FlaxEditor.Options;
@@ -194,133 +195,14 @@ namespace FlaxEditor.Windows
public bool Active; public bool Active;
} }
private class GameRoot : ContainerControl /// <summary>
/// Root control for game UI preview in Editor. Supports basic UI editing via <see cref="UIEditorRoot"/>.
/// </summary>
private class GameRoot : UIEditorRoot
{ {
public bool EnableEvents => !Time.GamePaused; public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode;
public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused;
public override bool RayCast(ref Float2 location, out Control hit) public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo;
{
return RayCastChildren(ref location, out hit);
}
public override bool ContainsPoint(ref Float2 location, bool precise = false)
{
if (precise)
return false;
return base.ContainsPoint(ref location, precise);
}
public override bool OnCharInput(char c)
{
if (!EnableEvents)
return false;
return base.OnCharInput(c);
}
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
if (!EnableEvents)
return DragDropEffect.None;
return base.OnDragDrop(ref location, data);
}
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
if (!EnableEvents)
return DragDropEffect.None;
return base.OnDragEnter(ref location, data);
}
public override void OnDragLeave()
{
if (!EnableEvents)
return;
base.OnDragLeave();
}
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
if (!EnableEvents)
return DragDropEffect.None;
return base.OnDragMove(ref location, data);
}
public override bool OnKeyDown(KeyboardKeys key)
{
if (!EnableEvents)
return false;
return base.OnKeyDown(key);
}
public override void OnKeyUp(KeyboardKeys key)
{
if (!EnableEvents)
return;
base.OnKeyUp(key);
}
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (!EnableEvents)
return false;
return base.OnMouseDoubleClick(location, button);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (!EnableEvents)
return false;
return base.OnMouseDown(location, button);
}
public override void OnMouseEnter(Float2 location)
{
if (!EnableEvents)
return;
base.OnMouseEnter(location);
}
public override void OnMouseLeave()
{
if (!EnableEvents)
return;
base.OnMouseLeave();
}
public override void OnMouseMove(Float2 location)
{
if (!EnableEvents)
return;
base.OnMouseMove(location);
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (!EnableEvents)
return false;
return base.OnMouseUp(location, button);
}
public override bool OnMouseWheel(Float2 location, float delta)
{
if (!EnableEvents)
return false;
return base.OnMouseWheel(location, delta);
}
} }
/// <summary> /// <summary>
@@ -348,13 +230,9 @@ namespace FlaxEditor.Windows
// Override the game GUI root // Override the game GUI root
_guiRoot = new GameRoot _guiRoot = new GameRoot
{ {
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
//Visible = false,
AutoFocus = false,
Parent = _viewport Parent = _viewport
}; };
RootControl.GameRoot = _guiRoot; RootControl.GameRoot = _guiRoot.UIRoot;
SizeChanged += control => { ResizeViewport(); }; SizeChanged += control => { ResizeViewport(); };
@@ -382,6 +260,56 @@ namespace FlaxEditor.Windows
Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.Windows.ProfilerWin.Clear();
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
}); });
InputActions.Add(options => options.Save, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SaveAll();
});
InputActions.Add(options => options.Undo, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.PerformUndo();
Focus();
});
InputActions.Add(options => options.Redo, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.PerformRedo();
Focus();
});
InputActions.Add(options => options.Cut, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Cut();
});
InputActions.Add(options => options.Copy, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Copy();
});
InputActions.Add(options => options.Paste, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Paste();
});
InputActions.Add(options => options.Duplicate, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Duplicate();
});
InputActions.Add(options => options.Delete, () =>
{
if (Editor.IsPlayMode)
return;
Editor.Instance.SceneEditing.Delete();
});
} }
private void ChangeViewportRatio(ViewportScaleOptions v) private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -916,35 +844,6 @@ namespace FlaxEditor.Windows
Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
} }
// Selected UI controls outline
bool drawAnySelectedControl = false;
// TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
var selection = Editor.SceneEditing.Selection;
for (var i = 0; i < selection.Count; i++)
{
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
{
if (!drawAnySelectedControl)
{
drawAnySelectedControl = true;
Render2D.PushTransform(ref _viewport._cachedTransform);
}
var options = Editor.Options.Options.Visual;
var control = controlActor.Control;
var bounds = control.EditorBounds;
var p1 = control.PointToParent(_viewport, bounds.UpperLeft);
var p2 = control.PointToParent(_viewport, bounds.UpperRight);
var p3 = control.PointToParent(_viewport, bounds.BottomLeft);
var p4 = control.PointToParent(_viewport, bounds.BottomRight);
var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4));
var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4));
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
}
}
if (drawAnySelectedControl)
Render2D.PopTransform();
// Play mode hints and overlay // Play mode hints and overlay
if (Editor.StateMachine.IsPlayMode) if (Editor.StateMachine.IsPlayMode)
{ {

View File

@@ -65,6 +65,11 @@ namespace FlaxEditor.Windows
/// </summary> /// </summary>
public OutputLogWindow Window; public OutputLogWindow Window;
/// <summary>
/// The input actions collection to processed during user input.
/// </summary>
public InputActionsContainer InputActions = new InputActionsContainer();
/// <summary> /// <summary>
/// The default text style. /// The default text style.
/// </summary> /// </summary>
@@ -80,6 +85,14 @@ namespace FlaxEditor.Windows
/// </summary> /// </summary>
public TextBlockStyle ErrorStyle; public TextBlockStyle ErrorStyle;
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (InputActions.Process(Editor.Instance, this, key))
return true;
return base.OnKeyDown(key);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnParseTextBlocks() protected override void OnParseTextBlocks()
{ {
@@ -201,6 +214,9 @@ namespace FlaxEditor.Windows
// Setup editor options // Setup editor options
Editor.Options.OptionsChanged += OnEditorOptionsChanged; Editor.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(Editor.Options.Options); OnEditorOptionsChanged(Editor.Options.Options);
_output.InputActions.Add(options => options.Search, () => _searchBox.Focus());
InputActions.Add(options => options.Search, () => _searchBox.Focus());
GameCooker.Event += OnGameCookerEvent; GameCooker.Event += OnGameCookerEvent;
ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed; ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed;

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