Merge branch 'master' into android-emulate

This commit is contained in:
Chandler Cox
2025-02-20 21:19:32 -06:00
212 changed files with 3017 additions and 810 deletions

View File

@@ -1,12 +1,13 @@
name: Continuous Deployment name: Continuous Deployment
on: on:
schedule: schedule:
- cron: '15 4 * * *' - cron: '15 6 * * *'
workflow_dispatch: workflow_dispatch:
env: env:
DOTNET_NOLOGO: true DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false DOTNET_CLI_TELEMETRY_OPTOUT: false
GIT_LFS_PULL_OPTIONS: '-c lfs.concurrenttransfers=1 -c lfs.transfer.maxretries=2 -c http.version="HTTP/1.1" -c lfs.activitytimeout=60'
jobs: jobs:
@@ -20,7 +21,7 @@ jobs:
- name: Checkout LFS - name: Checkout LFS
run: | run: |
git lfs version git lfs version
git lfs pull git ${{ env.GIT_LFS_PULL_OPTIONS }} lfs pull
- name: Setup Vulkan - name: Setup Vulkan
uses: ./.github/actions/vulkan uses: ./.github/actions/vulkan
- name: Setup .NET - name: Setup .NET
@@ -35,12 +36,12 @@ jobs:
run: | run: |
.\PackageEditor.bat -arch=x64 -platform=Windows -deployOutput=Output .\PackageEditor.bat -arch=x64 -platform=Windows -deployOutput=Output
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Windows-Editor name: Windows-Editor
path: Output/Editor.zip path: Output/Editor.zip
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Windows-EditorDebugSymbols name: Windows-EditorDebugSymbols
path: Output/EditorDebugSymbols.zip path: Output/EditorDebugSymbols.zip
@@ -53,7 +54,7 @@ jobs:
- name: Checkout LFS - name: Checkout LFS
run: | run: |
git lfs version git lfs version
git lfs pull git ${{ env.GIT_LFS_PULL_OPTIONS }} lfs pull
- name: Setup Vulkan - name: Setup Vulkan
uses: ./.github/actions/vulkan uses: ./.github/actions/vulkan
- name: Setup .NET - name: Setup .NET
@@ -68,7 +69,7 @@ jobs:
run: | run: |
.\PackagePlatforms.bat -arch=x64 -platform=Windows -deployOutput=Output .\PackagePlatforms.bat -arch=x64 -platform=Windows -deployOutput=Output
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Windows-Game name: Windows-Game
path: Output/Windows.zip path: Output/Windows.zip
@@ -83,7 +84,7 @@ jobs:
- name: Checkout LFS - name: Checkout LFS
run: | run: |
git lfs version git lfs version
git lfs pull git ${{ env.GIT_LFS_PULL_OPTIONS }} lfs pull
- name: Install dependencies - name: Install dependencies
run: | run: |
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
@@ -101,7 +102,7 @@ jobs:
run: | run: |
./PackageEditor.sh -arch=x64 -platform=Linux -deployOutput=Output ./PackageEditor.sh -arch=x64 -platform=Linux -deployOutput=Output
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Linux-Editor name: Linux-Editor
path: Output/FlaxEditorLinux.zip path: Output/FlaxEditorLinux.zip
@@ -114,7 +115,7 @@ jobs:
- name: Checkout LFS - name: Checkout LFS
run: | run: |
git lfs version git lfs version
git lfs pull git ${{ env.GIT_LFS_PULL_OPTIONS }} lfs pull
- name: Install dependencies - name: Install dependencies
run: | run: |
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
@@ -132,7 +133,7 @@ jobs:
run: | run: |
./PackagePlatforms.sh -arch=x64 -platform=Linux -deployOutput=Output ./PackagePlatforms.sh -arch=x64 -platform=Linux -deployOutput=Output
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Linux-Game name: Linux-Game
path: Output/Linux.zip path: Output/Linux.zip
@@ -147,7 +148,7 @@ jobs:
- name: Checkout LFS - name: Checkout LFS
run: | run: |
git lfs version git lfs version
git lfs pull git ${{ env.GIT_LFS_PULL_OPTIONS }} lfs pull
- name: Setup Vulkan - name: Setup Vulkan
uses: ./.github/actions/vulkan uses: ./.github/actions/vulkan
- name: Setup .NET - name: Setup .NET
@@ -162,7 +163,7 @@ jobs:
run: | run: |
./PackageEditor.command -arch=ARM64 -platform=Mac -deployOutput=Output ./PackageEditor.command -arch=ARM64 -platform=Mac -deployOutput=Output
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Mac-Editor name: Mac-Editor
path: Output/FlaxEditorMac.zip path: Output/FlaxEditorMac.zip
@@ -175,7 +176,7 @@ jobs:
- name: Checkout LFS - name: Checkout LFS
run: | run: |
git lfs version git lfs version
git lfs pull git ${{ env.GIT_LFS_PULL_OPTIONS }} lfs pull
- name: Setup Vulkan - name: Setup Vulkan
uses: ./.github/actions/vulkan uses: ./.github/actions/vulkan
- name: Setup .NET - name: Setup .NET
@@ -190,7 +191,7 @@ jobs:
run: | run: |
./PackagePlatforms.command -arch=ARM64 -platform=Mac -deployOutput=Output ./PackagePlatforms.command -arch=ARM64 -platform=Mac -deployOutput=Output
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Mac-Game name: Mac-Game
path: Output/Mac.zip path: Output/Mac.zip

BIN
Content/Editor/Camera/M_Camera.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Gizmo/Material.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Gizmo/MaterialWire.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Highlight Material.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/IconsMaterial.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,25 @@
%copyright%using System;
using System.Collections.Generic;
using FlaxEngine;
namespace %namespace%;
/// <summary>
/// %class% GamePlugin.
/// </summary>
public class %class% : GamePlugin
{
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
}
/// <inheritdoc/>
public override void Deinitialize()
{
base.Deinitialize();
}
}

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

BIN
Content/Editor/Wires Debug Material.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/Engine/DefaultMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/DefaultRadialMenu.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/DefaultTerrainMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/SingleColorMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/SkyboxMaterial.flax (Stored with Git LFS)

Binary file not shown.

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

Binary file not shown.

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 8 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)) * Install .NET 8 or 9 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,9 @@ Follow the instructions below to compile and run the engine from source.
## Linux ## Linux
* Install Visual Studio Code * Install Visual Studio Code
* 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 .NET 8 or 9 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-8.0` * Ubuntu: `sudo apt install dotnet-sdk-8.0`
* Arch: `sudo pacman -S dotnet-sdk-8.0 dotnet-runtime-8.0 dotnet-targeting-pack-8.0 dotnet-host`
* 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,12 +68,12 @@ Follow the instructions below to compile and run the engine from source.
## Mac ## Mac
* Install XCode * Install XCode
* 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 .NET 8 or 9 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`
* Open workspace with XCode or Visual Studio Code * Open workspace with XCode or Visual Studio Code
* Build and run (configuration `Editor.Mac.Development`) * Build and run (configuration `Editor.Mac.Development`)
#### Troubleshooting #### Troubleshooting

View File

@@ -750,7 +750,8 @@ namespace FlaxEditor.Content
// Draw short name // Draw short name
Render2D.PushClip(ref textRect); Render2D.PushClip(ref textRect);
Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f); var scale = 0.95f * view.ViewScale;
Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, scale);
Render2D.PopClip(); Render2D.PopClip();
if (IsBeingCut) if (IsBeingCut)

View File

@@ -106,6 +106,23 @@ namespace FlaxEditor.Content
} }
} }
/// <summary>
/// Context proxy object for C# GamePlugin files.
/// </summary>
/// <seealso cref="FlaxEditor.Content.CSharpProxy" />
[ContentContextMenu("New/C#/C# GamePlugin")]
public class CSharpGamePluginProxy : CSharpProxy
{
/// <inheritdoc />
public override string Name => "C# GamePlugin";
/// <inheritdoc />
protected override void GetTemplatePath(out string path)
{
path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/GamePluginTemplate.cs");
}
}
/// <summary> /// <summary>
/// Context proxy object for empty C# files. /// Context proxy object for empty C# files.
/// </summary> /// </summary>

View File

@@ -1,9 +1,11 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.Linq;
using FlaxEditor.Content.Create; using FlaxEditor.Content.Create;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Scripting;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
@@ -84,18 +86,67 @@ namespace FlaxEditor.Content
if (_element != null) if (_element != null)
{ {
// Define the rule for the types that can be used to create a json data asset _element.CustomControl.CheckValid += OnCheckValidJsonAssetType;
_element.CustomControl.CheckValid += type =>
type.Type != null &&
type.IsClass &&
type.Type.IsVisible &&
!type.IsAbstract &&
!type.IsGenericType &&
type.Type.GetConstructor(Type.EmptyTypes) != null &&
!typeof(FlaxEngine.GUI.Control).IsAssignableFrom(type.Type) &&
!typeof(FlaxEngine.Object).IsAssignableFrom(type.Type);
} }
} }
private static Type[] BlacklistedClasses =
[
typeof(System.Attribute),
typeof(FlaxEngine.Object),
typeof(FlaxEngine.GUI.Control),
];
private static Type[] BlacklistedStructs =
[
typeof(Float2),
typeof(Float3),
typeof(Float4),
typeof(Double2),
typeof(Double3),
typeof(Double4),
typeof(Vector2),
typeof(Vector3),
typeof(Vector4),
typeof(Half2),
typeof(Half3),
typeof(Half4),
typeof(Int2),
typeof(Int3),
typeof(Int4),
typeof(Transform),
typeof(Quaternion),
typeof(BoundingBox),
typeof(BoundingSphere),
typeof(BoundingFrustum),
typeof(Ray),
typeof(Plane),
typeof(Matrix),
typeof(Color),
typeof(Color32),
typeof(FloatR11G11B10),
typeof(FloatR10G10B10A2),
typeof(FlaxEngine.Half),
];
private static bool OnCheckValidJsonAssetType(ScriptType type)
{
// Define the rule for the types that can be used to create a json data asset
var mType = type.Type;
if (mType == null ||
type.IsAbstract ||
type.IsStatic ||
type.IsGenericType ||
!mType.IsVisible)
return false;
if (type.IsClass)
return mType.GetConstructor(Type.EmptyTypes) != null && BlacklistedClasses.FirstOrDefault(x => x.IsAssignableFrom(mType)) == null;
if (type.IsStructure)
return !type.IsPrimitive &&
!type.IsVoid &&
!BlacklistedStructs.Contains(mType);
return false;
}
} }
} }
@@ -175,7 +226,7 @@ namespace FlaxEditor.Content
{ {
_thumbnail = SpriteHandle.Invalid; _thumbnail = SpriteHandle.Invalid;
} }
/// <summary> /// <summary>
/// Constructor with overriden thumbnail. /// Constructor with overriden thumbnail.
/// </summary> /// </summary>
@@ -196,7 +247,7 @@ namespace FlaxEditor.Content
{ {
Editor.SaveJsonAsset(outputPath, new T()); Editor.SaveJsonAsset(outputPath, new T());
} }
/// <inheritdoc /> /// <inheritdoc />
public override AssetItem ConstructItem(string path, string typeName, ref Guid id) public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
{ {

View File

@@ -496,7 +496,7 @@ namespace FlaxEditor.Content.Thumbnails
// Prepare requests // Prepare requests
bool isAnyReady = false; bool isAnyReady = false;
int checks = Mathf.Min(10, _requests.Count); int checks = Mathf.Min(10, _requests.Count);
for (int i = 0; i < checks; i++) for (int i = 0; i < checks && i < _requests.Count; i++)
{ {
var request = _requests[i]; var request = _requests[i];
try try

View File

@@ -671,11 +671,14 @@ bool GameCookerImpl::Build()
MCore::Thread::Attach(); MCore::Thread::Attach();
// Build Started // Build Started
CallEvent(GameCooker::EventType::BuildStarted); if (!EnumHasAnyFlags(data.Options, BuildOptions::NoCook))
data.Tools->OnBuildStarted(data); {
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++) CallEvent(GameCooker::EventType::BuildStarted);
Steps[stepIndex]->OnBuildStarted(data); data.Tools->OnBuildStarted(data);
data.InitProgress(Steps.Count()); for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
Steps[stepIndex]->OnBuildStarted(data);
data.InitProgress(Steps.Count());
}
// Execute all steps in a sequence // Execute all steps in a sequence
bool failed = false; bool failed = false;
@@ -741,10 +744,13 @@ bool GameCookerImpl::Build()
} }
IsRunning = false; IsRunning = false;
CancelFlag = 0; CancelFlag = 0;
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++) if (!EnumHasAnyFlags(data.Options, BuildOptions::NoCook))
Steps[stepIndex]->OnBuildEnded(data, failed); {
data.Tools->OnBuildEnded(data, failed); for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
CallEvent(failed ? GameCooker::EventType::BuildFailed : GameCooker::EventType::BuildDone); Steps[stepIndex]->OnBuildEnded(data, failed);
data.Tools->OnBuildEnded(data, failed);
CallEvent(failed ? GameCooker::EventType::BuildFailed : GameCooker::EventType::BuildDone);
}
Delete(Data); Delete(Data);
Data = nullptr; Data = nullptr;

View File

@@ -364,6 +364,33 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
} }
#endif #endif
const bool distributionPackage = buildSettings->ForDistribution || data.Configuration == BuildConfiguration::Release; const bool distributionPackage = buildSettings->ForDistribution || data.Configuration == BuildConfiguration::Release;
if (platformSettings->BuildAAB)
{
// .aab
{
CreateProcessSettings procSettings;
procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT(":app:bundle") : TEXT(":app:bundleDebug"));
procSettings.WorkingDirectory = data.OriginalOutputPath;
const int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
{
data.Error(String::Format(TEXT("Failed to build Gradle project into .aab package (result code: {0}). See log for more info."), result));
return true;
}
}
// Copy result package
const String aab = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/bundle/release/app-release.aab") : TEXT("app/build/outputs/bundle/debug/app-debug.aab"));
const String outputAab = data.OriginalOutputPath / EditorUtilities::GetOutputName() + TEXT(".aab");
if (FileSystem::CopyFile(outputAab, aab))
{
LOG(Error, "Failed to copy .aab package from {0} to {1}", aab, outputAab);
return true;
}
LOG(Info, "Output Android AAB application package: {0} (size: {1} MB)", outputAab, FileSystem::GetFileSize(outputAab) / 1024 / 1024);
}
// .apk
{ {
CreateProcessSettings procSettings; CreateProcessSettings procSettings;
procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug")); procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
@@ -371,20 +398,20 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const int32 result = Platform::CreateProcess(procSettings); const int32 result = Platform::CreateProcess(procSettings);
if (result != 0) if (result != 0)
{ {
data.Error(String::Format(TEXT("Failed to build Gradle project into package (result code: {0}). See log for more info."), result)); data.Error(String::Format(TEXT("Failed to build Gradle project into .apk package (result code: {0}). See log for more info."), result));
return true; return true;
} }
} }
// 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 / EditorUtilities::GetOutputName() + 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 .apk package from {0} to {1}", apk, outputApk);
return true; return true;
} }
LOG(Info, "Output Android application package: {0} (size: {1} MB)", outputApk, FileSystem::GetFileSize(outputApk) / 1024 / 1024); LOG(Info, "Output Android APK application package: {0} (size: {1} MB)", outputApk, FileSystem::GetFileSize(outputApk) / 1024 / 1024);
return false; return false;
} }

View File

@@ -214,6 +214,33 @@ bool DeployDataStep::Perform(CookingData& data)
FileSystem::NormalizePath(srcDotnet); FileSystem::NormalizePath(srcDotnet);
LOG(Info, "Using .NET Runtime {} at {}", TEXT("Host"), srcDotnet); LOG(Info, "Using .NET Runtime {} at {}", TEXT("Host"), srcDotnet);
// Get major Version
Array<String> pathParts;
srcDotnet.Split('/', pathParts);
String version;
for (int i = 0; i < pathParts.Count(); i++)
{
if (pathParts[i] == TEXT("runtimes"))
{
Array<String> versionParts;
pathParts[i - 1].Split('.', versionParts);
if (!versionParts.IsEmpty())
{
const String majorVersion = versionParts[0].TrimTrailing();
int32 versionNum;
StringUtils::Parse(*majorVersion, majorVersion.Length(), &versionNum);
if (Math::IsInRange(versionNum, GAME_BUILD_DOTNET_RUNTIME_MIN_VER, GAME_BUILD_DOTNET_RUNTIME_MAX_VER)) // Check for major part
version = majorVersion;
}
}
}
if (version.IsEmpty())
{
data.Error(TEXT("Failed to find supported .NET version for the current host platform."));
return true;
}
// Deploy runtime files // Deploy runtime files
const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll"); const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll");
const bool srcDotnetFromEngine = srcDotnet.Contains(TEXT("Source/Platforms")); const bool srcDotnetFromEngine = srcDotnet.Contains(TEXT("Source/Platforms"));
@@ -226,14 +253,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/net8.0"); dstDotnetLibs /= String::Format(TEXT("lib/net{}.0"), version);
srcDotnetLibs = packFolder / TEXT("lib/net8.0"); srcDotnetLibs = packFolder / String::Format(TEXT("lib/net{}.0"), version);
} }
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/net8.0"); dstDotnetLibs /= String::Format(TEXT("lib/net{}.0"), version);
srcDotnetLibs /= TEXT("../lib/net8.0"); srcDotnetLibs /= String::Format(TEXT("../lib/net{}.0"), version);
} }
} }
else else
@@ -241,14 +268,14 @@ bool DeployDataStep::Perform(CookingData& data)
if (srcDotnetFromEngine) if (srcDotnetFromEngine)
{ {
// Runtime files inside Engine Platform folder // Runtime files inside Engine Platform folder
dstDotnetLibs /= TEXT("lib/net8.0"); dstDotnetLibs /= String::Format(TEXT("lib/net{}.0"), version);
srcDotnetLibs /= TEXT("lib/net8.0"); srcDotnetLibs /= String::Format(TEXT("lib/net{}.0"), version);
} }
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/net8.0"); srcDotnetLibs /= String::Format(TEXT("../lib/net{}.0"), version);
} }
} }
LOG(Info, "Copying .NET files from {} to {}", packFolder, dstDotnet); LOG(Info, "Copying .NET files from {} to {}", packFolder, dstDotnet);
@@ -273,6 +300,7 @@ bool DeployDataStep::Perform(CookingData& data)
DEPLOY_NATIVE_FILE("libmonosgen-2.0.so"); DEPLOY_NATIVE_FILE("libmonosgen-2.0.so");
DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.so"); DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.so");
DEPLOY_NATIVE_FILE("libSystem.Native.so"); DEPLOY_NATIVE_FILE("libSystem.Native.so");
DEPLOY_NATIVE_FILE("libSystem.Globalization.Native.so");
DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Android.so"); DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Android.so");
break; break;
case BuildPlatform::iOSARM64: case BuildPlatform::iOSARM64:

View File

@@ -883,7 +883,7 @@ namespace FlaxEditor.CustomEditors
/// </summary> /// </summary>
protected virtual void ClearToken() protected virtual void ClearToken()
{ {
ParentEditor.ClearToken(); ParentEditor?.ClearToken();
} }
} }
} }

View File

@@ -9,6 +9,7 @@ using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree; using FlaxEditor.GUI.Tree;
using FlaxEditor.Modules;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
@@ -67,12 +68,16 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Use default prefab instance as a reference for the editor // Use default prefab instance as a reference for the editor
Values.SetReferenceValue(prefabInstance); Values.SetReferenceValue(prefabInstance);
if (Presenter == Editor.Instance.Windows.PropertiesWin.Presenter) // Display prefab UI (when displaying object inside Prefab Window then display only nested prefabs)
var prefabId = prefab.ID;
Editor.GetPrefabNestedObject(ref prefabId, ref prefabObjectId, out var nestedPrefabId, out var nestedPrefabObjectId);
var nestedPrefab = FlaxEngine.Content.Load<Prefab>(nestedPrefabId);
var panel = layout.CustomContainer<UniformGridPanel>();
panel.CustomControl.Height = 20.0f;
panel.CustomControl.SlotsVertically = 1;
if (Presenter == Editor.Instance.Windows.PropertiesWin.Presenter || nestedPrefab)
{ {
// Add some UI var targetPrefab = nestedPrefab ?? prefab;
var panel = layout.CustomContainer<UniformGridPanel>();
panel.CustomControl.Height = 20.0f;
panel.CustomControl.SlotsVertically = 1;
panel.CustomControl.SlotsHorizontally = 3; panel.CustomControl.SlotsHorizontally = 3;
// Selecting actor prefab asset // Selecting actor prefab asset
@@ -80,22 +85,21 @@ namespace FlaxEditor.CustomEditors.Dedicated
selectPrefab.Button.Clicked += () => selectPrefab.Button.Clicked += () =>
{ {
Editor.Instance.Windows.ContentWin.ClearItemsSearch(); Editor.Instance.Windows.ContentWin.ClearItemsSearch();
Editor.Instance.Windows.ContentWin.Select(prefab); Editor.Instance.Windows.ContentWin.Select(targetPrefab);
}; };
// Edit selected prefab asset // Edit selected prefab asset
var editPrefab = panel.Button("Edit Prefab"); var editPrefab = panel.Button("Edit Prefab");
editPrefab.Button.Clicked += () => editPrefab.Button.Clicked += () => Editor.Instance.Windows.ContentWin.Open(Editor.Instance.ContentDatabase.FindAsset(targetPrefab.ID));
{
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
Editor.Instance.Windows.ContentWin.Select(prefab);
Editor.Instance.Windows.ContentWin.Open(Editor.Instance.Windows.ContentWin.View.Selection[0]);
};
// Viewing changes applied to this actor
var viewChanges = panel.Button("View Changes");
viewChanges.Button.Clicked += () => ViewChanges(viewChanges.Button, new Float2(0.0f, 20.0f));
} }
else
{
panel.CustomControl.SlotsHorizontally = 1;
}
// Viewing changes applied to this actor
var viewChanges = panel.Button("View Changes");
viewChanges.Button.Clicked += () => ViewChanges(viewChanges.Button, new Float2(0.0f, 20.0f));
// Link event to update editor on prefab apply // Link event to update editor on prefab apply
_linkedPrefabId = prefab.ID; _linkedPrefabId = prefab.ID;

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.Linq;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Surface; using FlaxEditor.Surface;
using FlaxEngine; using FlaxEngine;
@@ -35,6 +36,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
(instance, parameter, tag) => ((AnimatedModel)instance).GetParameterValue(parameter.Identifier), (instance, parameter, tag) => ((AnimatedModel)instance).GetParameterValue(parameter.Identifier),
(instance, value, parameter, tag) => ((AnimatedModel)instance).SetParameterValue(parameter.Identifier, value), (instance, value, parameter, tag) => ((AnimatedModel)instance).SetParameterValue(parameter.Identifier, value),
Values); Values);
if (!parameters.Any())
group.Label("No parameters", TextAlignment.Center);
_parametersAdded = true; _parametersAdded = true;
} }
} }

View File

@@ -15,13 +15,23 @@ namespace FlaxEditor.CustomEditors.Dedicated
private int _firstTimeShow; private int _firstTimeShow;
private BezierCurveEditor<T> _curve; private BezierCurveEditor<T> _curve;
private Splitter _splitter; private Splitter _splitter;
private string _heightCachedPath;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
var item = layout.CustomContainer<BezierCurveEditor<T>>(); var item = layout.CustomContainer<BezierCurveEditor<T>>();
_curve = item.CustomControl; _curve = item.CustomControl;
_curve.Height = 120.0f; var height = 120.0f;
var presenter = Presenter;
if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{
// Try to restore curve height
_heightCachedPath = layout.GetLayoutCachePath("Height");
if (Editor.Instance.ProjectCache.TryGetCustomData(_heightCachedPath, out float cachedHeight) && cachedHeight > 10.0f)
height = cachedHeight;
}
_curve.Height = height;
_curve.Edited += OnCurveEdited; _curve.Edited += OnCurveEdited;
_firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing) _firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing)
_splitter = new Splitter _splitter = new Splitter
@@ -45,7 +55,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void OnSplitterMoved(Float2 location) private void OnSplitterMoved(Float2 location)
{ {
_curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f); _curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
// Cache curve height
if (_heightCachedPath != null)
Editor.Instance.ProjectCache.SetCustomData(_heightCachedPath, _curve.Height);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -133,13 +147,23 @@ namespace FlaxEditor.CustomEditors.Dedicated
private int _firstTimeShow; private int _firstTimeShow;
private LinearCurveEditor<T> _curve; private LinearCurveEditor<T> _curve;
private Splitter _splitter; private Splitter _splitter;
private string _heightCachedPath;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
var item = layout.CustomContainer<LinearCurveEditor<T>>(); var item = layout.CustomContainer<LinearCurveEditor<T>>();
_curve = item.CustomControl; _curve = item.CustomControl;
_curve.Height = 120.0f; var height = 120.0f;
var presenter = Presenter;
if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{
// Try to restore curve height
_heightCachedPath = layout.GetLayoutCachePath("Height");
if (Editor.Instance.ProjectCache.TryGetCustomData(_heightCachedPath, out float cachedHeight) && cachedHeight > 10.0f)
height = cachedHeight;
}
_curve.Height = height;
_curve.Edited += OnCurveEdited; _curve.Edited += OnCurveEdited;
_firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing) _firstTimeShow = 4; // For some weird reason it needs several frames of warmup (probably due to sliders smoothing)
_splitter = new Splitter _splitter = new Splitter
@@ -164,6 +188,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void OnSplitterMoved(Float2 location) private void OnSplitterMoved(Float2 location)
{ {
_curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f); _curve.Height = Mathf.Clamp(_splitter.PointToParent(location).Y, 50.0f, 1000.0f);
// Cache curve height
if (_heightCachedPath != null)
Editor.Instance.ProjectCache.SetCustomData(_heightCachedPath, _curve.Height);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -53,6 +53,9 @@ public class ModelPrefabEditor : GenericEditor
} }
} }
// Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, modelPrefab.ImportPath, false);
var button = layout.Button("Reimport", "Reimports the source asset as prefab."); var button = layout.Button("Reimport", "Reimports the source asset as prefab.");
_reimportButton = button.Button; _reimportButton = button.Button;
_reimportButton.Clicked += OnReimport; _reimportButton.Clicked += OnReimport;

View File

@@ -117,6 +117,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
var data = SurfaceUtils.InitGraphParameters(parametersGroup); var data = SurfaceUtils.InitGraphParameters(parametersGroup);
SurfaceUtils.DisplayGraphParameters(group, data, ParameterGet, ParameterSet, Values, ParameterDefaultValue); SurfaceUtils.DisplayGraphParameters(group, data, ParameterGet, ParameterSet, Values, ParameterDefaultValue);
} }
if (!parameters.Any())
groups.Label("No parameters", TextAlignment.Center);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -4,6 +4,7 @@ using System;
using System.Linq; using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -122,7 +123,9 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
base.Refresh(); base.Refresh();
if (!HasDifferentValues) var differentValues = HasDifferentValues;
Picker.DifferentValues = differentValues;
if (!differentValues)
{ {
_isRefreshing = true; _isRefreshing = true;
var value = Values[0]; var value = Values[0];
@@ -156,6 +159,17 @@ namespace FlaxEditor.CustomEditors.Editors
private Rectangle DropdownRect => new Rectangle(Width - DropdownIconSize - DropdownIconMargin, DropdownIconMargin, DropdownIconSize, DropdownIconSize); private Rectangle DropdownRect => new Rectangle(Width - DropdownIconSize - DropdownIconMargin, DropdownIconMargin, DropdownIconSize, DropdownIconSize);
public Action ShowPicker; public Action ShowPicker;
public Action<ContentItem> OnAssetDropped;
private DragItems _dragItems;
private DragHandlers _dragHandlers;
private bool _hasValidDragOver;
private Func<ContentItem, bool> _validate;
public void SetValidationMethod(Func<ContentItem, bool> validate)
{
_validate = validate;
}
public override void Draw() public override void Draw()
{ {
@@ -164,6 +178,14 @@ namespace FlaxEditor.CustomEditors.Editors
var style = FlaxEngine.GUI.Style.Current; var style = FlaxEngine.GUI.Style.Current;
var dropdownRect = DropdownRect; var dropdownRect = DropdownRect;
Render2D.DrawSprite(style.ArrowDown, dropdownRect, Enabled ? (DropdownRect.Contains(PointFromWindow(RootWindow.MousePosition)) ? style.BorderSelected : style.Foreground) : style.ForegroundDisabled); Render2D.DrawSprite(style.ArrowDown, dropdownRect, Enabled ? (DropdownRect.Contains(PointFromWindow(RootWindow.MousePosition)) ? style.BorderSelected : style.Foreground) : style.ForegroundDisabled);
// Check if drag is over
if (IsDragOver && _hasValidDragOver)
{
var bounds = new Rectangle(Float2.Zero, Size);
Render2D.FillRectangle(bounds, style.Selection);
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
} }
public override bool OnMouseDown(Float2 location, MouseButton button) public override bool OnMouseDown(Float2 location, MouseButton button)
@@ -207,6 +229,68 @@ namespace FlaxEditor.CustomEditors.Editors
return result; return result;
} }
} }
private DragDropEffect DragEffect => _hasValidDragOver ? DragDropEffect.Move : DragDropEffect.None;
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
base.OnDragEnter(ref location, data);
// Ensure to have valid drag helpers (uses lazy init)
if (_dragItems == null)
_dragItems = new DragItems(ValidateDragAsset);
if (_dragHandlers == null)
{
_dragHandlers = new DragHandlers
{
_dragItems,
};
}
_hasValidDragOver = _dragHandlers.OnDragEnter(data) != DragDropEffect.None;
return DragEffect;
}
private bool ValidateDragAsset(ContentItem contentItem)
{
// Load or get asset
return _validate?.Invoke(contentItem) ?? false;
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
base.OnDragMove(ref location, data);
return DragEffect;
}
/// <inheritdoc />
public override void OnDragLeave()
{
_hasValidDragOver = false;
_dragHandlers.OnDragLeave();
base.OnDragLeave();
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
var result = DragEffect;
base.OnDragDrop(ref location, data);
if (_dragItems.HasValidDrag)
{
OnAssetDropped(_dragItems.Objects[0]);
}
return result;
}
} }
private TextBoxWithPicker _textBox; private TextBoxWithPicker _textBox;
@@ -221,13 +305,21 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
if (HasDifferentTypes) if (HasDifferentTypes)
return; return;
_validator = new AssetPickerValidator(ScriptType.Null);
_textBox = layout.Custom<TextBoxWithPicker>().CustomControl; _textBox = layout.Custom<TextBoxWithPicker>().CustomControl;
_textBox.ShowPicker = OnShowPicker; _textBox.ShowPicker = OnShowPicker;
_textBox.OnAssetDropped = OnItemDropped;
_textBox.EditEnd += OnEditEnd; _textBox.EditEnd += OnEditEnd;
_validator = new AssetPickerValidator(ScriptType.Null); _textBox.SetValidationMethod(_validator.IsValid);
AssetRefEditor.ApplyAssetReferenceAttribute(Values, out _, _validator); AssetRefEditor.ApplyAssetReferenceAttribute(Values, out _, _validator);
} }
private void OnItemDropped(ContentItem item)
{
SetPickerPath(item);
}
private void OnShowPicker() private void OnShowPicker()
{ {
if (_validator.AssetType != ScriptType.Null) if (_validator.AssetType != ScriptType.Null)
@@ -285,12 +377,9 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
base.Refresh(); base.Refresh();
if (!HasDifferentValues) _isRefreshing = true;
{ _textBox.Text = HasDifferentValues ? "Multiple Values" : GetPath();
_isRefreshing = true; _isRefreshing = false;
_textBox.Text = GetPath();
_isRefreshing = false;
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -9,6 +9,8 @@ using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.GUI; using FlaxEditor.SceneGraph.GUI;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
@@ -40,6 +42,11 @@ namespace FlaxEditor.CustomEditors.Editors
private DragScripts _dragScripts; private DragScripts _dragScripts;
private DragHandlers _dragHandlers; private DragHandlers _dragHandlers;
/// <summary>
/// The presenter using this control.
/// </summary>
public IPresenterOwner PresenterContext;
/// <summary> /// <summary>
/// Gets or sets the allowed objects type (given type and all sub classes). Must be <see cref="Object"/> type of any subclass. /// Gets or sets the allowed objects type (given type and all sub classes). Must be <see cref="Object"/> type of any subclass.
/// </summary> /// </summary>
@@ -129,6 +136,11 @@ namespace FlaxEditor.CustomEditors.Editors
/// </summary> /// </summary>
public Func<Object, ScriptType, bool> CheckValid; public Func<Object, ScriptType, bool> CheckValid;
/// <summary>
/// Utility flag used to indicate that there are different values assigned to this reference editor and user should be informed about it.
/// </summary>
public bool DifferentValues;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FlaxObjectRefPickerControl"/> class. /// Initializes a new instance of the <see cref="FlaxObjectRefPickerControl"/> class.
/// </summary> /// </summary>
@@ -154,7 +166,7 @@ namespace FlaxEditor.CustomEditors.Editors
Value = actor; Value = actor;
RootWindow.Focus(); RootWindow.Focus();
Focus(); Focus();
}); }, PresenterContext);
} }
else else
{ {
@@ -163,7 +175,7 @@ namespace FlaxEditor.CustomEditors.Editors
Value = script; Value = script;
RootWindow.Focus(); RootWindow.Focus();
Focus(); Focus();
}); }, PresenterContext);
} }
} }
@@ -197,7 +209,14 @@ namespace FlaxEditor.CustomEditors.Editors
Render2D.DrawRectangle(frameRect, isEnabled && (IsMouseOver || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal); Render2D.DrawRectangle(frameRect, isEnabled && (IsMouseOver || IsNavFocused) ? style.BorderHighlighted : style.BorderNormal);
// Check if has item selected // Check if has item selected
if (isSelected) if (DifferentValues)
{
// Draw info
Render2D.PushClip(nameRect);
Render2D.DrawText(style.FontMedium, Type != null ? $"Multiple Values ({Utilities.Utils.GetPropertyNameUI(Type.ToString())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center);
Render2D.PopClip();
}
else if (isSelected)
{ {
// Draw name // Draw name
Render2D.PushClip(nameRect); Render2D.PushClip(nameRect);
@@ -415,13 +434,13 @@ namespace FlaxEditor.CustomEditors.Editors
// Ensure to have valid drag helpers (uses lazy init) // Ensure to have valid drag helpers (uses lazy init)
if (_dragActors == null) if (_dragActors == null)
_dragActors = new DragActors(x => IsValid(x.Actor)); _dragActors = new DragActors(ValidateDragActor);
if (_dragActorsWithScript == null) if (_dragActorsWithScript == null)
_dragActorsWithScript = new DragActors(ValidateDragActorWithScript); _dragActorsWithScript = new DragActors(ValidateDragActorWithScript);
if (_dragAssets == null) if (_dragAssets == null)
_dragAssets = new DragAssets(ValidateDragAsset); _dragAssets = new DragAssets(ValidateDragAsset);
if (_dragScripts == null) if (_dragScripts == null)
_dragScripts = new DragScripts(IsValid); _dragScripts = new DragScripts(ValidateDragScript);
if (_dragHandlers == null) if (_dragHandlers == null)
{ {
_dragHandlers = new DragHandlers _dragHandlers = new DragHandlers
@@ -446,6 +465,43 @@ namespace FlaxEditor.CustomEditors.Editors
return DragEffect; return DragEffect;
} }
private bool ValidateDragActor(ActorNode a)
{
if (!IsValid(a.Actor))
return false;
if (PresenterContext is PrefabWindow prefabWindow)
{
if (prefabWindow.Tree == a.TreeNode.ParentTree)
return true;
}
else if (PresenterContext is PropertiesWindow || PresenterContext == null)
{
if (a.ParentScene != null)
return true;
}
return false;
}
private bool ValidateDragScript(Script script)
{
if (!IsValid(script))
return false;
if (PresenterContext is PrefabWindow prefabWindow)
{
var actorNode = prefabWindow.Graph.Root.Find(script.Actor);
if (actorNode != null)
return true;
}
else if (PresenterContext is PropertiesWindow || PresenterContext == null)
{
if (script.Actor.HasScene)
return true;
}
return false;
}
private bool ValidateDragAsset(AssetItem assetItem) private bool ValidateDragAsset(AssetItem assetItem)
{ {
// Check if can accept assets // Check if can accept assets
@@ -464,7 +520,18 @@ namespace FlaxEditor.CustomEditors.Editors
private bool ValidateDragActorWithScript(ActorNode node) private bool ValidateDragActorWithScript(ActorNode node)
{ {
return node.Actor.Scripts.Any(IsValid); bool isCorrectContext = false;
if (PresenterContext is PrefabWindow prefabWindow)
{
if (prefabWindow.Tree == node.TreeNode.ParentTree)
isCorrectContext = true;
}
else if (PresenterContext is PropertiesWindow || PresenterContext == null)
{
if (node.ParentScene != null)
isCorrectContext = true;
}
return node.Actor.Scripts.Any(IsValid) && isCorrectContext;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -536,6 +603,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (!HasDifferentTypes) if (!HasDifferentTypes)
{ {
_element = layout.Custom<FlaxObjectRefPickerControl>(); _element = layout.Custom<FlaxObjectRefPickerControl>();
_element.CustomControl.PresenterContext = Presenter.Owner;
_element.CustomControl.Type = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); _element.CustomControl.Type = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
_element.CustomControl.ValueChanged += () => SetValue(_element.CustomControl.Value); _element.CustomControl.ValueChanged += () => SetValue(_element.CustomControl.Value);
} }
@@ -546,7 +614,9 @@ namespace FlaxEditor.CustomEditors.Editors
{ {
base.Refresh(); base.Refresh();
if (!HasDifferentValues) var differentValues = HasDifferentValues;
_element.CustomControl.DifferentValues = differentValues;
if (!differentValues)
{ {
_element.CustomControl.Value = Values[0] as Object; _element.CustomControl.Value = Values[0] as Object;
} }

View File

@@ -228,7 +228,6 @@ namespace FlaxEditor.CustomEditors.Editors
// Handle Sliding // Handle Sliding
if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded)) if (AllowSlidingForDifferentValues && (isSliding || _slidingEnded))
{ {
// TODO: handle linked values
Float3 average = Float3.Zero; Float3 average = Float3.Zero;
for (int i = 0; i < Values.Count; i++) for (int i = 0; i < Values.Count; i++)
{ {
@@ -251,12 +250,24 @@ namespace FlaxEditor.CustomEditors.Editors
for (int i = 0; i < Values.Count; i++) for (int i = 0; i < Values.Count; i++)
{ {
var v = Values[i]; var v = Values[i];
if (v is Vector3 asVector3) if (LinkValues)
v = asVector3 + new Vector3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0); {
else if (v is Float3 asFloat3) if (v is Vector3 asVector3)
v = asFloat3 + new Float3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0); v = asVector3 + new Vector3(newValue.X, newValue.Y, newValue.Z);
else if (v is Double3 asDouble3) else if (v is Float3 asFloat3)
v = asDouble3 + new Double3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0); v = asFloat3 + new Float3(newValue.X, newValue.Y, newValue.Z);
else if (v is Double3 asDouble3)
v = asDouble3 + new Double3(newValue.X, newValue.Y, newValue.Z);
}
else
{
if (v is Vector3 asVector3)
v = asVector3 + new Vector3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
else if (v is Float3 asFloat3)
v = asFloat3 + new Float3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
else if (v is Double3 asDouble3)
v = asDouble3 + new Double3(_valueChanged == ValueChanged.X ? newValue.X : 0, _valueChanged == ValueChanged.Y ? newValue.Y : 0, _valueChanged == ValueChanged.Z ? newValue.Z : 0);
}
newObjects[i] = v; newObjects[i] = v;
} }
@@ -267,16 +278,27 @@ namespace FlaxEditor.CustomEditors.Editors
} }
else else
{ {
// TODO: handle linked values
for (int i = 0; i < Values.Count; i++) for (int i = 0; i < Values.Count; i++)
{ {
object v = Values[i]; object v = Values[i];
if (v is Vector3 asVector3) if (LinkValues)
v = new Vector3(_valueChanged == ValueChanged.X ? xValue : asVector3.X, _valueChanged == ValueChanged.Y ? yValue : asVector3.Y, _valueChanged == ValueChanged.Z ? zValue : asVector3.Z); {
else if (v is Float3 asFloat3) if (v is Vector3 asVector3)
v = new Float3(_valueChanged == ValueChanged.X ? xValue : asFloat3.X, _valueChanged == ValueChanged.Y ? yValue : asFloat3.Y, _valueChanged == ValueChanged.Z ? zValue : asFloat3.Z); v = asVector3 + new Vector3(xValue, yValue, zValue);
else if (v is Double3 asDouble3) else if (v is Float3 asFloat3)
v = new Double3(_valueChanged == ValueChanged.X ? xValue : asDouble3.X, _valueChanged == ValueChanged.Y ? yValue : asDouble3.Y, _valueChanged == ValueChanged.Z ? zValue : asDouble3.Z); v = asFloat3 + new Float3(xValue, yValue, zValue);
else if (v is Double3 asDouble3)
v = asDouble3 + new Double3(xValue, yValue, zValue);
}
else
{
if (v is Vector3 asVector3)
v = new Vector3(_valueChanged == ValueChanged.X ? xValue : asVector3.X, _valueChanged == ValueChanged.Y ? yValue : asVector3.Y, _valueChanged == ValueChanged.Z ? zValue : asVector3.Z);
else if (v is Float3 asFloat3)
v = new Float3(_valueChanged == ValueChanged.X ? xValue : asFloat3.X, _valueChanged == ValueChanged.Y ? yValue : asFloat3.Y, _valueChanged == ValueChanged.Z ? zValue : asFloat3.Z);
else if (v is Double3 asDouble3)
v = new Double3(_valueChanged == ValueChanged.X ? xValue : asDouble3.X, _valueChanged == ValueChanged.Y ? yValue : asDouble3.Y, _valueChanged == ValueChanged.Z ? zValue : asDouble3.Z);
}
newObjects[i] = v; newObjects[i] = v;
} }

View File

@@ -96,6 +96,20 @@ namespace FlaxEditor.CustomEditors
menu.Show(groupPanel, location); menu.Show(groupPanel, location);
} }
internal string GetLayoutCachePath(string name)
{
// Build group identifier (made of path from group titles)
var expandPath = name;
var container = this;
while (container != null && !(container is CustomEditorPresenter))
{
if (container.ContainerControl is DropPanel dropPanel)
expandPath = dropPanel.HeaderText + "/" + expandPath;
container = container._parent;
}
return expandPath;
}
/// <summary> /// <summary>
/// Adds new group element. /// Adds new group element.
/// </summary> /// </summary>
@@ -112,14 +126,7 @@ namespace FlaxEditor.CustomEditors
if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{ {
// Build group identifier (made of path from group titles) // Build group identifier (made of path from group titles)
var expandPath = title; var expandPath = GetLayoutCachePath(title);
var container = this;
while (container != null && !(container is CustomEditorPresenter))
{
if (container.ContainerControl is DropPanel dropPanel)
expandPath = dropPanel.HeaderText + "/" + expandPath;
container = container._parent;
}
// Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression) // Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression)
if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup) if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup)

View File

@@ -250,7 +250,7 @@ namespace FlaxEditor.CustomEditors
if (objA == null && objB is string objBStr && objBStr.Length == 0) if (objA == null && objB is string objBStr && objBStr.Length == 0)
return true; return true;
return Newtonsoft.Json.Utilities.MiscellaneousUtils.ValueEquals(objA, objB); return FlaxEngine.Json.JsonSerializer.ValueEquals(objA, objB);
} }
/// <summary> /// <summary>

View File

@@ -550,7 +550,7 @@ int32 Editor::LoadProduct()
} }
if (!FileSystem::FileExists(files[0])) if (!FileSystem::FileExists(files[0]))
{ {
Platform::Fatal(TEXT("Cannot opoen selected project file because it doesn't exist.")); Platform::Fatal(TEXT("Cannot open selected project file because it doesn't exist."));
return -1; return -1;
} }
projectPath = StringUtils::GetDirectoryName(files[0]); projectPath = StringUtils::GetDirectoryName(files[0]);

View File

@@ -1686,9 +1686,6 @@ namespace FlaxEditor
[return: MarshalAs(UnmanagedType.U1)] [return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetPrefabNestedObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_GetPrefabNestedObject(IntPtr prefabId, IntPtr prefabObjectId, IntPtr outPrefabId, IntPtr outPrefabObjectId);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial float Internal_GetAnimationTime(IntPtr animatedModel); internal static partial float Internal_GetAnimationTime(IntPtr animatedModel);

View File

@@ -48,6 +48,11 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
public bool CanEdit = true; public bool CanEdit = true;
/// <summary>
/// Utility flag used to indicate that there are different values assigned to this reference editor and user should be informed about it.
/// </summary>
public bool DifferentValues;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AssetPicker"/> class. /// Initializes a new instance of the <see cref="AssetPicker"/> class.
/// </summary> /// </summary>
@@ -121,7 +126,13 @@ namespace FlaxEditor.GUI
if (CanEdit) if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
if (Validator.SelectedItem != null) if (DifferentValues)
{
// No element selected
Render2D.FillRectangle(iconRect, style.BackgroundNormal);
Render2D.DrawText(style.FontMedium, "Multiple\nValues", iconRect, style.Foreground, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
}
else if (Validator.SelectedItem != null)
{ {
// Draw item preview // Draw item preview
Validator.SelectedItem.DrawThumbnail(ref iconRect); Validator.SelectedItem.DrawThumbnail(ref iconRect);

View File

@@ -30,8 +30,10 @@ namespace FlaxEditor.GUI
internal bool _isMovingTangent; internal bool _isMovingTangent;
internal bool _movedView; internal bool _movedView;
internal bool _movedKeyframes; internal bool _movedKeyframes;
internal bool _toggledSelection;
private TangentPoint _movingTangent; private TangentPoint _movingTangent;
private Float2 _movingSelectionStart; private Float2 _movingSelectionStart;
private Float2 _movingSelectionStartPosLock;
private Float2[] _movingSelectionOffsets; private Float2[] _movingSelectionOffsets;
private Float2 _cmShowPos; private Float2 _cmShowPos;
@@ -56,12 +58,11 @@ namespace FlaxEditor.GUI
internal void UpdateSelection(ref Rectangle selectionRect) internal void UpdateSelection(ref Rectangle selectionRect)
{ {
// Find controls to select // Find controls to select
for (int i = 0; i < Children.Count; i++) var children = _children;
for (int i = 0; i < children.Count; i++)
{ {
if (Children[i] is KeyframePoint p) if (children[i] is KeyframePoint p)
{
p.IsSelected = p.Bounds.Intersects(ref selectionRect); p.IsSelected = p.Bounds.Intersects(ref selectionRect);
}
} }
_editor.UpdateTangents(); _editor.UpdateTangents();
} }
@@ -72,6 +73,7 @@ namespace FlaxEditor.GUI
_isMovingSelection = true; _isMovingSelection = true;
_movedKeyframes = false; _movedKeyframes = false;
var viewRect = _editor._mainPanel.GetClientArea(); var viewRect = _editor._mainPanel.GetClientArea();
_movingSelectionStartPosLock = location;
_movingSelectionStart = PointToKeyframes(location, ref viewRect); _movingSelectionStart = PointToKeyframes(location, ref viewRect);
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._points.Count) if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._points.Count)
_movingSelectionOffsets = new Float2[_editor._points.Count]; _movingSelectionOffsets = new Float2[_editor._points.Count];
@@ -82,10 +84,17 @@ namespace FlaxEditor.GUI
internal void OnMove(Float2 location) internal void OnMove(Float2 location)
{ {
// Skip updating keyframes until move actual starts to be meaningful
if (Float2.Distance(ref _movingSelectionStartPosLock, ref location) < 1.5f)
return;
_movingSelectionStartPosLock = Float2.Minimum;
var viewRect = _editor._mainPanel.GetClientArea(); var viewRect = _editor._mainPanel.GetClientArea();
var locationKeyframes = PointToKeyframes(location, ref viewRect); var locationKeyframes = PointToKeyframes(location, ref viewRect);
var accessor = _editor.Accessor; var accessor = _editor.Accessor;
var components = accessor.GetCurveComponents(); var components = accessor.GetCurveComponents();
var snapEnabled = Root.GetKey(KeyboardKeys.Control);
var snapGrid = snapEnabled ? _editor.GetGridSnap() : Float2.One;
for (var i = 0; i < _editor._points.Count; i++) for (var i = 0; i < _editor._points.Count; i++)
{ {
var p = _editor._points[i]; var p = _editor._points[i];
@@ -122,7 +131,20 @@ namespace FlaxEditor.GUI
if (isFirstSelected) if (isFirstSelected)
{ {
time = locationKeyframes.X + offset.X; time = locationKeyframes.X + offset.X;
}
if (snapEnabled)
{
// Snap to the grid
var key = new Float2(time, value);
key = Float2.SnapToGrid(key, snapGrid);
time = key.X;
value = key.Y;
}
// Clamp and snap time to the valid range
if (isFirstSelected)
{
if (_editor.FPS.HasValue) if (_editor.FPS.HasValue)
{ {
float fps = _editor.FPS.Value; float fps = _editor.FPS.Value;
@@ -131,8 +153,6 @@ namespace FlaxEditor.GUI
time = Mathf.Clamp(time, minTime, maxTime); time = Mathf.Clamp(time, minTime, maxTime);
} }
// TODO: snapping keyframes to grid when moving
_editor.SetKeyframeInternal(p.Index, time, value, p.Component); _editor.SetKeyframeInternal(p.Index, time, value, p.Component);
} }
_editor.UpdateKeyframes(); _editor.UpdateKeyframes();
@@ -234,7 +254,11 @@ namespace FlaxEditor.GUI
var k = _editor.GetKeyframe(_movingTangent.Index); var k = _editor.GetKeyframe(_movingTangent.Index);
var kv = _editor.GetKeyframeValue(k); var kv = _editor.GetKeyframeValue(k);
var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component); var value = _editor.Accessor.GetCurveValue(ref kv, _movingTangent.Component);
_movingTangent.TangentValue = PointToKeyframes(location, ref viewRect).Y - value; var tangent = PointToKeyframes(location, ref viewRect).Y - value;
if (Root.GetKey(KeyboardKeys.Control))
tangent = Float2.SnapToGrid(new Float2(0, tangent), _editor.GetGridSnap()).Y; // Snap tangent over Y axis
tangent = tangent * _editor.ViewScale.X * 2;
_movingTangent.TangentValue = tangent;
_editor.UpdateTangents(); _editor.UpdateTangents();
Cursor = CursorType.SizeNS; Cursor = CursorType.SizeNS;
_movedKeyframes = true; _movedKeyframes = true;
@@ -283,6 +307,7 @@ namespace FlaxEditor.GUI
} }
// Cache data // Cache data
_toggledSelection = false;
_isMovingSelection = false; _isMovingSelection = false;
_isMovingTangent = false; _isMovingTangent = false;
_mousePos = location; _mousePos = location;
@@ -305,13 +330,7 @@ namespace FlaxEditor.GUI
{ {
if (_leftMouseDown) if (_leftMouseDown)
{ {
if (Root.GetKey(KeyboardKeys.Control)) if (Root.GetKey(KeyboardKeys.Shift))
{
// Toggle selection
keyframe.IsSelected = !keyframe.IsSelected;
_editor.UpdateTangents();
}
else if (Root.GetKey(KeyboardKeys.Shift))
{ {
// Select range // Select range
keyframe.IsSelected = true; keyframe.IsSelected = true;
@@ -335,10 +354,14 @@ namespace FlaxEditor.GUI
else if (!keyframe.IsSelected) else if (!keyframe.IsSelected)
{ {
// Select node // Select node
if (_editor.KeyframesEditorContext != null) if (!Root.GetKey(KeyboardKeys.Control))
_editor.KeyframesEditorContext.OnKeyframesDeselect(_editor); {
else if (_editor.KeyframesEditorContext != null)
_editor.ClearSelection(); _editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
else
_editor.ClearSelection();
}
_toggledSelection = true;
keyframe.IsSelected = true; keyframe.IsSelected = true;
_editor.UpdateTangents(); _editor.UpdateTangents();
} }
@@ -429,6 +452,12 @@ namespace FlaxEditor.GUI
else else
OnMoveEnd(location); OnMoveEnd(location);
} }
// Toggle selection
else if (!_toggledSelection && Root.GetKey(KeyboardKeys.Control) && GetChildAt(location) is KeyframePoint keyframe)
{
keyframe.IsSelected = !keyframe.IsSelected;
_editor.UpdateTangents();
}
_isMovingSelection = false; _isMovingSelection = false;
_isMovingTangent = false; _isMovingTangent = false;
@@ -514,11 +543,11 @@ namespace FlaxEditor.GUI
{ {
if (base.OnMouseDoubleClick(location, button)) if (base.OnMouseDoubleClick(location, button))
return true; return true;
// Add keyframe on double click // Add keyframe on double click
var child = GetChildAt(location); var child = GetChildAt(location);
if (child is not KeyframePoint && if (child is not KeyframePoint &&
child is not TangentPoint && child is not TangentPoint &&
_editor.KeyframesCount < _editor.MaxKeyframes) _editor.KeyframesCount < _editor.MaxKeyframes)
{ {
var viewRect = _editor._mainPanel.GetClientArea(); var viewRect = _editor._mainPanel.GetClientArea();
@@ -545,7 +574,7 @@ namespace FlaxEditor.GUI
var viewRect = _editor._mainPanel.GetClientArea(); var viewRect = _editor._mainPanel.GetClientArea();
var locationInKeyframes = PointToKeyframes(location, ref viewRect); var locationInKeyframes = PointToKeyframes(location, ref viewRect);
var locationInEditorBefore = _editor.PointFromKeyframes(locationInKeyframes, ref viewRect); var locationInEditorBefore = _editor.PointFromKeyframes(locationInKeyframes, ref viewRect);
// Scale relative to the curve size // Scale relative to the curve size
var scale = new Float2(delta * 0.1f); var scale = new Float2(delta * 0.1f);
_editor._mainPanel.GetDesireClientArea(out var mainPanelArea); _editor._mainPanel.GetDesireClientArea(out var mainPanelArea);

View File

@@ -163,10 +163,11 @@ namespace FlaxEditor.GUI
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size); var rect = new Rectangle(Float2.Zero, Size);
var color = Editor.ShowCollapsed ? Color.Gray : Editor.Colors[Component]; var color = Editor.ShowCollapsed ? style.ForegroundDisabled : Editor.Colors[Component];
if (IsSelected) if (IsSelected)
color = Editor.ContainsFocus ? Color.YellowGreen : Color.Lerp(Color.Gray, Color.YellowGreen, 0.4f); color = Editor.ContainsFocus ? style.SelectionBorder : Color.Lerp(style.ForegroundDisabled, style.SelectionBorder, 0.4f);
if (IsMouseOver) if (IsMouseOver)
color *= 1.1f; color *= 1.1f;
Render2D.FillRectangle(rect, color); Render2D.FillRectangle(rect, color);
@@ -244,14 +245,19 @@ namespace FlaxEditor.GUI
set => Editor.SetKeyframeTangentInternal(Index, IsIn, Component, value); set => Editor.SetKeyframeTangentInternal(Index, IsIn, Component, value);
} }
internal float TangentOffset => 50.0f / Editor.ViewScale.X;
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
var style = Style.Current;
var thickness = 6.0f / Mathf.Max(Editor.ViewScale.X, 1.0f);
var size = Size;
var pointPos = PointFromParent(Point.Center); var pointPos = PointFromParent(Point.Center);
Render2D.DrawLine(Size * 0.5f, pointPos, Color.Gray); Render2D.DrawLine(size * 0.5f, pointPos, style.ForegroundDisabled, thickness);
var rect = new Rectangle(Float2.Zero, Size); var rect = new Rectangle(Float2.Zero, size);
var color = Color.MediumVioletRed; var color = style.BorderSelected;
if (IsMouseOver) if (IsMouseOver)
color *= 1.1f; color *= 1.1f;
Render2D.FillRectangle(rect, color); Render2D.FillRectangle(rect, color);
@@ -289,7 +295,7 @@ namespace FlaxEditor.GUI
/// <summary> /// <summary>
/// The curve time/value axes tick steps. /// The curve time/value axes tick steps.
/// </summary> /// </summary>
protected float[] TickSteps = Utilities.Utils.CurveTickSteps; protected double[] TickSteps = Utilities.Utils.CurveTickSteps;
/// <summary> /// <summary>
/// The curve contents area. /// The curve contents area.
@@ -442,7 +448,7 @@ namespace FlaxEditor.GUI
_mainPanel = new Panel(ScrollBars.Both) _mainPanel = new Panel(ScrollBars.Both)
{ {
ScrollMargin = new Margin(150.0f), ScrollMargin = new Margin(150.0f),
AlwaysShowScrollbars = true, AlwaysShowScrollbars = false,
AnchorPreset = AnchorPresets.StretchAll, AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero, Offsets = Margin.Zero,
Parent = this Parent = this
@@ -668,26 +674,82 @@ namespace FlaxEditor.GUI
OnEditingEnd(); OnEditingEnd();
} }
private void ShowCurve(bool selectedOnly)
{
if (_points.Count == 0)
return;
int pass = 1;
REDO:
// Get curve bounds in Keyframes (time and value)
Float2 posMin = Float2.Maximum, posMax = Float2.Minimum;
// TODO: include bezier curve bounds calculation to handle curve outside the bounds made out of points
foreach (var point in _points)
{
if (selectedOnly && !point.IsSelected)
continue;
var pos = point.Point;
Float2.Min(ref posMin, ref pos, out posMin);
Float2.Max(ref posMax, ref pos, out posMax);
}
// Apply margin around the area
var posMargin = (posMax - posMin) * 0.05f;
posMin -= posMargin;
posMax += posMargin;
// Convert from Keyframes to Contents
_mainPanel.GetDesireClientArea(out var viewRect);
PointFromKeyframesToContents(ref posMin, ref viewRect);
PointFromKeyframesToContents(ref posMax, ref viewRect);
var tmp = posMin;
Float2.Min(ref posMin, ref posMax, out posMin);
Float2.Max(ref posMax, ref tmp, out posMax);
var contentsSize = posMax - posMin;
// Convert from Contents to Main Panel
posMin = _contents.PointToParent(posMin);
posMax = _contents.PointToParent(posMax);
tmp = posMin;
Float2.Min(ref posMin, ref posMax, out posMin);
Float2.Max(ref posMax, ref tmp, out posMax);
// Update zoom (leave unchanged when focusing a single point)
var zoomMask = EnableZoom;
if (Mathf.IsZero(posMargin.X))
zoomMask &= ~UseMode.Horizontal;
if (Mathf.IsZero(posMargin.Y))
zoomMask &= ~UseMode.Vertical;
ViewScale = ApplyUseModeMask(zoomMask, viewRect.Size / contentsSize, ViewScale);
// Update scroll (attempt to center the area when it's smaller than the view)
Float2 viewOffset = -posMin;
Float2 viewSize = _mainPanel.Size;
Float2 viewSizeLeft = viewSize - Float2.Clamp(posMax - posMin, Float2.Zero, viewSize);
viewOffset += viewSizeLeft * 0.5f;
viewOffset = ApplyUseModeMask(EnablePanning, viewOffset, _mainPanel.ViewOffset);
_mainPanel.ViewOffset = viewOffset;
// Do it multiple times so the view offset can be properly calculate once the view scale gets changes
if (pass++ <= 2)
goto REDO;
UpdateKeyframes();
}
/// <summary>
/// Focuses the view on the selected keyframes.
/// </summary>
public void FocusSelection()
{
// Fallback to showing whole curve if nothing is selected
ShowCurve(SelectionCount != 0);
}
/// <inheritdoc /> /// <inheritdoc />
public override void ShowWholeCurve() public override void ShowWholeCurve()
{ {
_mainPanel.GetDesireClientArea(out var mainPanelArea); ShowCurve(false);
ViewScale = ApplyUseModeMask(EnableZoom, mainPanelArea.Size / _contents.Size, ViewScale);
Float2 minPos = Float2.Maximum;
foreach (var point in _points)
{
var pos = point.PointToParent(point.Location);
Float2.Min(ref minPos, ref pos, out minPos);
}
var minPosPoint = _contents.PointToParent(ref minPos);
var scroll = new Float2(_mainPanel.HScrollBar?.TargetValue ?? 0, _mainPanel.VScrollBar?.TargetValue ?? 0);
scroll = ApplyUseModeMask(EnablePanning, minPosPoint, scroll);
if (_mainPanel.HScrollBar != null)
_mainPanel.HScrollBar.TargetValue = scroll.X;
if (_mainPanel.VScrollBar != null)
_mainPanel.VScrollBar.TargetValue = scroll.Y;
UpdateKeyframes();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -766,10 +828,7 @@ namespace FlaxEditor.GUI
point = _contents.PointFromParent(point); point = _contents.PointFromParent(point);
// Contents -> Keyframes // Contents -> Keyframes
return new Float2( return PointFromContentsToKeyframes(ref point, ref curveContentAreaBounds);
(point.X + _contents.Location.X) / UnitsPerSecond,
(point.Y + _contents.Location.Y - curveContentAreaBounds.Height) / -UnitsPerSecond
);
} }
/// <summary> /// <summary>
@@ -781,10 +840,7 @@ namespace FlaxEditor.GUI
protected Float2 PointFromKeyframes(Float2 point, ref Rectangle curveContentAreaBounds) protected Float2 PointFromKeyframes(Float2 point, ref Rectangle curveContentAreaBounds)
{ {
// Keyframes -> Contents // Keyframes -> Contents
point = new Float2( PointFromKeyframesToContents(ref point, ref curveContentAreaBounds);
point.X * UnitsPerSecond - _contents.Location.X,
point.Y * -UnitsPerSecond + curveContentAreaBounds.Height - _contents.Location.Y
);
// Contents -> Main Panel // Contents -> Main Panel
point = _contents.PointToParent(point); point = _contents.PointToParent(point);
@@ -793,11 +849,27 @@ namespace FlaxEditor.GUI
return _mainPanel.PointToParent(point); return _mainPanel.PointToParent(point);
} }
internal Float2 PointFromContentsToKeyframes(ref Float2 point, ref Rectangle curveContentAreaBounds)
{
return new Float2(
(point.X + _contents.Location.X) / UnitsPerSecond,
(point.Y + _contents.Location.Y - curveContentAreaBounds.Height) / -UnitsPerSecond
);
}
internal void PointFromKeyframesToContents(ref Float2 point, ref Rectangle curveContentAreaBounds)
{
point = new Float2(
point.X * UnitsPerSecond - _contents.Location.X,
point.Y * -UnitsPerSecond + curveContentAreaBounds.Height - _contents.Location.Y
);
}
private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange) private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
{ {
Utilities.Utils.DrawCurveTicks((float tick, float strength) => Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
{ {
var p = PointFromKeyframes(axis * tick, ref viewRect); var p = PointFromKeyframes(axis * (float)tick, ref viewRect);
// Draw line // Draw line
var lineRect = new Rectangle var lineRect = new Rectangle
@@ -820,6 +892,24 @@ namespace FlaxEditor.GUI
}, TickSteps, ref _tickStrengths, min, max, pixelRange); }, TickSteps, ref _tickStrengths, min, max, pixelRange);
} }
private void SetupGrid(out Float2 min, out Float2 max, out Float2 pixelRange)
{
var viewRect = _mainPanel.GetClientArea();
var upperLeft = PointToKeyframes(viewRect.Location, ref viewRect);
var bottomRight = PointToKeyframes(viewRect.Size, ref viewRect);
min = Float2.Min(upperLeft, bottomRight);
max = Float2.Max(upperLeft, bottomRight);
pixelRange = (max - min) * ViewScale * UnitsPerSecond;
}
private Float2 GetGridSnap()
{
SetupGrid(out var min, out var max, out var pixelRange);
return new Float2(Utilities.Utils.GetCurveGridSnap(TickSteps, ref _tickStrengths, min.X, max.X, pixelRange.X),
Utilities.Utils.GetCurveGridSnap(TickSteps, ref _tickStrengths, min.Y, max.Y, pixelRange.Y));
}
/// <summary> /// <summary>
/// Draws the curve. /// Draws the curve.
/// </summary> /// </summary>
@@ -849,12 +939,7 @@ namespace FlaxEditor.GUI
// Draw time and values axes // Draw time and values axes
if (ShowAxes != UseMode.Off) if (ShowAxes != UseMode.Off)
{ {
var upperLeft = PointToKeyframes(viewRect.Location, ref viewRect); SetupGrid(out var min, out var max, out var pixelRange);
var bottomRight = PointToKeyframes(viewRect.Size, ref viewRect);
var min = Float2.Min(upperLeft, bottomRight);
var max = Float2.Max(upperLeft, bottomRight);
var pixelRange = (max - min) * ViewScale * UnitsPerSecond;
Render2D.PushClip(ref viewRect); Render2D.PushClip(ref viewRect);
@@ -939,7 +1024,7 @@ namespace FlaxEditor.GUI
} }
else if (options.FocusSelection.Process(this)) else if (options.FocusSelection.Process(this))
{ {
ShowWholeCurve(); FocusSelection();
return true; return true;
} }
@@ -2200,7 +2285,7 @@ namespace FlaxEditor.GUI
var tangent = t.TangentValue; var tangent = t.TangentValue;
var direction = t.IsIn ? -1.0f : 1.0f; var direction = t.IsIn ? -1.0f : 1.0f;
var offset = 30.0f; var offset = t.TangentOffset;
var location = GetKeyframePoint(ref k, selectedComponent); var location = GetKeyframePoint(ref k, selectedComponent);
t.Size = KeyframesSize / ViewScale; t.Size = KeyframesSize / ViewScale;
t.Location = new Float2 t.Location = new Float2
@@ -2227,6 +2312,18 @@ namespace FlaxEditor.GUI
} }
} }
/// <inheritdoc />
protected override void SetScaleInternal(ref Float2 scale)
{
base.SetScaleInternal(ref scale);
if (!_showCollapsed)
{
// Refresh keyframes when zooming (their size depends on the scale)
UpdateKeyframes();
}
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnShowContextMenu(ContextMenu.ContextMenu cm, int selectionCount) protected override void OnShowContextMenu(ContextMenu.ContextMenu cm, int selectionCount)
{ {

View File

@@ -1,6 +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 FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -55,18 +57,26 @@ namespace FlaxEditor.GUI
private IsValidDelegate _isValid; private IsValidDelegate _isValid;
private Action<Actor> _selected; private Action<Actor> _selected;
private ActorSearchPopup(IsValidDelegate isValid, Action<Actor> selected) private ActorSearchPopup(IsValidDelegate isValid, Action<Actor> selected, CustomEditors.IPresenterOwner context)
{ {
_isValid = isValid; _isValid = isValid;
_selected = selected; _selected = selected;
ItemClicked += OnItemClicked; ItemClicked += OnItemClicked;
// TODO: use async thread to search scenes if (context is PropertiesWindow propertiesWindow || context == null)
for (int i = 0; i < Level.ScenesCount; i++)
{ {
Find(Level.GetScene(i)); // TODO: use async thread to search scenes
for (int i = 0; i < Level.ScenesCount; i++)
{
Find(Level.GetScene(i));
}
} }
else if (context is PrefabWindow prefabWindow)
{
Find(prefabWindow.Graph.MainActor);
}
SortItems(); SortItems();
} }
@@ -98,10 +108,11 @@ namespace FlaxEditor.GUI
/// <param name="showTargetLocation">The show target location.</param> /// <param name="showTargetLocation">The show target location.</param>
/// <param name="isValid">Event called to check if a given actor item is valid to be used.</param> /// <param name="isValid">Event called to check if a given actor item is valid to be used.</param>
/// <param name="selected">Event called on actor item pick.</param> /// <param name="selected">Event called on actor item pick.</param>
/// <param name="context">The presenter owner context (i.e. PrefabWindow, PropertiesWindow).</param>
/// <returns>The dialog.</returns> /// <returns>The dialog.</returns>
public static ActorSearchPopup Show(Control showTarget, Float2 showTargetLocation, IsValidDelegate isValid, Action<Actor> selected) public static ActorSearchPopup Show(Control showTarget, Float2 showTargetLocation, IsValidDelegate isValid, Action<Actor> selected, CustomEditors.IPresenterOwner context)
{ {
var popup = new ActorSearchPopup(isValid, selected); var popup = new ActorSearchPopup(isValid, selected, context);
popup.Show(showTarget, showTargetLocation); popup.Show(showTarget, showTargetLocation);
return popup; return popup;
} }

View File

@@ -1,6 +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 FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
@@ -66,18 +68,26 @@ namespace FlaxEditor.GUI
private IsValidDelegate _isValid; private IsValidDelegate _isValid;
private Action<Script> _selected; private Action<Script> _selected;
private ScriptSearchPopup(IsValidDelegate isValid, Action<Script> selected) private ScriptSearchPopup(IsValidDelegate isValid, Action<Script> selected, CustomEditors.IPresenterOwner context)
{ {
_isValid = isValid; _isValid = isValid;
_selected = selected; _selected = selected;
ItemClicked += OnItemClicked; ItemClicked += OnItemClicked;
// TODO: use async thread to search scenes if (context is PropertiesWindow propertiesWindow || context == null)
for (int i = 0; i < Level.ScenesCount; i++)
{ {
Find(Level.GetScene(i)); // TODO: use async thread to search scenes
for (int i = 0; i < Level.ScenesCount; i++)
{
Find(Level.GetScene(i));
}
} }
else if (context is PrefabWindow prefabWindow)
{
Find(prefabWindow.Graph.MainActor);
}
SortItems(); SortItems();
} }
@@ -113,10 +123,11 @@ namespace FlaxEditor.GUI
/// <param name="showTargetLocation">The show target location.</param> /// <param name="showTargetLocation">The show target location.</param>
/// <param name="isValid">Event called to check if a given script item is valid to be used.</param> /// <param name="isValid">Event called to check if a given script item is valid to be used.</param>
/// <param name="selected">Event called on script item pick.</param> /// <param name="selected">Event called on script item pick.</param>
/// <param name="context">The presenter owner context (i.e. PrefabWindow, PropertiesWindow).</param>
/// <returns>The dialog.</returns> /// <returns>The dialog.</returns>
public static ScriptSearchPopup Show(Control showTarget, Float2 showTargetLocation, IsValidDelegate isValid, Action<Script> selected) public static ScriptSearchPopup Show(Control showTarget, Float2 showTargetLocation, IsValidDelegate isValid, Action<Script> selected, CustomEditors.IPresenterOwner context)
{ {
var popup = new ScriptSearchPopup(isValid, selected); var popup = new ScriptSearchPopup(isValid, selected, context);
popup.Show(showTarget, showTargetLocation); popup.Show(showTarget, showTargetLocation);
return popup; return popup;
} }

View File

@@ -1,10 +1,13 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.ComponentModel;
using System.Linq; using System.Linq;
using FlaxEditor.History;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using CategoryAttribute = FlaxEngine.CategoryAttribute;
namespace FlaxEditor.GUI namespace FlaxEditor.GUI
{ {
@@ -101,11 +104,26 @@ namespace FlaxEditor.GUI
if (_isValid(type)) if (_isValid(type))
{ {
var attributes = type.GetAttributes(true); var attributes = type.GetAttributes(true);
if (attributes.FirstOrDefault(x => x is HideInEditorAttribute || x is System.Runtime.CompilerServices.CompilerGeneratedAttribute) == null) if (IsHideAttributes(attributes))
{ {
var mType = type.Type; var mType = type.Type;
if (mType != null && mType.IsValueType && mType.ReflectedType != null && string.Equals(mType.ReflectedType.Name, "<PrivateImplementationDetails>", StringComparison.Ordinal)) if (mType != null)
continue; {
// Skip if type is compiler-generated
if (mType.IsValueType && mType.ReflectedType != null && string.Equals(mType.ReflectedType.Name, "<PrivateImplementationDetails>", StringComparison.Ordinal))
continue;
// Skip if outer type is hidden
if (mType.DeclaringType != null && IsHideAttributes(mType.DeclaringType.GetCustomAttributes(true)))
continue;
// Blacklist some types
if (typeof(TypeConverter).IsAssignableFrom(mType) ||
typeof(IHistoryAction).IsAssignableFrom(mType) ||
(mType.Namespace != null && mType.Namespace.StartsWith("Newtonsoft.Json")))
continue;
}
AddItem(new TypeItemView(type, attributes)); AddItem(new TypeItemView(type, attributes));
} }
} }
@@ -113,6 +131,17 @@ namespace FlaxEditor.GUI
SortItems(); SortItems();
} }
private bool IsHideAttributes(object[] attributes)
{
return attributes.FirstOrDefault(IsHideAttribute) == null;
}
private bool IsHideAttribute(object attr)
{
return attr is HideInEditorAttribute ||
attr is System.Runtime.CompilerServices.CompilerGeneratedAttribute;
}
private void OnItemClicked(Item item) private void OnItemClicked(Item item)
{ {
_selected(((TypeItemView)item).Type); _selected(((TypeItemView)item).Type);

View File

@@ -14,7 +14,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
class Background : ContainerControl class Background : ContainerControl
{ {
private readonly Timeline _timeline; private readonly Timeline _timeline;
private float[] _tickSteps; private double[] _tickSteps;
private float[] _tickStrengths; private float[] _tickStrengths;
private bool _isSelecting; private bool _isSelecting;
private Float2 _selectingStartPos = Float2.Minimum; private Float2 _selectingStartPos = Float2.Minimum;
@@ -176,9 +176,9 @@ namespace FlaxEditor.GUI.Timeline.GUI
// Draw vertical lines for time axis // Draw vertical lines for time axis
var pixelsInRange = _timeline.Zoom; var pixelsInRange = _timeline.Zoom;
var pixelRange = pixelsInRange * (max - min); var pixelRange = pixelsInRange * (max - min);
var tickRange = Utilities.Utils.DrawCurveTicks((float tick, float strength) => var tickRange = Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
{ {
var time = tick / _timeline.FramesPerSecond; var time = (float)tick / _timeline.FramesPerSecond;
var x = time * zoom + Timeline.StartOffset; var x = time * zoom + Timeline.StartOffset;
var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength); var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor); Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
@@ -233,20 +233,20 @@ namespace FlaxEditor.GUI.Timeline.GUI
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 2); int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 2);
var lStep = _tickSteps[l]; var lStep = _tickSteps[l];
var lNextStep = _tickSteps[l + 1]; var lNextStep = _tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep); var startTick = Mathd.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep); var endTick = Mathd.CeilToInt(max / lStep);
Color lineColor = style.Foreground.RGBMultiplied(0.8f).AlphaMultiplied(strength); Color lineColor = style.Foreground.RGBMultiplied(0.8f).AlphaMultiplied(strength);
Color labelColor = style.ForegroundDisabled.AlphaMultiplied(strength); Color labelColor = style.ForegroundDisabled.AlphaMultiplied(strength);
for (int i = startTick; i <= endTick; i++) for (var i = startTick; i <= endTick; i++)
{ {
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) if (l < biggestTick && (i % Mathd.RoundToInt(lNextStep / lStep) == 0))
continue; continue;
var tick = i * lStep; var tick = (decimal)lStep * i;
var time = tick / _timeline.FramesPerSecond; var time = (double)tick / _timeline.FramesPerSecond;
var x = time * zoom + Timeline.StartOffset; var x = (float)time * zoom + Timeline.StartOffset;
// Header line // Header line
var lineRect = new Rectangle(x - 0.5f, -verticalLinesHeaderExtend * 0.6f + timeAxisHeaderOffset, 1.0f, verticalLinesHeaderExtend * 0.6f); var lineRect = new Rectangle((float)x - 0.5f, -verticalLinesHeaderExtend * 0.6f + timeAxisHeaderOffset, 1.0f, verticalLinesHeaderExtend * 0.6f);
Render2D.FillRectangle(lineRect, lineColor); Render2D.FillRectangle(lineRect, lineColor);
// Time label // Time label

View File

@@ -8,7 +8,6 @@ using System.Text;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options; using FlaxEditor.Options;
using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;

View File

@@ -264,7 +264,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnClickedSelect() private void OnClickedSelect()
{ {
ActorSearchPopup.Show(this, PointFromScreen(FlaxEngine.Input.MouseScreenPosition), IsActorValid, SetActor); ActorSearchPopup.Show(this, PointFromScreen(FlaxEngine.Input.MouseScreenPosition), IsActorValid, SetActor, null);
} }
private void OnClickedSelectActor(Image image, MouseButton button) private void OnClickedSelectActor(Image image, MouseButton button)

View File

@@ -19,12 +19,11 @@ namespace FlaxEditor.Gizmo
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
private struct Data private struct Data
{ {
public Matrix WorldMatrix;
public Matrix ViewProjectionMatrix; public Matrix ViewProjectionMatrix;
public Float4 GridColor; public Float4 GridColor;
public Float3 ViewPos; public Float3 ViewPos;
public float Far; public float Far;
public Float3 Padding; public Float3 ViewOrigin;
public float GridSize; public float GridSize;
} }
@@ -62,7 +61,6 @@ namespace FlaxEditor.Gizmo
Profiler.BeginEventGPU("Editor Grid"); Profiler.BeginEventGPU("Editor Grid");
var options = Editor.Instance.Options.Options; var options = Editor.Instance.Options.Options;
Float3 camPos = renderContext.View.WorldPosition;
float gridSize = renderContext.View.Far + 20000; float gridSize = renderContext.View.Far + 20000;
// Lazy-init resources // Lazy-init resources
@@ -97,10 +95,10 @@ namespace FlaxEditor.Gizmo
float y = 1.5f; // Add small bias to reduce Z-fighting with geometry at scene origin float y = 1.5f; // Add small bias to reduce Z-fighting with geometry at scene origin
var vertices = new Float3[] var vertices = new Float3[]
{ {
new Float3(-gridSize + camPos.X, y, -gridSize + camPos.Z), new Float3(-gridSize, y, -gridSize),
new Float3(gridSize + camPos.X, y, gridSize + camPos.Z), new Float3(gridSize, y, gridSize),
new Float3(-gridSize + camPos.X, y, gridSize + camPos.Z), new Float3(-gridSize, y, gridSize),
new Float3(gridSize + camPos.X, y, -gridSize + camPos.Z), new Float3(gridSize, y, -gridSize),
}; };
fixed (Float3* ptr = vertices) fixed (Float3* ptr = vertices)
{ {
@@ -113,12 +111,12 @@ namespace FlaxEditor.Gizmo
{ {
var data = new Data(); var data = new Data();
Matrix.Multiply(ref renderContext.View.View, ref renderContext.View.Projection, out var viewProjection); Matrix.Multiply(ref renderContext.View.View, ref renderContext.View.Projection, out var viewProjection);
data.WorldMatrix = Matrix.Identity;
Matrix.Transpose(ref viewProjection, out data.ViewProjectionMatrix); Matrix.Transpose(ref viewProjection, out data.ViewProjectionMatrix);
data.ViewPos = renderContext.View.WorldPosition; data.ViewPos = renderContext.View.WorldPosition;
data.GridColor = options.Viewport.ViewportGridColor; data.GridColor = options.Viewport.ViewportGridColor;
data.Far = renderContext.View.Far; data.Far = renderContext.View.Far;
data.GridSize = options.Viewport.ViewportGridViewDistance; data.GridSize = options.Viewport.ViewportGridViewDistance;
data.ViewOrigin = renderContext.View.Origin;
context.UpdateCB(cb, new IntPtr(&data)); context.UpdateCB(cb, new IntPtr(&data));
} }

View File

@@ -169,12 +169,12 @@ namespace FlaxEditor.Gizmo
closestIntersection = intersection; closestIntersection = intersection;
} }
/*// Center // Center
if (CenterBoxRaw.Intersects(ref localRay, out intersection) && intersection < closestIntersection) if (CenterBoxRaw.Intersects(ref localRay, out intersection) && intersection > closestIntersection)
{ {
_activeAxis = Axis.Center; _activeAxis = Axis.Center;
closestIntersection = intersection; closestIntersection = intersection;
}*/ }
break; break;
} }

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor.Gizmo
/// <summary> /// <summary>
/// Offset to move axis away from center /// Offset to move axis away from center
/// </summary> /// </summary>
private const float AxisOffset = 0.8f; private const float AxisOffset = 1.2f;
/// <summary> /// <summary>
/// How thick the axis should be /// How thick the axis should be

View File

@@ -501,7 +501,7 @@ namespace FlaxEditor.Gizmo
_scaleDelta = Vector3.Zero; _scaleDelta = Vector3.Zero;
if (ActiveAxis == Axis.Center) if (ActiveAxis == Axis.Center)
scaleDelta = new Vector3(scaleDelta.AvgValue); scaleDelta = new Vector3(scaleDelta.ValuesSum);
} }
// Apply transformation (but to the parents, not whole selection pool) // Apply transformation (but to the parents, not whole selection pool)

View File

@@ -1,5 +1,6 @@
// 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 System.Linq; using System.Linq;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
@@ -149,7 +150,8 @@ namespace FlaxEditor
private float _mouseMoveSum; private float _mouseMoveSum;
private UndoMultiBlock _undoBlock; private UndoMultiBlock _undoBlock;
private View _view; private View _view;
private float[] _gridTickSteps = Utilities.Utils.CurveTickSteps, _gridTickStrengths; private double[] _gridTickSteps = Utilities.Utils.CurveTickSteps;
private float[] _gridTickStrengths;
private List<Widget> _widgets; private List<Widget> _widgets;
private Widget _activeWidget; private Widget _activeWidget;
@@ -563,9 +565,9 @@ namespace FlaxEditor
var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f); var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f);
var labelsColor = style.ForegroundDisabled; var labelsColor = style.ForegroundDisabled;
var labelsSize = 10.0f; var labelsSize = 10.0f;
Utilities.Utils.DrawCurveTicks((float tick, float strength) => Utilities.Utils.DrawCurveTicks((decimal tick, double step, float strength) =>
{ {
var p = _view.PointToParent(axis * tick); var p = _view.PointToParent(axis * (float)tick);
// Draw line // Draw line
var lineRect = new Rectangle var lineRect = new Rectangle
@@ -640,14 +642,103 @@ namespace FlaxEditor
DrawControlWidget(uiControl, ref eu, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, -1), CursorType.SizeNS); DrawControlWidget(uiControl, ref eu, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, -1), CursorType.SizeNS);
DrawControlWidget(uiControl, ref eb, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, 1), CursorType.SizeNS); DrawControlWidget(uiControl, ref eb, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, 1), CursorType.SizeNS);
// TODO: draw anchors // Draw pivot
var pivotSize = 8.0f;
if (viewScale < 0.7f)
pivotSize *= viewScale;
var pivotX = Mathf.Remap(control.Pivot.X, 0, 1, bounds.Location.X, bounds.Location.X + bounds.Width);
var pivotY = Mathf.Remap(control.Pivot.Y, 0, 1, bounds.Location.Y, bounds.Location.Y + bounds.Height);
var pivotLoc = control.PointToParent(this, new Float2(pivotX, pivotY));
var pivotRect = new Rectangle(pivotLoc - pivotSize * 0.5f, new Float2(pivotSize));
var pivotColor = options.UIPivotColor;
Render2D.FillRectangle(pivotRect, pivotColor);
// Draw anchors
var controlParent = control.Parent;
if (controlParent != null)
{
var parentBounds = controlParent.EditorBounds;
var anchorMin = control.AnchorMin;
var anchorMax = control.AnchorMax;
var newMinX = Mathf.Remap(anchorMin.X, 0, 1, parentBounds.UpperLeft.X, parentBounds.UpperRight.X);
var newMinY = Mathf.Remap(anchorMin.Y, 0, 1, parentBounds.UpperLeft.Y, parentBounds.LowerLeft.Y);
var newMaxX = Mathf.Remap(anchorMax.X, 0, 1, parentBounds.UpperLeft.X, parentBounds.UpperRight.X);
var newMaxY = Mathf.Remap(anchorMax.Y, 0, 1, parentBounds.UpperLeft.Y, parentBounds.LowerLeft.Y);
var anchorUpperLeft = controlParent.PointToParent(this, new Float2(newMinX, newMinY));
var anchorUpperRight = controlParent.PointToParent(this, new Float2(newMaxX, newMinY));
var anchorLowerLeft = controlParent.PointToParent(this, new Float2(newMinX, newMaxY));
var anchorLowerRight = controlParent.PointToParent(this, new Float2(newMaxX, newMaxY));
var anchorRectSize = 8.0f;
if (viewScale < 0.7f)
anchorRectSize *= viewScale;
// Make anchor rects and rotate if parent is rotated.
var parentRotation = controlParent.Rotation * Mathf.DegreesToRadians;
var rect1Axis = new Float2(-1, -1);
var rect1 = new Rectangle(anchorUpperLeft +
new Float2(anchorRectSize * rect1Axis.X * Mathf.Cos(parentRotation) - anchorRectSize * rect1Axis.Y * Mathf.Sin(parentRotation),
anchorRectSize * rect1Axis.Y * Mathf.Cos(parentRotation) + anchorRectSize * rect1Axis.X * Mathf.Sin(parentRotation)) - anchorRectSize * 0.5f, new Float2(anchorRectSize));
var rect2Axis = new Float2(1, -1);
var rect2 = new Rectangle(anchorUpperRight +
new Float2(anchorRectSize * rect2Axis.X * Mathf.Cos(parentRotation) - anchorRectSize * rect2Axis.Y * Mathf.Sin(parentRotation),
anchorRectSize * rect2Axis.Y * Mathf.Cos(parentRotation) + anchorRectSize * rect2Axis.X * Mathf.Sin(parentRotation)) - anchorRectSize * 0.5f, new Float2(anchorRectSize));
var rect3Axis = new Float2(-1, 1);
var rect3 = new Rectangle(anchorLowerLeft +
new Float2(anchorRectSize * rect3Axis.X * Mathf.Cos(parentRotation) - anchorRectSize * rect3Axis.Y * Mathf.Sin(parentRotation),
anchorRectSize * rect3Axis.Y * Mathf.Cos(parentRotation) + anchorRectSize * rect3Axis.X * Mathf.Sin(parentRotation)) - anchorRectSize * 0.5f, new Float2(anchorRectSize));
var rect4Axis = new Float2(1, 1);
var rect4 = new Rectangle(anchorLowerRight +
new Float2(anchorRectSize * rect4Axis.X * Mathf.Cos(parentRotation) - anchorRectSize * rect4Axis.Y * Mathf.Sin(parentRotation),
anchorRectSize * rect4Axis.Y * Mathf.Cos(parentRotation) + anchorRectSize * rect4Axis.X * Mathf.Sin(parentRotation)) - anchorRectSize * 0.5f, new Float2(anchorRectSize));
var rectColor = options.UIAnchorColor;
Render2D.DrawLine(anchorUpperLeft, anchorUpperRight, rectColor);
Render2D.DrawLine(anchorUpperRight, anchorLowerRight, rectColor);
Render2D.DrawLine(anchorLowerRight, anchorLowerLeft, rectColor);
Render2D.DrawLine(anchorLowerLeft, anchorUpperLeft, rectColor);
Render2D.FillRectangle(rect1, rectColor);
Render2D.FillRectangle(rect2, rectColor);
Render2D.FillRectangle(rect3, rectColor);
Render2D.FillRectangle(rect4, rectColor);
}
} }
} }
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, float scale, Float2 resizeAxis, CursorType cursor) private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, float scale, Float2 resizeAxis, CursorType cursor)
{ {
var style = Style.Current; var style = Style.Current;
var rect = new Rectangle((pos + resizeAxis * 10 * scale) - size * 0.5f, size); var control = uiControl.Control;
var rotation = control.Rotation;
var rotationInRadians = rotation * Mathf.DegreesToRadians;
var rect = new Rectangle((pos +
new Float2(resizeAxis.X * Mathf.Cos(rotationInRadians) - resizeAxis.Y * Mathf.Sin(rotationInRadians),
resizeAxis.Y * Mathf.Cos(rotationInRadians) + resizeAxis.X * Mathf.Sin(rotationInRadians)) * 10 * scale) - size * 0.5f,
size);
// Find more correct cursor at different angles
var unwindRotation = Mathf.UnwindDegrees(rotation);
if (unwindRotation is (>= 45 and < 135) or (> -135 and <= -45) )
{
switch (cursor)
{
case CursorType.SizeNESW:
cursor = CursorType.SizeNWSE;
break;
case CursorType.SizeNS:
cursor = CursorType.SizeWE;
break;
case CursorType.SizeNWSE:
cursor = CursorType.SizeNESW;
break;
case CursorType.SizeWE:
cursor = CursorType.SizeNS;
break;
default: break;
}
}
if (rect.Contains(ref mousePos)) if (rect.Contains(ref mousePos))
{ {
Render2D.FillRectangle(rect, style.Foreground); Render2D.FillRectangle(rect, style.Foreground);
@@ -684,16 +775,15 @@ namespace FlaxEditor
private bool RayCastControl(ref Float2 location, out Control hit) private bool RayCastControl(ref Float2 location, out Control hit)
{ {
#if false // First, raycast only controls with content (eg. skips transparent panels)
// Raycast only controls with content (eg. skips transparent panels) RayCastChildren(ref location, out hit);
return RayCastChildren(ref location, out hit);
#else // If raycast failed, then find any control under mouse (hierarchical)
// Find any control under mouse (hierarchical) hit = hit ?? GetChildAtRecursive(location);
hit = GetChildAtRecursive(location);
if (hit is View || hit is CanvasContainer) if (hit is View || hit is CanvasContainer)
hit = null; hit = null;
return hit != null; return hit != null;
#endif
} }
private UIControlNode FindUIControlNode(Control control) private UIControlNode FindUIControlNode(Control control)

View File

@@ -501,7 +501,8 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* ta
return false; return false;
const ISerializable::DeserializeStream& newRootData = **newRootDataPtr; const ISerializable::DeserializeStream& newRootData = **newRootDataPtr;
Guid prefabId, prefabObjectID; Guid prefabId, prefabObjectID;
if (JsonTools::GetGuidIfValid(prefabId, newRootData, "PrefabID") && JsonTools::GetGuidIfValid(prefabObjectID, newRootData, "PrefabObjectID")) if (JsonTools::GetGuidIfValid(prefabId, newRootData, "PrefabID") &&
JsonTools::GetGuidIfValid(prefabObjectID, newRootData, "PrefabObjectID"))
{ {
const auto nestedPrefab = Content::Load<Prefab>(prefabId); const auto nestedPrefab = Content::Load<Prefab>(prefabId);
if (nestedPrefab && nestedPrefab->GetRootObjectId() != prefabObjectID) if (nestedPrefab && nestedPrefab->GetRootObjectId() != prefabObjectID)
@@ -511,21 +512,6 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* ta
return true; return true;
} }
DEFINE_INTERNAL_CALL(void) EditorInternal_GetPrefabNestedObject(Guid* prefabId, Guid* prefabObjectId, Guid* outPrefabId, Guid* outPrefabObjectId)
{
*outPrefabId = Guid::Empty;
*outPrefabObjectId = Guid::Empty;
const auto prefab = Content::Load<Prefab>(*prefabId);
if (!prefab)
return;
const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(*prefabObjectId);
if (!prefabObjectDataPtr)
return;
const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr;
JsonTools::GetGuidIfValid(*outPrefabId, prefabObjectData, "PrefabID");
JsonTools::GetGuidIfValid(*outPrefabObjectId, prefabObjectData, "PrefabObjectID");
}
DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel) DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel)
{ {
return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f; return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f;

View File

@@ -12,11 +12,14 @@
#include "Engine/Scripting/ManagedCLR/MException.h" #include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h" #include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Content/Assets/VisualScript.h" #include "Engine/Content/Assets/VisualScript.h"
#include "Engine/Content/Content.h"
#include "Engine/CSG/CSGBuilder.h" #include "Engine/CSG/CSGBuilder.h"
#include "Engine/Engine/CommandLine.h" #include "Engine/Engine/CommandLine.h"
#include "Engine/Renderer/ProbesRenderer.h" #include "Engine/Renderer/ProbesRenderer.h"
#include "Engine/Animations/Graph/AnimGraph.h" #include "Engine/Animations/Graph/AnimGraph.h"
#include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Serialization/JsonTools.h"
ManagedEditor::InternalOptions ManagedEditor::ManagedEditorOptions; ManagedEditor::InternalOptions ManagedEditor::ManagedEditorOptions;
@@ -592,6 +595,7 @@ bool ManagedEditor::EvaluateVisualScriptLocal(VisualScript* script, VisualScript
void ManagedEditor::WipeOutLeftoverSceneObjects() void ManagedEditor::WipeOutLeftoverSceneObjects()
{ {
PROFILE_CPU();
Array<ScriptingObject*> objects = Scripting::GetObjects(); Array<ScriptingObject*> objects = Scripting::GetObjects();
bool removedAny = false; bool removedAny = false;
for (ScriptingObject* object : objects) for (ScriptingObject* object : objects)
@@ -613,6 +617,21 @@ void ManagedEditor::WipeOutLeftoverSceneObjects()
ObjectsRemovalService::Flush(); ObjectsRemovalService::Flush();
} }
void ManagedEditor::GetPrefabNestedObject(const Guid& prefabId, const Guid& prefabObjectId, Guid& outPrefabId, Guid& outPrefabObjectId)
{
outPrefabId = Guid::Empty;
outPrefabObjectId = Guid::Empty;
const auto prefab = Content::Load<Prefab>(prefabId);
if (!prefab)
return;
const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(prefabObjectId);
if (!prefabObjectDataPtr)
return;
const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr;
JsonTools::GetGuidIfValid(outPrefabId, prefabObjectData, "PrefabID");
JsonTools::GetGuidIfValid(outPrefabObjectId, prefabObjectData, "PrefabObjectID");
}
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
{ {
ASSERT(!HasManagedInstance()); ASSERT(!HasManagedInstance());

View File

@@ -259,6 +259,7 @@ public:
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals(); API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local); API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects(); API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects();
API_FUNCTION(Internal) static void GetPrefabNestedObject(API_PARAM(Ref) const Guid& prefabId, API_PARAM(Ref) const Guid& prefabObjectId, API_PARAM(Out) Guid& outPrefabId, API_PARAM(Out) Guid& outPrefabObjectId);
private: private:
void OnEditorAssemblyLoaded(MAssembly* assembly); void OnEditorAssemblyLoaded(MAssembly* assembly);

View File

@@ -1140,6 +1140,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new CSharpEmptyStructProxy()); Proxy.Add(new CSharpEmptyStructProxy());
Proxy.Add(new CSharpEmptyInterfaceProxy()); Proxy.Add(new CSharpEmptyInterfaceProxy());
Proxy.Add(new CSharpActorProxy()); Proxy.Add(new CSharpActorProxy());
Proxy.Add(new CSharpGamePluginProxy());
Proxy.Add(new CppAssetProxy()); Proxy.Add(new CppAssetProxy());
Proxy.Add(new CppStaticClassProxy()); Proxy.Add(new CppStaticClassProxy());
Proxy.Add(new CppScriptProxy()); Proxy.Add(new CppScriptProxy());

View File

@@ -1,7 +1,9 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Text;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;
@@ -31,11 +33,33 @@ namespace FlaxEditor.Options
private void OnResetButtonClicked() private void OnResetButtonClicked()
{ {
var obj = new T(); var editorClassName = typeof(T).Name;
var str = JsonSerializer.Serialize(obj);
JsonSerializer.Deserialize(Values[0], str); var editorName = new StringBuilder();
SetValue(Values[0]); editorName.Append(editorClassName[0]);
Refresh(); for (var i = 1; i < editorClassName.Length; i++)
{
// Whenever there is an uppercase letter, add a space to make it more pretty for the end user
if (char.IsUpper(editorClassName[i]))
{
editorName.Append(' ');
}
editorName.Append(editorClassName[i]);
}
var result = MessageBox.Show($"Are you sure you want to reset \"{editorName}\" to default values?",
"Reset values?",
MessageBoxButtons.YesNo
);
if (result == DialogResult.Yes || result == DialogResult.OK)
{
var obj = new T();
var str = JsonSerializer.Serialize(obj);
JsonSerializer.Deserialize(Values[0], str);
SetValue(Values[0]);
Refresh();
}
} }
} }
} }

View File

@@ -39,6 +39,20 @@ namespace FlaxEditor.Options
[EditorDisplay("UI Gizmo", "UI Control Outline Size"), EditorOrder(103), Tooltip("The size of the selection outline for UI controls.")] [EditorDisplay("UI Gizmo", "UI Control Outline Size"), EditorOrder(103), Tooltip("The size of the selection outline for UI controls.")]
public float UISelectionOutlineSize { get; set; } = 2.0f; public float UISelectionOutlineSize { get; set; } = 2.0f;
/// <summary>
/// Gets or sets the pivot color for the UI Gizmo.
/// </summary>
[DefaultValue(typeof(Color), "0.0,0.5725,0.8,0.5")]
[EditorDisplay("UI Gizmo", "Pivot Color"), EditorOrder(103), Tooltip("The color of the pivot for the UI Gizmo.")]
public Color UIPivotColor { get; set; } = new Color(0.0f, 0.5725f, 0.8f, 0.5f);
/// <summary>
/// Gets or sets the anchor color for the UI Gizmo.
/// </summary>
[DefaultValue(typeof(Color), "0.8392,0.8471,0.8706,0.5")]
[EditorDisplay("UI Gizmo", "Anchor Color"), EditorOrder(103), Tooltip("The color of the anchors for the UI Gizmo.")]
public Color UIAnchorColor { get; set; } = new Color(0.8392f, 0.8471f, 0.8706f, 0.5f);
/// <summary> /// <summary>
/// Gets or sets the transform gizmo size. /// Gets or sets the transform gizmo size.
/// </summary> /// </summary>

View File

@@ -215,10 +215,10 @@ namespace FlaxEditor.SceneGraph
public override bool CanDuplicate => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; public override bool CanDuplicate => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0;
/// <inheritdoc /> /// <inheritdoc />
public override bool IsActive => _actor.IsActive; public override bool IsActive => _actor?.IsActive ?? false;
/// <inheritdoc /> /// <inheritdoc />
public override bool IsActiveInHierarchy => _actor.IsActiveInHierarchy; public override bool IsActiveInHierarchy => _actor?.IsActiveInHierarchy ?? false;
/// <inheritdoc /> /// <inheritdoc />
public override int OrderInParent public override int OrderInParent

View File

@@ -596,11 +596,17 @@ namespace FlaxEditor.SceneGraph.GUI
{ {
bool worldPositionsStays = Root.GetKey(KeyboardKeys.Control) == false; bool worldPositionsStays = Root.GetKey(KeyboardKeys.Control) == false;
var objects = new SceneObject[_dragActors.Objects.Count]; var objects = new SceneObject[_dragActors.Objects.Count];
var treeNodes = new TreeNode[_dragActors.Objects.Count];
for (int i = 0; i < objects.Length; i++) for (int i = 0; i < objects.Length; i++)
{
objects[i] = _dragActors.Objects[i].Actor; objects[i] = _dragActors.Objects[i].Actor;
treeNodes[i] = _dragActors.Objects[i].TreeNode;
}
var action = new ParentActorsAction(objects, newParent, newOrder, worldPositionsStays); var action = new ParentActorsAction(objects, newParent, newOrder, worldPositionsStays);
ActorNode.Root.Undo?.AddAction(action); ActorNode.Root.Undo?.AddAction(action);
action.Do(); action.Do();
ParentTree.Focus();
ParentTree.Select(treeNodes.ToList());
result = DragDropEffect.Move; result = DragDropEffect.Move;
} }
// Drag scripts // Drag scripts

View File

@@ -1182,9 +1182,9 @@ namespace FlaxEditor.Surface.Archetypes
} }
/// <inheritdoc /> /// <inheritdoc />
public override void DrawEditorBackground(ref Rectangle rect) public override void DrawEditorGrid(ref Rectangle rect)
{ {
base.DrawEditorBackground(ref rect); base.DrawEditorGrid(ref rect);
// Draw triangulated multi blend space // Draw triangulated multi blend space
var style = Style.Current; var style = Style.Current;
@@ -1195,23 +1195,19 @@ namespace FlaxEditor.Surface.Archetypes
else else
Render2D.FillTriangles(_triangles, style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f)); Render2D.FillTriangles(_triangles, style.TextBoxBackgroundSelected.AlphaMultiplied(0.6f));
Render2D.DrawTriangles(_triangles, style.Foreground); Render2D.DrawTriangles(_triangles, style.Foreground);
}
/// <inheritdoc />
public override void DrawEditorGrid(ref Rectangle rect)
{
base.DrawEditorGrid(ref rect);
// Highlight selected blend point // Highlight selected blend point
var style = Style.Current;
var selectedIndex = _selectedAnimation.SelectedIndex; var selectedIndex = _selectedAnimation.SelectedIndex;
if (selectedIndex != -1 && (ContainsFocus || IsMouseOver)) if (selectedIndex != -1 && selectedIndex < _editor.BlendPoints.Count && (ContainsFocus || IsMouseOver))
{ {
var point = _editor.BlendPoints[selectedIndex]; var point = _editor.BlendPoints[selectedIndex];
var highlightColor = point.IsMouseDown ? style.SelectionBorder : style.BackgroundSelected; if (point != null)
Render2D.PushTint(ref highlightColor); {
Render2D.DrawTriangles(_selectedTriangles, _selectedColors); var highlightColor = point.IsMouseDown ? style.SelectionBorder : style.BackgroundSelected;
Render2D.PopTint(); Render2D.PushTint(ref highlightColor);
Render2D.DrawTriangles(_selectedTriangles, _selectedColors);
Render2D.PopTint();
}
} }
} }

View File

@@ -924,6 +924,25 @@ namespace FlaxEditor.Surface.Archetypes
(int)ModuleType.Initialize, (int)ModuleType.Initialize,
}, },
}, },
new NodeArchetype
{
TypeID = 216,
Create = CreateParticleModuleNode,
Title = "Rotate Position Shape",
Description = "Rotate the shape.",
Flags = DefaultModuleFlags,
Size = new Float2(200, 1 * Surface.Constants.LayoutOffsetY),
DefaultValues = new object[]
{
true,
(int)ModuleType.Initialize,
Quaternion.Identity,
},
Elements = new []
{
NodeElementArchetype.Factory.Input(-0.5f, "Rotation", true, typeof(Quaternion), 0, 2),
}
},
GetParticleAttribute(ModuleType.Initialize, 250, "Set Position", "Sets the particle position", typeof(Float3), Float3.Zero), GetParticleAttribute(ModuleType.Initialize, 250, "Set Position", "Sets the particle position", typeof(Float3), Float3.Zero),
GetParticleAttribute(ModuleType.Initialize, 251, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f), GetParticleAttribute(ModuleType.Initialize, 251, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f),
GetParticleAttribute(ModuleType.Initialize, 252, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f), GetParticleAttribute(ModuleType.Initialize, 252, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f),

View File

@@ -87,11 +87,17 @@ namespace FlaxEditor.Surface.Archetypes
private class GradientStop : Control private class GradientStop : Control
{ {
private bool _isMoving; private bool _isMoving;
private float _movedToTime = float.MaxValue;
private Float2 _startMovePos; private Float2 _startMovePos;
public ColorGradientNode Node; public ColorGradientNode Node;
public Color Color; public Color Color;
public void UpdateLocation(float time)
{
Location = Node._gradient.BottomLeft + new Float2(time * Node._gradient.Width - Width * 0.5f, 0.0f);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
@@ -118,6 +124,7 @@ namespace FlaxEditor.Surface.Archetypes
if (button == MouseButton.Left) if (button == MouseButton.Left)
{ {
Node.Select(this); Node.Select(this);
_movedToTime = float.MaxValue;
_isMoving = true; _isMoving = true;
_startMovePos = location; _startMovePos = location;
StartMouseCapture(); StartMouseCapture();
@@ -133,6 +140,12 @@ namespace FlaxEditor.Surface.Archetypes
if (button == MouseButton.Left && _isMoving) if (button == MouseButton.Left && _isMoving)
{ {
_isMoving = false; _isMoving = false;
if (_movedToTime < float.MaxValue)
{
int index = Node._stops.IndexOf(this);
Node.SetStopTime(index, _movedToTime);
_movedToTime = float.MaxValue;
}
EndMouseCapture(); EndMouseCapture();
} }
@@ -159,9 +172,10 @@ namespace FlaxEditor.Surface.Archetypes
if (_isMoving && Float2.DistanceSquared(ref location, ref _startMovePos) > 25.0f) if (_isMoving && Float2.DistanceSquared(ref location, ref _startMovePos) > 25.0f)
{ {
_startMovePos = Float2.Minimum; _startMovePos = Float2.Minimum;
var index = Node._stops.IndexOf(this); int index = Node._stops.IndexOf(this);
var time = (PointToParent(location).X - Node._gradient.BottomLeft.X) / Node._gradient.Width; _movedToTime = (PointToParent(location).X - Node._gradient.BottomLeft.X) / Node._gradient.Width;
Node.SetStopTime(index, time); _movedToTime = Node.ClampStopTime(index, _movedToTime);
UpdateLocation(_movedToTime);
} }
base.OnMouseMove(location); base.OnMouseMove(location);
@@ -171,6 +185,7 @@ namespace FlaxEditor.Surface.Archetypes
public override void OnEndMouseCapture() public override void OnEndMouseCapture()
{ {
_isMoving = false; _isMoving = false;
_movedToTime = float.MaxValue;
base.OnEndMouseCapture(); base.OnEndMouseCapture();
} }
@@ -223,6 +238,7 @@ namespace FlaxEditor.Surface.Archetypes
Parent = this Parent = this
}; };
_timeValue.ValueChanged += OnTimeValueChanged; _timeValue.ValueChanged += OnTimeValueChanged;
_timeValue.SlidingEnd += OnTimeValueChanged;
_colorValue = new ColorValueBox(Color.Black, _timeValue.Right + 4.0f, controlsLevel) _colorValue = new ColorValueBox(Color.Black, _timeValue.Right + 4.0f, controlsLevel)
{ {
@@ -301,8 +317,16 @@ namespace FlaxEditor.Surface.Archetypes
private void OnTimeValueChanged() private void OnTimeValueChanged()
{ {
var time = _timeValue.Value;
var index = _stops.IndexOf(_selected); var index = _stops.IndexOf(_selected);
SetStopTime(index, _timeValue.Value); if (_timeValue.IsSliding)
{
// Preview-only while sliding
time = ClampStopTime(index, time);
_selected.UpdateLocation(time);
return;
}
SetStopTime(index, time);
} }
private void OnColorValueChanged() private void OnColorValueChanged()
@@ -346,7 +370,7 @@ namespace FlaxEditor.Surface.Archetypes
UpdateStops(); UpdateStops();
} }
private void SetStopTime(int index, float time) private float ClampStopTime(int index, float time)
{ {
time = Mathf.Saturate(time); time = Mathf.Saturate(time);
if (index != 0) if (index != 0)
@@ -357,6 +381,12 @@ namespace FlaxEditor.Surface.Archetypes
{ {
time = Mathf.Min(time, (float)Values[1 + index * 2 + 2]); time = Mathf.Min(time, (float)Values[1 + index * 2 + 2]);
} }
return time;
}
private void SetStopTime(int index, float time)
{
time = ClampStopTime(index, time);
SetValue(1 + index * 2, time); SetValue(1 + index * 2, time);
} }
@@ -395,7 +425,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
var stop = _stops[i]; var stop = _stops[i];
var time = (float)Values[i * 2 + 1]; var time = (float)Values[i * 2 + 1];
stop.Location = _gradient.BottomLeft + new Float2(time * _gradient.Width - stop.Width * 0.5f, 0.0f); stop.UpdateLocation(time);
stop.Color = (Color)Values[i * 2 + 2]; stop.Color = (Color)Values[i * 2 + 2];
stop.TooltipText = stop.Color + " at " + time; stop.TooltipText = stop.Color + " at " + time;
} }

View File

@@ -23,7 +23,7 @@ namespace FlaxEditor.Actions
public Guid PrefabID; public Guid PrefabID;
public Guid PrefabObjectID; public Guid PrefabObjectID;
public unsafe Item(SceneObject obj, List<Item> nestedPrefabLinks) public Item(SceneObject obj, List<Item> nestedPrefabLinks)
{ {
ID = obj.ID; ID = obj.ID;
PrefabID = obj.PrefabID; PrefabID = obj.PrefabID;
@@ -33,8 +33,7 @@ namespace FlaxEditor.Actions
// Check if this object comes from another nested prefab (to break link only from the top-level prefab) // Check if this object comes from another nested prefab (to break link only from the top-level prefab)
Item nested; Item nested;
nested.ID = ID; nested.ID = ID;
fixed (Item* i = &this) Editor.GetPrefabNestedObject(ref PrefabID, ref PrefabObjectID, out nested.PrefabID, out nested.PrefabObjectID);
Editor.Internal_GetPrefabNestedObject(new IntPtr(&i->PrefabID), new IntPtr(&i->PrefabObjectID), new IntPtr(&nested.PrefabID), new IntPtr(&nested.PrefabObjectID));
if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty) if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty)
nestedPrefabLinks.Add(nested); nestedPrefabLinks.Add(nested);
} }

View File

@@ -42,7 +42,7 @@ namespace FlaxEditor
/// </summary> /// </summary>
/// <typeparam name="TData">The type of the data. Must have <see cref="SerializableAttribute"/>.</typeparam> /// <typeparam name="TData">The type of the data. Must have <see cref="SerializableAttribute"/>.</typeparam>
/// <seealso cref="FlaxEditor.IUndoAction" /> /// <seealso cref="FlaxEditor.IUndoAction" />
[Serializable] [Serializable, HideInEditor]
public abstract class UndoActionBase<TData> : IUndoAction where TData : struct public abstract class UndoActionBase<TData> : IUndoAction where TData : struct
{ {
/// <summary> /// <summary>

View File

@@ -5,7 +5,7 @@ namespace FlaxEditor.Utilities;
/// <summary> /// <summary>
/// Units display utilities for Editor. /// Units display utilities for Editor.
/// </summary> /// </summary>
public class Units public static class Units
{ {
/// <summary> /// <summary>
/// Factor of units per meter. /// Factor of units per meter.

View File

@@ -236,19 +236,19 @@ namespace FlaxEditor.Utilities
/// <summary> /// <summary>
/// The time/value axes tick steps for editors with timeline. /// The time/value axes tick steps for editors with timeline.
/// </summary> /// </summary>
internal static readonly float[] CurveTickSteps = internal static readonly double[] CurveTickSteps =
{ {
0.0000001f, 0.0000005f, 0.000001f, 0.000005f, 0.00001f, 0.0000001, 0.0000005, 0.000001, 0.000005, 0.00001,
0.00005f, 0.0001f, 0.0005f, 0.001f, 0.005f, 0.00005, 0.0001, 0.0005, 0.001, 0.005,
0.01f, 0.05f, 0.1f, 0.5f, 1, 0.01, 0.05, 0.1, 0.5, 1,
5, 10, 50, 100, 500, 5, 10, 50, 100, 500,
1000, 5000, 10000, 50000, 100000, 1000, 5000, 10000, 50000, 100000,
500000, 1000000, 5000000, 10000000, 100000000 500000, 1000000, 5000000, 10000000, 100000000
}; };
internal delegate void DrawCurveTick(float tick, float strength); internal delegate void DrawCurveTick(decimal tick, double step, 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) internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, double[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{ {
if (pixelRange <= Mathf.Epsilon || maxDistanceBetweenTicks <= minDistanceBetweenTicks) if (pixelRange <= Mathf.Epsilon || maxDistanceBetweenTicks <= minDistanceBetweenTicks)
return Int2.Zero; return Int2.Zero;
@@ -262,10 +262,10 @@ namespace FlaxEditor.Utilities
for (int i = tickSteps.Length - 1; i >= 0; i--) for (int i = tickSteps.Length - 1; i >= 0; i--)
{ {
// Calculate how far apart these modulo tick steps are spaced // Calculate how far apart these modulo tick steps are spaced
float tickSpacing = tickSteps[i] * pixelsInRange; var tickSpacing = tickSteps[i] * pixelsInRange;
// Calculate the strength of the tick markers based on the spacing // Calculate the strength of the tick markers based on the spacing
tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); tickStrengths[i] = (float)Mathd.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
// Beyond threshold the ticks don't get any bigger or fatter // Beyond threshold the ticks don't get any bigger or fatter
if (tickStrengths[i] >= 1) if (tickStrengths[i] >= 1)
@@ -283,7 +283,7 @@ namespace FlaxEditor.Utilities
// Draw all tick levels // Draw all tick levels
for (int level = 0; level < tickLevels; level++) for (int level = 0; level < tickLevels; level++)
{ {
float strength = tickStrengths[smallestTick + level]; var strength = tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon) if (strength <= Mathf.Epsilon)
continue; continue;
@@ -291,20 +291,36 @@ namespace FlaxEditor.Utilities
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 2); int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 2);
var lStep = tickSteps[l]; var lStep = tickSteps[l];
var lNextStep = tickSteps[l + 1]; var lNextStep = tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep); var startTick = Mathd.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep); var endTick = Mathd.CeilToInt(max / lStep);
for (int i = startTick; i <= endTick; i++) for (var i = startTick; i <= endTick; i++)
{ {
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) if (l < biggestTick && (i % Mathd.RoundToInt(lNextStep / lStep) == 0))
continue; continue;
var tick = i * lStep; var tick = (decimal)lStep * i;
drawTick(tick, strength); drawTick(tick, lStep, strength);
} }
} }
return new Int2(smallestTick, biggestTick); return new Int2(smallestTick, biggestTick);
} }
internal static float GetCurveGridSnap(double[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{
double gridStep = 0; // No grid
float gridWeight = 0.0f;
DrawCurveTicks((decimal tick, double step, float strength) =>
{
// Find the smallest grid step that has meaningful strength (it's the most visible to the user)
if (strength > gridWeight && (step < gridStep || gridStep <= 0.0) && strength > 0.5f)
{
gridStep = Math.Abs(step);
gridWeight = strength;
}
}, tickSteps, ref tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks);
return (float)gridStep;
}
/// <summary> /// <summary>
/// Determines whether the specified path string contains any invalid character. /// Determines whether the specified path string contains any invalid character.
/// </summary> /// </summary>
@@ -383,6 +399,40 @@ namespace FlaxEditor.Utilities
File.Delete(file); File.Delete(file);
} }
/// <summary>
/// Creates an Import path ui that show the asset import path and adds a button to show the folder in the file system.
/// </summary>
/// <param name="parentLayout">The parent layout container.</param>
/// <param name="assetItem">The asset item to get the import path of.</param>
public static void CreateImportPathUI(CustomEditors.LayoutElementsContainer parentLayout, Content.BinaryAssetItem assetItem)
{
assetItem.GetImportPath(out var path);
CreateImportPathUI(parentLayout, path);
}
/// <summary>
/// Creates an Import path ui that show the import path and adds a button to show the folder in the file system.
/// </summary>
/// <param name="parentLayout">The parent layout container.</param>
/// <param name="path">The import path.</param>
/// <param name="useInitialSpacing">Whether to use an initial layout space of 5 for separation.</param>
public static void CreateImportPathUI(CustomEditors.LayoutElementsContainer parentLayout, string path, bool useInitialSpacing = true)
{
if (!string.IsNullOrEmpty(path))
{
if (useInitialSpacing)
parentLayout.Space(5);
parentLayout.Label("Import Path:").Label.TooltipText = "Source asset path (can be relative or absolute to the project)";
var textBox = parentLayout.TextBox().TextBox;
textBox.TooltipText = "Path is not editable here.";
textBox.IsReadOnly = true;
textBox.Text = path;
parentLayout.Space(2);
var button = parentLayout.Button(Constants.ShowInExplorer).Button;
button.Clicked += () => FileSystem.ShowFileExplorer(Path.GetDirectoryName(path));
}
}
/// <summary> /// <summary>
/// Copies the directory. Supports subdirectories copy with files override option. /// Copies the directory. Supports subdirectories copy with files override option.
/// </summary> /// </summary>

View File

@@ -135,6 +135,8 @@ namespace FlaxEditor.Viewport.Cameras
float a = Mathf.Saturate(progress); float a = Mathf.Saturate(progress);
a = a * a * a; a = a * a * a;
var targetTransform = Transform.Lerp(_startMove, _endMove, a); var targetTransform = Transform.Lerp(_startMove, _endMove, a);
if (progress >= 1.0f)
targetTransform = _endMove; // Be precise
targetTransform.Scale = Vector3.Zero; targetTransform.Scale = Vector3.Zero;
Viewport.ViewPosition = targetTransform.Translation; Viewport.ViewPosition = targetTransform.Translation;
Viewport.ViewOrientation = targetTransform.Orientation; Viewport.ViewOrientation = targetTransform.Orientation;

View File

@@ -229,13 +229,11 @@ namespace FlaxEditor.Viewport
{ {
if (Mathf.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon || Mathf.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon) if (Mathf.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon || Mathf.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon)
return "{0:0.##}"; return "{0:0.##}";
if (_movementSpeed < 10.0f) if (_movementSpeed < 10.0f)
return "{0:0.00}"; return "{0:0.00}";
else if (_movementSpeed < 100.0f) if (_movementSpeed < 100.0f)
return "{0:0.0}"; return "{0:0.0}";
else return "{0:#}";
return "{0:#}";
} }
} }
@@ -286,11 +284,6 @@ namespace FlaxEditor.Viewport
/// </summary> /// </summary>
public Float2 MousePositionDelta => _mouseDelta; public Float2 MousePositionDelta => _mouseDelta;
/// <summary>
/// Camera's pitch angle clamp range (in degrees).
/// </summary>
public Float2 CamPitchAngles = new Float2(-88, 88);
/// <summary> /// <summary>
/// Gets the view transform. /// Gets the view transform.
/// </summary> /// </summary>
@@ -326,7 +319,7 @@ namespace FlaxEditor.Viewport
get => Float3.Forward * ViewOrientation; get => Float3.Forward * ViewOrientation;
set set
{ {
var right = Float3.Cross(value, Float3.Up); var right = Mathf.Abs(Float3.Dot(value, Float3.Up)) < 1.0f - Mathf.Epsilon ? Float3.Cross(value, Float3.Up) : Float3.Forward;
var up = Float3.Cross(right, value); var up = Float3.Cross(right, value);
ViewOrientation = Quaternion.LookRotation(value, up); ViewOrientation = Quaternion.LookRotation(value, up);
} }
@@ -376,7 +369,11 @@ namespace FlaxEditor.Viewport
public float Pitch public float Pitch
{ {
get => _pitch; get => _pitch;
set => _pitch = Mathf.Clamp(value, CamPitchAngles.X, CamPitchAngles.Y); set
{
var pitchLimit = _isOrtho ? new Float2(-90, 90) : new Float2(-88, 88);
_pitch = Mathf.Clamp(value, pitchLimit.X, pitchLimit.Y);
}
} }
/// <summary> /// <summary>
@@ -1155,8 +1152,7 @@ namespace FlaxEditor.Viewport
/// <param name="orientation">The orientation.</param> /// <param name="orientation">The orientation.</param>
protected void OrientViewport(Quaternion orientation) protected void OrientViewport(Quaternion orientation)
{ {
var quat = orientation; OrientViewport(ref orientation);
OrientViewport(ref quat);
} }
/// <summary> /// <summary>
@@ -1367,7 +1363,7 @@ namespace FlaxEditor.Viewport
{ {
var direction = ViewDirection; var direction = ViewDirection;
var target = position + direction; var target = position + direction;
var right = Float3.Normalize(Float3.Cross(Float3.Up, direction)); var right = Mathf.Abs(Float3.Dot(direction, Float3.Up)) < 1.0f - Mathf.Epsilon ? Float3.Normalize(Float3.Cross(Float3.Up, direction)) : Float3.Forward;
var up = Float3.Normalize(Float3.Cross(direction, right)); var up = Float3.Normalize(Float3.Cross(direction, right));
Matrix.LookAt(ref position, ref target, ref up, out result); Matrix.LookAt(ref position, ref target, ref up, out result);
} }

View File

@@ -5,10 +5,12 @@ 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;
@@ -44,7 +46,7 @@ namespace FlaxEditor.Viewport
private sealed class PrefabUIEditorRoot : UIEditorRoot private sealed class PrefabUIEditorRoot : UIEditorRoot
{ {
private readonly PrefabWindowViewport _viewport; private readonly PrefabWindowViewport _viewport;
private bool UI => _viewport._hasUILinkedCached; private bool UI => _viewport.ShowUI;
public PrefabUIEditorRoot(PrefabWindowViewport viewport) public PrefabUIEditorRoot(PrefabWindowViewport viewport)
: base(true) : base(true)
@@ -67,8 +69,78 @@ namespace FlaxEditor.Viewport
private PrefabSpritesRenderer _spritesRenderer; private PrefabSpritesRenderer _spritesRenderer;
private IntPtr _tempDebugDrawContext; private IntPtr _tempDebugDrawContext;
private bool _hasUILinkedCached;
private PrefabUIEditorRoot _uiRoot; private PrefabUIEditorRoot _uiRoot;
private bool _showUI = false;
private ContextMenuButton _uiModeButton;
/// <summary>
/// Event fired when the UI Mode is toggled.
/// </summary>
public event Action<bool> UIModeToggled;
/// <summary>
/// set the initial UI mod
/// </summary>
/// <param name="value">the initial ShowUI value</param>
public void SetInitialUIMode(bool value)
{
ShowUI = value;
_uiModeButton.Checked = value;
}
/// <summary>
/// Whether to show the UI mode or not.
/// </summary>
public bool ShowUI
{
get => _showUI;
set
{
_showUI = value;
if (_showUI)
{
// 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);
_uiRoot.Visible = true;
}
else
{
// Generic prefab
Gizmos.Active = TransformGizmo;
ViewportCamera = new FPSCamera();
_uiRoot.Visible = false;
}
// Update default components usage
bool defaultFeatures = !_showUI;
_disableInputUpdate = _showUI;
_spritesRenderer.Enabled = defaultFeatures;
SelectionOutline.Enabled = defaultFeatures;
_showDefaultSceneButton.Visible = defaultFeatures;
_cameraWidget.Visible = defaultFeatures;
_cameraButton.Visible = defaultFeatures;
_orthographicModeButton.Visible = defaultFeatures;
Task.Enabled = defaultFeatures;
UseAutomaticTaskManagement = defaultFeatures;
ShowDefaultSceneActors = defaultFeatures;
TintColor = defaultFeatures ? Color.White : Color.Transparent;
UIModeToggled?.Invoke(_showUI);
}
}
/// <summary> /// <summary>
/// Drag and drop handlers /// Drag and drop handlers
@@ -138,6 +210,11 @@ namespace FlaxEditor.Viewport
_uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport _uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport
_uiParentLink = _uiRoot.UIRoot; _uiParentLink = _uiRoot.UIRoot;
// UI mode buton
_uiModeButton = ViewWidgetShowMenu.AddButton("UI Mode", (button) => ShowUI = button.Checked);
_uiModeButton.AutoCheck = true;
_uiModeButton.VisibleChanged += control => (control as ContextMenuButton).Checked = ShowUI;
EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo); EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo);
// Setup input actions // Setup input actions
@@ -146,58 +223,8 @@ namespace FlaxEditor.Viewport
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);

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Xml; using System.Xml;
using FlaxEditor.Content; using FlaxEditor.Content;
@@ -210,8 +211,11 @@ namespace FlaxEditor.Windows.Assets
var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings };
group.Object(importSettingsValues); group.Object(importSettingsValues);
// Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, proxy.Window.Item as BinaryAssetItem);
layout.Space(5); layout.Space(5);
var reimportButton = group.Button("Reimport"); var reimportButton = layout.Button("Reimport");
reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport(); reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport();
} }
} }

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.IO;
using System.Xml; using System.Xml;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Content.Import; using FlaxEditor.Content.Import;
@@ -100,7 +101,10 @@ namespace FlaxEditor.Windows.Assets
base.Initialize(layout); base.Initialize(layout);
layout.Space(10); // Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, window.Item as BinaryAssetItem);
layout.Space(5);
var reimportButton = layout.Button("Reimport"); var reimportButton = layout.Button("Reimport");
reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport(); reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport();
} }

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.IO;
using System.Xml; using System.Xml;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Content.Import; using FlaxEditor.Content.Import;
@@ -53,7 +54,10 @@ namespace FlaxEditor.Windows.Assets
base.Initialize(layout); base.Initialize(layout);
layout.Space(10); // Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, window.Item as BinaryAssetItem);
layout.Space(5);
var reimportButton = layout.Button("Reimport"); var reimportButton = layout.Button("Reimport");
reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport(); reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport();
} }

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.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -758,8 +759,11 @@ namespace FlaxEditor.Windows.Assets
var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings };
group.Object(importSettingsValues); group.Object(importSettingsValues);
// Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, proxy.Window.Item as BinaryAssetItem);
layout.Space(5); layout.Space(5);
var reimportButton = group.Button("Reimport"); var reimportButton = layout.Button("Reimport");
reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport(); reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport();
} }
} }

View File

@@ -355,13 +355,22 @@ namespace FlaxEditor.Windows.Assets
private void OnPrefabOpened() private void OnPrefabOpened()
{ {
_viewport.Prefab = _asset; _viewport.Prefab = _asset;
_viewport.UpdateGizmoMode(); if (Editor.ProjectCache.TryGetCustomData($"UIMode:{_asset.ID}", out bool value))
_viewport.SetInitialUIMode(value);
else
_viewport.SetInitialUIMode(_viewport._hasUILinked);
_viewport.UIModeToggled += OnUIModeToggled;
Graph.MainActor = _viewport.Instance; Graph.MainActor = _viewport.Instance;
Selection.Clear(); Selection.Clear();
Select(Graph.Main); Select(Graph.Main);
Graph.Root.TreeNode.Expand(true); Graph.Root.TreeNode.Expand(true);
} }
private void OnUIModeToggled(bool value)
{
Editor.ProjectCache.SetCustomData($"UIMode:{_asset.ID}", value);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Save() public override void Save()
{ {

View File

@@ -1023,8 +1023,11 @@ namespace FlaxEditor.Windows.Assets
var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings };
group.Object(importSettingsValues); group.Object(importSettingsValues);
// Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, proxy.Window.Item as BinaryAssetItem);
layout.Space(5); layout.Space(5);
var reimportButton = group.Button("Reimport"); var reimportButton = layout.Button("Reimport");
reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport(); reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport();
} }
} }

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
using FlaxEditor.Content; using FlaxEditor.Content;
@@ -110,9 +111,19 @@ namespace FlaxEditor.Windows.Assets
{ {
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
var proxy = (PropertiesProxy)Values[0];
if (proxy._window == null)
{
layout.Label("Loading...", TextAlignment.Center);
return;
}
base.Initialize(layout); base.Initialize(layout);
// Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, proxy._window.Item as BinaryAssetItem);
layout.Space(10); layout.Space(5);
var reimportButton = layout.Button("Reimport"); var reimportButton = layout.Button("Reimport");
reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport(); reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport();
} }

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.IO;
using System.Xml; using System.Xml;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Content.Import; using FlaxEditor.Content.Import;
@@ -134,11 +135,21 @@ namespace FlaxEditor.Windows.Assets
{ {
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
var proxy = (ImportPropertiesProxy)Values[0];
if (proxy._window == null)
{
layout.Label("Loading...", TextAlignment.Center);
return;
}
// Import settings // Import settings
base.Initialize(layout); base.Initialize(layout);
// Creates the import path UI
Utilities.Utils.CreateImportPathUI(layout, proxy._window.Item as BinaryAssetItem);
// Reimport // Reimport
layout.Space(10); layout.Space(5);
var reimportButton = layout.Button("Reimport"); var reimportButton = layout.Button("Reimport");
reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport(); reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport();
} }

View File

@@ -23,6 +23,8 @@ namespace FlaxEditor.Windows
private readonly GameRoot _guiRoot; private readonly GameRoot _guiRoot;
private bool _showGUI = true; private bool _showGUI = true;
private bool _showDebugDraw = false; private bool _showDebugDraw = false;
private bool _audioMuted = false;
private float _audioVolume = 1;
private bool _isMaximized = false, _isUnlockingMouse = false; private bool _isMaximized = false, _isUnlockingMouse = false;
private bool _isFloating = false, _isBorderless = false; private bool _isFloating = false, _isBorderless = false;
private bool _cursorVisible = true; private bool _cursorVisible = true;
@@ -91,6 +93,35 @@ namespace FlaxEditor.Windows
set => _showDebugDraw = value; set => _showDebugDraw = value;
} }
/// <summary>
/// Gets or set a value indicating whether Audio is muted.
/// </summary>
public bool AudioMuted
{
get => _audioMuted;
set
{
Audio.MasterVolume = value ? 0 : AudioVolume;
_audioMuted = value;
}
}
/// <summary>
/// Gets or sets a value that set the audio volume.
/// </summary>
public float AudioVolume
{
get => _audioVolume;
set
{
if (!AudioMuted)
Audio.MasterVolume = value;
_audioVolume = value;
}
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the game window is maximized (only in play mode). /// Gets or sets a value indicating whether the game window is maximized (only in play mode).
/// </summary> /// </summary>
@@ -646,6 +677,24 @@ namespace FlaxEditor.Windows
checkbox.StateChanged += x => ShowDebugDraw = x.Checked; checkbox.StateChanged += x => ShowDebugDraw = x.Checked;
} }
menu.AddSeparator();
// Mute Audio
{
var button = menu.AddButton("Mute Audio");
button.CloseMenuOnClick = false;
var checkbox = new CheckBox(140, 2, AudioMuted) { Parent = button };
checkbox.StateChanged += x => AudioMuted = x.Checked;
}
// Audio Volume
{
var button = menu.AddButton("Audio Volume");
button.CloseMenuOnClick = false;
var slider = new FloatValueBox(AudioVolume, 140, 2, 50, 0, 1) { Parent = button };
slider.ValueChanged += () => AudioVolume = slider.Value;
}
menu.MinimumWidth = 200; menu.MinimumWidth = 200;
menu.AddSeparator(); menu.AddSeparator();
} }

View File

@@ -175,7 +175,7 @@ namespace FlaxEditor.Windows.Profiler
private string FormatCellBytes(object x) private string FormatCellBytes(object x)
{ {
return Utilities.Utils.FormatBytesCount((ulong)x); return Utilities.Utils.FormatBytesCount(Convert.ToUInt64(x));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -27,6 +27,7 @@ class AnimationsSystem : public TaskGraphSystem
{ {
public: public:
float DeltaTime, UnscaledDeltaTime, Time, UnscaledTime; float DeltaTime, UnscaledDeltaTime, Time, UnscaledTime;
bool Active;
void Job(int32 index); void Job(int32 index);
void Execute(TaskGraph* graph) override; void Execute(TaskGraph* graph) override;
@@ -51,6 +52,7 @@ namespace
AnimationsService AnimationManagerInstance; AnimationsService AnimationManagerInstance;
TaskGraphSystem* Animations::System = nullptr; TaskGraphSystem* Animations::System = nullptr;
ConcurrentSystemLocker Animations::SystemLocker;
#if USE_EDITOR #if USE_EDITOR
Delegate<Animations::DebugFlowInfo> Animations::DebugFlow; Delegate<Animations::DebugFlowInfo> Animations::DebugFlow;
#endif #endif
@@ -116,6 +118,10 @@ void AnimationsSystem::Execute(TaskGraph* graph)
{ {
if (AnimationManagerInstance.UpdateList.Count() == 0) if (AnimationManagerInstance.UpdateList.Count() == 0)
return; return;
Active = true;
// Ensure no animation assets can be reloaded/modified during async update
Animations::SystemLocker.Begin(false);
// Setup data for async update // Setup data for async update
const auto& tickData = Time::Update; const auto& tickData = Time::Update;
@@ -138,6 +144,8 @@ void AnimationsSystem::Execute(TaskGraph* graph)
void AnimationsSystem::PostExecute(TaskGraph* graph) void AnimationsSystem::PostExecute(TaskGraph* graph)
{ {
if (!Active)
return;
PROFILE_CPU_NAMED("Animations.PostExecute"); PROFILE_CPU_NAMED("Animations.PostExecute");
// Update gameplay // Update gameplay
@@ -153,6 +161,8 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
// Cleanup // Cleanup
AnimationManagerInstance.UpdateList.Clear(); AnimationManagerInstance.UpdateList.Clear();
Animations::SystemLocker.End(false);
Active = false;
} }
void Animations::AddToUpdate(AnimatedModel* obj) void Animations::AddToUpdate(AnimatedModel* obj)

View File

@@ -4,6 +4,7 @@
#include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/ScriptingType.h"
#include "Engine/Core/Delegate.h" #include "Engine/Core/Delegate.h"
#include "Engine/Threading/ConcurrentSystemLocker.h"
class TaskGraphSystem; class TaskGraphSystem;
class AnimatedModel; class AnimatedModel;
@@ -21,6 +22,9 @@ API_CLASS(Static) class FLAXENGINE_API Animations
/// </summary> /// </summary>
API_FIELD(ReadOnly) static TaskGraphSystem* System; API_FIELD(ReadOnly) static TaskGraphSystem* System;
// Data access locker for animations data.
static ConcurrentSystemLocker SystemLocker;
#if USE_EDITOR #if USE_EDITOR
// Data wrapper for the debug flow information. // Data wrapper for the debug flow information.
API_STRUCT(NoDefault) struct DebugFlowInfo API_STRUCT(NoDefault) struct DebugFlowInfo

View File

@@ -161,7 +161,10 @@ public:
case 2: case 2:
default: // TODO: implement multi-channel support (eg. 5.1, 7.1) default: // TODO: implement multi-channel support (eg. 5.1, 7.1)
outputMatrix[0] = channels[FrontLeft]; outputMatrix[0] = channels[FrontLeft];
outputMatrix[sourceChannels + 1] = channels[FrontRight]; if (sourceChannels == 1)
outputMatrix[1] = channels[FrontRight];
else
outputMatrix[sourceChannels + 1] = channels[FrontRight];
break; break;
} }
} }

View File

@@ -40,11 +40,17 @@
namespace ALC namespace ALC
{ {
struct SourceData
{
AudioDataInfo Format;
bool Spatial;
};
ALCdevice* Device = nullptr; ALCdevice* Device = nullptr;
ALCcontext* Context = nullptr; ALCcontext* Context = nullptr;
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None; AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
CriticalSection Locker; CriticalSection Locker;
Dictionary<uint32, AudioDataInfo> SourceIDtoFormat; Dictionary<uint32, SourceData> SourcesData;
bool IsExtensionSupported(const char* extension) bool IsExtensionSupported(const char* extension)
{ {
@@ -88,32 +94,32 @@ namespace ALC
alSourcef(sourceID, AL_PITCH, pitch); alSourcef(sourceID, AL_PITCH, pitch);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f); alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcei(sourceID, AL_LOOPING, loop); alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); // Non-spatial sounds use AL_POSITION for panning
alSourcei(sourceID, AL_BUFFER, 0); alSourcei(sourceID, AL_BUFFER, 0);
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); // Always spatialize, fixes multi-channel played as spatial
#endif
if (spatial) if (spatial)
{ {
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
#endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation); alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler); alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance)); alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position)); alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(Vector3::Zero)); alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(Vector3::Zero));
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
} }
else else
{ {
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f); alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f); alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f); alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f); alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
} }
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
} }
} }
@@ -303,7 +309,9 @@ uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& p
// Cache audio data format assigned on source (used in Source_GetCurrentBufferTime) // Cache audio data format assigned on source (used in Source_GetCurrentBufferTime)
ALC::Locker.Lock(); ALC::Locker.Lock();
ALC::SourceIDtoFormat[sourceID] = format; auto& data = ALC::SourcesData[sourceID];
data.Format = format;
data.Spatial = spatial;
ALC::Locker.Unlock(); ALC::Locker.Unlock();
return sourceID; return sourceID;
@@ -317,18 +325,30 @@ void AudioBackendOAL::Source_Remove(uint32 sourceID)
ALC_CHECK_ERROR(alDeleteSources); ALC_CHECK_ERROR(alDeleteSources);
ALC::Locker.Lock(); ALC::Locker.Lock();
ALC::SourceIDtoFormat.Remove(sourceID); ALC::SourcesData.Remove(sourceID);
ALC::Locker.Unlock(); ALC::Locker.Unlock();
} }
void AudioBackendOAL::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) void AudioBackendOAL::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity)
{ {
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(velocity)); ALC::Locker.Lock();
const bool spatial = ALC::SourcesData[sourceID].Spatial;
ALC::Locker.Unlock();
if (spatial)
{
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
}
} }
void AudioBackendOAL::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) void AudioBackendOAL::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{ {
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position)); ALC::Locker.Lock();
const bool spatial = ALC::SourcesData[sourceID].Spatial;
ALC::Locker.Unlock();
if (spatial)
{
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
}
} }
void AudioBackendOAL::Source_VolumeChanged(uint32 sourceID, float volume) void AudioBackendOAL::Source_VolumeChanged(uint32 sourceID, float volume)
@@ -343,11 +363,21 @@ void AudioBackendOAL::Source_PitchChanged(uint32 sourceID, float pitch)
void AudioBackendOAL::Source_PanChanged(uint32 sourceID, float pan) void AudioBackendOAL::Source_PanChanged(uint32 sourceID, float pan)
{ {
ALC::Locker.Lock();
const bool spatial = ALC::SourcesData[sourceID].Spatial;
ALC::Locker.Unlock();
if (spatial)
{
#ifdef AL_EXT_STEREO_ANGLES #ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF; const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles); alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif #endif
}
else
{
alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
}
} }
void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop) void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
@@ -357,12 +387,8 @@ void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{ {
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial);
if (spatial) if (spatial)
{ {
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
#endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation); alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler); alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance)); alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
@@ -411,7 +437,7 @@ float AudioBackendOAL::Source_GetCurrentBufferTime(uint32 sourceID)
alGetSourcef(sourceID, AL_SEC_OFFSET, &time); alGetSourcef(sourceID, AL_SEC_OFFSET, &time);
#else #else
ALC::Locker.Lock(); ALC::Locker.Lock();
AudioDataInfo clipInfo = ALC::SourceIDtoFormat[sourceID]; AudioDataInfo clipInfo = ALC::SourcesData[sourceID].Format;
ALC::Locker.Unlock(); ALC::Locker.Unlock();
ALint samplesPlayed; ALint samplesPlayed;
alGetSourcei(sourceID, AL_SAMPLE_OFFSET, &samplesPlayed); alGetSourcei(sourceID, AL_SAMPLE_OFFSET, &samplesPlayed);

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