Merge remote-tracking branch 'origin/master' into 1.8
# Conflicts: # Source/Editor/Utilities/EditorUtilities.cpp # Source/Editor/Utilities/EditorUtilities.h
This commit is contained in:
@@ -81,7 +81,7 @@ namespace FlaxEditor.Content.Create
|
||||
switch (_options.Template)
|
||||
{
|
||||
case Templates.Empty:
|
||||
return Editor.CreateAsset(Editor.NewAssetType.ParticleEmitter, ResultUrl);
|
||||
return Editor.CreateAsset("ParticleEmitter", ResultUrl);
|
||||
case Templates.ConstantBurst:
|
||||
templateName = "Constant Burst";
|
||||
break;
|
||||
|
||||
@@ -56,6 +56,9 @@ namespace FlaxEditor.Content.GUI
|
||||
|
||||
private float _viewScale = 1.0f;
|
||||
private ContentViewType _viewType = ContentViewType.Tiles;
|
||||
private bool _isRubberBandSpanning = false;
|
||||
private Float2 _mousePresslocation;
|
||||
private Rectangle _rubberBandRectangle;
|
||||
|
||||
#region External Events
|
||||
|
||||
@@ -600,6 +603,12 @@ namespace FlaxEditor.Content.GUI
|
||||
{
|
||||
Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
|
||||
if (_isRubberBandSpanning)
|
||||
{
|
||||
Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f);
|
||||
Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -607,9 +616,54 @@ namespace FlaxEditor.Content.GUI
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_mousePresslocation = location;
|
||||
_rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0);
|
||||
_isRubberBandSpanning = true;
|
||||
StartMouseCapture();
|
||||
}
|
||||
return AutoFocus && Focus(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (_isRubberBandSpanning)
|
||||
{
|
||||
_rubberBandRectangle.Width = location.X - _mousePresslocation.X;
|
||||
_rubberBandRectangle.Height = location.Y - _mousePresslocation.Y;
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (_isRubberBandSpanning)
|
||||
{
|
||||
_isRubberBandSpanning = false;
|
||||
EndMouseCapture();
|
||||
if (_rubberBandRectangle.Width < 0 || _rubberBandRectangle.Height < 0)
|
||||
{
|
||||
// make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner
|
||||
var size = _rubberBandRectangle.Size;
|
||||
_rubberBandRectangle.X = Mathf.Min(_rubberBandRectangle.X, _rubberBandRectangle.X + _rubberBandRectangle.Width);
|
||||
_rubberBandRectangle.Y = Mathf.Min(_rubberBandRectangle.Y, _rubberBandRectangle.Y + _rubberBandRectangle.Height);
|
||||
size.X = Mathf.Abs(size.X);
|
||||
size.Y = Mathf.Abs(size.Y);
|
||||
_rubberBandRectangle.Size = size;
|
||||
}
|
||||
var itemsInRectangle = _items.Where(t => _rubberBandRectangle.Intersects(t.Bounds)).ToList();
|
||||
Select(itemsInRectangle, Input.GetKey(KeyboardKeys.Shift) || Input.GetKey(KeyboardKeys.Control));
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseWheel(Float2 location, float delta)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace FlaxEngine.Tools
|
||||
private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab;
|
||||
private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
|
||||
private bool ShowAnimation => Type == ModelType.Animation || Type == ModelType.Prefab;
|
||||
private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None;
|
||||
private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
|
||||
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
|
||||
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraphFunction, outputPath))
|
||||
if (Editor.CreateAsset("AnimationGraphFunction", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraph, outputPath))
|
||||
if (Editor.CreateAsset("AnimationGraph", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.Animation, outputPath))
|
||||
if (Editor.CreateAsset("Animation", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath))
|
||||
if (Editor.CreateAsset("BehaviorTree", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.CollisionData, outputPath))
|
||||
if (Editor.CreateAsset("CollisionData", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.MaterialFunction, outputPath))
|
||||
if (Editor.CreateAsset("MaterialFunction", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.MaterialInstance, outputPath))
|
||||
if (Editor.CreateAsset("MaterialInstance", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.Material, outputPath))
|
||||
if (Editor.CreateAsset("Material", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.ParticleEmitterFunction, outputPath))
|
||||
if (Editor.CreateAsset("ParticleEmitterFunction", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.ParticleSystem, outputPath))
|
||||
if (Editor.CreateAsset("ParticleSystem", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.SceneAnimation, outputPath))
|
||||
if (Editor.CreateAsset("SceneAnimation", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override bool IsFileNameValid(string filename)
|
||||
{
|
||||
// Scripts cannot start with digit.
|
||||
if (Char.IsDigit(filename[0]))
|
||||
if (char.IsDigit(filename[0]))
|
||||
return false;
|
||||
if (filename.Equals("Script"))
|
||||
return false;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
if (Editor.CreateAsset(Editor.NewAssetType.SkeletonMask, outputPath))
|
||||
if (Editor.CreateAsset("SkeletonMask", outputPath))
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,463 @@
|
||||
#include "WindowsPlatformTools.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
|
||||
#include "Engine/Core/Math/Color32.h"
|
||||
#include "Engine/Core/Config/GameSettings.h"
|
||||
#include "Editor/Utilities/EditorUtilities.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
#include <fstream>
|
||||
|
||||
#define MSDOS_SIGNATURE 0x5A4D
|
||||
#define PE_SIGNATURE 0x00004550
|
||||
#define PE_32BIT_SIGNATURE 0x10B
|
||||
#define PE_64BIT_SIGNATURE 0x20B
|
||||
#define PE_SECTION_UNINITIALIZED_DATA 0x00000080
|
||||
#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2
|
||||
#define PE_IMAGE_RT_ICON 3
|
||||
|
||||
/// <summary>
|
||||
/// MS-DOS header found at the beginning in a PE format file.
|
||||
/// </summary>
|
||||
struct MSDOSHeader
|
||||
{
|
||||
uint16 signature;
|
||||
uint16 lastSize;
|
||||
uint16 numBlocks;
|
||||
uint16 numReloc;
|
||||
uint16 hdrSize;
|
||||
uint16 minAlloc;
|
||||
uint16 maxAlloc;
|
||||
uint16 ss;
|
||||
uint16 sp;
|
||||
uint16 checksum;
|
||||
uint16 ip;
|
||||
uint16 cs;
|
||||
uint16 relocPos;
|
||||
uint16 numOverlay;
|
||||
uint16 reserved1[4];
|
||||
uint16 oemId;
|
||||
uint16 oemInfo;
|
||||
uint16 reserved2[10];
|
||||
uint32 lfanew;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// COFF header found in a PE format file.
|
||||
/// </summary>
|
||||
struct COFFHeader
|
||||
{
|
||||
uint16 machine;
|
||||
uint16 numSections;
|
||||
uint32 timeDateStamp;
|
||||
uint32 ptrSymbolTable;
|
||||
uint32 numSymbols;
|
||||
uint16 sizeOptHeader;
|
||||
uint16 characteristics;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Contains address and size of data areas in a PE image.
|
||||
/// </summary>
|
||||
struct PEDataDirectory
|
||||
{
|
||||
uint32 virtualAddress;
|
||||
uint32 size;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Optional header in a 32-bit PE format file.
|
||||
/// </summary>
|
||||
struct PEOptionalHeader32
|
||||
{
|
||||
uint16 signature;
|
||||
uint8 majorLinkerVersion;
|
||||
uint8 minorLinkerVersion;
|
||||
uint32 sizeCode;
|
||||
uint32 sizeInitializedData;
|
||||
uint32 sizeUninitializedData;
|
||||
uint32 addressEntryPoint;
|
||||
uint32 baseCode;
|
||||
uint32 baseData;
|
||||
uint32 baseImage;
|
||||
uint32 alignmentSection;
|
||||
uint32 alignmentFile;
|
||||
uint16 majorOSVersion;
|
||||
uint16 minorOSVersion;
|
||||
uint16 majorImageVersion;
|
||||
uint16 minorImageVersion;
|
||||
uint16 majorSubsystemVersion;
|
||||
uint16 minorSubsystemVersion;
|
||||
uint32 reserved;
|
||||
uint32 sizeImage;
|
||||
uint32 sizeHeaders;
|
||||
uint32 checksum;
|
||||
uint16 subsystem;
|
||||
uint16 characteristics;
|
||||
uint32 sizeStackReserve;
|
||||
uint32 sizeStackCommit;
|
||||
uint32 sizeHeapReserve;
|
||||
uint32 sizeHeapCommit;
|
||||
uint32 loaderFlags;
|
||||
uint32 NumRvaAndSizes;
|
||||
PEDataDirectory dataDirectory[16];
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Optional header in a 64-bit PE format file.
|
||||
/// </summary>
|
||||
struct PEOptionalHeader64
|
||||
{
|
||||
uint16 signature;
|
||||
uint8 majorLinkerVersion;
|
||||
uint8 minorLinkerVersion;
|
||||
uint32 sizeCode;
|
||||
uint32 sizeInitializedData;
|
||||
uint32 sizeUninitializedData;
|
||||
uint32 addressEntryPoint;
|
||||
uint32 baseCode;
|
||||
uint64 baseImage;
|
||||
uint32 alignmentSection;
|
||||
uint32 alignmentFile;
|
||||
uint16 majorOSVersion;
|
||||
uint16 minorOSVersion;
|
||||
uint16 majorImageVersion;
|
||||
uint16 minorImageVersion;
|
||||
uint16 majorSubsystemVersion;
|
||||
uint16 minorSubsystemVersion;
|
||||
uint32 reserved;
|
||||
uint32 sizeImage;
|
||||
uint32 sizeHeaders;
|
||||
uint32 checksum;
|
||||
uint16 subsystem;
|
||||
uint16 characteristics;
|
||||
uint64 sizeStackReserve;
|
||||
uint64 sizeStackCommit;
|
||||
uint64 sizeHeapReserve;
|
||||
uint64 sizeHeapCommit;
|
||||
uint32 loaderFlags;
|
||||
uint32 NumRvaAndSizes;
|
||||
PEDataDirectory dataDirectory[16];
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A section header in a PE format file.
|
||||
/// </summary>
|
||||
struct PESectionHeader
|
||||
{
|
||||
char name[8];
|
||||
uint32 virtualSize;
|
||||
uint32 relativeVirtualAddress;
|
||||
uint32 physicalSize;
|
||||
uint32 physicalAddress;
|
||||
uint8 deprecated[12];
|
||||
uint32 flags;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A resource table header within a .rsrc section in a PE format file.
|
||||
/// </summary>
|
||||
struct PEImageResourceDirectory
|
||||
{
|
||||
uint32 flags;
|
||||
uint32 timeDateStamp;
|
||||
uint16 majorVersion;
|
||||
uint16 minorVersion;
|
||||
uint16 numNamedEntries;
|
||||
uint16 numIdEntries;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A single entry in a resource table within a .rsrc section in a PE format file.
|
||||
/// </summary>
|
||||
struct PEImageResourceEntry
|
||||
{
|
||||
uint32 type;
|
||||
uint32 offsetDirectory : 31;
|
||||
uint32 isDirectory : 1;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file.
|
||||
/// </summary>
|
||||
struct PEImageResourceEntryData
|
||||
{
|
||||
uint32 offsetData;
|
||||
uint32 size;
|
||||
uint32 codePage;
|
||||
uint32 resourceHandle;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Header used in icon file format.
|
||||
/// </summary>
|
||||
struct IconHeader
|
||||
{
|
||||
uint32 size;
|
||||
int32 width;
|
||||
int32 height;
|
||||
uint16 planes;
|
||||
uint16 bitCount;
|
||||
uint32 compression;
|
||||
uint32 sizeImage;
|
||||
int32 xPelsPerMeter;
|
||||
int32 yPelsPerMeter;
|
||||
uint32 clrUsed;
|
||||
uint32 clrImportant;
|
||||
};
|
||||
|
||||
void UpdateIconData(uint8* iconData, const TextureData* icon)
|
||||
{
|
||||
IconHeader* iconHeader = (IconHeader*)iconData;
|
||||
if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32)
|
||||
{
|
||||
// Unsupported format
|
||||
return;
|
||||
}
|
||||
uint8* iconPixels = iconData + sizeof(IconHeader);
|
||||
const uint32 width = iconHeader->width;
|
||||
const uint32 height = iconHeader->height / 2;
|
||||
|
||||
// Try to pick a proper mip (require the same size)
|
||||
int32 srcPixelsMip = 0;
|
||||
const int32 mipLevels = icon->GetMipLevels();
|
||||
for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
|
||||
{
|
||||
const uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex);
|
||||
const uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex);
|
||||
if (width == iconWidth && height == iconHeight)
|
||||
{
|
||||
srcPixelsMip = mipIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip);
|
||||
const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get();
|
||||
const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip));
|
||||
const auto sampler = TextureTool::GetSampler(icon->Format);
|
||||
ASSERT_LOW_LAYER(sampler);
|
||||
|
||||
// Write colors
|
||||
uint32* colorData = (uint32*)iconPixels;
|
||||
uint32 idx = 0;
|
||||
for (int32 y = (int32)height - 1; y >= 0; y--)
|
||||
{
|
||||
float v = (float)y / height;
|
||||
for (uint32 x = 0; x < width; x++)
|
||||
{
|
||||
float u = (float)x / width;
|
||||
const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
|
||||
colorData[idx++] = Color32(c).GetAsBGRA();
|
||||
}
|
||||
}
|
||||
|
||||
// Write AND mask
|
||||
uint32 colorDataSize = width * height * sizeof(uint32);
|
||||
uint8* maskData = iconPixels + colorDataSize;
|
||||
uint32 numPackedPixels = width / 8; // One per bit in byte
|
||||
for (int32 y = (int32)height - 1; y >= 0; y--)
|
||||
{
|
||||
uint8 mask = 0;
|
||||
float v = (float)y / height;
|
||||
for (uint32 packedX = 0; packedX < numPackedPixels; packedX++)
|
||||
{
|
||||
for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++)
|
||||
{
|
||||
uint32 x = packedX * 8 + pixelIdx;
|
||||
float u = (float)x / width;
|
||||
const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
|
||||
if (c.A < 0.25f)
|
||||
mask |= 1 << (7 - pixelIdx);
|
||||
}
|
||||
*maskData = mask;
|
||||
maskData++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8)
|
||||
{
|
||||
uint32 numEntries = current->numIdEntries; // Not supporting name entries
|
||||
PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1);
|
||||
for (uint32 i = 0; i < numEntries; i++)
|
||||
{
|
||||
// Only at root does the type identify resource type
|
||||
if (base == current && entries[i].type != PE_IMAGE_RT_ICON)
|
||||
continue;
|
||||
|
||||
if (entries[i].isDirectory)
|
||||
{
|
||||
PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory);
|
||||
SetIconData(base, child, imageData, sectionAddress, iconRGBA8);
|
||||
}
|
||||
else
|
||||
{
|
||||
PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory);
|
||||
uint8* iconData = imageData + (data->offsetData - sectionAddress);
|
||||
UpdateIconData(iconData, iconRGBA8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateExeIcon(const String& path, const TextureData& icon)
|
||||
{
|
||||
if (!FileSystem::FileExists(path))
|
||||
{
|
||||
LOG(Warning, "Missing file");
|
||||
return true;
|
||||
}
|
||||
if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0)
|
||||
{
|
||||
LOG(Warning, "Inalid icon data");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure that image format can be sampled
|
||||
const TextureData* iconRGBA8 = &icon;
|
||||
TextureData tmpData1;
|
||||
//if (icon.Format != PixelFormat::R8G8B8A8_UNorm)
|
||||
if (TextureTool::GetSampler(icon.Format) == nullptr)
|
||||
{
|
||||
if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm))
|
||||
{
|
||||
LOG(Warning, "Failed convert icon data.");
|
||||
return true;
|
||||
}
|
||||
iconRGBA8 = &tmpData1;
|
||||
}
|
||||
|
||||
// Use fixed-size input icon image
|
||||
TextureData tmpData2;
|
||||
if (iconRGBA8->Width != 256 || iconRGBA8->Height != 256)
|
||||
{
|
||||
if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256))
|
||||
{
|
||||
LOG(Warning, "Failed resize icon data.");
|
||||
return true;
|
||||
}
|
||||
iconRGBA8 = &tmpData2;
|
||||
}
|
||||
|
||||
// A PE file is structured as such:
|
||||
// - MSDOS Header
|
||||
// - PE Signature
|
||||
// - COFF Header
|
||||
// - PE Optional Header
|
||||
// - One or multiple sections
|
||||
// - .code
|
||||
// - .data
|
||||
// - ...
|
||||
// - .rsrc
|
||||
// - icon/cursor/etc data
|
||||
|
||||
std::fstream stream;
|
||||
#if PLATFORM_WINDOWS
|
||||
stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary);
|
||||
#else
|
||||
StringAsANSI<> pathAnsi(path.Get());
|
||||
stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary);
|
||||
#endif
|
||||
if (!stream.is_open())
|
||||
{
|
||||
LOG(Warning, "Cannot open file");
|
||||
return true;
|
||||
}
|
||||
|
||||
// First check magic number to ensure file is even an executable
|
||||
uint16 magicNum;
|
||||
stream.read((char*)&magicNum, sizeof(magicNum));
|
||||
if (magicNum != MSDOS_SIGNATURE)
|
||||
{
|
||||
LOG(Warning, "Provided file is not a valid executable.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read the MSDOS header and skip over it
|
||||
stream.seekg(0);
|
||||
MSDOSHeader msdosHeader;
|
||||
stream.read((char*)&msdosHeader, sizeof(MSDOSHeader));
|
||||
|
||||
// Read PE signature
|
||||
stream.seekg(msdosHeader.lfanew);
|
||||
uint32 peSignature;
|
||||
stream.read((char*)&peSignature, sizeof(peSignature));
|
||||
if (peSignature != PE_SIGNATURE)
|
||||
{
|
||||
LOG(Warning, "Provided file is not in PE format.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read COFF header
|
||||
COFFHeader coffHeader;
|
||||
stream.read((char*)&coffHeader, sizeof(COFFHeader));
|
||||
if (coffHeader.sizeOptHeader == 0)
|
||||
{
|
||||
LOG(Warning, "Provided file is not a valid executable.");
|
||||
return true;
|
||||
}
|
||||
uint32 sectionHeadersCount = coffHeader.numSections;
|
||||
|
||||
// Read optional header
|
||||
auto optionalHeaderPos = stream.tellg();
|
||||
uint16 optionalHeaderSignature;
|
||||
stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature));
|
||||
PEDataDirectory* dataDirectory = nullptr;
|
||||
stream.seekg(optionalHeaderPos);
|
||||
if (optionalHeaderSignature == PE_32BIT_SIGNATURE)
|
||||
{
|
||||
PEOptionalHeader32 optionalHeader;
|
||||
stream.read((char*)&optionalHeader, sizeof(optionalHeader));
|
||||
dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
|
||||
}
|
||||
else if (optionalHeaderSignature == PE_64BIT_SIGNATURE)
|
||||
{
|
||||
PEOptionalHeader64 optionalHeader;
|
||||
stream.read((char*)&optionalHeader, sizeof(optionalHeader));
|
||||
dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Unrecognized PE format.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read section headers
|
||||
auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader;
|
||||
stream.seekg(sectionHeaderPos);
|
||||
Array<PESectionHeader> sectionHeaders;
|
||||
sectionHeaders.Resize(sectionHeadersCount);
|
||||
stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * sectionHeadersCount);
|
||||
|
||||
// Look for .rsrc section header
|
||||
for (uint32 i = 0; i < sectionHeadersCount; i++)
|
||||
{
|
||||
PESectionHeader& sectionHeader = sectionHeaders[i];
|
||||
if (sectionHeader.flags & PE_SECTION_UNINITIALIZED_DATA)
|
||||
continue;
|
||||
if (strcmp(sectionHeader.name, ".rsrc") == 0)
|
||||
{
|
||||
uint32 imageSize = sectionHeader.physicalSize;
|
||||
Array<uint8> imageData;
|
||||
imageData.Resize(imageSize);
|
||||
|
||||
stream.seekg(sectionHeader.physicalAddress);
|
||||
stream.read((char*)imageData.Get(), imageSize);
|
||||
|
||||
uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeader.relativeVirtualAddress;
|
||||
PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset];
|
||||
|
||||
SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeader.relativeVirtualAddress, iconRGBA8);
|
||||
stream.seekp(sectionHeader.physicalAddress);
|
||||
stream.write((char*)imageData.Get(), imageSize);
|
||||
}
|
||||
}
|
||||
|
||||
stream.close();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IMPLEMENT_ENGINE_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform);
|
||||
|
||||
@@ -50,7 +502,7 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
TextureData iconData;
|
||||
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
|
||||
{
|
||||
if (EditorUtilities::UpdateExeIcon(files[0], iconData))
|
||||
if (UpdateExeIcon(files[0], iconData))
|
||||
{
|
||||
data.Error(TEXT("Failed to change output executable file icon."));
|
||||
return true;
|
||||
|
||||
@@ -543,10 +543,11 @@ namespace FlaxEditor.CustomEditors
|
||||
try
|
||||
{
|
||||
string text;
|
||||
var value = Values[0];
|
||||
if (ParentEditor is Dedicated.ScriptsEditor)
|
||||
{
|
||||
// Script
|
||||
text = JsonSerializer.Serialize(Values[0]);
|
||||
text = JsonSerializer.Serialize(value);
|
||||
|
||||
// Remove properties that should be ignored when copy/pasting data
|
||||
if (text == null)
|
||||
@@ -576,12 +577,12 @@ namespace FlaxEditor.CustomEditors
|
||||
else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type))
|
||||
{
|
||||
// Object reference
|
||||
text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object);
|
||||
text = JsonSerializer.GetStringID(value as FlaxEngine.Object);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default
|
||||
text = JsonSerializer.Serialize(Values[0]);
|
||||
text = JsonSerializer.Serialize(value, TypeUtils.GetType(Values.Type));
|
||||
}
|
||||
Clipboard.Text = text;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI;
|
||||
@@ -16,6 +18,27 @@ using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
internal class NewScriptItem : ItemsListContextMenu.Item
|
||||
{
|
||||
private string _scriptName;
|
||||
|
||||
public string ScriptName
|
||||
{
|
||||
get => _scriptName;
|
||||
set
|
||||
{
|
||||
_scriptName = value;
|
||||
Name = $"Create script '{value}'";
|
||||
}
|
||||
}
|
||||
|
||||
public NewScriptItem(string scriptName)
|
||||
{
|
||||
ScriptName = scriptName;
|
||||
TooltipText = "Create a new script";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drag and drop scripts area control.
|
||||
/// </summary>
|
||||
@@ -74,7 +97,43 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
|
||||
}
|
||||
cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
|
||||
cm.TextChanged += text =>
|
||||
{
|
||||
if (!IsValidScriptName(text))
|
||||
return;
|
||||
if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem))
|
||||
{
|
||||
// If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time
|
||||
var newScriptItem = (NewScriptItem)cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem);
|
||||
if (newScriptItem != null)
|
||||
{
|
||||
newScriptItem.Visible = true;
|
||||
newScriptItem.ScriptName = text;
|
||||
}
|
||||
else
|
||||
{
|
||||
cm.AddItem(new NewScriptItem(text));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure to hide the create script button if there
|
||||
var newScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem);
|
||||
if (newScriptItem != null)
|
||||
newScriptItem.Visible = false;
|
||||
}
|
||||
};
|
||||
cm.ItemClicked += item =>
|
||||
{
|
||||
if (item.Tag is ScriptType script)
|
||||
{
|
||||
AddScript(script);
|
||||
}
|
||||
else if (item is NewScriptItem newScriptItem)
|
||||
{
|
||||
CreateScript(newScriptItem);
|
||||
}
|
||||
};
|
||||
cm.SortItems();
|
||||
cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0));
|
||||
}
|
||||
@@ -113,6 +172,19 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsValidScriptName(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return false;
|
||||
if (text.Contains(' '))
|
||||
return false;
|
||||
if (char.IsDigit(text[0]))
|
||||
return false;
|
||||
if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
|
||||
return false;
|
||||
return Editor.Instance.ContentDatabase.GetProxy("cs").IsFileNameValid(text);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
@@ -163,6 +235,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
if (_dragScripts.HasValidDrag)
|
||||
{
|
||||
result = _dragScripts.Effect;
|
||||
|
||||
AddScripts(_dragScripts.Objects);
|
||||
}
|
||||
else if (_dragAssets.HasValidDrag)
|
||||
@@ -177,7 +250,43 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AddScript(ScriptType item)
|
||||
private void CreateScript(NewScriptItem item)
|
||||
{
|
||||
ScriptsEditor.NewScriptName = item.ScriptName;
|
||||
var paths = Directory.GetFiles(Globals.ProjectSourceFolder, "*.Build.cs");
|
||||
|
||||
string moduleName = null;
|
||||
foreach (var p in paths)
|
||||
{
|
||||
var file = File.ReadAllText(p);
|
||||
if (!file.Contains("GameProjectTarget"))
|
||||
continue; // Skip
|
||||
|
||||
if (file.Contains("Modules.Add(\"Game\")"))
|
||||
{
|
||||
// Assume Game represents the main game module
|
||||
moduleName = "Game";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the path slashes are correct for the OS
|
||||
var correctedPath = Path.GetFullPath(Globals.ProjectSourceFolder);
|
||||
if (string.IsNullOrEmpty(moduleName))
|
||||
{
|
||||
var error = FileSystem.ShowBrowseFolderDialog(Editor.Instance.Windows.MainWindow, correctedPath, "Select a module folder to put the new script in", out moduleName);
|
||||
if (error)
|
||||
return;
|
||||
}
|
||||
var path = Path.Combine(Globals.ProjectSourceFolder, moduleName, item.ScriptName + ".cs");
|
||||
Editor.Instance.ContentDatabase.GetProxy("cs").Create(path, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attach a script to the actor.
|
||||
/// </summary>
|
||||
/// <param name="item">The script.</param>
|
||||
public void AddScript(ScriptType item)
|
||||
{
|
||||
var list = new List<ScriptType>(1) { item };
|
||||
AddScripts(list);
|
||||
@@ -224,16 +333,67 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
|
||||
private void AddScripts(List<ScriptType> items)
|
||||
{
|
||||
var actions = new List<IUndoAction>(4);
|
||||
var actions = new List<IUndoAction>();
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var scriptType = items[i];
|
||||
RequireScriptAttribute scriptAttribute = null;
|
||||
if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false))
|
||||
{
|
||||
foreach (var e in scriptType.GetAttributes(false))
|
||||
{
|
||||
if (e is not RequireScriptAttribute requireScriptAttribute)
|
||||
continue;
|
||||
scriptAttribute = requireScriptAttribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// See if script requires a specific actor type
|
||||
RequireActorAttribute actorAttribute = null;
|
||||
if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
|
||||
{
|
||||
foreach (var e in scriptType.GetAttributes(false))
|
||||
{
|
||||
if (e is not RequireActorAttribute requireActorAttribute)
|
||||
continue;
|
||||
actorAttribute = requireActorAttribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var actors = ScriptsEditor.ParentEditor.Values;
|
||||
for (int j = 0; j < actors.Count; j++)
|
||||
{
|
||||
var actor = (Actor)actors[j];
|
||||
|
||||
// If required actor exists but is not this actor type then skip adding to actor
|
||||
if (actorAttribute != null)
|
||||
{
|
||||
if (actor.GetType() != actorAttribute.RequiredType && !actor.GetType().IsSubclassOf(actorAttribute.RequiredType))
|
||||
{
|
||||
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` not added to `{actor}` due to script requiring an Actor type of `{actorAttribute.RequiredType}`.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
actions.Add(AddRemoveScript.Add(actor, scriptType));
|
||||
// Check if actor has required scripts and add them if the actor does not.
|
||||
if (scriptAttribute != null)
|
||||
{
|
||||
foreach (var type in scriptAttribute.RequiredTypes)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeof(Script)))
|
||||
{
|
||||
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(type.Name)}` not added to `{actor}` due to the class not being a subclass of Script.");
|
||||
continue;
|
||||
}
|
||||
if (actor.GetScript(type) != null)
|
||||
continue;
|
||||
actions.Add(AddRemoveScript.Add(actor, new ScriptType(type)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +600,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<object> UndoObjects => _scripts;
|
||||
|
||||
/// <summary>
|
||||
/// Cached the newly created script name - used to add script after compilation.
|
||||
/// </summary>
|
||||
internal static string NewScriptName;
|
||||
|
||||
private void AddMissingScript(int index, LayoutElementsContainer layout)
|
||||
{
|
||||
var group = layout.Group("Missing script");
|
||||
@@ -548,6 +713,21 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var dragArea = layout.CustomContainer<DragAreaControl>();
|
||||
dragArea.CustomControl.ScriptsEditor = this;
|
||||
|
||||
// If the initialization is triggered by an editor recompilation, check if it was due to script generation from DragAreaControl
|
||||
if (NewScriptName != null)
|
||||
{
|
||||
var script = Editor.Instance.CodeEditing.Scripts.Get().FirstOrDefault(x => x.Name == NewScriptName);
|
||||
NewScriptName = null;
|
||||
if (script != null)
|
||||
{
|
||||
dragArea.CustomControl.AddScript(script);
|
||||
}
|
||||
else
|
||||
{
|
||||
Editor.LogWarning("Failed to find newly created script.");
|
||||
}
|
||||
}
|
||||
|
||||
// No support for showing scripts from multiple actors that have different set of scripts
|
||||
var scripts = (Script[])Values[0];
|
||||
_scripts.Clear();
|
||||
@@ -586,19 +766,71 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var scriptType = TypeUtils.GetObjectType(script);
|
||||
var editor = CustomEditorsUtil.CreateEditor(scriptType, false);
|
||||
|
||||
// Check if actor has all the required scripts
|
||||
bool hasAllRequirements = true;
|
||||
if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false))
|
||||
{
|
||||
RequireScriptAttribute scriptAttribute = null;
|
||||
foreach (var e in scriptType.GetAttributes(false))
|
||||
{
|
||||
if (e is not RequireScriptAttribute requireScriptAttribute)
|
||||
continue;
|
||||
scriptAttribute = requireScriptAttribute;
|
||||
}
|
||||
|
||||
if (scriptAttribute != null)
|
||||
{
|
||||
foreach (var type in scriptAttribute.RequiredTypes)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeof(Script)))
|
||||
continue;
|
||||
var requiredScript = script.Actor.GetScript(type);
|
||||
if (requiredScript == null)
|
||||
{
|
||||
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Script of type `{type}`.");
|
||||
hasAllRequirements = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
|
||||
{
|
||||
RequireActorAttribute attribute = null;
|
||||
foreach (var e in scriptType.GetAttributes(false))
|
||||
{
|
||||
if (e is not RequireActorAttribute requireActorAttribute)
|
||||
continue;
|
||||
attribute = requireActorAttribute;
|
||||
break;
|
||||
}
|
||||
|
||||
if (attribute != null)
|
||||
{
|
||||
var actor = script.Actor;
|
||||
if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType))
|
||||
{
|
||||
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`.");
|
||||
hasAllRequirements = false;
|
||||
// Maybe call to remove script here?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create group
|
||||
var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name);
|
||||
var group = layout.Group(title, editor);
|
||||
if (!hasAllRequirements)
|
||||
group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed;
|
||||
if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
|
||||
{
|
||||
if (Editor.Instance.ProjectCache.IsCollapsedGroup(title))
|
||||
group.Panel.Close(false);
|
||||
if (Editor.Instance.ProjectCache.IsGroupToggled(title))
|
||||
group.Panel.Close();
|
||||
else
|
||||
group.Panel.Open(false);
|
||||
group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed);
|
||||
group.Panel.Open();
|
||||
group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(panel.HeaderText, panel.IsClosed);
|
||||
}
|
||||
else
|
||||
group.Panel.Open(false);
|
||||
group.Panel.Open();
|
||||
|
||||
// Customize
|
||||
group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
@@ -51,10 +51,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
return;
|
||||
Picker = layout.Custom<AssetPicker>().CustomControl;
|
||||
|
||||
_valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
|
||||
var value = Values[0];
|
||||
_valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value);
|
||||
var assetType = _valueType;
|
||||
if (assetType == typeof(string))
|
||||
assetType = new ScriptType(typeof(Asset));
|
||||
else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name)
|
||||
assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]);
|
||||
|
||||
float height = 48;
|
||||
var attributes = Values.GetAttributes();
|
||||
@@ -102,6 +105,12 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
SetValue(new SceneReference(Picker.Validator.SelectedID));
|
||||
else if (_valueType.Type == typeof(string))
|
||||
SetValue(Picker.Validator.SelectedPath);
|
||||
else if (_valueType.Type.Name == typeof(JsonAssetReference<>).Name)
|
||||
{
|
||||
var value = Values[0];
|
||||
value.GetType().GetField("Asset").SetValue(value, Picker.Validator.SelectedAsset as JsonAsset);
|
||||
SetValue(value);
|
||||
}
|
||||
else
|
||||
SetValue(Picker.Validator.SelectedAsset);
|
||||
}
|
||||
@@ -114,16 +123,19 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (!HasDifferentValues)
|
||||
{
|
||||
_isRefreshing = true;
|
||||
if (Values[0] is AssetItem assetItem)
|
||||
var value = Values[0];
|
||||
if (value is AssetItem assetItem)
|
||||
Picker.Validator.SelectedItem = assetItem;
|
||||
else if (Values[0] is Guid guid)
|
||||
else if (value is Guid guid)
|
||||
Picker.Validator.SelectedID = guid;
|
||||
else if (Values[0] is SceneReference sceneAsset)
|
||||
else if (value is SceneReference sceneAsset)
|
||||
Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
|
||||
else if (Values[0] is string path)
|
||||
else if (value is string path)
|
||||
Picker.Validator.SelectedPath = path;
|
||||
else if (value != null && value.GetType().Name == typeof(JsonAssetReference<>).Name)
|
||||
Picker.Validator.SelectedAsset = value.GetType().GetField("Asset").GetValue(value) as JsonAsset;
|
||||
else
|
||||
Picker.Validator.SelectedAsset = Values[0] as Asset;
|
||||
Picker.Validator.SelectedAsset = value as Asset;
|
||||
_isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,8 +176,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
private IntValueBox _sizeBox;
|
||||
private Color _background;
|
||||
private int _elementsCount;
|
||||
private bool _readOnly;
|
||||
private int _elementsCount, _minCount, _maxCount;
|
||||
private bool _canResize;
|
||||
private bool _canReorderItems;
|
||||
private CollectionAttribute.DisplayType _displayType;
|
||||
|
||||
@@ -209,8 +209,10 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
return;
|
||||
|
||||
var size = Count;
|
||||
_readOnly = false;
|
||||
_canResize = true;
|
||||
_canReorderItems = true;
|
||||
_minCount = 0;
|
||||
_maxCount = 0;
|
||||
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
|
||||
_displayType = CollectionAttribute.DisplayType.Header;
|
||||
NotNullItems = false;
|
||||
@@ -222,7 +224,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
|
||||
if (collection != null)
|
||||
{
|
||||
_readOnly = collection.ReadOnly;
|
||||
_canResize = !collection.ReadOnly;
|
||||
_minCount = collection.MinCount;
|
||||
_maxCount = collection.MaxCount;
|
||||
_canReorderItems = collection.CanReorderItems;
|
||||
NotNullItems = collection.NotNullItems;
|
||||
if (collection.BackgroundColor.HasValue)
|
||||
@@ -231,6 +235,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
spacing = collection.Spacing;
|
||||
_displayType = collection.Display;
|
||||
}
|
||||
if (_maxCount == 0)
|
||||
_maxCount = ushort.MaxValue;
|
||||
_canResize &= _minCount < _maxCount;
|
||||
|
||||
var dragArea = layout.CustomContainer<DragAreaControl>();
|
||||
dragArea.CustomControl.Editor = this;
|
||||
@@ -268,8 +275,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top;
|
||||
_sizeBox = new IntValueBox(size)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = ushort.MaxValue,
|
||||
MinValue = _minCount,
|
||||
MaxValue = _maxCount,
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height),
|
||||
Parent = dropPanel,
|
||||
@@ -283,7 +290,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
Parent = dropPanel
|
||||
};
|
||||
|
||||
if (_readOnly || (NotNullItems && size == 0))
|
||||
if (!_canResize || (NotNullItems && size == 0))
|
||||
{
|
||||
_sizeBox.IsReadOnly = true;
|
||||
_sizeBox.Enabled = false;
|
||||
@@ -339,7 +346,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
_elementsCount = size;
|
||||
|
||||
// Add/Remove buttons
|
||||
if (!_readOnly)
|
||||
if (_canResize)
|
||||
{
|
||||
var panel = dragArea.HorizontalPanel();
|
||||
panel.Panel.Size = new Float2(0, 20);
|
||||
@@ -347,25 +354,23 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
var removeButton = panel.Button("-", "Remove last item");
|
||||
removeButton.Button.Size = new Float2(16, 16);
|
||||
removeButton.Button.Enabled = size > 0;
|
||||
removeButton.Button.Enabled = size > _minCount;
|
||||
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||
removeButton.Button.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count - 1);
|
||||
};
|
||||
|
||||
var addButton = panel.Button("+", "Add new item");
|
||||
addButton.Button.Size = new Float2(16, 16);
|
||||
addButton.Button.Enabled = !NotNullItems || size > 0;
|
||||
addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount;
|
||||
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||
addButton.Button.Clicked += () =>
|
||||
{
|
||||
if (IsSetBlocked)
|
||||
return;
|
||||
|
||||
Resize(Count + 1);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
internal bool isRootGroup = true;
|
||||
|
||||
/// <summary>
|
||||
/// Parent container who created this one.
|
||||
/// </summary>
|
||||
internal LayoutElementsContainer _parent;
|
||||
|
||||
/// <summary>
|
||||
/// The children.
|
||||
/// </summary>
|
||||
@@ -40,6 +45,24 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
public abstract ContainerControl ContainerControl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Custom Editors layout presenter.
|
||||
/// </summary>
|
||||
internal CustomEditorPresenter Presenter
|
||||
{
|
||||
get
|
||||
{
|
||||
CustomEditorPresenter result;
|
||||
var container = this;
|
||||
do
|
||||
{
|
||||
result = container as CustomEditorPresenter;
|
||||
container = container._parent;
|
||||
} while (container != null);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new group element.
|
||||
/// </summary>
|
||||
@@ -81,17 +104,31 @@ namespace FlaxEditor.CustomEditors
|
||||
public GroupElement Group(string title, bool useTransparentHeader = false)
|
||||
{
|
||||
var element = new GroupElement();
|
||||
if (!isRootGroup)
|
||||
var presenter = Presenter;
|
||||
var isSubGroup = !isRootGroup;
|
||||
if (isSubGroup)
|
||||
element.Panel.Close();
|
||||
if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
|
||||
{
|
||||
element.Panel.Close(false);
|
||||
}
|
||||
else if (this is CustomEditorPresenter presenter && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
|
||||
{
|
||||
if (Editor.Instance.ProjectCache.IsCollapsedGroup(title))
|
||||
element.Panel.Close(false);
|
||||
element.Panel.IsClosedChanged += OnPanelIsClosedChanged;
|
||||
// Build group identifier (made of path from group titles)
|
||||
var expandPath = 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)
|
||||
if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup)
|
||||
element.Panel.Close();
|
||||
else
|
||||
element.Panel.Open();
|
||||
element.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(expandPath, panel.IsClosed ^ isSubGroup);
|
||||
}
|
||||
element.isRootGroup = false;
|
||||
element._parent = this;
|
||||
element.Panel.HeaderText = title;
|
||||
if (useTransparentHeader)
|
||||
{
|
||||
@@ -103,11 +140,6 @@ namespace FlaxEditor.CustomEditors
|
||||
return element;
|
||||
}
|
||||
|
||||
private void OnPanelIsClosedChanged(DropPanel panel)
|
||||
{
|
||||
Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new horizontal panel element.
|
||||
/// </summary>
|
||||
@@ -627,7 +659,6 @@ namespace FlaxEditor.CustomEditors
|
||||
if (style == DisplayStyle.Group)
|
||||
{
|
||||
var group = Group(name, editor, true);
|
||||
group.Panel.Close(false);
|
||||
group.Panel.TooltipText = tooltip;
|
||||
return group.Object(values, editor);
|
||||
}
|
||||
@@ -657,7 +688,6 @@ namespace FlaxEditor.CustomEditors
|
||||
if (style == DisplayStyle.Group)
|
||||
{
|
||||
var group = Group(label.Text, editor, true);
|
||||
group.Panel.Close(false);
|
||||
group.Panel.TooltipText = tooltip;
|
||||
return group.Object(values, editor);
|
||||
}
|
||||
|
||||
+50
-5
@@ -869,7 +869,9 @@ namespace FlaxEditor
|
||||
|
||||
/// <summary>
|
||||
/// New asset types allowed to create.
|
||||
/// [Deprecated in v1.8]
|
||||
/// </summary>
|
||||
[Obsolete("Use CreateAsset with named tag.")]
|
||||
public enum NewAssetType
|
||||
{
|
||||
/// <summary>
|
||||
@@ -1046,12 +1048,59 @@ namespace FlaxEditor
|
||||
|
||||
/// <summary>
|
||||
/// Creates new asset at the target location.
|
||||
/// [Deprecated in v1.8]
|
||||
/// </summary>
|
||||
/// <param name="type">New asset type.</param>
|
||||
/// <param name="outputPath">Output asset path.</param>
|
||||
[Obsolete("Use CreateAsset with named tag.")]
|
||||
public static bool CreateAsset(NewAssetType type, string outputPath)
|
||||
{
|
||||
return Internal_CreateAsset(type, outputPath);
|
||||
// [Deprecated on 18.02.2024, expires on 18.02.2025]
|
||||
string tag;
|
||||
switch (type)
|
||||
{
|
||||
case NewAssetType.Material:
|
||||
tag = "Material";
|
||||
break;
|
||||
case NewAssetType.MaterialInstance:
|
||||
tag = "MaterialInstance";
|
||||
break;
|
||||
case NewAssetType.CollisionData:
|
||||
tag = "CollisionData";
|
||||
break;
|
||||
case NewAssetType.AnimationGraph:
|
||||
tag = "AnimationGraph";
|
||||
break;
|
||||
case NewAssetType.SkeletonMask:
|
||||
tag = "SkeletonMask";
|
||||
break;
|
||||
case NewAssetType.ParticleEmitter:
|
||||
tag = "ParticleEmitter";
|
||||
break;
|
||||
case NewAssetType.ParticleSystem:
|
||||
tag = "ParticleSystem";
|
||||
break;
|
||||
case NewAssetType.SceneAnimation:
|
||||
tag = "SceneAnimation";
|
||||
break;
|
||||
case NewAssetType.MaterialFunction:
|
||||
tag = "MaterialFunction";
|
||||
break;
|
||||
case NewAssetType.ParticleEmitterFunction:
|
||||
tag = "ParticleEmitterFunction";
|
||||
break;
|
||||
case NewAssetType.AnimationGraphFunction:
|
||||
tag = "AnimationGraphFunction";
|
||||
break;
|
||||
case NewAssetType.Animation:
|
||||
tag = "Animation";
|
||||
break;
|
||||
case NewAssetType.BehaviorTree:
|
||||
tag = "BehaviorTree";
|
||||
break;
|
||||
default: return true;
|
||||
}
|
||||
return CreateAsset(tag, outputPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1588,10 +1637,6 @@ namespace FlaxEditor
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
internal static partial void Internal_CloseSplashScreen();
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_CreateAsset(NewAssetType type, string outputPath);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename);
|
||||
|
||||
@@ -54,6 +54,11 @@ namespace FlaxEditor
|
||||
/// </summary>
|
||||
public static string PrimaryFont = "Editor/Fonts/Roboto-Regular";
|
||||
|
||||
/// <summary>
|
||||
/// The secondary (fallback) font to use for missing characters rendering (CJK - Chinese/Japanese/Korean characters).
|
||||
/// </summary>
|
||||
public static string FallbackFont = "Editor/Fonts/NotoSansSC-Regular";
|
||||
|
||||
/// <summary>
|
||||
/// The Inconsolata Regular font.
|
||||
/// </summary>
|
||||
|
||||
@@ -189,6 +189,11 @@ namespace FlaxEditor.GUI
|
||||
/// </summary>
|
||||
public event Action<Item> ItemClicked;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when search text in this popup menu gets changed.
|
||||
/// </summary>
|
||||
public event Action<string> TextChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The panel control where you should add your items.
|
||||
/// </summary>
|
||||
@@ -263,6 +268,7 @@ namespace FlaxEditor.GUI
|
||||
UnlockChildrenRecursive();
|
||||
PerformLayout(true);
|
||||
_searchBox.Focus();
|
||||
TextChanged?.Invoke(_searchBox.Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -439,6 +445,7 @@ namespace FlaxEditor.GUI
|
||||
Hide();
|
||||
return true;
|
||||
case KeyboardKeys.ArrowDown:
|
||||
{
|
||||
if (RootWindow.FocusedControl == null)
|
||||
{
|
||||
// Focus search box if nothing is focused
|
||||
@@ -447,20 +454,19 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
|
||||
// Focus the first visible item or then next one
|
||||
var items = GetVisibleItems();
|
||||
var focusedIndex = items.IndexOf(focusedItem);
|
||||
if (focusedIndex == -1)
|
||||
focusedIndex = -1;
|
||||
if (focusedIndex + 1 < items.Count)
|
||||
{
|
||||
var items = GetVisibleItems();
|
||||
var focusedIndex = items.IndexOf(focusedItem);
|
||||
if (focusedIndex == -1)
|
||||
focusedIndex = -1;
|
||||
if (focusedIndex + 1 < items.Count)
|
||||
{
|
||||
var item = items[focusedIndex + 1];
|
||||
item.Focus();
|
||||
_scrollPanel.ScrollViewTo(item);
|
||||
return true;
|
||||
}
|
||||
var item = items[focusedIndex + 1];
|
||||
item.Focus();
|
||||
_scrollPanel.ScrollViewTo(item);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KeyboardKeys.ArrowUp:
|
||||
if (focusedItem != null)
|
||||
{
|
||||
|
||||
@@ -43,8 +43,9 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
Depth = -1;
|
||||
|
||||
if (Height < Style.Current.FontMedium.Height)
|
||||
Height = Style.Current.FontMedium.Height + 4;
|
||||
var fontHeight = Style.Current.FontMedium.Height;
|
||||
if (Height < fontHeight)
|
||||
Height = fontHeight + 4;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs
|
||||
/// </summary>
|
||||
public Tab SelectedTab
|
||||
{
|
||||
get => _selectedIndex < 0 || Children.Count <= _selectedIndex ? null : Children[_selectedIndex + 1] as Tab;
|
||||
get => _selectedIndex < 0 || Children.Count <= (_selectedIndex+1) ? null : Children[_selectedIndex + 1] as Tab;
|
||||
set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,78 +170,6 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CloneAssetFile(MString* dstPathObj, MS
|
||||
return Content::CloneAssetFile(dstPath, srcPath, *dstId);
|
||||
}
|
||||
|
||||
enum class NewAssetType
|
||||
{
|
||||
Material = 0,
|
||||
MaterialInstance = 1,
|
||||
CollisionData = 2,
|
||||
AnimationGraph = 3,
|
||||
SkeletonMask = 4,
|
||||
ParticleEmitter = 5,
|
||||
ParticleSystem = 6,
|
||||
SceneAnimation = 7,
|
||||
MaterialFunction = 8,
|
||||
ParticleEmitterFunction = 9,
|
||||
AnimationGraphFunction = 10,
|
||||
Animation = 11,
|
||||
BehaviorTree = 12,
|
||||
};
|
||||
|
||||
DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj)
|
||||
{
|
||||
String tag;
|
||||
switch (type)
|
||||
{
|
||||
case NewAssetType::Material:
|
||||
tag = AssetsImportingManager::CreateMaterialTag;
|
||||
break;
|
||||
case NewAssetType::MaterialInstance:
|
||||
tag = AssetsImportingManager::CreateMaterialInstanceTag;
|
||||
break;
|
||||
case NewAssetType::CollisionData:
|
||||
tag = AssetsImportingManager::CreateCollisionDataTag;
|
||||
break;
|
||||
case NewAssetType::AnimationGraph:
|
||||
tag = AssetsImportingManager::CreateAnimationGraphTag;
|
||||
break;
|
||||
case NewAssetType::SkeletonMask:
|
||||
tag = AssetsImportingManager::CreateSkeletonMaskTag;
|
||||
break;
|
||||
case NewAssetType::ParticleEmitter:
|
||||
tag = AssetsImportingManager::CreateParticleEmitterTag;
|
||||
break;
|
||||
case NewAssetType::ParticleSystem:
|
||||
tag = AssetsImportingManager::CreateParticleSystemTag;
|
||||
break;
|
||||
case NewAssetType::SceneAnimation:
|
||||
tag = AssetsImportingManager::CreateSceneAnimationTag;
|
||||
break;
|
||||
case NewAssetType::MaterialFunction:
|
||||
tag = AssetsImportingManager::CreateMaterialFunctionTag;
|
||||
break;
|
||||
case NewAssetType::ParticleEmitterFunction:
|
||||
tag = AssetsImportingManager::CreateParticleEmitterFunctionTag;
|
||||
break;
|
||||
case NewAssetType::AnimationGraphFunction:
|
||||
tag = AssetsImportingManager::CreateAnimationGraphFunctionTag;
|
||||
break;
|
||||
case NewAssetType::Animation:
|
||||
tag = AssetsImportingManager::CreateAnimationTag;
|
||||
break;
|
||||
case NewAssetType::BehaviorTree:
|
||||
tag = AssetsImportingManager::CreateBehaviorTreeTag;
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
String outputPath;
|
||||
MUtils::ToString(outputPathObj, outputPath);
|
||||
FileSystem::NormalizePath(outputPath);
|
||||
|
||||
return AssetsImportingManager::Create(tag, outputPath);
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateVisualScript(MString* outputPathObj, MString* baseTypenameObj)
|
||||
{
|
||||
String outputPath;
|
||||
@@ -634,13 +562,11 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co
|
||||
|
||||
bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String assetPath)
|
||||
{
|
||||
// Initialize defaults
|
||||
// Initialize defaults
|
||||
if (const auto* graphicsSettings = GraphicsSettings::Get())
|
||||
{
|
||||
options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport;
|
||||
}
|
||||
|
||||
// Get options from model
|
||||
FileSystem::NormalizePath(assetPath);
|
||||
return ImportModel::TryGetImportOptions(assetPath, options);
|
||||
}
|
||||
@@ -652,7 +578,12 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co
|
||||
|
||||
bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath)
|
||||
{
|
||||
// Get options from model
|
||||
FileSystem::NormalizePath(assetPath);
|
||||
return ImportAudio::TryGetImportOptions(assetPath, options);
|
||||
}
|
||||
|
||||
bool ManagedEditor::CreateAsset(const String& tag, String outputPath)
|
||||
{
|
||||
FileSystem::NormalizePath(outputPath);
|
||||
return AssetsImportingManager::Create(tag, outputPath);
|
||||
}
|
||||
|
||||
@@ -210,6 +210,13 @@ public:
|
||||
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new asset at the target location.
|
||||
/// </summary>
|
||||
/// <param name="tag">New asset type.</param>
|
||||
/// <param name="outputPath">Output asset path.</param>
|
||||
API_FUNCTION() static bool CreateAsset(const String& tag, String outputPath);
|
||||
|
||||
public:
|
||||
API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace FlaxEditor.Modules
|
||||
private DateTime _lastSaveTime;
|
||||
|
||||
private readonly HashSet<Guid> _expandedActors = new HashSet<Guid>();
|
||||
private readonly HashSet<string> _collapsedGroups = new HashSet<string>();
|
||||
private readonly HashSet<string> _toggledGroups = new HashSet<string>();
|
||||
private readonly Dictionary<string, string> _customData = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
@@ -62,26 +62,26 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether group identified by the given title is collapsed in the UI.
|
||||
/// Determines whether group identified by the given title is collapsed/opened in the UI.
|
||||
/// </summary>
|
||||
/// <param name="title">The group title.</param>
|
||||
/// <returns><c>true</c> if group is collapsed; otherwise, <c>false</c>.</returns>
|
||||
public bool IsCollapsedGroup(string title)
|
||||
/// <returns><c>true</c> if group is toggled; otherwise, <c>false</c>.</returns>
|
||||
public bool IsGroupToggled(string title)
|
||||
{
|
||||
return _collapsedGroups.Contains(title);
|
||||
return _toggledGroups.Contains(title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the group collapsed cached value.
|
||||
/// Sets the group collapsed/opened cached value.
|
||||
/// </summary>
|
||||
/// <param name="title">The group title.</param>
|
||||
/// <param name="isCollapsed">If set to <c>true</c> group will be cached as an collapsed, otherwise false.</param>
|
||||
public void SetCollapsedGroup(string title, bool isCollapsed)
|
||||
/// <param name="isToggled">If set to <c>true</c> group will be cached as a toggled, otherwise false.</param>
|
||||
public void SetGroupToggle(string title, bool isToggled)
|
||||
{
|
||||
if (isCollapsed)
|
||||
_collapsedGroups.Add(title);
|
||||
if (isToggled)
|
||||
_toggledGroups.Add(title);
|
||||
else
|
||||
_collapsedGroups.Remove(title);
|
||||
_toggledGroups.Remove(title);
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace FlaxEditor.Modules
|
||||
_expandedActors.Add(new Guid(bytes16));
|
||||
}
|
||||
|
||||
_collapsedGroups.Clear();
|
||||
_toggledGroups.Clear();
|
||||
_customData.Clear();
|
||||
|
||||
break;
|
||||
@@ -176,7 +176,7 @@ namespace FlaxEditor.Modules
|
||||
_expandedActors.Add(new Guid(bytes16));
|
||||
}
|
||||
|
||||
_collapsedGroups.Clear();
|
||||
_toggledGroups.Clear();
|
||||
|
||||
_customData.Clear();
|
||||
int customDataCount = reader.ReadInt32();
|
||||
@@ -201,11 +201,9 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
|
||||
int collapsedGroupsCount = reader.ReadInt32();
|
||||
_collapsedGroups.Clear();
|
||||
_toggledGroups.Clear();
|
||||
for (int i = 0; i < collapsedGroupsCount; i++)
|
||||
{
|
||||
_collapsedGroups.Add(reader.ReadString());
|
||||
}
|
||||
_toggledGroups.Add(reader.ReadString());
|
||||
|
||||
_customData.Clear();
|
||||
int customDataCount = reader.ReadInt32();
|
||||
@@ -259,11 +257,9 @@ namespace FlaxEditor.Modules
|
||||
writer.Write(e.ToByteArray());
|
||||
}
|
||||
|
||||
writer.Write(_collapsedGroups.Count);
|
||||
foreach (var e in _collapsedGroups)
|
||||
{
|
||||
writer.Write(_toggledGroups.Count);
|
||||
foreach (var e in _toggledGroups)
|
||||
writer.Write(e);
|
||||
}
|
||||
|
||||
writer.Write(_customData.Count);
|
||||
foreach (var e in _customData)
|
||||
@@ -284,7 +280,6 @@ namespace FlaxEditor.Modules
|
||||
try
|
||||
{
|
||||
SaveGuarded();
|
||||
|
||||
_isDirty = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -534,6 +534,41 @@ namespace FlaxEditor.Modules
|
||||
Delete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create parent for selected actors.
|
||||
/// </summary>
|
||||
public void CreateParentForSelectedActors()
|
||||
{
|
||||
Actor actor = new EmptyActor();
|
||||
Editor.SceneEditing.Spawn(actor, null, false);
|
||||
List<SceneGraphNode> selection = Editor.SceneEditing.Selection;
|
||||
var actors = selection.Where(x => x is ActorNode).Select(x => ((ActorNode)x).Actor);
|
||||
using (new UndoMultiBlock(Undo, actors, "Reparent actors"))
|
||||
{
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (selection[i] is ActorNode node)
|
||||
{
|
||||
if (node.ParentNode != node.ParentScene) // If parent node is not a scene
|
||||
{
|
||||
if (selection.Contains(node.ParentNode))
|
||||
{
|
||||
continue; // If parent and child nodes selected together, don't touch child nodes
|
||||
}
|
||||
|
||||
// Put created node as child of the Parent Node of node
|
||||
int parentOrder = node.Actor.OrderInParent;
|
||||
actor.Parent = node.Actor.Parent;
|
||||
actor.OrderInParent = parentOrder;
|
||||
}
|
||||
node.Actor.Parent = actor;
|
||||
}
|
||||
}
|
||||
}
|
||||
Editor.SceneEditing.Select(actor);
|
||||
Editor.Scene.GetActorNode(actor).TreeNode.StartRenaming(Editor.Windows.SceneWin, Editor.Windows.SceneWin.SceneTreePanel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Duplicates the selected objects. Supports undo/redo.
|
||||
/// </summary>
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace FlaxEditor.Modules
|
||||
private ContextMenuButton _menuEditCut;
|
||||
private ContextMenuButton _menuEditCopy;
|
||||
private ContextMenuButton _menuEditPaste;
|
||||
private ContextMenuButton _menuCreateParentForSelectedActors;
|
||||
private ContextMenuButton _menuEditDelete;
|
||||
private ContextMenuButton _menuEditDuplicate;
|
||||
private ContextMenuButton _menuEditSelectAll;
|
||||
@@ -535,6 +536,7 @@ namespace FlaxEditor.Modules
|
||||
_menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile);
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Open project...", OpenProject);
|
||||
cm.AddButton("Reload project", ReloadProject);
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Exit", "Alt+F4", () => Editor.Windows.MainWindow.Close(ClosingReason.User));
|
||||
|
||||
@@ -548,11 +550,11 @@ namespace FlaxEditor.Modules
|
||||
_menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
|
||||
_menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
|
||||
_menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
|
||||
cm.AddSeparator();
|
||||
_menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
|
||||
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
|
||||
cm.AddSeparator();
|
||||
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
|
||||
_menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors);
|
||||
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Game Settings", () =>
|
||||
@@ -822,6 +824,13 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private void ReloadProject()
|
||||
{
|
||||
// Open project, then close it
|
||||
Editor.OpenProject(Editor.GameProject.ProjectPath);
|
||||
Editor.Windows.MainWindow.Close(ClosingReason.User);
|
||||
}
|
||||
|
||||
private void OnMenuFileShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
@@ -858,6 +867,7 @@ namespace FlaxEditor.Modules
|
||||
_menuEditCut.Enabled = hasSthSelected;
|
||||
_menuEditCopy.Enabled = hasSthSelected;
|
||||
_menuEditPaste.Enabled = canEditScene;
|
||||
_menuCreateParentForSelectedActors.Enabled = canEditScene && hasSthSelected;
|
||||
_menuEditDelete.Enabled = hasSthSelected;
|
||||
_menuEditDuplicate.Enabled = hasSthSelected;
|
||||
_menuEditSelectAll.Enabled = Level.IsAnySceneLoaded;
|
||||
|
||||
@@ -725,9 +725,7 @@ namespace FlaxEditor.Modules
|
||||
for (int i = 0; i < Windows.Count; i++)
|
||||
{
|
||||
if (string.Equals(Windows[i].SerializationTypename, typename, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Windows[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's an asset ID
|
||||
|
||||
@@ -172,9 +172,9 @@ namespace FlaxEditor.Options
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
_outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
|
||||
_outputLogFont = new FontReference(ConsoleFont, 10);
|
||||
else if (!value.Font)
|
||||
_outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);
|
||||
_outputLogFont.Font = ConsoleFont;
|
||||
else
|
||||
_outputLogFont = value;
|
||||
}
|
||||
@@ -237,11 +237,19 @@ namespace FlaxEditor.Options
|
||||
public int NumberOfGameClientsToLaunch = 1;
|
||||
|
||||
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
|
||||
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);
|
||||
|
||||
private FontReference _titleFont = new FontReference(DefaultFont, 18);
|
||||
private FontReference _largeFont = new FontReference(DefaultFont, 14);
|
||||
private FontReference _mediumFont = new FontReference(DefaultFont, 9);
|
||||
private FontReference _smallFont = new FontReference(DefaultFont, 9);
|
||||
private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
|
||||
private FontReference _outputLogFont = new FontReference(ConsoleFont, 10);
|
||||
|
||||
/// <summary>
|
||||
/// The list of fallback fonts to use when main text font is missing certain characters. Empty to use fonts from GraphicsSettings.
|
||||
/// </summary>
|
||||
[EditorDisplay("Fonts"), EditorOrder(650)]
|
||||
public FontAsset[] FallbackFonts = new FontAsset[1] { FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.FallbackFont) };
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title font for editor UI.
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content.Settings;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -217,12 +219,18 @@ namespace FlaxEditor.Options
|
||||
if (styleName == ThemeOptions.LightDefault)
|
||||
{
|
||||
Style.Current = CreateLightStyle();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Style.Current = CreateDefaultStyle();
|
||||
}
|
||||
}
|
||||
|
||||
// Set fallback fonts
|
||||
var fallbackFonts = Options.Interface.FallbackFonts;
|
||||
if (fallbackFonts == null || fallbackFonts.Length == 0 || fallbackFonts.All(x => x == null))
|
||||
fallbackFonts = GameSettings.Load<GraphicsSettings>().FallbackFonts;
|
||||
Font.FallbackFonts = fallbackFonts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
base.OnDebugDraw(data);
|
||||
|
||||
var transform = Actor.Transform;
|
||||
DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, Color.Red, 0.0f, false);
|
||||
DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, 0.5f, Color.Red, 0.0f, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,14 @@ using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Json;
|
||||
using FlaxEngine.Utilities;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.Actors
|
||||
@@ -288,6 +291,8 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
|
||||
private const Real PointNodeSize = 1.5f;
|
||||
private const Real TangentNodeSize = 1.0f;
|
||||
private const Real SnapIndicatorSize = 1.7f;
|
||||
private const Real SnapPointIndicatorSize = 2f;
|
||||
|
||||
/// <inheritdoc />
|
||||
public SplineNode(Actor actor)
|
||||
@@ -297,9 +302,26 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
FlaxEngine.Scripting.Update += OnUpdate;
|
||||
}
|
||||
|
||||
private unsafe void OnUpdate()
|
||||
private void OnUpdate()
|
||||
{
|
||||
// If this node's point is selected
|
||||
var selection = Editor.Instance.SceneEditing.Selection;
|
||||
if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this)
|
||||
{
|
||||
if (Input.Keyboard.GetKey(KeyboardKeys.Shift))
|
||||
EditSplineWithSnap(selectedPoint);
|
||||
|
||||
var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero;
|
||||
var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right);
|
||||
if (requestAddSplinePoint && canAddSplinePoint)
|
||||
AddSplinePoint(selectedPoint);
|
||||
}
|
||||
|
||||
SyncSplineKeyframeWithNodes();
|
||||
}
|
||||
|
||||
private unsafe void SyncSplineKeyframeWithNodes()
|
||||
{
|
||||
// Sync spline points with gizmo handles
|
||||
var actor = (Spline)Actor;
|
||||
var dstCount = actor.SplinePointsCount;
|
||||
if (dstCount > 1 && actor.IsLoop)
|
||||
@@ -329,6 +351,119 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void AddSplinePoint(SplinePointNode selectedPoint)
|
||||
{
|
||||
// Check mouse hit on scene
|
||||
var spline = (Spline)Actor;
|
||||
var viewport = Editor.Instance.Windows.EditWin.Viewport;
|
||||
var mouseRay = viewport.MouseRay;
|
||||
var viewRay = viewport.ViewRay;
|
||||
var flags = RayCastData.FlagTypes.SkipColliders | RayCastData.FlagTypes.SkipEditorPrimitives;
|
||||
var hit = Editor.Instance.Scene.Root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags);
|
||||
if (hit == null)
|
||||
return;
|
||||
|
||||
// Undo data
|
||||
var oldSpline = spline.SplineKeyframes;
|
||||
var editAction = new EditSplineAction(spline, oldSpline);
|
||||
Root.Undo.AddAction(editAction);
|
||||
|
||||
// Get spline point to duplicate
|
||||
var hitPoint = mouseRay.Position + mouseRay.Direction * closest;
|
||||
var lastPointIndex = selectedPoint.Index;
|
||||
var newPointIndex = lastPointIndex > 0 ? lastPointIndex + 1 : 0;
|
||||
var lastKeyframe = spline.GetSplineKeyframe(lastPointIndex);
|
||||
var isLastPoint = lastPointIndex == spline.SplinePointsCount - 1;
|
||||
var isFirstPoint = lastPointIndex == 0;
|
||||
|
||||
// Get data to create new point
|
||||
var lastPointTime = spline.GetSplineTime(lastPointIndex);
|
||||
var nextPointTime = isLastPoint ? lastPointTime : spline.GetSplineTime(newPointIndex);
|
||||
var newTime = isLastPoint ? lastPointTime + 1.0f : (lastPointTime + nextPointTime) * 0.5f;
|
||||
var distanceFromLastPoint = Vector3.Distance(hitPoint, spline.GetSplinePoint(lastPointIndex));
|
||||
var newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint;
|
||||
|
||||
// Set correctly keyframe direction on spawn point
|
||||
if (isFirstPoint)
|
||||
newPointDirection = hitPoint - spline.GetSplineTangent(lastPointIndex, true).Translation;
|
||||
else if (isLastPoint)
|
||||
newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint;
|
||||
var newPointLocalPosition = spline.Transform.WorldToLocal(hitPoint);
|
||||
var newPointLocalOrientation = Quaternion.LookRotation(newPointDirection);
|
||||
|
||||
// Add new point
|
||||
spline.InsertSplinePoint(newPointIndex, newTime, Transform.Identity, false);
|
||||
var newKeyframe = lastKeyframe.DeepClone();
|
||||
var newKeyframeTransform = newKeyframe.Value;
|
||||
newKeyframeTransform.Translation = newPointLocalPosition;
|
||||
newKeyframeTransform.Orientation = newPointLocalOrientation;
|
||||
newKeyframe.Value = newKeyframeTransform;
|
||||
|
||||
// Set new point keyframe
|
||||
var newKeyframeTangentIn = Transform.Identity;
|
||||
var newKeyframeTangentOut = Transform.Identity;
|
||||
newKeyframeTangentIn.Translation = (Vector3.Forward * newPointLocalOrientation) * distanceFromLastPoint;
|
||||
newKeyframeTangentOut.Translation = (Vector3.Backward * newPointLocalOrientation) * distanceFromLastPoint;
|
||||
newKeyframe.TangentIn = newKeyframeTangentIn;
|
||||
newKeyframe.TangentOut = newKeyframeTangentOut;
|
||||
spline.SetSplineKeyframe(newPointIndex, newKeyframe);
|
||||
|
||||
for (int i = 1; i < spline.SplinePointsCount; i++)
|
||||
{
|
||||
// check all elements to don't left keyframe has invalid time
|
||||
// because points can be added on start or on middle of spline
|
||||
// conflicting with time of another keyframes
|
||||
spline.SetSplinePointTime(i, i, false);
|
||||
}
|
||||
|
||||
// Select new point node
|
||||
SyncSplineKeyframeWithNodes();
|
||||
Editor.Instance.SceneEditing.Select(ChildNodes[newPointIndex]);
|
||||
|
||||
spline.UpdateSpline();
|
||||
}
|
||||
|
||||
private void EditSplineWithSnap(SplinePointNode selectedPoint)
|
||||
{
|
||||
var spline = (Spline)Actor;
|
||||
var selectedPointBounds = new BoundingSphere(selectedPoint.Transform.Translation, 1f);
|
||||
var allSplinesInView = GetSplinesInView();
|
||||
allSplinesInView.Remove(spline);
|
||||
if (allSplinesInView.Count == 0)
|
||||
return;
|
||||
|
||||
var snappedOnSplinePoint = false;
|
||||
for (int i = 0; i < allSplinesInView.Count; i++)
|
||||
{
|
||||
for (int x = 0; x < allSplinesInView[i].SplineKeyframes.Length; x++)
|
||||
{
|
||||
var keyframePosition = allSplinesInView[i].GetSplinePoint(x);
|
||||
var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize);
|
||||
var keyframeBounds = new BoundingSphere(keyframePosition, pointIndicatorSize);
|
||||
DebugDraw.DrawSphere(keyframeBounds, Color.Red, 0, false);
|
||||
|
||||
if (keyframeBounds.Intersects(selectedPointBounds))
|
||||
{
|
||||
spline.SetSplinePoint(selectedPoint.Index, keyframeBounds.Center);
|
||||
snappedOnSplinePoint = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!snappedOnSplinePoint)
|
||||
{
|
||||
var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedPoint.Transform.Translation, allSplinesInView);
|
||||
var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize);
|
||||
var snapBounds = new BoundingSphere(nearSplineSnapPoint, snapIndicatorSize);
|
||||
if (snapBounds.Intersects(selectedPointBounds))
|
||||
{
|
||||
spline.SetSplinePoint(selectedPoint.Index, snapBounds.Center);
|
||||
}
|
||||
DebugDraw.DrawSphere(snapBounds, Color.Yellow, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PostSpawn()
|
||||
{
|
||||
@@ -343,14 +478,12 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
var spline = (Spline)Actor;
|
||||
spline.AddSplineLocalPoint(Vector3.Zero, false);
|
||||
spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f));
|
||||
|
||||
spline.SetSplineKeyframe(0, new BezierCurve<Transform>.Keyframe()
|
||||
{
|
||||
Value = new Transform(Vector3.Zero, Quaternion.Identity, Vector3.One),
|
||||
TangentIn = new Transform(Vector3.Backward * 100, Quaternion.Identity, Vector3.One),
|
||||
TangentOut = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One),
|
||||
});
|
||||
|
||||
spline.SetSplineKeyframe(1, new BezierCurve<Transform>.Keyframe()
|
||||
{
|
||||
Value = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One),
|
||||
@@ -413,6 +546,39 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Spline> GetSplinesInView()
|
||||
{
|
||||
var splines = Level.GetActors<Spline>(true);
|
||||
var result = new List<Spline>();
|
||||
var viewBounds = Editor.Instance.Windows.EditWin.Viewport.ViewFrustum;
|
||||
foreach (var s in splines)
|
||||
{
|
||||
var contains = viewBounds.Contains(s.EditorBox);
|
||||
if (contains == ContainmentType.Contains || contains == ContainmentType.Intersects)
|
||||
result.Add(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Vector3 GetNearSplineSnapPosition(Vector3 position, List<Spline> splines)
|
||||
{
|
||||
var nearPoint = splines[0].GetSplinePointClosestToPoint(position);
|
||||
var nearDistance = Vector3.Distance(nearPoint, position);
|
||||
|
||||
for (int i = 1; i < splines.Count; i++)
|
||||
{
|
||||
var point = splines[i].GetSplinePointClosestToPoint(position);
|
||||
var distance = Vector3.Distance(point, position);
|
||||
if (distance < nearDistance)
|
||||
{
|
||||
nearPoint = point;
|
||||
nearDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return nearPoint;
|
||||
}
|
||||
|
||||
internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize)
|
||||
{
|
||||
var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
base.OnDebugDraw(data);
|
||||
|
||||
var transform = Actor.Transform;
|
||||
DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, Color.Red, 0.0f, false);
|
||||
DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, 0.15f, Color.Red, 0.0f, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,16 +115,31 @@ namespace FlaxEditor.Surface
|
||||
|
||||
internal AnimGraphTraceEvent[] LastTraceEvents;
|
||||
|
||||
internal bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent)
|
||||
internal unsafe bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent)
|
||||
{
|
||||
if (LastTraceEvents != null)
|
||||
{
|
||||
foreach (var e in LastTraceEvents)
|
||||
{
|
||||
// Node IDs must match
|
||||
if (e.NodeId == node.ID)
|
||||
{
|
||||
traceEvent = e;
|
||||
return true;
|
||||
uint* nodePath = e.NodePath0;
|
||||
|
||||
// Get size of the path
|
||||
int nodePathSize = 0;
|
||||
while (nodePathSize < 8 && nodePath[nodePathSize] != 0)
|
||||
nodePathSize++;
|
||||
|
||||
// Follow input node contexts path to verify if it matches with the path in the event
|
||||
var c = node.Context;
|
||||
for (int i = nodePathSize - 1; i >= 0 && c != null; i--)
|
||||
c = c.OwnerNodeID == nodePath[i] ? c.Parent : null;
|
||||
if (c != null)
|
||||
{
|
||||
traceEvent = e;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,9 +191,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var value = title;
|
||||
int count = 1;
|
||||
while (!OnRenameValidate(null, value))
|
||||
{
|
||||
value = title + " " + count++;
|
||||
}
|
||||
Values[0] = value;
|
||||
Title = value;
|
||||
|
||||
@@ -484,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var startPos = PointToParent(ref center);
|
||||
targetState.GetConnectionEndPoint(ref startPos, out var endPos);
|
||||
var color = style.Foreground;
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
SurfaceStyle.DrawStraightConnection(startPos, endPos, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +512,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
SurfaceStyle.DrawStraightConnection(startPos, endPos, color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -655,6 +653,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
protected Rectangle _renameButtonRect;
|
||||
private bool _cursorChanged = false;
|
||||
private bool _textRectHovered = false;
|
||||
private bool _debugActive;
|
||||
|
||||
/// <summary>
|
||||
/// The transitions list from this state to the others.
|
||||
@@ -677,38 +676,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the connection between two state machine nodes.
|
||||
/// </summary>
|
||||
/// <param name="startPos">The start position.</param>
|
||||
/// <param name="endPos">The end position.</param>
|
||||
/// <param name="color">The line color.</param>
|
||||
public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
var sub = endPos - startPos;
|
||||
var length = sub.Length;
|
||||
if (length > Mathf.Epsilon)
|
||||
{
|
||||
var dir = sub / length;
|
||||
var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f);
|
||||
float rotation = Float2.Dot(dir, Float2.UnitY);
|
||||
if (endPos.X < startPos.X)
|
||||
rotation = 2 - rotation;
|
||||
var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
|
||||
var arrowTransform =
|
||||
Matrix3x3.Translation2D(-6.5f, -8) *
|
||||
Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
|
||||
Matrix3x3.Translation2D(endPos - dir * 8);
|
||||
|
||||
Render2D.PushTransform(ref arrowTransform);
|
||||
Render2D.DrawSprite(sprite, arrowRect, color);
|
||||
Render2D.PopTransform();
|
||||
|
||||
endPos -= dir * 4.0f;
|
||||
}
|
||||
Render2D.DrawLine(startPos, endPos, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection end point for the given input position. Puts the end point near the edge of the node bounds.
|
||||
/// </summary>
|
||||
@@ -1092,6 +1059,16 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
// TODO: maybe update only on actual transitions change?
|
||||
UpdateTransitions();
|
||||
|
||||
// Debug current state
|
||||
if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent))
|
||||
{
|
||||
_debugActive = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_debugActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1132,6 +1109,10 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
// Close button
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Debug outline
|
||||
if (_debugActive)
|
||||
Render2D.DrawRectangle(_textRect.MakeExpanded(1.0f), style.ProgressNormal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1295,7 +1276,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f;
|
||||
}
|
||||
var color = isMouseOver ? Color.Wheat : t.LineColor;
|
||||
DrawConnection(ref t.StartPos, ref t.EndPos, ref color);
|
||||
SurfaceStyle.DrawStraightConnection(t.StartPos, t.EndPos, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1324,7 +1305,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
DrawConnection(ref startPos, ref endPos, ref color);
|
||||
SurfaceStyle.DrawStraightConnection(startPos, endPos, color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace FlaxEditor.Surface
|
||||
var editor = Editor.Instance;
|
||||
var style = SurfaceStyle.CreateStyleHandler(editor);
|
||||
style.DrawBox = DrawBox;
|
||||
style.DrawConnection = DrawConnection;
|
||||
style.DrawConnection = SurfaceStyle.DrawStraightConnection;
|
||||
return style;
|
||||
}
|
||||
|
||||
@@ -49,11 +49,6 @@ namespace FlaxEditor.Surface
|
||||
Render2D.FillRectangle(rect, color);
|
||||
}
|
||||
|
||||
private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness)
|
||||
{
|
||||
Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color);
|
||||
}
|
||||
|
||||
private void OnActiveContextMenuVisibleChanged(Control activeCM)
|
||||
{
|
||||
_nodesCache.Wait();
|
||||
|
||||
@@ -492,8 +492,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
|
||||
// If no item is selected (or it's not visible anymore), select the top one
|
||||
Profiler.BeginEvent("VisjectCM.Layout");
|
||||
if (SelectedItem == null || !SelectedItem.VisibleInHierarchy)
|
||||
SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem;
|
||||
SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem;
|
||||
PerformLayout();
|
||||
if (SelectedItem != null)
|
||||
_panel1.ScrollViewTo(SelectedItem);
|
||||
@@ -704,7 +703,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
else if (key == KeyboardKeys.Return)
|
||||
else if (key == KeyboardKeys.Return || key == KeyboardKeys.Tab)
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
OnClickItem(SelectedItem);
|
||||
|
||||
@@ -73,8 +73,6 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
{
|
||||
SortScore = 0;
|
||||
|
||||
if (!(_highlights?.Count > 0))
|
||||
return;
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
@@ -83,6 +81,8 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
SortScore += 1;
|
||||
if (Data != null)
|
||||
SortScore += 1;
|
||||
if (_highlights is { Count: > 0 })
|
||||
SortScore += 1;
|
||||
if (_isStartsWithMatch)
|
||||
SortScore += 2;
|
||||
if (_isFullMatch)
|
||||
@@ -169,6 +169,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
/// <param name="groupHeaderMatches">True if item's group header got a filter match and item should stay visible.</param>
|
||||
public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false)
|
||||
{
|
||||
// When dragging connection out of a box, validate if the box is compatible with this item's type
|
||||
if (selectedBox != null)
|
||||
{
|
||||
Visible = CanConnectTo(selectedBox);
|
||||
@@ -185,72 +186,87 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
// Clear filter
|
||||
_highlights?.Clear();
|
||||
Visible = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
GetTextRectangle(out var textRect);
|
||||
|
||||
// Check archetype title
|
||||
if (QueryFilterHelper.Match(filterText, _archetype.Title, out var ranges))
|
||||
{
|
||||
GetTextRectangle(out var textRect);
|
||||
if (QueryFilterHelper.Match(filterText, _archetype.Title, out var ranges))
|
||||
// Update highlights
|
||||
if (_highlights == null)
|
||||
_highlights = new List<Rectangle>(ranges.Length);
|
||||
else
|
||||
_highlights.Clear();
|
||||
var style = Style.Current;
|
||||
var font = style.FontSmall;
|
||||
for (int i = 0; i < ranges.Length; i++)
|
||||
{
|
||||
// Update highlights
|
||||
if (_highlights == null)
|
||||
_highlights = new List<Rectangle>(ranges.Length);
|
||||
else
|
||||
_highlights.Clear();
|
||||
var style = Style.Current;
|
||||
var font = style.FontSmall;
|
||||
for (int i = 0; i < ranges.Length; i++)
|
||||
var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex);
|
||||
var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex);
|
||||
_highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height));
|
||||
|
||||
if (ranges[i].StartIndex <= 0)
|
||||
{
|
||||
var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex);
|
||||
var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex);
|
||||
_highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height));
|
||||
|
||||
if (ranges[i].StartIndex <= 0)
|
||||
{
|
||||
_isStartsWithMatch = true;
|
||||
if (ranges[i].Length == _archetype.Title.Length)
|
||||
_isFullMatch = true;
|
||||
}
|
||||
_isStartsWithMatch = true;
|
||||
if (ranges[i].Length == _archetype.Title.Length)
|
||||
_isFullMatch = true;
|
||||
}
|
||||
Visible = true;
|
||||
}
|
||||
else if (_archetype.AlternativeTitles?.Any(altTitle => string.Equals(filterText, altTitle, StringComparison.CurrentCultureIgnoreCase)) == true)
|
||||
{
|
||||
// Update highlights
|
||||
if (_highlights == null)
|
||||
_highlights = new List<Rectangle>(1);
|
||||
else
|
||||
_highlights.Clear();
|
||||
var style = Style.Current;
|
||||
var font = style.FontSmall;
|
||||
var start = font.GetCharPosition(_archetype.Title, 0);
|
||||
var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1);
|
||||
_highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height));
|
||||
_isFullMatch = true;
|
||||
Visible = true;
|
||||
}
|
||||
else if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data))
|
||||
{
|
||||
// Update highlights
|
||||
if (_highlights == null)
|
||||
_highlights = new List<Rectangle>(1);
|
||||
else
|
||||
_highlights.Clear();
|
||||
var style = Style.Current;
|
||||
var font = style.FontSmall;
|
||||
var start = font.GetCharPosition(_archetype.Title, 0);
|
||||
var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1);
|
||||
_highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height));
|
||||
Visible = true;
|
||||
|
||||
Data = data;
|
||||
}
|
||||
else if (!groupHeaderMatches)
|
||||
{
|
||||
// Hide
|
||||
_highlights?.Clear();
|
||||
Visible = false;
|
||||
}
|
||||
Visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check archetype synonyms
|
||||
if (_archetype.AlternativeTitles!= null && _archetype.AlternativeTitles.Any(altTitle => QueryFilterHelper.Match(filterText, altTitle, out ranges)))
|
||||
{
|
||||
// Update highlights
|
||||
if (_highlights == null)
|
||||
_highlights = new List<Rectangle>(1);
|
||||
else
|
||||
_highlights.Clear();
|
||||
var style = Style.Current;
|
||||
var font = style.FontSmall;
|
||||
var start = font.GetCharPosition(_archetype.Title, 0);
|
||||
var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1);
|
||||
_highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height));
|
||||
|
||||
for (int i = 0; i < ranges.Length; i++)
|
||||
{
|
||||
if (ranges[i].StartIndex <= 0)
|
||||
{
|
||||
_isStartsWithMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
Visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check archetype data (if it exists)
|
||||
if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data))
|
||||
{
|
||||
// Update highlights
|
||||
if (_highlights == null)
|
||||
_highlights = new List<Rectangle>(1);
|
||||
else
|
||||
_highlights.Clear();
|
||||
var style = Style.Current;
|
||||
var font = style.FontSmall;
|
||||
var start = font.GetCharPosition(_archetype.Title, 0);
|
||||
var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1);
|
||||
_highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height));
|
||||
Visible = true;
|
||||
Data = data;
|
||||
return;
|
||||
}
|
||||
|
||||
_highlights?.Clear();
|
||||
|
||||
// Hide
|
||||
if (!groupHeaderMatches)
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -295,5 +295,38 @@ namespace FlaxEditor.Surface
|
||||
Background = editor.UI.VisjectSurfaceBackground,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a simple straight connection between two locations.
|
||||
/// </summary>
|
||||
/// <param name="startPos">The start position.</param>
|
||||
/// <param name="endPos">The end position.</param>
|
||||
/// <param name="color">The line color.</param>
|
||||
/// <param name="thickness">The line thickness.</param>
|
||||
public static void DrawStraightConnection(Float2 startPos, Float2 endPos, Color color, float thickness = 1.0f)
|
||||
{
|
||||
var sub = endPos - startPos;
|
||||
var length = sub.Length;
|
||||
if (length > Mathf.Epsilon)
|
||||
{
|
||||
var dir = sub / length;
|
||||
var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f);
|
||||
float rotation = Float2.Dot(dir, Float2.UnitY);
|
||||
if (endPos.X < startPos.X)
|
||||
rotation = 2 - rotation;
|
||||
var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
|
||||
var arrowTransform =
|
||||
Matrix3x3.Translation2D(-6.5f, -8) *
|
||||
Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
|
||||
Matrix3x3.Translation2D(endPos - dir * 8);
|
||||
|
||||
Render2D.PushTransform(ref arrowTransform);
|
||||
Render2D.DrawSprite(sprite, arrowRect, color);
|
||||
Render2D.PopTransform();
|
||||
|
||||
endPos -= dir * 4.0f;
|
||||
}
|
||||
Render2D.DrawLine(startPos, endPos, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,30 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public event Action<VisjectSurfaceContext> ContextChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the surface context with the given owning nodes IDs path.
|
||||
/// </summary>
|
||||
/// <param name="nodePath">The node ids path.</param>
|
||||
/// <returns>Found context or null if cannot.</returns>
|
||||
public VisjectSurfaceContext FindContext(Span<uint> nodePath)
|
||||
{
|
||||
// Get size of the path
|
||||
int nodePathSize = 0;
|
||||
while (nodePathSize < nodePath.Length && nodePath[nodePathSize] != 0)
|
||||
nodePathSize++;
|
||||
|
||||
// Follow each context path to verify if it matches with the path in the input path
|
||||
foreach (var e in _contextCache)
|
||||
{
|
||||
var c = e.Value;
|
||||
for (int i = nodePathSize - 1; i >= 0 && c != null; i--)
|
||||
c = c.OwnerNodeID == nodePath[i] ? c.Parent : null;
|
||||
if (c != null)
|
||||
return e.Value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the Visject surface context for the given surface data source context.
|
||||
/// </summary>
|
||||
@@ -62,6 +86,8 @@ namespace FlaxEditor.Surface
|
||||
surfaceContext = CreateContext(_context, context);
|
||||
_context?.Children.Add(surfaceContext);
|
||||
_contextCache.Add(contextHandle, surfaceContext);
|
||||
if (context is SurfaceNode asNode)
|
||||
surfaceContext.OwnerNodeID = asNode.ID;
|
||||
|
||||
context.OnContextCreated(surfaceContext);
|
||||
|
||||
|
||||
@@ -20,8 +20,15 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Utility for easy nodes archetypes generation for Visject Surface based on scripting types.
|
||||
/// </summary>
|
||||
internal class NodesCache
|
||||
[HideInEditor]
|
||||
public class NodesCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for scripting types filtering into cache.
|
||||
/// </summary>
|
||||
/// <param name="scriptType">The input type to process.</param>
|
||||
/// <param name="cache">Node groups cache that can be used for reusing groups for different nodes.</param>
|
||||
/// <param name="version">The cache version number. Can be used to reject any cached data after <see cref="NodesCache"/> rebuilt.</param>
|
||||
public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version);
|
||||
|
||||
internal static readonly List<NodesCache> Caches = new List<NodesCache>(8);
|
||||
@@ -33,11 +40,18 @@ namespace FlaxEditor.Surface
|
||||
private VisjectCM _taskContextMenu;
|
||||
private Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NodesCache"/> class.
|
||||
/// </summary>
|
||||
/// <param name="iterator">The iterator callback to build node types from Scripting.</param>
|
||||
public NodesCache(IterateType iterator)
|
||||
{
|
||||
_iterator = iterator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the async caching job to finish.
|
||||
/// </summary>
|
||||
public void Wait()
|
||||
{
|
||||
if (_task != null)
|
||||
@@ -48,6 +62,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears cache.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Wait();
|
||||
@@ -62,6 +79,10 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Visject Context Menu to contain current nodes.
|
||||
/// </summary>
|
||||
/// <param name="contextMenu">The output context menu to setup.</param>
|
||||
public void Get(VisjectCM contextMenu)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Context Menu");
|
||||
|
||||
@@ -156,6 +156,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public event Action<SurfaceControl> ControlDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the node that 'owns' this context (eg. State Machine which created this graph of state nodes).
|
||||
/// </summary>
|
||||
public uint OwnerNodeID;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VisjectSurfaceContext"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace FlaxEditor.Surface
|
||||
/// The base interface for editor windows that use <see cref="FlaxEditor.Surface.VisjectSurface"/> for content editing.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Surface.IVisjectSurfaceOwner" />
|
||||
interface IVisjectSurfaceWindow : IVisjectSurfaceOwner
|
||||
public interface IVisjectSurfaceWindow : IVisjectSurfaceOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the asset edited by the window.
|
||||
|
||||
@@ -43,6 +43,19 @@ namespace FlaxEngine.Tools
|
||||
/// Enables continuous painting, otherwise single paint on click.
|
||||
/// </summary>
|
||||
public bool ContinuousPaint;
|
||||
|
||||
/// <summary>
|
||||
/// Enables drawing cloth paint debugging with Depth Test enabled (skips occluded vertices).
|
||||
/// </summary>
|
||||
public bool DebugDrawDepthTest
|
||||
{
|
||||
get => Gizmo.Cloth?.DebugDrawDepthTest ?? true;
|
||||
set
|
||||
{
|
||||
if (Gizmo.Cloth != null)
|
||||
Gizmo.Cloth.DebugDrawDepthTest = value;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public override void Init(IGizmoOwner owner)
|
||||
@@ -62,6 +75,7 @@ namespace FlaxEngine.Tools
|
||||
public override void Dispose()
|
||||
{
|
||||
Owner.Gizmos.Remove(Gizmo);
|
||||
Gizmo = null;
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
@@ -83,6 +97,7 @@ namespace FlaxEngine.Tools
|
||||
private EditClothPaintAction _undoAction;
|
||||
|
||||
public bool IsPainting => _isPainting;
|
||||
public Cloth Cloth => _cloth;
|
||||
|
||||
public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode)
|
||||
: base(owner)
|
||||
|
||||
@@ -45,9 +45,6 @@ namespace FlaxEditor.Tools.Terrain
|
||||
[EditorOrder(130), EditorDisplay("Layout"), DefaultValue(null), Tooltip("The default material used for terrain rendering (chunks can override this). It must have Domain set to terrain.")]
|
||||
public MaterialBase Material;
|
||||
|
||||
[EditorOrder(200), EditorDisplay("Collision"), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), Tooltip("Terrain default physical material used to define the collider physical properties.")]
|
||||
public JsonAsset PhysicalMaterial;
|
||||
|
||||
[EditorOrder(210), EditorDisplay("Collision", "Collision LOD"), DefaultValue(-1), Limit(-1, 100, 0.1f), Tooltip("Terrain geometry LOD index used for collision.")]
|
||||
public int CollisionLOD = -1;
|
||||
|
||||
@@ -152,7 +149,6 @@ namespace FlaxEditor.Tools.Terrain
|
||||
terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
|
||||
terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale);
|
||||
terrain.Material = _options.Material;
|
||||
terrain.PhysicalMaterial = _options.PhysicalMaterial;
|
||||
terrain.CollisionLOD = _options.CollisionLOD;
|
||||
if (_options.Heightmap)
|
||||
terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0);
|
||||
|
||||
@@ -67,11 +67,13 @@ namespace FlaxEditor.Tools.Terrain.Paint
|
||||
|
||||
// Prepare
|
||||
var splatmapIndex = ActiveSplatmapIndex;
|
||||
var splatmapIndexOther = (splatmapIndex + 1) % 2;
|
||||
var chunkSize = terrain.ChunkSize;
|
||||
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
|
||||
var heightmapLength = heightmapSize * heightmapSize;
|
||||
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
|
||||
var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes).ToPointer();
|
||||
var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer();
|
||||
var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer();
|
||||
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
|
||||
ApplyParams p = new ApplyParams
|
||||
{
|
||||
@@ -81,8 +83,10 @@ namespace FlaxEditor.Tools.Terrain.Paint
|
||||
Options = options,
|
||||
Strength = strength,
|
||||
SplatmapIndex = splatmapIndex,
|
||||
SplatmapIndexOther = splatmapIndexOther,
|
||||
HeightmapSize = heightmapSize,
|
||||
TempBuffer = tempBuffer,
|
||||
TempBufferOther = tempBufferOther,
|
||||
};
|
||||
|
||||
// Get brush bounds in terrain local space
|
||||
@@ -131,11 +135,16 @@ namespace FlaxEditor.Tools.Terrain.Paint
|
||||
var sourceData = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndex);
|
||||
if (sourceData == null)
|
||||
throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info.");
|
||||
|
||||
var sourceDataOther = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndexOther);
|
||||
if (sourceDataOther == null)
|
||||
throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info.");
|
||||
|
||||
// Record patch data before editing it
|
||||
if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
|
||||
{
|
||||
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex);
|
||||
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndexOther);
|
||||
}
|
||||
|
||||
// Apply modification
|
||||
@@ -144,6 +153,7 @@ namespace FlaxEditor.Tools.Terrain.Paint
|
||||
p.PatchCoord = patch.PatchCoord;
|
||||
p.PatchPositionLocal = patchPositionLocal;
|
||||
p.SourceData = sourceData;
|
||||
p.SourceDataOther = sourceDataOther;
|
||||
Apply(ref p);
|
||||
}
|
||||
}
|
||||
@@ -197,16 +207,32 @@ namespace FlaxEditor.Tools.Terrain.Paint
|
||||
/// The splatmap texture index.
|
||||
/// </summary>
|
||||
public int SplatmapIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The splatmap texture index. If <see cref="SplatmapIndex"/> is 0, this will be 1. If <see cref="SplatmapIndex"/> is 1, this will be 0.
|
||||
/// </summary>
|
||||
public int SplatmapIndexOther;
|
||||
|
||||
/// <summary>
|
||||
/// The temporary data buffer (for modified data).
|
||||
/// </summary>
|
||||
public Color32* TempBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The 'other' temporary data buffer (for modified data). If <see cref="TempBuffer"/> refersto the splatmap with index 0, this one will refer to the one with index 1.
|
||||
/// </summary>
|
||||
public Color32* TempBufferOther;
|
||||
|
||||
/// <summary>
|
||||
/// The source data buffer.
|
||||
/// </summary>
|
||||
public Color32* SourceData;
|
||||
|
||||
/// <summary>
|
||||
/// The 'other' source data buffer. If <see cref="SourceData"/> refers
|
||||
/// to the splatmap with index 0, this one will refer to the one with index 1.
|
||||
/// </summary>
|
||||
public Color32* SourceDataOther;
|
||||
|
||||
/// <summary>
|
||||
/// The heightmap size (edge).
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Tools.Terrain.Paint
|
||||
@@ -73,53 +72,53 @@ namespace FlaxEditor.Tools.Terrain.Paint
|
||||
var strength = p.Strength;
|
||||
var layer = (int)Layer;
|
||||
var brushPosition = p.Gizmo.CursorPosition;
|
||||
var layerComponent = layer % 4;
|
||||
var c = layer % 4;
|
||||
|
||||
// Apply brush modification
|
||||
Profiler.BeginEvent("Apply Brush");
|
||||
bool otherModified = false;
|
||||
for (int z = 0; z < p.ModifiedSize.Y; z++)
|
||||
{
|
||||
var zz = z + p.ModifiedOffset.Y;
|
||||
for (int x = 0; x < p.ModifiedSize.X; x++)
|
||||
{
|
||||
var xx = x + p.ModifiedOffset.X;
|
||||
var src = p.SourceData[zz * p.HeightmapSize + xx];
|
||||
var src = (Color)p.SourceData[zz * p.HeightmapSize + xx];
|
||||
|
||||
var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex);
|
||||
Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld);
|
||||
var sample = Mathf.Saturate(p.Brush.Sample(ref brushPosition, ref samplePositionWorld));
|
||||
|
||||
var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength;
|
||||
var paintAmount = sample * strength;
|
||||
if (paintAmount < 0.0f)
|
||||
continue; // Skip when pixel won't be affected
|
||||
|
||||
// Extract layer weight
|
||||
byte* srcPtr = &src.R;
|
||||
var srcWeight = *(srcPtr + layerComponent) / 255.0f;
|
||||
|
||||
// Accumulate weight
|
||||
float dstWeight = srcWeight + paintAmount;
|
||||
|
||||
// Check for solid layer case
|
||||
if (dstWeight >= 1.0f)
|
||||
{
|
||||
// Erase other layers
|
||||
// TODO: maybe erase only the higher layers?
|
||||
// TODO: need to erase also weights form the other splatmaps
|
||||
src = Color32.Transparent;
|
||||
|
||||
// Use limit value
|
||||
dstWeight = 1.0f;
|
||||
}
|
||||
|
||||
// Modify packed weight
|
||||
*(srcPtr + layerComponent) = (byte)(dstWeight * 255.0f);
|
||||
|
||||
// Write back
|
||||
// Paint on the active splatmap texture
|
||||
src[c] = Mathf.Saturate(src[c] + paintAmount);
|
||||
src[(c + 1) % 4] = Mathf.Saturate(src[(c + 1) % 4] - paintAmount);
|
||||
src[(c + 2) % 4] = Mathf.Saturate(src[(c + 2) % 4] - paintAmount);
|
||||
src[(c + 3) % 4] = Mathf.Saturate(src[(c + 3) % 4] - paintAmount);
|
||||
p.TempBuffer[z * p.ModifiedSize.X + x] = src;
|
||||
|
||||
var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx];
|
||||
//if (other.ValuesSum > 0.0f) // Skip editing the other splatmap if it's empty
|
||||
{
|
||||
// Remove 'paint' from the other splatmap texture
|
||||
other[c] = Mathf.Saturate(other[c] - paintAmount);
|
||||
other[(c + 1) % 4] = Mathf.Saturate(other[(c + 1) % 4] - paintAmount);
|
||||
other[(c + 2) % 4] = Mathf.Saturate(other[(c + 2) % 4] - paintAmount);
|
||||
other[(c + 3) % 4] = Mathf.Saturate(other[(c + 3) % 4] - paintAmount);
|
||||
p.TempBufferOther[z * p.ModifiedSize.X + x] = other;
|
||||
otherModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Profiler.EndEvent();
|
||||
|
||||
// Update terrain patch
|
||||
TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize);
|
||||
if (otherModified)
|
||||
TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,35 @@ namespace FlaxEditor.Tools.Terrain
|
||||
"Layer 7",
|
||||
};
|
||||
|
||||
private IntPtr _cachedSplatmapData;
|
||||
private int _cachedSplatmapDataSize;
|
||||
private struct SplatmapData
|
||||
{
|
||||
public IntPtr DataPtr;
|
||||
public int Size;
|
||||
|
||||
public void EnsureCapacity(int size)
|
||||
{
|
||||
if (Size < size)
|
||||
{
|
||||
if (DataPtr != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(DataPtr);
|
||||
DataPtr = Marshal.AllocHGlobal(size);
|
||||
Utils.MemoryClear(DataPtr, (ulong)size);
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
if (DataPtr == IntPtr.Zero)
|
||||
return;
|
||||
Marshal.FreeHGlobal(DataPtr);
|
||||
DataPtr = IntPtr.Zero;
|
||||
Size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private EditTerrainMapAction _activeAction;
|
||||
private SplatmapData[] _cachedSplatmapData = new SplatmapData[2];
|
||||
|
||||
/// <summary>
|
||||
/// The terrain painting gizmo.
|
||||
@@ -230,20 +256,13 @@ namespace FlaxEditor.Tools.Terrain
|
||||
/// Gets the splatmap temporary scratch memory buffer used to modify terrain samples. Allocated memory is unmanaged by GC.
|
||||
/// </summary>
|
||||
/// <param name="size">The minimum buffer size (in bytes).</param>
|
||||
/// <param name="splatmapIndex">The splatmap index for which to return/create the temp buffer.</param>
|
||||
/// <returns>The allocated memory using <see cref="Marshal"/> interface.</returns>
|
||||
public IntPtr GetSplatmapTempBuffer(int size)
|
||||
public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex)
|
||||
{
|
||||
if (_cachedSplatmapDataSize < size)
|
||||
{
|
||||
if (_cachedSplatmapData != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(_cachedSplatmapData);
|
||||
}
|
||||
_cachedSplatmapData = Marshal.AllocHGlobal(size);
|
||||
_cachedSplatmapDataSize = size;
|
||||
}
|
||||
|
||||
return _cachedSplatmapData;
|
||||
ref var splatmapData = ref _cachedSplatmapData[splatmapIndex];
|
||||
splatmapData.EnsureCapacity(size);
|
||||
return splatmapData.DataPtr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -276,12 +295,8 @@ namespace FlaxEditor.Tools.Terrain
|
||||
base.OnDeactivated();
|
||||
|
||||
// Free temporary memory buffer
|
||||
if (_cachedSplatmapData != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(_cachedSplatmapData);
|
||||
_cachedSplatmapData = IntPtr.Zero;
|
||||
_cachedSplatmapDataSize = 0;
|
||||
}
|
||||
foreach (ref var splatmapData in _cachedSplatmapData.AsSpan())
|
||||
splatmapData.Free();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,7 +20,7 @@ bool TerrainTools::TryGetPatchCoordToAdd(Terrain* terrain, const Ray& ray, Int2&
|
||||
{
|
||||
CHECK_RETURN(terrain, true);
|
||||
result = Int2::Zero;
|
||||
const float patchSize = terrain->GetChunkSize() * TERRAIN_UNITS_PER_VERTEX * TerrainPatch::CHUNKS_COUNT_EDGE;
|
||||
const float patchSize = terrain->GetChunkSize() * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
|
||||
|
||||
// Try to pick any of the patch edges
|
||||
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
|
||||
@@ -179,7 +179,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
|
||||
terrain->AddPatches(numberOfPatches);
|
||||
|
||||
// Prepare data
|
||||
const auto heightmapSize = terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
|
||||
const auto heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
|
||||
Array<float> heightmapData;
|
||||
heightmapData.Resize(heightmapSize * heightmapSize);
|
||||
|
||||
@@ -380,7 +380,7 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
|
||||
const auto firstPatch = terrain->GetPatch(0);
|
||||
|
||||
// Calculate texture size
|
||||
const int32 patchEdgeVertexCount = terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
|
||||
const int32 patchEdgeVertexCount = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
|
||||
const int32 patchVertexCount = patchEdgeVertexCount * patchEdgeVertexCount;
|
||||
|
||||
// Find size of heightmap in patches
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Core/Math/Color32.h"
|
||||
#include "Engine/Core/Config/GameSettings.h"
|
||||
#include "Engine/Core/Config/BuildSettings.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
@@ -19,498 +18,6 @@
|
||||
#if PLATFORM_MAC
|
||||
#include "Engine/Platform/Apple/ApplePlatformSettings.h"
|
||||
#endif
|
||||
#include <fstream>
|
||||
|
||||
#define MSDOS_SIGNATURE 0x5A4D
|
||||
#define PE_SIGNATURE 0x00004550
|
||||
#define PE_32BIT_SIGNATURE 0x10B
|
||||
#define PE_64BIT_SIGNATURE 0x20B
|
||||
#define PE_NUM_DIRECTORY_ENTRIES 16
|
||||
#define PE_SECTION_UNINITIALIZED_DATA 0x00000080
|
||||
#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2
|
||||
#define PE_IMAGE_RT_ICON 3
|
||||
|
||||
/// <summary>
|
||||
/// MS-DOS header found at the beginning in a PE format file.
|
||||
/// </summary>
|
||||
struct MSDOSHeader
|
||||
{
|
||||
uint16 signature;
|
||||
uint16 lastSize;
|
||||
uint16 numBlocks;
|
||||
uint16 numReloc;
|
||||
uint16 hdrSize;
|
||||
uint16 minAlloc;
|
||||
uint16 maxAlloc;
|
||||
uint16 ss;
|
||||
uint16 sp;
|
||||
uint16 checksum;
|
||||
uint16 ip;
|
||||
uint16 cs;
|
||||
uint16 relocPos;
|
||||
uint16 numOverlay;
|
||||
uint16 reserved1[4];
|
||||
uint16 oemId;
|
||||
uint16 oemInfo;
|
||||
uint16 reserved2[10];
|
||||
uint32 lfanew;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// COFF header found in a PE format file.
|
||||
/// </summary>
|
||||
struct COFFHeader
|
||||
{
|
||||
uint16 machine;
|
||||
uint16 numSections;
|
||||
uint32 timeDateStamp;
|
||||
uint32 ptrSymbolTable;
|
||||
uint32 numSymbols;
|
||||
uint16 sizeOptHeader;
|
||||
uint16 characteristics;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Contains address and size of data areas in a PE image.
|
||||
/// </summary>
|
||||
struct PEDataDirectory
|
||||
{
|
||||
uint32 virtualAddress;
|
||||
uint32 size;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Optional header in a 32-bit PE format file.
|
||||
/// </summary>
|
||||
struct PEOptionalHeader32
|
||||
{
|
||||
uint16 signature;
|
||||
uint8 majorLinkerVersion;
|
||||
uint8 minorLinkerVersion;
|
||||
uint32 sizeCode;
|
||||
uint32 sizeInitializedData;
|
||||
uint32 sizeUninitializedData;
|
||||
uint32 addressEntryPoint;
|
||||
uint32 baseCode;
|
||||
uint32 baseData;
|
||||
uint32 baseImage;
|
||||
uint32 alignmentSection;
|
||||
uint32 alignmentFile;
|
||||
uint16 majorOSVersion;
|
||||
uint16 minorOSVersion;
|
||||
uint16 majorImageVersion;
|
||||
uint16 minorImageVersion;
|
||||
uint16 majorSubsystemVersion;
|
||||
uint16 minorSubsystemVersion;
|
||||
uint32 reserved;
|
||||
uint32 sizeImage;
|
||||
uint32 sizeHeaders;
|
||||
uint32 checksum;
|
||||
uint16 subsystem;
|
||||
uint16 characteristics;
|
||||
uint32 sizeStackReserve;
|
||||
uint32 sizeStackCommit;
|
||||
uint32 sizeHeapReserve;
|
||||
uint32 sizeHeapCommit;
|
||||
uint32 loaderFlags;
|
||||
uint32 NumRvaAndSizes;
|
||||
PEDataDirectory dataDirectory[16];
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Optional header in a 64-bit PE format file.
|
||||
/// </summary>
|
||||
struct PEOptionalHeader64
|
||||
{
|
||||
uint16 signature;
|
||||
uint8 majorLinkerVersion;
|
||||
uint8 minorLinkerVersion;
|
||||
uint32 sizeCode;
|
||||
uint32 sizeInitializedData;
|
||||
uint32 sizeUninitializedData;
|
||||
uint32 addressEntryPoint;
|
||||
uint32 baseCode;
|
||||
uint64 baseImage;
|
||||
uint32 alignmentSection;
|
||||
uint32 alignmentFile;
|
||||
uint16 majorOSVersion;
|
||||
uint16 minorOSVersion;
|
||||
uint16 majorImageVersion;
|
||||
uint16 minorImageVersion;
|
||||
uint16 majorSubsystemVersion;
|
||||
uint16 minorSubsystemVersion;
|
||||
uint32 reserved;
|
||||
uint32 sizeImage;
|
||||
uint32 sizeHeaders;
|
||||
uint32 checksum;
|
||||
uint16 subsystem;
|
||||
uint16 characteristics;
|
||||
uint64 sizeStackReserve;
|
||||
uint64 sizeStackCommit;
|
||||
uint64 sizeHeapReserve;
|
||||
uint64 sizeHeapCommit;
|
||||
uint32 loaderFlags;
|
||||
uint32 NumRvaAndSizes;
|
||||
PEDataDirectory dataDirectory[16];
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A section header in a PE format file.
|
||||
/// </summary>
|
||||
struct PESectionHeader
|
||||
{
|
||||
char name[8];
|
||||
uint32 virtualSize;
|
||||
uint32 relativeVirtualAddress;
|
||||
uint32 physicalSize;
|
||||
uint32 physicalAddress;
|
||||
uint8 deprecated[12];
|
||||
uint32 flags;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A resource table header within a .rsrc section in a PE format file.
|
||||
/// </summary>
|
||||
struct PEImageResourceDirectory
|
||||
{
|
||||
uint32 flags;
|
||||
uint32 timeDateStamp;
|
||||
uint16 majorVersion;
|
||||
uint16 minorVersion;
|
||||
uint16 numNamedEntries;
|
||||
uint16 numIdEntries;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A single entry in a resource table within a .rsrc section in a PE format file.
|
||||
/// </summary>
|
||||
struct PEImageResourceEntry
|
||||
{
|
||||
uint32 type;
|
||||
uint32 offsetDirectory : 31;
|
||||
uint32 isDirectory : 1;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file.
|
||||
/// </summary>
|
||||
struct PEImageResourceEntryData
|
||||
{
|
||||
uint32 offsetData;
|
||||
uint32 size;
|
||||
uint32 codePage;
|
||||
uint32 resourceHandle;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Header used in icon file format.
|
||||
/// </summary>
|
||||
struct IconHeader
|
||||
{
|
||||
uint32 size;
|
||||
int32 width;
|
||||
int32 height;
|
||||
uint16 planes;
|
||||
uint16 bitCount;
|
||||
uint32 compression;
|
||||
uint32 sizeImage;
|
||||
int32 xPelsPerMeter;
|
||||
int32 yPelsPerMeter;
|
||||
uint32 clrUsed;
|
||||
uint32 clrImportant;
|
||||
};
|
||||
|
||||
void UpdateIconData(uint8* iconData, const TextureData* icon)
|
||||
{
|
||||
IconHeader* iconHeader = (IconHeader*)iconData;
|
||||
|
||||
if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32)
|
||||
{
|
||||
// Unsupported format
|
||||
return;
|
||||
}
|
||||
|
||||
uint8* iconPixels = iconData + sizeof(IconHeader);
|
||||
uint32 width = iconHeader->width;
|
||||
uint32 height = iconHeader->height / 2;
|
||||
|
||||
// Check if can use mip from texture data or sample different mip
|
||||
uint32 iconTexSize;
|
||||
if (width != height)
|
||||
{
|
||||
// Only square icons are supported
|
||||
return;
|
||||
}
|
||||
if (Math::IsPowerOfTwo(width))
|
||||
{
|
||||
// Use mip
|
||||
iconTexSize = width;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use resized mip
|
||||
iconTexSize = Math::RoundUpToPowerOf2(width);
|
||||
}
|
||||
|
||||
// Try to pick a proper mip (require the same size)
|
||||
const TextureMipData* srcPixels = nullptr;
|
||||
int32 mipLevels = icon->GetMipLevels();
|
||||
for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
|
||||
{
|
||||
uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex);
|
||||
uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex);
|
||||
|
||||
if (iconTexSize == iconWidth && iconTexSize == iconHeight)
|
||||
{
|
||||
srcPixels = icon->GetData(0, mipIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (srcPixels == nullptr)
|
||||
{
|
||||
// No icon of this size provided
|
||||
return;
|
||||
}
|
||||
|
||||
// Write colors
|
||||
uint32* colorData = (uint32*)iconPixels;
|
||||
|
||||
uint32 idx = 0;
|
||||
for (int32 y = (int32)height - 1; y >= 0; y--)
|
||||
{
|
||||
float v = (float)y / height;
|
||||
for (uint32 x = 0; x < width; x++)
|
||||
{
|
||||
float u = (float)x / width;
|
||||
|
||||
int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32);
|
||||
colorData[idx++] = ((Color32*)&srcPixels->Data.Get()[i])->GetAsBGRA();
|
||||
}
|
||||
}
|
||||
|
||||
// Write AND mask
|
||||
uint32 colorDataSize = width * height * sizeof(uint32);
|
||||
uint8* maskData = iconPixels + colorDataSize;
|
||||
|
||||
// One per bit in byte
|
||||
uint32 numPackedPixels = width / 8;
|
||||
|
||||
for (int32 y = (int32)height - 1; y >= 0; y--)
|
||||
{
|
||||
uint8 mask = 0;
|
||||
float v = (float)y / height;
|
||||
for (uint32 packedX = 0; packedX < numPackedPixels; packedX++)
|
||||
{
|
||||
for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++)
|
||||
{
|
||||
uint32 x = packedX * 8 + pixelIdx;
|
||||
float u = (float)x / width;
|
||||
int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32);
|
||||
Color32 color = *((Color32*)&srcPixels->Data.Get()[i]);
|
||||
if (color.A < 64)
|
||||
mask |= 1 << (7 - pixelIdx);
|
||||
}
|
||||
|
||||
*maskData = mask;
|
||||
maskData++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8)
|
||||
{
|
||||
uint32 numEntries = current->numIdEntries; // Not supporting name entries
|
||||
PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1);
|
||||
|
||||
for (uint32 i = 0; i < numEntries; i++)
|
||||
{
|
||||
// Only at root does the type identify resource type
|
||||
if (base == current && entries[i].type != PE_IMAGE_RT_ICON)
|
||||
continue;
|
||||
|
||||
if (entries[i].isDirectory)
|
||||
{
|
||||
PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory);
|
||||
SetIconData(base, child, imageData, sectionAddress, iconRGBA8);
|
||||
}
|
||||
else
|
||||
{
|
||||
PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory);
|
||||
|
||||
uint8* iconData = imageData + (data->offsetData - sectionAddress);
|
||||
UpdateIconData(iconData, iconRGBA8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon)
|
||||
{
|
||||
// Validate input
|
||||
if (!FileSystem::FileExists(path))
|
||||
{
|
||||
LOG(Warning, "Missing file");
|
||||
return true;
|
||||
}
|
||||
if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0)
|
||||
{
|
||||
LOG(Warning, "Inalid icon data");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert to RGBA8 format if need to
|
||||
const TextureData* iconRGBA8 = &icon;
|
||||
TextureData tmpData1;
|
||||
if (icon.Format != PixelFormat::R8G8B8A8_UNorm)
|
||||
{
|
||||
if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm))
|
||||
{
|
||||
LOG(Warning, "Failed convert icon data.");
|
||||
return true;
|
||||
}
|
||||
iconRGBA8 = &tmpData1;
|
||||
}
|
||||
|
||||
// Resize if need to
|
||||
TextureData tmpData2;
|
||||
if (iconRGBA8->Width > 256 || iconRGBA8->Height > 256)
|
||||
{
|
||||
if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256))
|
||||
{
|
||||
LOG(Warning, "Failed resize icon data.");
|
||||
return true;
|
||||
}
|
||||
iconRGBA8 = &tmpData2;
|
||||
}
|
||||
|
||||
// A PE file is structured as such:
|
||||
// - MSDOS Header
|
||||
// - PE Signature
|
||||
// - COFF Header
|
||||
// - PE Optional Header
|
||||
// - One or multiple sections
|
||||
// - .code
|
||||
// - .data
|
||||
// - ...
|
||||
// - .rsrc
|
||||
// - icon/cursor/etc data
|
||||
|
||||
std::fstream stream;
|
||||
#if PLATFORM_WINDOWS
|
||||
stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary);
|
||||
#else
|
||||
StringAsANSI<> pathAnsi(path.Get());
|
||||
stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary);
|
||||
#endif
|
||||
if (!stream.is_open())
|
||||
{
|
||||
LOG(Warning, "Cannot open file");
|
||||
return true;
|
||||
}
|
||||
|
||||
// First check magic number to ensure file is even an executable
|
||||
uint16 magicNum;
|
||||
stream.read((char*)&magicNum, sizeof(magicNum));
|
||||
if (magicNum != MSDOS_SIGNATURE)
|
||||
{
|
||||
LOG(Warning, "Provided file is not a valid executable.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read the MSDOS header and skip over it
|
||||
stream.seekg(0);
|
||||
|
||||
MSDOSHeader msdosHeader;
|
||||
stream.read((char*)&msdosHeader, sizeof(MSDOSHeader));
|
||||
|
||||
// Read PE signature
|
||||
stream.seekg(msdosHeader.lfanew);
|
||||
|
||||
uint32 peSignature;
|
||||
stream.read((char*)&peSignature, sizeof(peSignature));
|
||||
|
||||
if (peSignature != PE_SIGNATURE)
|
||||
{
|
||||
LOG(Warning, "Provided file is not in PE format.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read COFF header
|
||||
COFFHeader coffHeader;
|
||||
stream.read((char*)&coffHeader, sizeof(COFFHeader));
|
||||
|
||||
// .exe files always have an optional header
|
||||
if (coffHeader.sizeOptHeader == 0)
|
||||
{
|
||||
LOG(Warning, "Provided file is not a valid executable.");
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 numSectionHeaders = coffHeader.numSections;
|
||||
|
||||
// Read optional header
|
||||
auto optionalHeaderPos = stream.tellg();
|
||||
|
||||
uint16 optionalHeaderSignature;
|
||||
stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature));
|
||||
|
||||
PEDataDirectory* dataDirectory = nullptr;
|
||||
stream.seekg(optionalHeaderPos);
|
||||
if (optionalHeaderSignature == PE_32BIT_SIGNATURE)
|
||||
{
|
||||
PEOptionalHeader32 optionalHeader;
|
||||
stream.read((char*)&optionalHeader, sizeof(optionalHeader));
|
||||
|
||||
dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
|
||||
}
|
||||
else if (optionalHeaderSignature == PE_64BIT_SIGNATURE)
|
||||
{
|
||||
PEOptionalHeader64 optionalHeader;
|
||||
stream.read((char*)&optionalHeader, sizeof(optionalHeader));
|
||||
|
||||
dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Unrecognized PE format.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read section headers
|
||||
auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader;
|
||||
stream.seekg(sectionHeaderPos);
|
||||
|
||||
Array<PESectionHeader> sectionHeaders;
|
||||
sectionHeaders.Resize(numSectionHeaders);
|
||||
stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * numSectionHeaders);
|
||||
|
||||
// Look for .rsrc section header
|
||||
for (uint32 i = 0; i < numSectionHeaders; i++)
|
||||
{
|
||||
if (sectionHeaders[i].flags & PE_SECTION_UNINITIALIZED_DATA)
|
||||
continue;
|
||||
|
||||
if (strcmp(sectionHeaders[i].name, ".rsrc") == 0)
|
||||
{
|
||||
uint32 imageSize = sectionHeaders[i].physicalSize;
|
||||
Array<uint8> imageData;
|
||||
imageData.Resize(imageSize);
|
||||
|
||||
stream.seekg(sectionHeaders[i].physicalAddress);
|
||||
stream.read((char*)imageData.Get(), imageSize);
|
||||
|
||||
uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeaders[i].relativeVirtualAddress;
|
||||
PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset];
|
||||
|
||||
SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeaders[i].relativeVirtualAddress, iconRGBA8);
|
||||
stream.seekp(sectionHeaders[i].physicalAddress);
|
||||
stream.write((char*)imageData.Get(), imageSize);
|
||||
}
|
||||
}
|
||||
|
||||
stream.close();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String EditorUtilities::GetOutputName()
|
||||
{
|
||||
|
||||
@@ -22,14 +22,6 @@ public:
|
||||
SplashScreen,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Win32 executable file icon.
|
||||
/// </summary>
|
||||
/// <param name="path">The exe path.</param>
|
||||
/// <param name="icon">The icon image data.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
static bool UpdateExeIcon(const String& path, const TextureData& icon);
|
||||
|
||||
static String GetOutputName();
|
||||
static bool FormatAppPackageName(String& packageName);
|
||||
static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon);
|
||||
|
||||
@@ -140,8 +140,7 @@ namespace FlaxEditor.Utilities
|
||||
// Check if start the matching sequence
|
||||
if (matchStartPos == -1)
|
||||
{
|
||||
if (ranges == null)
|
||||
ranges = new List<Range>();
|
||||
ranges ??= new List<Range>();
|
||||
matchStartPos = textPos;
|
||||
}
|
||||
}
|
||||
@@ -152,7 +151,7 @@ namespace FlaxEditor.Utilities
|
||||
{
|
||||
var length = textPos - matchStartPos;
|
||||
if (length >= MinLength)
|
||||
ranges.Add(new Range(matchStartPos, length));
|
||||
ranges!.Add(new Range(matchStartPos, length));
|
||||
textPos = matchStartPos + length;
|
||||
matchStartPos = -1;
|
||||
}
|
||||
@@ -165,13 +164,13 @@ namespace FlaxEditor.Utilities
|
||||
{
|
||||
var length = endPos - matchStartPos;
|
||||
if (length >= MinLength)
|
||||
ranges.Add(new Range(matchStartPos, length));
|
||||
ranges!.Add(new Range(matchStartPos, length));
|
||||
textPos = matchStartPos + length;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if has any range
|
||||
if (ranges != null && ranges.Count > 0)
|
||||
if (ranges is { Count: > 0 })
|
||||
{
|
||||
matches = ranges.ToArray();
|
||||
return true;
|
||||
|
||||
@@ -174,7 +174,10 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
}
|
||||
else
|
||||
{
|
||||
position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f);
|
||||
// calculate the min. distance so that the sphere fits roughly 70% in FOV
|
||||
// clip to far plane as a disappearing big object might be confusing
|
||||
var distance = Mathf.Min(1.4f * sphere.Radius / Mathf.Tan(Mathf.DegreesToRadians * Viewport.FieldOfView / 2), Viewport.FarPlane);
|
||||
position = sphere.Center - Vector3.Forward * orientation * distance;
|
||||
}
|
||||
TargetPoint = sphere.Center;
|
||||
MoveViewport(position, orientation);
|
||||
|
||||
@@ -334,6 +334,22 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding frustum of the current viewport camera.
|
||||
/// </summary>
|
||||
public BoundingFrustum ViewFrustum
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector3 viewOrigin = Task.View.Origin;
|
||||
Float3 position = ViewPosition - viewOrigin;
|
||||
CreateViewMatrix(position, out var view);
|
||||
CreateProjectionMatrix(out var projection);
|
||||
Matrix.Multiply(ref view, ref projection, out var viewProjection);
|
||||
return new BoundingFrustum(ref viewProjection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the yaw angle (in degrees).
|
||||
/// </summary>
|
||||
|
||||
@@ -234,7 +234,6 @@ namespace FlaxEditor.Viewport
|
||||
LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal),
|
||||
Name = item.ShortName
|
||||
};
|
||||
DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000);
|
||||
Spawn(actor, ref hitLocation, ref hitNormal);
|
||||
}
|
||||
else if (hit is StaticModelNode staticModelNode)
|
||||
|
||||
@@ -154,10 +154,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct AnimGraphDebugFlowInfo
|
||||
private unsafe struct AnimGraphDebugFlowInfo
|
||||
{
|
||||
public uint NodeId;
|
||||
public int BoxId;
|
||||
public fixed uint NodePath[8];
|
||||
}
|
||||
|
||||
private FlaxObjectRefPickerControl _debugPicker;
|
||||
@@ -252,25 +253,26 @@ namespace FlaxEditor.Windows.Assets
|
||||
return obj is AnimatedModel player && player.AnimationGraph == OriginalAsset;
|
||||
}
|
||||
|
||||
private void OnDebugFlow(Asset asset, Object obj, uint nodeId, uint boxId)
|
||||
private unsafe void OnDebugFlow(Animations.DebugFlowInfo flowInfo)
|
||||
{
|
||||
// Filter the flow
|
||||
if (_debugPicker.Value != null)
|
||||
{
|
||||
if (asset != OriginalAsset || _debugPicker.Value != obj)
|
||||
if (flowInfo.Asset != OriginalAsset || _debugPicker.Value != flowInfo.Instance)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (asset != Asset || _preview.PreviewActor != obj)
|
||||
if (flowInfo.Asset != Asset || _preview.PreviewActor != flowInfo.Instance)
|
||||
return;
|
||||
}
|
||||
|
||||
// Register flow to show it in UI on a surface
|
||||
var flowInfo = new AnimGraphDebugFlowInfo { NodeId = nodeId, BoxId = (int)boxId };
|
||||
var flow = new AnimGraphDebugFlowInfo { NodeId = flowInfo.NodeId, BoxId = (int)flowInfo.BoxId };
|
||||
Utils.MemoryCopy(new IntPtr(flow.NodePath), new IntPtr(flowInfo.NodePath0), sizeof(uint) * 8ul);
|
||||
lock (_debugFlows)
|
||||
{
|
||||
_debugFlows.Add(flowInfo);
|
||||
_debugFlows.Add(flow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +396,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnUpdate()
|
||||
public override unsafe void OnUpdate()
|
||||
{
|
||||
// Extract animations playback state from the events tracing
|
||||
var debugActor = _debugPicker.Value as AnimatedModel;
|
||||
@@ -413,7 +415,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
{
|
||||
foreach (var debugFlow in _debugFlows)
|
||||
{
|
||||
var node = Surface.Context.FindNode(debugFlow.NodeId);
|
||||
var context = Surface.FindContext(new Span<uint>(debugFlow.NodePath, 8));
|
||||
var node = context?.FindNode(debugFlow.NodeId);
|
||||
var box = node?.GetBox(debugFlow.BoxId);
|
||||
box?.HighlightConnections();
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
// Use virtual animation graph to playback the animation
|
||||
_animGraph = FlaxEngine.Content.CreateVirtualAsset<AnimationGraph>();
|
||||
_animGraph.InitAsAnimation(model, _window.Asset);
|
||||
_animGraph.InitAsAnimation(model, _window.Asset, true, true);
|
||||
PreviewActor.AnimationGraph = _animGraph;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
protected override void OnAssetLinked()
|
||||
{
|
||||
Asset.WaitForLoaded();
|
||||
_textPreview.Font = new FontReference(Asset.CreateFont(30));
|
||||
_textPreview.Font = new FontReference(Asset, 30);
|
||||
_inputText.Text = string.Format("This is a sample text using font {0}.", Asset.FamilyName);
|
||||
var options = Asset.Options;
|
||||
_proxy.Set(ref options);
|
||||
|
||||
@@ -79,7 +79,14 @@ namespace FlaxEditor.Windows
|
||||
|
||||
if (item.HasDefaultThumbnail == false)
|
||||
{
|
||||
cm.AddButton("Refresh thumbnail", item.RefreshThumbnail);
|
||||
if (_view.SelectedCount > 1)
|
||||
cm.AddButton("Refresh thumbnails", () =>
|
||||
{
|
||||
foreach (var e in _view.Selection)
|
||||
e.RefreshThumbnail();
|
||||
});
|
||||
else
|
||||
cm.AddButton("Refresh thumbnail", item.RefreshThumbnail);
|
||||
}
|
||||
|
||||
if (!isFolder)
|
||||
|
||||
@@ -263,6 +263,8 @@ namespace FlaxEditor.Windows
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
OnExit();
|
||||
|
||||
// Unregister
|
||||
|
||||
@@ -485,6 +485,16 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
base.OnShowContextMenu(menu);
|
||||
|
||||
// Focus on play
|
||||
{
|
||||
var focus = menu.AddButton("Start Focused");
|
||||
focus.CloseMenuOnClick = false;
|
||||
var checkbox = new CheckBox(140, 2, FocusOnPlay) { Parent = focus };
|
||||
checkbox.StateChanged += state => FocusOnPlay = state.Checked;
|
||||
}
|
||||
|
||||
menu.AddSeparator();
|
||||
|
||||
// Viewport Brightness
|
||||
{
|
||||
var brightness = menu.AddButton("Viewport Brightness");
|
||||
|
||||
@@ -20,9 +20,7 @@ namespace FlaxEngine
|
||||
get
|
||||
{
|
||||
fixed (short* name = Name0)
|
||||
{
|
||||
return new string((char*)name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +29,7 @@ namespace FlaxEngine
|
||||
fixed (short* name = Name0)
|
||||
{
|
||||
fixed (char* p = prefix)
|
||||
{
|
||||
return Utils.MemoryCompare(new IntPtr(name), new IntPtr(p), (ulong)(prefix.Length * 2)) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +132,13 @@ namespace FlaxEditor.Windows
|
||||
b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
|
||||
b.Enabled = canEditScene;
|
||||
|
||||
// Prefab options
|
||||
// Create option
|
||||
|
||||
contextMenu.AddSeparator();
|
||||
|
||||
b = contextMenu.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors);
|
||||
b.Enabled = canEditScene && hasSthSelected;
|
||||
|
||||
b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab);
|
||||
b.Enabled = isSingleActorSelected &&
|
||||
((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab &&
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
@@ -14,7 +13,6 @@ using FlaxEditor.Scripting;
|
||||
using FlaxEditor.States;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using static FlaxEditor.GUI.ItemsListContextMenu;
|
||||
|
||||
namespace FlaxEditor.Windows
|
||||
{
|
||||
@@ -35,6 +33,11 @@ namespace FlaxEditor.Windows
|
||||
private DragScriptItems _dragScriptItems;
|
||||
private DragHandlers _dragHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Scene tree panel.
|
||||
/// </summary>
|
||||
public Panel SceneTreePanel => _sceneTreePanel;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SceneTreeWindow"/> class.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user