Merge remote-tracking branch 'origin/master' into 1.11
# Conflicts: # Content/Editor/DebugMaterials/DDGIDebugProbes.flax # Source/Engine/Scripting/Scripting.cpp
This commit is contained in:
BIN
Content/Editor/Camera/M_Camera.flax
(Stored with Git LFS)
BIN
Content/Editor/Camera/M_Camera.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/CubeTexturePreviewMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/CubeTexturePreviewMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/DebugMaterials/SingleColor/Decal.flax
(Stored with Git LFS)
BIN
Content/Editor/DebugMaterials/SingleColor/Decal.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/DebugMaterials/SingleColor/Particle.flax
(Stored with Git LFS)
BIN
Content/Editor/DebugMaterials/SingleColor/Particle.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/DebugMaterials/SingleColor/Surface.flax
(Stored with Git LFS)
BIN
Content/Editor/DebugMaterials/SingleColor/Surface.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
(Stored with Git LFS)
BIN
Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/DebugMaterials/SingleColor/Terrain.flax
(Stored with Git LFS)
BIN
Content/Editor/DebugMaterials/SingleColor/Terrain.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/DefaultFontMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/DefaultFontMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Gizmo/FoliageBrushMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/Gizmo/FoliageBrushMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Gizmo/Material.flax
(Stored with Git LFS)
BIN
Content/Editor/Gizmo/Material.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Gizmo/MaterialWire.flax
(Stored with Git LFS)
BIN
Content/Editor/Gizmo/MaterialWire.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Gizmo/SelectionOutlineMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/Gizmo/SelectionOutlineMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Highlight Material.flax
(Stored with Git LFS)
BIN
Content/Editor/Highlight Material.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Icons/IconsMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/Icons/IconsMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/IconsAtlas.flax
(Stored with Git LFS)
BIN
Content/Editor/IconsAtlas.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/IesProfilePreviewMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/IesProfilePreviewMaterial.flax
(Stored with Git LFS)
Binary file not shown.
@@ -13,7 +13,7 @@
|
||||
META_CB_BEGIN(0, Data)
|
||||
float4x4 WorldMatrix;
|
||||
float4x4 InvWorld;
|
||||
float4x4 SVPositionToWorld;
|
||||
float4x4 SvPositionToWorld;
|
||||
@1META_CB_END
|
||||
|
||||
// Use depth buffer for per-pixel decal layering
|
||||
@@ -27,12 +27,63 @@ struct MaterialInput
|
||||
float3 WorldPosition;
|
||||
float TwoSidedSign;
|
||||
float2 TexCoord;
|
||||
float4 TexCoord_DDX_DDY;
|
||||
float3x3 TBN;
|
||||
float4 SvPosition;
|
||||
float3 PreSkinnedPosition;
|
||||
float3 PreSkinnedNormal;
|
||||
};
|
||||
|
||||
// Calculates decal texcoords for a given pixel position (sampels depth buffer and projects value to decal space).
|
||||
float2 SvPositionToDecalUV(float4 svPosition)
|
||||
{
|
||||
float2 screenUV = svPosition.xy * ScreenSize.zw;
|
||||
svPosition.z = SAMPLE_RT(DepthBuffer, screenUV).r;
|
||||
float4 positionHS = mul(float4(svPosition.xyz, 1), SvPositionToWorld);
|
||||
float3 positionWS = positionHS.xyz / positionHS.w;
|
||||
float3 positionOS = mul(float4(positionWS, 1), InvWorld).xyz;
|
||||
return positionOS.xz + 0.5f;
|
||||
}
|
||||
|
||||
// Manually compute ddx/ddy for decal texture cooordinates to avoid the 2x2 pixels artifacts on the edges of geometry under decal
|
||||
// [Reference: https://www.humus.name/index.php?page=3D&ID=84]
|
||||
float4 CalculateTextureDerivatives(float4 svPosition, float2 texCoord)
|
||||
{
|
||||
float4 svDiffX = float4(1, 0, 0, 0);
|
||||
float2 uvDiffX0 = texCoord - SvPositionToDecalUV(svPosition - svDiffX);
|
||||
float2 uvDiffX1 = SvPositionToDecalUV(svPosition + svDiffX) - texCoord;
|
||||
float2 dx = dot(uvDiffX0, uvDiffX0) < dot(uvDiffX1, uvDiffX1) ? uvDiffX0 : uvDiffX1;
|
||||
|
||||
float4 svDiffY = float4(0, 1, 0, 0);
|
||||
float2 uvDiffY0 = texCoord - SvPositionToDecalUV(svPosition - svDiffY);
|
||||
float2 uvDiffY1 = SvPositionToDecalUV(svPosition + svDiffY) - texCoord;
|
||||
float2 dy = dot(uvDiffY0, uvDiffY0) < dot(uvDiffY1, uvDiffY1) ? uvDiffY0 : uvDiffY1;
|
||||
|
||||
return float4(dx, dy);
|
||||
}
|
||||
|
||||
// Computes the mipmap level for a specific texture dimensions to be sampled at decal texture cooordinates.
|
||||
// [Reference: https://hugi.scene.org/online/coding/hugi%2014%20-%20comipmap.htm]
|
||||
float CalculateTextureMipmap(MaterialInput input, float2 textureSize)
|
||||
{
|
||||
float2 dx = input.TexCoord_DDX_DDY.xy * textureSize;
|
||||
float2 dy = input.TexCoord_DDX_DDY.zw * textureSize;
|
||||
float d = max(dot(dx, dx), dot(dy, dy));
|
||||
return (0.5 * 0.5) * log2(d); // Hardcoded half-mip rate reduction to avoid artifacts when decal is moved over dither texture
|
||||
}
|
||||
float CalculateTextureMipmap(MaterialInput input, Texture2D t)
|
||||
{
|
||||
float2 textureSize;
|
||||
t.GetDimensions(textureSize.x, textureSize.y);
|
||||
return CalculateTextureMipmap(input, textureSize);
|
||||
}
|
||||
float CalculateTextureMipmap(MaterialInput input, TextureCube t)
|
||||
{
|
||||
float2 textureSize;
|
||||
t.GetDimensions(textureSize.x, textureSize.y);
|
||||
return CalculateTextureMipmap(input, textureSize);
|
||||
}
|
||||
|
||||
// Transforms a vector from tangent space to world space
|
||||
float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector)
|
||||
{
|
||||
@@ -116,7 +167,6 @@ Material GetMaterialPS(MaterialInput input)
|
||||
}
|
||||
|
||||
// Input macro specified by the material: DECAL_BLEND_MODE
|
||||
|
||||
#define DECAL_BLEND_MODE_TRANSLUCENT 0
|
||||
#define DECAL_BLEND_MODE_STAIN 1
|
||||
#define DECAL_BLEND_MODE_NORMAL 2
|
||||
@@ -153,7 +203,7 @@ void PS_Decal(
|
||||
float2 screenUV = SvPosition.xy * ScreenSize.zw;
|
||||
SvPosition.z = SAMPLE_RT(DepthBuffer, screenUV).r;
|
||||
|
||||
float4 positionHS = mul(float4(SvPosition.xyz, 1), SVPositionToWorld);
|
||||
float4 positionHS = mul(float4(SvPosition.xyz, 1), SvPositionToWorld);
|
||||
float3 positionWS = positionHS.xyz / positionHS.w;
|
||||
float3 positionOS = mul(float4(positionWS, 1), InvWorld).xyz;
|
||||
|
||||
@@ -166,8 +216,9 @@ void PS_Decal(
|
||||
materialInput.TexCoord = decalUVs;
|
||||
materialInput.TwoSidedSign = 1;
|
||||
materialInput.SvPosition = SvPosition;
|
||||
|
||||
// Build tangent to world transformation matrix
|
||||
materialInput.TexCoord_DDX_DDY = CalculateTextureDerivatives(materialInput.SvPosition, materialInput.TexCoord);
|
||||
|
||||
// Calculate tangent-space
|
||||
float3 ddxWp = ddx(positionWS);
|
||||
float3 ddyWp = ddy(positionWS);
|
||||
materialInput.TBN[0] = normalize(ddyWp);
|
||||
|
||||
@@ -20,6 +20,8 @@ float TimeParam;
|
||||
float4 ViewInfo;
|
||||
float4 ScreenSize;
|
||||
float4 ViewSize;
|
||||
float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
@1META_CB_END
|
||||
|
||||
// Shader resources
|
||||
|
||||
@@ -19,6 +19,8 @@ float4 ViewInfo;
|
||||
float4 ScreenSize;
|
||||
float4 TemporalAAJitter;
|
||||
float4x4 InverseViewProjectionMatrix;
|
||||
float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
@1META_CB_END
|
||||
|
||||
// Shader resources
|
||||
|
||||
BIN
Content/Editor/Particles/Particle Material Color.flax
(Stored with Git LFS)
BIN
Content/Editor/Particles/Particle Material Color.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Particles/Smoke Material.flax
(Stored with Git LFS)
BIN
Content/Editor/Particles/Smoke Material.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/SpriteMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/SpriteMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Terrain/Circle Brush Material.flax
(Stored with Git LFS)
BIN
Content/Editor/Terrain/Circle Brush Material.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Terrain/Highlight Terrain Material.flax
(Stored with Git LFS)
BIN
Content/Editor/Terrain/Highlight Terrain Material.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/TexturePreviewMaterial.flax
(Stored with Git LFS)
BIN
Content/Editor/TexturePreviewMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Wires Debug Material.flax
(Stored with Git LFS)
BIN
Content/Editor/Wires Debug Material.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/DefaultDeformableMaterial.flax
(Stored with Git LFS)
BIN
Content/Engine/DefaultDeformableMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/DefaultMaterial.flax
(Stored with Git LFS)
BIN
Content/Engine/DefaultMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/DefaultRadialMenu.flax
(Stored with Git LFS)
BIN
Content/Engine/DefaultRadialMenu.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/DefaultTerrainMaterial.flax
(Stored with Git LFS)
BIN
Content/Engine/DefaultTerrainMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/SingleColorMaterial.flax
(Stored with Git LFS)
BIN
Content/Engine/SingleColorMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/SkyboxMaterial.flax
(Stored with Git LFS)
BIN
Content/Engine/SkyboxMaterial.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/SSAO.flax
(Stored with Git LFS)
BIN
Content/Shaders/SSAO.flax
(Stored with Git LFS)
Binary file not shown.
@@ -73,6 +73,24 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=TYPEDEF/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION_005FMEMBER/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Abbreviations/=CCD/@EntryIndexedValue">CCD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Abbreviations/=GPU/@EntryIndexedValue">GPU</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=175CE9C669E52F4D92FD2C07848740BD/@EntryIndexedValue"><NamingElement Priority="11" Title="Class and struct public fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="PUBLIC"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=32EB6D69783B3E4481A733193E338089/@EntryIndexedValue"><NamingElement Priority="9" Title="Class and struct methods"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=3C4E0D59F298854F9608A9B454E8FF5E/@EntryIndexedValue"><NamingElement Priority="17" Title="Typedefs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="type alias" /><type Name="typedef" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=499C9026DADA2B448BCD0B2C54746A59/@EntryIndexedValue"><NamingElement Priority="14" Title="Other constants"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=50D2535711CE1A43A3B06EF841C36CFD/@EntryIndexedValue"><NamingElement Priority="13" Title="Enum members"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="scoped enumerator" /><type Name="unscoped enumerator" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=6AD3BADA1260CC4D840AB26323C51827/@EntryIndexedValue"><NamingElement Priority="15" Title="Global constants"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=904CDCA174AACE4AA52660A247CDF9A0/@EntryIndexedValue"><NamingElement Priority="7" Title="Global variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=95BCDE767C97B64DB3DAE800DBBBC758/@EntryIndexedValue"><NamingElement Priority="5" Title="Parameters"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="function parameter" /><type Name="lambda parameter" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=C03AE454FC2CBA43819AC75E4D6C9C8C/@EntryIndexedValue"><NamingElement Priority="1" Title="Classes and structs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=D49E31C610641E4CAD0407DB79ACC851/@EntryIndexedValue"><NamingElement Priority="8" Title="Global functions"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global function" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=D73FBB3529BC5449B6C85BB37B26A8D4/@EntryIndexedValue"><NamingElement Priority="16" Title="Namespaces"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="namespace" /><type Name="namespace alias" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=DA41807CE47AEB4CBE1724C44D0B786E/@EntryIndexedValue"><NamingElement Priority="6" Title="Local variables"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=DDF30C9A1DA74B4DBBC56D25FDF886AA/@EntryIndexedValue"><NamingElement Priority="10" Title="Class and struct fields"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=EF70A6BF54ACE446971DDB32344C25A3/@EntryIndexedValue"><NamingElement Priority="12" Title="Union members"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="union member" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=F37818C54C323A4A80B1A478629985AE/@EntryIndexedValue"><NamingElement Priority="2" Title="Enums"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enum" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LO/@EntryIndexedValue">LO</s:String>
|
||||
@@ -213,6 +231,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=YZ/@EntryIndexedValue">YZ</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/CustomLocation/@EntryValue">C:\Users\Wojtek\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v08_f9eacea9\SolutionCaches</s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ENaming_002ECppNamingOptionsMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ESettingsUpgrade_002EFunctionReturnStyleSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ESettingsUpgrade_002ENamespaceIndentationSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace FlaxEditor
|
||||
private readonly CustomEditorPresenter _presenter;
|
||||
private CustomEditorWindow _customEditor;
|
||||
|
||||
public Win(CustomEditorWindow customEditor)
|
||||
: base(Editor.Instance, false, ScrollBars.Vertical)
|
||||
public Win(CustomEditorWindow customEditor, bool hideOnClose, ScrollBars scrollBars)
|
||||
: base(Editor.Instance, hideOnClose, scrollBars)
|
||||
{
|
||||
Title = customEditor.GetType().Name;
|
||||
_customEditor = customEditor;
|
||||
@@ -64,9 +64,9 @@ namespace FlaxEditor
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomEditorWindow"/> class.
|
||||
/// </summary>
|
||||
protected CustomEditorWindow()
|
||||
protected CustomEditorWindow(bool hideOnClose = false, ScrollBars scrollBars = ScrollBars.Vertical)
|
||||
{
|
||||
_win = new Win(this);
|
||||
_win = new Win(this, hideOnClose, scrollBars);
|
||||
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
_linkButton = new Button
|
||||
{
|
||||
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32),
|
||||
Parent = LinkedLabel,
|
||||
Width = 18,
|
||||
Height = 18,
|
||||
@@ -189,6 +188,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
_linkButton.SetColors(backgroundColor);
|
||||
_linkButton.BorderColor = _linkButton.BorderColorSelected = _linkButton.BorderColorHighlighted = Color.Transparent;
|
||||
_linkButton.TooltipText = LinkValues ? "Unlinks scale components from uniform scaling" : "Links scale components for uniform scaling";
|
||||
_linkButton.BackgroundBrush = new SpriteBrush(LinkValues ? Editor.Instance.Icons.Link32 : Editor.Instance.Icons.BrokenLink32);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ namespace FlaxEditor.CustomEditors.GUI
|
||||
public class PropertiesList : PanelWithMargins
|
||||
{
|
||||
// TODO: sync splitter for whole presenter
|
||||
|
||||
private const float SplitterPadding = 15;
|
||||
private const float EditorsMinWidthRatio = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// The splitter size (in pixels).
|
||||
@@ -25,6 +28,7 @@ namespace FlaxEditor.CustomEditors.GUI
|
||||
private Rectangle _splitterRect;
|
||||
private bool _splitterClicked, _mouseOverSplitter;
|
||||
private bool _cursorChanged;
|
||||
private bool _hasCustomSplitterValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the splitter value (always in range [0; 1]).
|
||||
@@ -66,6 +70,26 @@ namespace FlaxEditor.CustomEditors.GUI
|
||||
UpdateSplitRect();
|
||||
}
|
||||
|
||||
private void AutoSizeSplitter()
|
||||
{
|
||||
if (_hasCustomSplitterValue || !Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter)
|
||||
return;
|
||||
|
||||
Font font = Style.Current.FontMedium;
|
||||
|
||||
float largestWidth = 0f;
|
||||
for (int i = 0; i < _element.Labels.Count; i++)
|
||||
{
|
||||
Label currentLabel = _element.Labels[i];
|
||||
Float2 dimensions = font.MeasureText(currentLabel.Text);
|
||||
float width = dimensions.X + currentLabel.Margin.Left + SplitterPadding;
|
||||
|
||||
largestWidth = Mathf.Max(largestWidth, width);
|
||||
}
|
||||
|
||||
SplitterValue = Mathf.Clamp(largestWidth / Width, 0, 1 - EditorsMinWidthRatio);
|
||||
}
|
||||
|
||||
private void UpdateSplitRect()
|
||||
{
|
||||
_splitterRect = new Rectangle(Mathf.Clamp(_splitterValue * Width - SplitterSize * 0.5f, 0.0f, Width), 0, SplitterSize, Height);
|
||||
@@ -122,6 +146,7 @@ namespace FlaxEditor.CustomEditors.GUI
|
||||
SplitterValue = location.X / Width;
|
||||
Cursor = CursorType.SizeWE;
|
||||
_cursorChanged = true;
|
||||
_hasCustomSplitterValue = true;
|
||||
}
|
||||
else if (_mouseOverSplitter)
|
||||
{
|
||||
@@ -195,6 +220,7 @@ namespace FlaxEditor.CustomEditors.GUI
|
||||
// Refresh
|
||||
UpdateSplitRect();
|
||||
PerformLayout(true);
|
||||
AutoSizeSplitter();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -528,7 +528,11 @@ namespace FlaxEditor
|
||||
var timeSinceLastSave = Time.UnscaledGameTime - _lastAutoSaveTimer;
|
||||
var timeToNextSave = options.AutoSaveFrequency * 60.0f - timeSinceLastSave;
|
||||
|
||||
if (timeToNextSave <= 0.0f || _autoSaveNow)
|
||||
if (timeToNextSave <= 0.0f && GetWindows().Any(x => x.GUI.Children.Any(c => c is GUI.ContextMenu.ContextMenuBase)))
|
||||
{
|
||||
// Skip aut-save if any context menu is opened to wait for user to end interaction
|
||||
}
|
||||
else if (timeToNextSave <= 0.0f || _autoSaveNow)
|
||||
{
|
||||
Log("Auto save");
|
||||
_lastAutoSaveTimer = Time.UnscaledGameTime;
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace FlaxEditor
|
||||
public SpriteHandle Globe32;
|
||||
public SpriteHandle CamSpeed32;
|
||||
public SpriteHandle Link32;
|
||||
public SpriteHandle BrokenLink32;
|
||||
public SpriteHandle Add32;
|
||||
public SpriteHandle Left32;
|
||||
public SpriteHandle Right32;
|
||||
@@ -94,6 +95,7 @@ namespace FlaxEditor
|
||||
public SpriteHandle Search64;
|
||||
public SpriteHandle Bone64;
|
||||
public SpriteHandle Link64;
|
||||
public SpriteHandle BrokenLink64;
|
||||
public SpriteHandle Build64;
|
||||
public SpriteHandle Add64;
|
||||
public SpriteHandle ShipIt64;
|
||||
|
||||
@@ -215,8 +215,8 @@ namespace FlaxEditor.GUI.Docking
|
||||
switch (state)
|
||||
{
|
||||
case DockState.DockFill:
|
||||
result.Location.Y += DockPanel.DefaultHeaderHeight;
|
||||
result.Size.Y -= DockPanel.DefaultHeaderHeight;
|
||||
result.Location.Y += Editor.Instance.Options.Options.Interface.TabHeight;
|
||||
result.Size.Y -= Editor.Instance.Options.Options.Interface.TabHeight;
|
||||
break;
|
||||
case DockState.DockTop:
|
||||
result.Size.Y *= DockPanel.DefaultSplitterValue;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -13,12 +14,16 @@ namespace FlaxEditor.GUI.Docking
|
||||
public class DockPanelProxy : ContainerControl
|
||||
{
|
||||
private DockPanel _panel;
|
||||
private InterfaceOptions.TabCloseButtonVisibility closeButtonVisibility;
|
||||
private double _dragEnterTime = -1;
|
||||
#if PLATFORM_WINDOWS
|
||||
private const bool HideTabForSingleTab = true;
|
||||
#else
|
||||
private const bool HideTabForSingleTab = false;
|
||||
#endif
|
||||
private float _tabHeight = Editor.Instance.Options.Options.Interface.TabHeight;
|
||||
private bool _useMinimumTabWidth = Editor.Instance.Options.Options.Interface.UseMinimumTabWidth;
|
||||
private float _minimumTabWidth = Editor.Instance.Options.Options.Interface.MinimumTabWidth;
|
||||
#if PLATFORM_WINDOWS
|
||||
private readonly bool _hideTabForSingleTab = Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars;
|
||||
#else
|
||||
private readonly bool _hideTabForSingleTab = false;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The is mouse down flag (left button).
|
||||
@@ -55,8 +60,8 @@ namespace FlaxEditor.GUI.Docking
|
||||
/// </summary>
|
||||
public DockWindow StartDragAsyncWindow;
|
||||
|
||||
private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight);
|
||||
private bool IsSingleFloatingWindow => HideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0;
|
||||
private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, _tabHeight);
|
||||
private bool IsSingleFloatingWindow => _hideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DockPanelProxy"/> class.
|
||||
@@ -68,6 +73,14 @@ namespace FlaxEditor.GUI.Docking
|
||||
_panel = panel;
|
||||
AnchorPreset = AnchorPresets.StretchAll;
|
||||
Offsets = Margin.Zero;
|
||||
|
||||
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
|
||||
OnEditorOptionsChanged(Editor.Instance.Options.Options);
|
||||
}
|
||||
|
||||
private void OnEditorOptionsChanged(EditorOptions options)
|
||||
{
|
||||
closeButtonVisibility = options.Interface.ShowTabCloseButton;
|
||||
}
|
||||
|
||||
private DockWindow GetTabAtPos(Float2 position, out bool closeButton)
|
||||
@@ -78,11 +91,11 @@ namespace FlaxEditor.GUI.Docking
|
||||
var tabsCount = _panel.TabsCount;
|
||||
if (tabsCount == 1)
|
||||
{
|
||||
var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
if (HeaderRectangle.Contains(position))
|
||||
{
|
||||
closeButton = crossRect.Contains(position);
|
||||
result = _panel.GetTab(0);
|
||||
closeButton = crossRect.Contains(position) && IsCloseButtonVisible(result, closeButtonVisibility);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -91,15 +104,17 @@ namespace FlaxEditor.GUI.Docking
|
||||
for (int i = 0; i < tabsCount; i++)
|
||||
{
|
||||
var tab = _panel.GetTab(i);
|
||||
var titleSize = tab.TitleSize;
|
||||
var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0;
|
||||
var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth;
|
||||
var tabRect = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight);
|
||||
float width = CalculateTabWidth(tab, closeButtonVisibility);
|
||||
|
||||
if (_useMinimumTabWidth && width < _minimumTabWidth)
|
||||
width = _minimumTabWidth;
|
||||
|
||||
var tabRect = new Rectangle(x, 0, width, HeaderRectangle.Height);
|
||||
var isMouseOver = tabRect.Contains(position);
|
||||
if (isMouseOver)
|
||||
{
|
||||
var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
closeButton = crossRect.Contains(position);
|
||||
var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
closeButton = crossRect.Contains(position) && IsCloseButtonVisible(tab, closeButtonVisibility);
|
||||
result = tab;
|
||||
break;
|
||||
}
|
||||
@@ -110,6 +125,24 @@ namespace FlaxEditor.GUI.Docking
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool IsCloseButtonVisible(DockWindow win, InterfaceOptions.TabCloseButtonVisibility visibilityMode)
|
||||
{
|
||||
return visibilityMode != InterfaceOptions.TabCloseButtonVisibility.Never &&
|
||||
(visibilityMode == InterfaceOptions.TabCloseButtonVisibility.Always ||
|
||||
(visibilityMode == InterfaceOptions.TabCloseButtonVisibility.SelectedTab && _panel.SelectedTab == win));
|
||||
}
|
||||
|
||||
private float CalculateTabWidth(DockWindow win, InterfaceOptions.TabCloseButtonVisibility visibilityMode)
|
||||
{
|
||||
var iconWidth = win.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0;
|
||||
var width = win.TitleSize.X + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth;
|
||||
|
||||
if (IsCloseButtonVisible(win, visibilityMode))
|
||||
width += 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultButtonsSize;
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
private void GetTabRect(DockWindow win, out Rectangle bounds)
|
||||
{
|
||||
FlaxEngine.Assertions.Assert.IsTrue(_panel.ContainsTab(win));
|
||||
@@ -127,10 +160,10 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
var tab = _panel.GetTab(i);
|
||||
var titleSize = tab.TitleSize;
|
||||
float width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin;
|
||||
float width = CalculateTabWidth(tab, closeButtonVisibility);
|
||||
if (tab == win)
|
||||
{
|
||||
bounds = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight);
|
||||
bounds = new Rectangle(x, 0, width, HeaderRectangle.Height);
|
||||
return;
|
||||
}
|
||||
x += width;
|
||||
@@ -210,7 +243,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
Render2D.DrawSprite(
|
||||
tab.Icon,
|
||||
new Rectangle(DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize),
|
||||
new Rectangle(DockPanel.DefaultLeftTextMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize),
|
||||
style.Foreground);
|
||||
|
||||
}
|
||||
@@ -219,17 +252,20 @@ namespace FlaxEditor.GUI.Docking
|
||||
Render2D.DrawText(
|
||||
style.FontMedium,
|
||||
tab.Title,
|
||||
new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, DockPanel.DefaultHeaderHeight),
|
||||
new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, HeaderRectangle.Height),
|
||||
style.Foreground,
|
||||
TextAlignment.Near,
|
||||
TextAlignment.Center);
|
||||
|
||||
// Draw cross
|
||||
var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition);
|
||||
if (isMouseOverCross)
|
||||
Render2D.FillRectangle(crossRect, (containsFocus ? style.BackgroundSelected : style.LightBackground) * 1.3f);
|
||||
Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey);
|
||||
if (IsCloseButtonVisible(tab, closeButtonVisibility))
|
||||
{
|
||||
// Draw cross
|
||||
var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition);
|
||||
if (isMouseOverCross)
|
||||
Render2D.FillRectangle(crossRect, (containsFocus ? style.BackgroundSelected : style.LightBackground) * 1.3f);
|
||||
Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -243,10 +279,14 @@ namespace FlaxEditor.GUI.Docking
|
||||
// Cache data
|
||||
var tab = _panel.GetTab(i);
|
||||
var tabColor = Color.Black;
|
||||
var titleSize = tab.TitleSize;
|
||||
var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0;
|
||||
var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth;
|
||||
var tabRect = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight);
|
||||
|
||||
float width = CalculateTabWidth(tab, closeButtonVisibility);
|
||||
|
||||
if (_useMinimumTabWidth && width < _minimumTabWidth)
|
||||
width = _minimumTabWidth;
|
||||
|
||||
var tabRect = new Rectangle(x, 0, width, headerRect.Height);
|
||||
var isMouseOver = tabRect.Contains(MousePosition);
|
||||
var isSelected = _panel.SelectedTab == tab;
|
||||
|
||||
@@ -273,7 +313,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
{
|
||||
Render2D.DrawSprite(
|
||||
tab.Icon,
|
||||
new Rectangle(x + DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize),
|
||||
new Rectangle(x + DockPanel.DefaultLeftTextMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize),
|
||||
style.Foreground);
|
||||
|
||||
}
|
||||
@@ -282,27 +322,27 @@ namespace FlaxEditor.GUI.Docking
|
||||
Render2D.DrawText(
|
||||
style.FontMedium,
|
||||
tab.Title,
|
||||
new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, DockPanel.DefaultHeaderHeight),
|
||||
new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, HeaderRectangle.Height),
|
||||
style.Foreground,
|
||||
TextAlignment.Near,
|
||||
TextAlignment.Center);
|
||||
|
||||
// Draw cross
|
||||
if (isSelected || isMouseOver)
|
||||
if (IsCloseButtonVisible(tab, closeButtonVisibility))
|
||||
{
|
||||
var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize);
|
||||
bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition);
|
||||
if (isMouseOverCross)
|
||||
Render2D.FillRectangle(crossRect, tabColor * 1.3f);
|
||||
Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
// Move
|
||||
// Set the start position for the next tab
|
||||
x += width;
|
||||
}
|
||||
|
||||
// Draw selected tab strip
|
||||
Render2D.FillRectangle(new Rectangle(0, DockPanel.DefaultHeaderHeight - 2, Width, 2), containsFocus ? style.BackgroundSelected : style.BackgroundNormal);
|
||||
Render2D.FillRectangle(new Rectangle(0, HeaderRectangle.Height - 2, Width, 2), containsFocus ? style.BackgroundSelected : style.BackgroundNormal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +560,7 @@ namespace FlaxEditor.GUI.Docking
|
||||
if (IsSingleFloatingWindow)
|
||||
rect = new Rectangle(0, 0, Width, Height);
|
||||
else
|
||||
rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight);
|
||||
rect = new Rectangle(0, HeaderRectangle.Height, Width, Height - HeaderRectangle.Height);
|
||||
}
|
||||
|
||||
private DragDropEffect TrySelectTabUnderLocation(ref Float2 location)
|
||||
|
||||
@@ -514,20 +514,15 @@ namespace FlaxEditor.GUI
|
||||
var items = ItemsPanel.Children;
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (items[i] is Item item && item.Visible)
|
||||
var currentItem = items[i];
|
||||
if (currentItem is Item item && item.Visible)
|
||||
result.Add(item);
|
||||
}
|
||||
if (_categoryPanels != null)
|
||||
{
|
||||
for (int i = 0; i < _categoryPanels.Count; i++)
|
||||
else if (currentItem is DropPanel category && (!ignoreFoldedCategories || !category.IsClosed) && currentItem.Visible)
|
||||
{
|
||||
var category = _categoryPanels[i];
|
||||
if (!category.Visible || (ignoreFoldedCategories && category is DropPanel panel && panel.IsClosed))
|
||||
continue;
|
||||
for (int j = 0; j < category.Children.Count; j++)
|
||||
{
|
||||
if (category.Children[j] is Item item2 && item2.Visible)
|
||||
result.Add(item2);
|
||||
if (category.Children[j] is Item categoryItem && categoryItem.Visible)
|
||||
result.Add(categoryItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,10 +586,6 @@ namespace FlaxEditor.GUI
|
||||
var items = GetVisibleItems(!controlDown);
|
||||
var focusedIndex = items.IndexOf(focusedItem);
|
||||
|
||||
// If the user hasn't selected anything yet and is holding control, focus first folded item
|
||||
if (focusedIndex == -1 && controlDown)
|
||||
focusedIndex = GetVisibleItems(true).Count - 1;
|
||||
|
||||
int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
|
||||
int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, items.Count - 1);
|
||||
var nextItem = items[nextIndex];
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace FlaxEditor.GUI
|
||||
/// <seealso cref="FlaxEngine.GUI.Panel" />
|
||||
public class NavigationBar : Panel
|
||||
{
|
||||
private float _toolstripHeight = 0;
|
||||
private Margin _toolstripMargin;
|
||||
|
||||
/// <summary>
|
||||
/// The default buttons margin.
|
||||
/// </summary>
|
||||
@@ -50,9 +53,42 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
if (toolstrip == null)
|
||||
return;
|
||||
|
||||
if (_toolstripHeight <= 0.0f)
|
||||
{
|
||||
// Cache initial toolstrip state
|
||||
_toolstripHeight = toolstrip.Height;
|
||||
_toolstripMargin = toolstrip.ItemsMargin;
|
||||
}
|
||||
|
||||
// Control toolstrip bottom margin to prevent navigation bar scroll going over the buttons
|
||||
var toolstripLocked = toolstrip.IsLayoutLocked;
|
||||
toolstrip.IsLayoutLocked = true;
|
||||
var toolstripHeight = _toolstripHeight;
|
||||
var toolstripMargin = _toolstripMargin;
|
||||
if (HScrollBar.Visible)
|
||||
{
|
||||
float scrollMargin = 8;
|
||||
toolstripHeight += scrollMargin;
|
||||
toolstripMargin.Bottom += scrollMargin;
|
||||
}
|
||||
toolstrip.Height = toolstripHeight;
|
||||
toolstrip.IsLayoutLocked = toolstripLocked;
|
||||
toolstrip.ItemsMargin = toolstripMargin;
|
||||
|
||||
var lastToolstripButton = toolstrip.LastButton;
|
||||
var parentSize = Parent.Size;
|
||||
Bounds = new Rectangle(lastToolstripButton.Right + 8.0f, 0, parentSize.X - X - 8.0f, toolstrip.Height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PerformLayout(bool force = false)
|
||||
{
|
||||
base.PerformLayout(force);
|
||||
|
||||
// Stretch excluding toolstrip margin to fill the space
|
||||
if (Parent is ToolStrip toolStrip)
|
||||
Height = toolStrip.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,10 @@ namespace FlaxEditor.GUI
|
||||
/// <returns>Created popup.</returns>
|
||||
public static RenamePopup Show(Control control, Rectangle area, string value, bool isMultiline)
|
||||
{
|
||||
// hardcoded flushing layout for tree controls
|
||||
if (control is Tree.TreeNode treeNode && treeNode.ParentTree != null)
|
||||
treeNode.ParentTree.FlushPendingPerformLayout();
|
||||
|
||||
// Calculate the control size in the window space to handle scaled controls
|
||||
var upperLeft = control.PointToWindow(area.UpperLeft);
|
||||
var bottomRight = control.PointToWindow(area.BottomRight);
|
||||
|
||||
@@ -13,15 +13,7 @@ namespace FlaxEditor.GUI
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
public class ToolStrip : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The default margin vertically.
|
||||
/// </summary>
|
||||
public const int DefaultMarginV = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The default margin horizontally.
|
||||
/// </summary>
|
||||
public const int DefaultMarginH = 2;
|
||||
private Margin _itemsMargin;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when button gets clicked with the primary mouse button.
|
||||
@@ -66,10 +58,26 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the space around items.
|
||||
/// </summary>
|
||||
public Margin ItemsMargin
|
||||
{
|
||||
get => _itemsMargin;
|
||||
set
|
||||
{
|
||||
if (_itemsMargin != value)
|
||||
{
|
||||
_itemsMargin = value;
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height for the items.
|
||||
/// </summary>
|
||||
public float ItemsHeight => Height - 2 * DefaultMarginV;
|
||||
public float ItemsHeight => Height - _itemsMargin.Height;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToolStrip"/> class.
|
||||
@@ -82,6 +90,7 @@ namespace FlaxEditor.GUI
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop;
|
||||
BackgroundColor = Style.Current.LightBackground;
|
||||
Offsets = new Margin(0, 0, y, height * Editor.Instance.Options.Options.Interface.IconsScale);
|
||||
_itemsMargin = new Margin(2, 2, 1, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -161,7 +170,7 @@ namespace FlaxEditor.GUI
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
// Arrange controls
|
||||
float x = DefaultMarginH;
|
||||
float x = _itemsMargin.Left;
|
||||
float h = ItemsHeight;
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
@@ -169,8 +178,8 @@ namespace FlaxEditor.GUI
|
||||
if (c.Visible)
|
||||
{
|
||||
var w = c.Width;
|
||||
c.Bounds = new Rectangle(x, DefaultMarginV, w, h);
|
||||
x += w + DefaultMarginH;
|
||||
c.Bounds = new Rectangle(x, _itemsMargin.Top, w, h);
|
||||
x += w + _itemsMargin.Width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
private Margin _margin;
|
||||
private bool _autoSize = true;
|
||||
private bool _deferLayoutUpdate = false;
|
||||
private TreeNode _lastSelectedNode;
|
||||
|
||||
/// <summary>
|
||||
/// The TreeNode that is being dragged over. This could have a value when not dragging.
|
||||
@@ -67,7 +68,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
/// Gets the first selected node or null.
|
||||
/// </summary>
|
||||
public TreeNode SelectedNode => Selection.Count > 0 ? Selection[0] : null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allow nodes to Draw the root tree line.
|
||||
/// </summary>
|
||||
@@ -364,6 +365,19 @@ namespace FlaxEditor.GUI.Tree
|
||||
BulkSelectUpdateExpanded(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any pending layout perming action that has been delayed until next update to optimize performance of the complex tree hierarchy.
|
||||
/// </summary>
|
||||
public void FlushPendingPerformLayout()
|
||||
{
|
||||
if (_deferLayoutUpdate)
|
||||
{
|
||||
base.PerformLayout();
|
||||
AfterDeferredLayout?.Invoke();
|
||||
_deferLayoutUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PerformLayout(bool force = false)
|
||||
{
|
||||
@@ -378,25 +392,31 @@ namespace FlaxEditor.GUI.Tree
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (_deferLayoutUpdate)
|
||||
{
|
||||
base.PerformLayout();
|
||||
AfterDeferredLayout?.Invoke();
|
||||
_deferLayoutUpdate = false;
|
||||
}
|
||||
FlushPendingPerformLayout();
|
||||
var window = Root;
|
||||
bool shiftDown = window.GetKey(KeyboardKeys.Shift);
|
||||
bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp);
|
||||
bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown);
|
||||
|
||||
var node = SelectedNode;
|
||||
// Use last selection for last selected node if sift is down
|
||||
if (Selection.Count < 2)
|
||||
_lastSelectedNode = null;
|
||||
else if (shiftDown)
|
||||
_lastSelectedNode ??= Selection[^1];
|
||||
|
||||
// Skip root to prevent blocking input
|
||||
if (_lastSelectedNode != null && _lastSelectedNode.IsRoot)
|
||||
_lastSelectedNode = null;
|
||||
|
||||
var node = _lastSelectedNode ?? SelectedNode;
|
||||
|
||||
// Check if has focus and if any node is focused and it isn't a root
|
||||
if (ContainsFocus && node != null && node.AutoFocus)
|
||||
{
|
||||
var window = Root;
|
||||
if (window.GetKeyDown(KeyboardKeys.ArrowUp) || window.GetKeyDown(KeyboardKeys.ArrowDown))
|
||||
_keyUpdateTime = KeyUpdateTimeout;
|
||||
if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused)
|
||||
{
|
||||
bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp);
|
||||
bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown);
|
||||
|
||||
// Check if arrow flags are different
|
||||
if (keyDownArrow != keyUpArrow)
|
||||
{
|
||||
@@ -406,24 +426,38 @@ namespace FlaxEditor.GUI.Tree
|
||||
Assert.AreNotEqual(-1, myIndex);
|
||||
|
||||
// Up
|
||||
TreeNode toSelect = null;
|
||||
List<TreeNode> toSelect = new List<TreeNode>();
|
||||
if (shiftDown && _supportMultiSelect)
|
||||
{
|
||||
toSelect.AddRange(Selection);
|
||||
}
|
||||
if (keyUpArrow)
|
||||
{
|
||||
if (myIndex == 0)
|
||||
{
|
||||
// Select parent
|
||||
toSelect = parentNode;
|
||||
if (toSelect.Contains(parentNode))
|
||||
toSelect.Remove(node);
|
||||
else if (parentNode != null)
|
||||
toSelect.Add(parentNode);
|
||||
_lastSelectedNode = parentNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select previous parent child
|
||||
toSelect = nodeParent.GetChild(myIndex - 1) as TreeNode;
|
||||
var select = nodeParent.GetChild(myIndex - 1) as TreeNode;
|
||||
|
||||
// Select last child if is valid and expanded and has any children
|
||||
if (toSelect != null && toSelect.IsExpanded && toSelect.HasAnyVisibleChild)
|
||||
if (select != null && select.IsExpanded && select.HasAnyVisibleChild)
|
||||
{
|
||||
toSelect = toSelect.GetChild(toSelect.ChildrenCount - 1) as TreeNode;
|
||||
select = select.GetChild(select.ChildrenCount - 1) as TreeNode;
|
||||
}
|
||||
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
}
|
||||
// Down
|
||||
@@ -432,32 +466,48 @@ namespace FlaxEditor.GUI.Tree
|
||||
if (node.IsExpanded && node.HasAnyVisibleChild)
|
||||
{
|
||||
// Select the first child
|
||||
toSelect = node.GetChild(0) as TreeNode;
|
||||
var select = node.GetChild(0) as TreeNode;
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
else if (myIndex == nodeParent.ChildrenCount - 1)
|
||||
{
|
||||
// Select next node after parent
|
||||
while (parentNode != null && toSelect == null)
|
||||
TreeNode select = null;
|
||||
while (parentNode != null && select == null)
|
||||
{
|
||||
int parentIndex = parentNode.IndexInParent;
|
||||
if (parentIndex != -1 && parentIndex < parentNode.Parent.ChildrenCount - 1)
|
||||
{
|
||||
toSelect = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode;
|
||||
select = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode;
|
||||
}
|
||||
parentNode = parentNode.Parent as TreeNode;
|
||||
}
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select next parent child
|
||||
toSelect = nodeParent.GetChild(myIndex + 1) as TreeNode;
|
||||
var select = nodeParent.GetChild(myIndex + 1) as TreeNode;
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
}
|
||||
if (toSelect != null && toSelect.AutoFocus)
|
||||
if (toSelect.Count > 0)
|
||||
{
|
||||
// Select
|
||||
Select(toSelect);
|
||||
toSelect.Focus();
|
||||
_lastSelectedNode?.Focus();
|
||||
}
|
||||
|
||||
// Reset time
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MException.h"
|
||||
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
|
||||
#include "Engine/Platform/WindowsManager.h"
|
||||
#include "Engine/Content/Assets/VisualScript.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
@@ -627,6 +628,14 @@ void ManagedEditor::WipeOutLeftoverSceneObjects()
|
||||
ObjectsRemovalService::Flush();
|
||||
}
|
||||
|
||||
Array<Window*> ManagedEditor::GetWindows()
|
||||
{
|
||||
WindowsManager::WindowsLocker.Lock();
|
||||
auto result = WindowsManager::Windows;
|
||||
WindowsManager::WindowsLocker.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
|
||||
{
|
||||
ASSERT(!HasManagedInstance());
|
||||
|
||||
@@ -259,6 +259,7 @@ public:
|
||||
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
|
||||
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
|
||||
API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects();
|
||||
API_FUNCTION(Internal) static Array<Window*> GetWindows();
|
||||
|
||||
private:
|
||||
void OnEditorAssemblyLoaded(MAssembly* assembly);
|
||||
|
||||
@@ -652,43 +652,47 @@ namespace FlaxEditor.Options
|
||||
|
||||
#endregion
|
||||
|
||||
#region Node editors
|
||||
#region Node Editors
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+W")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4500)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4500)]
|
||||
public InputBinding NodesAlignTop = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+A")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4510)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4510)]
|
||||
public InputBinding NodesAlignLeft = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+S")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4520)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4520)]
|
||||
public InputBinding NodesAlignBottom = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+D")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4530)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4530)]
|
||||
public InputBinding NodesAlignRight = new InputBinding(KeyboardKeys.D, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Alt+Shift+W")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4540)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4540)]
|
||||
public InputBinding NodesAlignMiddle = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift, KeyboardKeys.Alt);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Alt+Shift+S")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4550)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4550)]
|
||||
public InputBinding NodesAlignCenter = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift, KeyboardKeys.Alt);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Q")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4560)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4560)]
|
||||
public InputBinding NodesAutoFormat = new InputBinding(KeyboardKeys.Q);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "None")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4570)]
|
||||
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.None);
|
||||
[DefaultValue(typeof(InputBinding), "Shift+Q")]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4560)]
|
||||
public InputBinding NodesStraightenConnections = new InputBinding(KeyboardKeys.Q, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "None")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4580)]
|
||||
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.None);
|
||||
[DefaultValue(typeof(InputBinding), "Alt+W")]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4570)]
|
||||
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.W, KeyboardKeys.Alt);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Alt+A")]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4580)]
|
||||
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -76,6 +76,25 @@ namespace FlaxEditor.Options
|
||||
DockBottom = DockState.DockBottom
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for the visibility status of the tab close button.
|
||||
/// </summary>
|
||||
public enum TabCloseButtonVisibility
|
||||
{
|
||||
/// <summary>
|
||||
/// Never show the close button.
|
||||
/// </summary>
|
||||
Never,
|
||||
/// <summary>
|
||||
/// Show the close button on tabs that are currently selected.
|
||||
/// </summary>
|
||||
SelectedTab,
|
||||
/// <summary>
|
||||
/// Show the close button on all tabs that can be closed.
|
||||
/// </summary>
|
||||
Always
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for the action taken by the play button.
|
||||
/// </summary>
|
||||
@@ -167,15 +186,6 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Interface"), EditorOrder(10), Tooltip("Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.")]
|
||||
public float InterfaceScale { get; set; } = 1.0f;
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether use native window title bar. Editor restart required.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Interface"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")]
|
||||
public bool UseNativeWindowSystem { get; set; } = false;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether show selected camera preview in the editor window.
|
||||
/// </summary>
|
||||
@@ -183,20 +193,6 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Interface"), EditorOrder(80), Tooltip("Determines whether show selected camera preview in the edit window.")]
|
||||
public bool ShowSelectedCameraPreview { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Interface", "Center Mouse On Game Window Focus"), EditorOrder(100), Tooltip("Determines whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.")]
|
||||
public bool CenterMouseOnGameWinFocus { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method window opening.
|
||||
/// </summary>
|
||||
[DefaultValue(DockStateProxy.Float)]
|
||||
[EditorDisplay("Interface", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")]
|
||||
public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the editor icons scale. Editor restart required.
|
||||
/// </summary>
|
||||
@@ -232,6 +228,13 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Interface"), EditorOrder(310)]
|
||||
public bool SeparateValueAndUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option to auto size the Properties panel splitter based on the longest property name. Editor restart recommended.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Interface"), EditorOrder(311)]
|
||||
public bool AutoSizePropertiesPanelSplitter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets tree line visibility.
|
||||
/// </summary>
|
||||
@@ -265,6 +268,66 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Interface"), EditorOrder(322)]
|
||||
public bool ScrollToScriptOnAdd { get; set; } = true;
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether use native window title bar. Editor restart required.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Tabs & Windows"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")]
|
||||
public bool UseNativeWindowSystem { get; set; } = false;
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a window containing a single tabs hides the tab bar. Editor restart recommended.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
[EditorDisplay("Tabs & Windows", "Hide Single-Tab Window Tab Bars"), EditorOrder(71)]
|
||||
public bool HideSingleTabWindowTabBars { get; set; } = true;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating wether the minum tab width should be used. Editor restart required.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Tabs & Windows"), EditorOrder(99)]
|
||||
public bool UseMinimumTabWidth { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the minimum tab width. If a tab is smaller than this width, its width will be set to this. Editor restart required.
|
||||
/// </summary>
|
||||
[DefaultValue(80.0f), Limit(50.0f, 150.0f)]
|
||||
[EditorDisplay("Tabs & Windows"), EditorOrder(99), VisibleIf(nameof(UseMinimumTabWidth))]
|
||||
public float MinimumTabWidth { get; set; } = 80.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the height of window tabs. Editor restart required.
|
||||
/// </summary>
|
||||
[DefaultValue(20.0f), Limit(15.0f, 40.0f)]
|
||||
[EditorDisplay("Tabs & Windows"), EditorOrder(100)]
|
||||
public float TabHeight { get; set; } = 20.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[EditorDisplay("Tabs & Windows", "Center Mouse On Game Window Focus"), EditorOrder(101), Tooltip("Determines whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.")]
|
||||
public bool CenterMouseOnGameWinFocus { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method window opening.
|
||||
/// </summary>
|
||||
[DefaultValue(DockStateProxy.Float)]
|
||||
[EditorDisplay("Tabs & Windows", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")]
|
||||
public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating when the tab close button should be visible.
|
||||
/// </summary>
|
||||
[DefaultValue(TabCloseButtonVisibility.SelectedTab)]
|
||||
[EditorDisplay("Tabs & Windows"), EditorOrder(151)]
|
||||
public TabCloseButtonVisibility ShowTabCloseButton { get; set; } = TabCloseButtonVisibility.SelectedTab;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamps prefix mode for output log messages.
|
||||
/// </summary>
|
||||
@@ -420,6 +483,12 @@ namespace FlaxEditor.Options
|
||||
[DefaultValue(1), Range(1, 4)]
|
||||
[EditorDisplay("Cook & Run"), EditorOrder(600)]
|
||||
public int NumberOfGameClientsToLaunch = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build configuration to use when using Cook and Run option in the editor.
|
||||
/// </summary>
|
||||
[EditorDisplay("Cook & Run"), EditorOrder(601), ExpandGroups, Tooltip("The build configuration to use when using Cook and Run option in the editor.")]
|
||||
public BuildConfiguration CookAndRunBuildConfiguration { get; set; } = BuildConfiguration.Development;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the curvature of the line connecting to connected visject nodes.
|
||||
|
||||
@@ -810,7 +810,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Create = (id, context, arch, groupArch) => new StateMachineState(id, context, arch, groupArch),
|
||||
Title = "State",
|
||||
Description = "The animation states machine state node",
|
||||
Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
|
||||
Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(100, 0),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
private Label _labelValue;
|
||||
private FloatValueBox _timeValue;
|
||||
private ColorValueBox _colorValue;
|
||||
private const int MaxStops = 8;
|
||||
private const int MaxStops = 12;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ColorGradientNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
@@ -1386,10 +1386,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Title = "Time",
|
||||
Description = "Game time constant",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(110, 20),
|
||||
Size = new Float2(110, 40),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, "", typeof(float), 0),
|
||||
NodeElementArchetype.Factory.Output(0, "Time", typeof(float), 0),
|
||||
NodeElementArchetype.Factory.Output(1, "Unscaled Time", typeof(float), 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -1506,7 +1507,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
0.95f,
|
||||
Color.White,
|
||||
|
||||
// Empty stops 2-7
|
||||
// Empty stops 2-11
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
|
||||
@@ -5,39 +5,39 @@ using FlaxEngine;
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
/// <summary>
|
||||
/// Node Alignment type
|
||||
/// Node Alignment type.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public enum NodeAlignmentType
|
||||
{
|
||||
/// <summary>
|
||||
/// Align nodes vertically to top, matching top-most node
|
||||
/// Align nodes vertically to top, matching top-most node.
|
||||
/// </summary>
|
||||
Top,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes vertically to middle, using average of all nodes
|
||||
/// Align nodes vertically to middle, using average of all nodes.
|
||||
/// </summary>
|
||||
Middle,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes vertically to bottom, matching bottom-most node
|
||||
/// Align nodes vertically to bottom, matching bottom-most node.
|
||||
/// </summary>
|
||||
Bottom,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes horizontally to left, matching left-most node
|
||||
/// Align nodes horizontally to left, matching left-most node.
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes horizontally to center, using average of all nodes
|
||||
/// Align nodes horizontally to center, using average of all nodes.
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes horizontally to right, matching right-most node
|
||||
/// Align nodes horizontally to right, matching right-most node.
|
||||
/// </summary>
|
||||
Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace FlaxEditor.Surface
|
||||
/// </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>
|
||||
/// <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);
|
||||
@@ -412,6 +412,7 @@ namespace FlaxEditor.Surface
|
||||
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
|
||||
|
||||
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
|
||||
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); });
|
||||
|
||||
_cmFormatNodesMenu.ContextMenu.AddSeparator();
|
||||
_cmAlignNodesTopButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align top", Editor.Instance.Options.Options.Input.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); });
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
using FlaxEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEngine;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
@@ -14,26 +14,26 @@ namespace FlaxEditor.Surface
|
||||
private class NodeFormattingData
|
||||
{
|
||||
/// <summary>
|
||||
/// Starting from 0 at the main nodes
|
||||
/// Starting from 0 at the main nodes.
|
||||
/// </summary>
|
||||
public int Layer;
|
||||
|
||||
/// <summary>
|
||||
/// Position in the layer
|
||||
/// Position in the layer.
|
||||
/// </summary>
|
||||
public int Offset;
|
||||
|
||||
/// <summary>
|
||||
/// How far the subtree needs to be moved additionally
|
||||
/// How far the subtree needs to be moved additionally.
|
||||
/// </summary>
|
||||
public int SubtreeOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a graph where the nodes can be disjointed.
|
||||
/// Uses the Sugiyama method
|
||||
/// Uses the Sugiyama method.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of nodes</param>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
public void FormatGraph(List<SurfaceNode> nodes)
|
||||
{
|
||||
if (nodes.Count <= 1)
|
||||
@@ -78,9 +78,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a graph where all nodes are connected
|
||||
/// Formats a graph where all nodes are connected.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of connected nodes</param>
|
||||
/// <param name="nodes">List of connected nodes.</param>
|
||||
protected void FormatConnectedGraph(List<SurfaceNode> nodes)
|
||||
{
|
||||
if (nodes.Count <= 1)
|
||||
@@ -160,11 +160,71 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a layer to every node
|
||||
/// Straightens every connection between nodes in <paramref name="nodes"/>.
|
||||
/// </summary>
|
||||
/// <param name="nodeData">The exta node data</param>
|
||||
/// <param name="endNodes">The end nodes</param>
|
||||
/// <returns>The number of the maximum layer</returns>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
public void StraightenGraphConnections(List<SurfaceNode> nodes)
|
||||
{
|
||||
if (nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
|
||||
|
||||
// Only process nodes that have any connection
|
||||
List<SurfaceNode> connectedNodes = nodes.Where(n => n.GetBoxes().Any(b => b.HasAnyConnection)).ToList();
|
||||
|
||||
if (connectedNodes.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < connectedNodes.Count - 1; i++)
|
||||
{
|
||||
SurfaceNode nodeA = connectedNodes[i];
|
||||
List<Box> connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.IsOutput && b.HasAnyConnection).ToList();
|
||||
|
||||
for (int j = 0; j < connectedOutputBoxes.Count; j++)
|
||||
{
|
||||
Box boxA = connectedOutputBoxes[j];
|
||||
|
||||
for (int b = 0; b < boxA.Connections.Count; b++)
|
||||
{
|
||||
Box boxB = boxA.Connections[b];
|
||||
|
||||
// Ensure the other node is selected
|
||||
if (!connectedNodes.Contains(boxB.ParentNode))
|
||||
continue;
|
||||
|
||||
// Node with no outgoing connections reached. Advance to next node in list
|
||||
if (boxA == null || boxB == null)
|
||||
continue;
|
||||
|
||||
SurfaceNode nodeB = boxB.ParentNode;
|
||||
|
||||
// Calculate the Y offset needed for nodeB to align boxB's Y to boxA's Y
|
||||
float boxASurfaceY = boxA.PointToParent(this, Float2.Zero).Y;
|
||||
float boxBSurfaceY = boxB.PointToParent(this, Float2.Zero).Y;
|
||||
float deltaY = (boxASurfaceY - boxBSurfaceY) / ViewScale;
|
||||
Float2 delta = new Float2(0f, deltaY);
|
||||
|
||||
nodeB.Location += delta;
|
||||
|
||||
if (Undo != null)
|
||||
undoActions.Add(new MoveNodesAction(Context, new[] { nodeB.ID }, delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (undoActions.Count > 0)
|
||||
Undo?.AddAction(new MultiUndoAction(undoActions, "Straightned "));
|
||||
|
||||
MarkAsEdited(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a layer to every node.
|
||||
/// </summary>
|
||||
/// <param name="nodeData">The exta node data.</param>
|
||||
/// <param name="endNodes">The end nodes.</param>
|
||||
/// <returns>The number of the maximum layer.</returns>
|
||||
private int SetLayers(Dictionary<SurfaceNode, NodeFormattingData> nodeData, List<SurfaceNode> endNodes)
|
||||
{
|
||||
// Longest path layering
|
||||
@@ -201,12 +261,12 @@ namespace FlaxEditor.Surface
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node offsets
|
||||
/// Sets the node offsets.
|
||||
/// </summary>
|
||||
/// <param name="nodeData">The exta node data</param>
|
||||
/// <param name="endNodes">The end nodes</param>
|
||||
/// <param name="maxLayer">The number of the maximum layer</param>
|
||||
/// <returns>The number of the maximum offset</returns>
|
||||
/// <param name="nodeData">The exta node data.</param>
|
||||
/// <param name="endNodes">The end nodes.</param>
|
||||
/// <param name="maxLayer">The number of the maximum layer.</param>
|
||||
/// <returns>The number of the maximum offset.</returns>
|
||||
private int SetOffsets(Dictionary<SurfaceNode, NodeFormattingData> nodeData, List<SurfaceNode> endNodes, int maxLayer)
|
||||
{
|
||||
int maxOffset = 0;
|
||||
@@ -287,10 +347,10 @@ namespace FlaxEditor.Surface
|
||||
/// Align given nodes on a graph using the given alignment type.
|
||||
/// Ignores any potential overlap.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of nodes</param>
|
||||
/// <param name="alignmentType">Alignemnt type</param>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
/// <param name="alignmentType">Alignemnt type.</param>
|
||||
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
|
||||
{
|
||||
{
|
||||
if(nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
@@ -328,8 +388,8 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Distribute the given nodes as equally as possible inside the bounding box, if no fit can be done it will use a default pad of 10 pixels between nodes.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of nodes</param>
|
||||
/// <param name="vertically">If false will be done horizontally, if true will be done vertically</param>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
/// <param name="vertically">If false will be done horizontally, if true will be done vertically.</param>
|
||||
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
|
||||
{
|
||||
if(nodes.Count <= 1)
|
||||
|
||||
@@ -416,6 +416,7 @@ namespace FlaxEditor.Surface
|
||||
new InputActionsContainer.Binding(options => options.Cut, Cut),
|
||||
new InputActionsContainer.Binding(options => options.Duplicate, Duplicate),
|
||||
new InputActionsContainer.Binding(options => options.NodesAutoFormat, () => { FormatGraph(SelectedNodes); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); }),
|
||||
@@ -542,11 +543,12 @@ namespace FlaxEditor.Surface
|
||||
nodes.Add(context);
|
||||
context = context.Parent;
|
||||
}
|
||||
float margin = 1;
|
||||
float x = NavigationBar.DefaultButtonsMargin;
|
||||
float h = toolStrip.ItemsHeight - 2 * ToolStrip.DefaultMarginV;
|
||||
float h = toolStrip.ItemsHeight - 2 * margin;
|
||||
for (int i = nodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var button = new VisjectContextNavigationButton(this, nodes[i].Context, x, ToolStrip.DefaultMarginV, h);
|
||||
var button = new VisjectContextNavigationButton(this, nodes[i].Context, x, margin, h);
|
||||
button.PerformLayout();
|
||||
x += button.Width + NavigationBar.DefaultButtonsMargin;
|
||||
navigationBar.AddChild(button);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
@@ -471,7 +470,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
private void OnOverrideMethodClicked()
|
||||
{
|
||||
var cm = new ContextMenu();
|
||||
var cm = new ItemsListContextMenu(235);
|
||||
var window = (VisualScriptWindow)Values[0];
|
||||
var scriptMeta = window.Asset.Meta;
|
||||
var baseType = TypeUtils.GetType(scriptMeta.BaseTypename);
|
||||
@@ -499,27 +498,39 @@ namespace FlaxEditor.Windows.Assets
|
||||
if (isAlreadyAdded)
|
||||
continue;
|
||||
|
||||
var cmButton = cm.AddButton($"{name} (in {member.DeclaringType.Name})");
|
||||
cmButton.TooltipText = Editor.Instance.CodeDocs.GetTooltip(member);
|
||||
cmButton.Clicked += () =>
|
||||
var item = new ItemsListContextMenu.Item
|
||||
{
|
||||
var surface = ((VisualScriptWindow)Values[0]).Surface;
|
||||
var surfaceBounds = surface.AllNodesBounds;
|
||||
surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Float2(200, 150)).MakeExpanded(400.0f));
|
||||
var node = surface.Context.SpawnNode(16, 3, surfaceBounds.BottomLeft + new Float2(0, 50), new object[]
|
||||
{
|
||||
name,
|
||||
parameters.Length,
|
||||
Utils.GetEmptyArray<byte>()
|
||||
});
|
||||
surface.Select(node);
|
||||
Name = $"{name} (in {member.DeclaringType.Name})",
|
||||
TooltipText = Editor.Instance.CodeDocs.GetTooltip(member),
|
||||
Tag = new object[] { name, parameters.Length, Utils.GetEmptyArray<byte>() },
|
||||
// Do some basic sorting based on if the method is defined directly in the script base class
|
||||
SortScore = member.DeclaringType == member.Type.ReflectedType ? 1 : 0,
|
||||
};
|
||||
cm.AddItem(item);
|
||||
}
|
||||
}
|
||||
if (!cm.Items.Any())
|
||||
|
||||
cm.ItemClicked += (item) =>
|
||||
{
|
||||
cm.AddButton("Nothing to override");
|
||||
var surface = ((VisualScriptWindow)Values[0]).Surface;
|
||||
var surfaceBounds = surface.AllNodesBounds;
|
||||
surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Float2(200, 150)).MakeExpanded(400.0f));
|
||||
var node = surface.Context.SpawnNode(16, 3, surfaceBounds.BottomLeft + new Float2(0, 50), item.Tag as object[]);
|
||||
surface.Select(node);
|
||||
};
|
||||
|
||||
if (cm.ItemsPanel.ChildrenCount == 0)
|
||||
{
|
||||
var item = new ItemsListContextMenu.Item
|
||||
{
|
||||
Name = "Nothing to override"
|
||||
};
|
||||
item.Enabled = false;
|
||||
|
||||
cm.AddItem(item);
|
||||
}
|
||||
|
||||
cm.SortItems();
|
||||
cm.Show(_overrideButton, new Float2(0, _overrideButton.Height));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,17 +194,18 @@ namespace FlaxEditor.Windows
|
||||
nodes.Add(node);
|
||||
node = node.ParentNode;
|
||||
}
|
||||
float margin = 1;
|
||||
float x = NavigationBar.DefaultButtonsMargin;
|
||||
float h = _toolStrip.ItemsHeight - 2 * ToolStrip.DefaultMarginV;
|
||||
float h = _toolStrip.ItemsHeight - 2 * margin;
|
||||
for (int i = nodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var button = new ContentNavigationButton(nodes[i], x, ToolStrip.DefaultMarginV, h);
|
||||
var button = new ContentNavigationButton(nodes[i], x, margin, h);
|
||||
button.PerformLayout();
|
||||
x += button.Width + NavigationBar.DefaultButtonsMargin;
|
||||
_navigationBar.AddChild(button);
|
||||
if (i > 0)
|
||||
{
|
||||
var separator = new ContentNavigationSeparator(button, x, ToolStrip.DefaultMarginV, h);
|
||||
var separator = new ContentNavigationSeparator(button, x, margin, h);
|
||||
separator.PerformLayout();
|
||||
x += separator.Width + NavigationBar.DefaultButtonsMargin;
|
||||
_navigationBar.AddChild(separator);
|
||||
@@ -215,6 +216,7 @@ namespace FlaxEditor.Windows
|
||||
// Update
|
||||
_navigationBar.IsLayoutLocked = wasLayoutLocked;
|
||||
_navigationBar.PerformLayout();
|
||||
UpdateNavigationBarBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1016,6 +1016,21 @@ namespace FlaxEditor.Windows
|
||||
_navigateUpButton.Enabled = folder != null && _tree.SelectedNode != _root;
|
||||
}
|
||||
|
||||
private void UpdateNavigationBarBounds()
|
||||
{
|
||||
if (_navigationBar != null && _toolStrip != null)
|
||||
{
|
||||
var bottomPrev = _toolStrip.Bottom;
|
||||
_navigationBar.UpdateBounds(_toolStrip);
|
||||
if (bottomPrev != _toolStrip.Bottom)
|
||||
{
|
||||
// Navigation bar changed toolstrip height
|
||||
_split.Offsets = new Margin(0, 0, _toolStrip.Bottom, 0);
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInit()
|
||||
{
|
||||
@@ -1200,9 +1215,9 @@ namespace FlaxEditor.Windows
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
UpdateNavigationBarBounds();
|
||||
|
||||
_navigationBar?.UpdateBounds(_toolStrip);
|
||||
base.PerformLayoutBeforeChildren();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -335,12 +335,12 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Parent = this,
|
||||
};
|
||||
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries");
|
||||
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries.");
|
||||
_clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play", () =>
|
||||
{
|
||||
editor.Options.Options.Interface.DebugLogClearOnPlay = _clearOnPlayButton.Checked;
|
||||
editor.Options.Apply(editor.Options.Options);
|
||||
}).SetAutoCheck(true).LinkTooltip("Clears all log entries on enter playmode");
|
||||
}).SetAutoCheck(true).LinkTooltip("Clears all log entries on enter playmode.");
|
||||
_collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse", () =>
|
||||
{
|
||||
editor.Options.Options.Interface.DebugLogCollapse = _collapseLogsButton.Checked;
|
||||
@@ -350,14 +350,14 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
editor.Options.Options.Interface.DebugLogPauseOnError = _pauseOnErrorButton.Checked;
|
||||
editor.Options.Apply(editor.Options.Options);
|
||||
}).SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
|
||||
}).SetAutoCheck(true).LinkTooltip("Performs auto pause on error.");
|
||||
toolstrip.AddSeparator();
|
||||
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => { OnGroupButtonPressed(0); })
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides error messages");
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides error messages.");
|
||||
_groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => { OnGroupButtonPressed(1); })
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides warning messages");
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides warning messages.");
|
||||
_groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => { OnGroupButtonPressed(2); })
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides info messages");
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides info messages.");
|
||||
UpdateCount();
|
||||
|
||||
// Split panel
|
||||
@@ -495,6 +495,7 @@ namespace FlaxEditor.Windows
|
||||
// Pause on Error (we should do it as fast as possible)
|
||||
if (newEntry.Group == LogGroup.Error && _pauseOnErrorButton.Checked && Editor.StateMachine.CurrentState == Editor.StateMachine.PlayingState)
|
||||
{
|
||||
Editor.Log("Pause Play mode on error (toggle this behaviour in the Debug Log panel)");
|
||||
Editor.Simulation.RequestPausePlay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,8 +970,9 @@ namespace FlaxEditor.Windows
|
||||
public void BuildAndRun()
|
||||
{
|
||||
Editor.Log("Building and running");
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _);
|
||||
var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
|
||||
var buildConfig = Editor.Options.Options.Interface.CookAndRunBuildConfiguration;
|
||||
for (int i = 0; i < numberOfClients; i++)
|
||||
{
|
||||
var buildOptions = BuildOptions.AutoRun;
|
||||
@@ -984,7 +985,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
|
||||
Platform = buildPlatform,
|
||||
Mode = buildConfiguration,
|
||||
Mode = buildConfig,
|
||||
},
|
||||
Options = buildOptions,
|
||||
});
|
||||
@@ -997,8 +998,9 @@ namespace FlaxEditor.Windows
|
||||
public void RunCooked()
|
||||
{
|
||||
Editor.Log("Running cooked build");
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _);
|
||||
var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
|
||||
var buildConfig = Editor.Options.Options.Interface.CookAndRunBuildConfiguration;
|
||||
for (int i = 0; i < numberOfClients; i++)
|
||||
{
|
||||
_buildingQueue.Enqueue(new QueueItem
|
||||
@@ -1007,7 +1009,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
|
||||
Platform = buildPlatform,
|
||||
Mode = buildConfiguration,
|
||||
Mode = buildConfig,
|
||||
},
|
||||
Options = BuildOptions.AutoRun | BuildOptions.NoCook,
|
||||
});
|
||||
|
||||
@@ -63,6 +63,16 @@ namespace FlaxEditor.Windows
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the game window audio is muted.
|
||||
/// </summary>
|
||||
public event Action MuteAudio;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the game window master audio volume is changed.
|
||||
/// </summary>
|
||||
public event Action<float> MasterVolumeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the viewport.
|
||||
/// </summary>
|
||||
@@ -120,6 +130,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Audio.MasterVolume = value ? 0 : AudioVolume;
|
||||
_audioMuted = value;
|
||||
MuteAudio?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +145,7 @@ namespace FlaxEditor.Windows
|
||||
if (!AudioMuted)
|
||||
Audio.MasterVolume = value;
|
||||
_audioVolume = value;
|
||||
MasterVolumeChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Engine/Render2D/Font.h"
|
||||
#include "Engine/Render2D/TextLayoutOptions.h"
|
||||
#include "Engine/Render2D/Render2D.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
@@ -188,8 +189,7 @@ void SplashScreen::Show()
|
||||
|
||||
// Setup
|
||||
_dpiScale = dpiScale;
|
||||
_width = settings.Size.X;
|
||||
_height = settings.Size.Y;
|
||||
_size = settings.Size;
|
||||
_startTime = DateTime::NowUTC();
|
||||
auto str = Globals::ProjectFolder;
|
||||
#if PLATFORM_WIN32
|
||||
@@ -214,6 +214,12 @@ void SplashScreen::Show()
|
||||
font->OnLoaded.Bind<SplashScreen, &SplashScreen::OnFontLoaded>(this);
|
||||
}
|
||||
|
||||
// Load custom image
|
||||
_splashTexture.Loaded.Bind<SplashScreen, &SplashScreen::OnSplashLoaded>(this);
|
||||
String splashImagePath = Globals::ProjectContentFolder / TEXT("SplashImage.flax");
|
||||
if (FileSystem::FileExists(splashImagePath))
|
||||
_splashTexture = Content::LoadAsync<Texture>(splashImagePath);
|
||||
|
||||
_window->Show();
|
||||
}
|
||||
|
||||
@@ -227,6 +233,10 @@ void SplashScreen::Close()
|
||||
// Close window
|
||||
_window->Close(ClosingReason::CloseEvent);
|
||||
_window = nullptr;
|
||||
|
||||
_titleFont = nullptr;
|
||||
_subtitleFont = nullptr;
|
||||
_splashTexture = nullptr;
|
||||
}
|
||||
|
||||
void SplashScreen::OnShown()
|
||||
@@ -239,16 +249,29 @@ void SplashScreen::OnShown()
|
||||
void SplashScreen::OnDraw()
|
||||
{
|
||||
const float s = _dpiScale;
|
||||
const float width = _width;
|
||||
const float height = _height;
|
||||
const float width = _size.X;
|
||||
const float height = _size.Y;
|
||||
|
||||
// Peek time
|
||||
const float time = static_cast<float>((DateTime::NowUTC() - _startTime).GetTotalSeconds());
|
||||
|
||||
// Background
|
||||
const float lightBarHeight = 112 * s;
|
||||
Render2D::FillRectangle(Rectangle(0, 0, width, 150 * s), Color::FromRGB(0x1C1C1C));
|
||||
Render2D::FillRectangle(Rectangle(0, lightBarHeight, width, height), Color::FromRGB(0x0C0C0C));
|
||||
float lightBarHeight = 112 * s;
|
||||
if (_splashTexture != nullptr)
|
||||
{
|
||||
if (_splashTexture->IsLoaded())
|
||||
{
|
||||
lightBarHeight = height - lightBarHeight + 20 * s;
|
||||
Render2D::DrawTexture(_splashTexture, Rectangle(0, 0, width, height));
|
||||
Color rectColor = Color::FromRGB(0x0C0C0C);
|
||||
Render2D::FillRectangle(Rectangle(0, lightBarHeight, width, height - lightBarHeight),rectColor.AlphaMultiplied(0.85f), rectColor.AlphaMultiplied(0.85f), rectColor, rectColor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Render2D::FillRectangle(Rectangle(0, 0, width, 150 * s), Color::FromRGB(0x1C1C1C));
|
||||
Render2D::FillRectangle(Rectangle(0, lightBarHeight, width, height), Color::FromRGB(0x0C0C0C));
|
||||
}
|
||||
|
||||
// Animated border
|
||||
const float anim = Math::Sin(time * 4.0f) * 0.5f + 0.5f;
|
||||
@@ -276,15 +299,27 @@ void SplashScreen::OnDraw()
|
||||
for (int32 i = 0; i < 4 - static_cast<int32>(time * 2.0f) % 4; i++)
|
||||
subtitle += TEXT(' ');
|
||||
}
|
||||
layout.Bounds = Rectangle(width - 224 * s, lightBarHeight - 39 * s, 220 * s, 35 * s);
|
||||
if (_splashTexture != nullptr)
|
||||
{
|
||||
layout.Bounds = Rectangle(width - 224 * s, lightBarHeight + 4 * s, 220 * s, 35 * s);
|
||||
layout.VerticalAlignment = TextAlignment::Near;
|
||||
}
|
||||
else
|
||||
{
|
||||
layout.Bounds = Rectangle(width - 224 * s, lightBarHeight - 39 * s, 220 * s, 35 * s);
|
||||
layout.VerticalAlignment = TextAlignment::Far;
|
||||
}
|
||||
layout.Scale = 1.0f;
|
||||
layout.HorizontalAlignment = TextAlignment::Far;
|
||||
layout.VerticalAlignment = TextAlignment::Far;
|
||||
Render2D::DrawText(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout);
|
||||
|
||||
// Additional info
|
||||
const float infoMargin = 6 * s;
|
||||
layout.Bounds = Rectangle(infoMargin, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin));
|
||||
if (_splashTexture != nullptr)
|
||||
layout.Bounds = Rectangle(infoMargin + 4 * s, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin));
|
||||
else
|
||||
layout.Bounds = Rectangle(infoMargin, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin));
|
||||
|
||||
layout.HorizontalAlignment = TextAlignment::Near;
|
||||
layout.VerticalAlignment = TextAlignment::Center;
|
||||
Render2D::DrawText(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout);
|
||||
@@ -307,3 +342,14 @@ void SplashScreen::OnFontLoaded(Asset* asset)
|
||||
_titleFont = font->CreateFont(35 * s);
|
||||
_subtitleFont = font->CreateFont(9 * s);
|
||||
}
|
||||
|
||||
void SplashScreen::OnSplashLoaded()
|
||||
{
|
||||
// Resize window to be larger if texture is being used
|
||||
auto desktopSize = Platform::GetDesktopSize();
|
||||
auto xSize = (desktopSize.X / (600.0f * 3.0f)) * 600.0f;
|
||||
auto ySize = (desktopSize.Y / (200.0f * 3.0f)) * 200.0f;
|
||||
_window->SetClientSize(Float2(xSize, ySize));
|
||||
_size = _window->GetSize();
|
||||
_window->SetPosition((desktopSize - _size) * 0.5f);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Content/Assets/Texture.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Platform/Window.h"
|
||||
|
||||
@@ -18,10 +20,12 @@ private:
|
||||
Window* _window = nullptr;
|
||||
Font* _titleFont = nullptr;
|
||||
Font* _subtitleFont = nullptr;
|
||||
AssetReference<Texture> _splashTexture;
|
||||
String _title;
|
||||
DateTime _startTime;
|
||||
String _infoText;
|
||||
float _dpiScale, _width, _height;
|
||||
float _dpiScale;
|
||||
Float2 _size;
|
||||
StringView _quote;
|
||||
|
||||
public:
|
||||
@@ -78,4 +82,5 @@ private:
|
||||
void OnDraw();
|
||||
bool HasLoadedFonts() const;
|
||||
void OnFontLoaded(Asset* asset);
|
||||
void OnSplashLoaded();
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
@@ -98,10 +99,22 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum SearchFilter
|
||||
{
|
||||
UI = 1,
|
||||
Actors = 2,
|
||||
Primitives = 4,
|
||||
[HideInEditor]
|
||||
Default = UI | Actors | Primitives,
|
||||
}
|
||||
|
||||
private TextBox _searchBox;
|
||||
private ContainerControl _groupSearch;
|
||||
private Tabs _actorGroups;
|
||||
private ContainerControl groupPrimitives;
|
||||
private Button _viewDropdown;
|
||||
private int _searchFilterMask = (int)SearchFilter.Default;
|
||||
|
||||
/// <summary>
|
||||
/// The editor instance.
|
||||
@@ -127,16 +140,25 @@ namespace FlaxEditor.Windows
|
||||
UseScroll = true,
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
TabsSize = new Float2(120, 32),
|
||||
TabsSize = new Float2(90, 32),
|
||||
Parent = this,
|
||||
};
|
||||
|
||||
_groupSearch = CreateGroupWithList(_actorGroups, "Search", 26);
|
||||
|
||||
_viewDropdown = new Button(2, 2, 45.0f, TextBoxBase.DefaultHeight)
|
||||
{
|
||||
TooltipText = "Change search filter options.",
|
||||
Text = "Filters",
|
||||
Parent = _groupSearch.Parent.Parent,
|
||||
};
|
||||
_viewDropdown.Clicked += OnViewButtonClicked;
|
||||
|
||||
_searchBox = new SearchBox
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
Parent = _groupSearch.Parent.Parent,
|
||||
Bounds = new Rectangle(4, 4, _actorGroups.Width - 8, 18),
|
||||
Bounds = new Rectangle(_viewDropdown.Right + 2, 2, _actorGroups.Width - 4, TextBoxBase.DefaultHeight),
|
||||
};
|
||||
_searchBox.TextChanged += OnSearchBoxTextChanged;
|
||||
|
||||
@@ -145,10 +167,38 @@ namespace FlaxEditor.Windows
|
||||
_actorGroups.SelectedTabIndex = 1;
|
||||
}
|
||||
|
||||
private void OnViewButtonClicked()
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
AddSearchFilterButton(menu, SearchFilter.UI, "UI");
|
||||
AddSearchFilterButton(menu, SearchFilter.Actors, "Actors");
|
||||
AddSearchFilterButton(menu, SearchFilter.Primitives, "Primitives");
|
||||
menu.Show(_viewDropdown.Parent, _viewDropdown.BottomLeft);
|
||||
}
|
||||
|
||||
private void AddSearchFilterButton(ContextMenu menu, SearchFilter value, string name)
|
||||
{
|
||||
var button = menu.AddButton(name);
|
||||
button.AutoCheck = true;
|
||||
button.Checked = (_searchFilterMask & (int)value) != 0;
|
||||
button.Clicked += () =>
|
||||
{
|
||||
_searchFilterMask ^= (int)value;
|
||||
OnSearchBoxTextChanged();
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
|
||||
_searchBox.Width = _groupSearch.Width - _viewDropdown.Right - 4;
|
||||
}
|
||||
|
||||
private void OnScriptsReload()
|
||||
{
|
||||
// Prevent any references to actor types from the game assemblies that will be reloaded
|
||||
_searchBox.Clear();
|
||||
_groupSearch.DisposeChildren();
|
||||
_groupSearch.PerformLayout();
|
||||
|
||||
@@ -172,6 +222,7 @@ namespace FlaxEditor.Windows
|
||||
private void OnScriptsReloadEnd()
|
||||
{
|
||||
RefreshActorTabs();
|
||||
OnSearchBoxTextChanged();
|
||||
}
|
||||
|
||||
private void RefreshActorTabs()
|
||||
@@ -192,14 +243,21 @@ namespace FlaxEditor.Windows
|
||||
group.Dispose();
|
||||
}
|
||||
|
||||
// Setup primitives tabs
|
||||
// Add primitives to primtives and search tab
|
||||
groupPrimitives = CreateGroupWithList(_actorGroups, "Primitives");
|
||||
|
||||
groupPrimitives.AddChild(CreateEditorAssetItem("Cube", "Primitives/Cube.flax"));
|
||||
_groupSearch.AddChild(CreateEditorAssetItem("Cube", "Primitives/Cube.flax"));
|
||||
groupPrimitives.AddChild(CreateEditorAssetItem("Sphere", "Primitives/Sphere.flax"));
|
||||
_groupSearch.AddChild(CreateEditorAssetItem("Sphere", "Primitives/Sphere.flax"));
|
||||
groupPrimitives.AddChild(CreateEditorAssetItem("Plane", "Primitives/Plane.flax"));
|
||||
_groupSearch.AddChild(CreateEditorAssetItem("Plane", "Primitives/Plane.flax"));
|
||||
groupPrimitives.AddChild(CreateEditorAssetItem("Cylinder", "Primitives/Cylinder.flax"));
|
||||
_groupSearch.AddChild(CreateEditorAssetItem("Cylinder", "Primitives/Cylinder.flax"));
|
||||
groupPrimitives.AddChild(CreateEditorAssetItem("Cone", "Primitives/Cone.flax"));
|
||||
_groupSearch.AddChild(CreateEditorAssetItem("Cone", "Primitives/Cone.flax"));
|
||||
groupPrimitives.AddChild(CreateEditorAssetItem("Capsule", "Primitives/Capsule.flax"));
|
||||
_groupSearch.AddChild(CreateEditorAssetItem("Capsule", "Primitives/Capsule.flax"));
|
||||
|
||||
// Created first to order specific tabs
|
||||
CreateGroupWithList(_actorGroups, "Lights");
|
||||
@@ -312,57 +370,115 @@ namespace FlaxEditor.Windows
|
||||
_groupSearch.LockChildrenRecursive();
|
||||
_groupSearch.DisposeChildren();
|
||||
|
||||
foreach (var actorType in Editor.CodeEditing.Actors.Get())
|
||||
if (((int)SearchFilter.Actors & _searchFilterMask) != 0)
|
||||
{
|
||||
ActorToolboxAttribute attribute = null;
|
||||
foreach (var e in actorType.GetAttributes(false))
|
||||
foreach (var actorType in Editor.CodeEditing.Actors.Get())
|
||||
{
|
||||
if (e is ActorToolboxAttribute actorToolboxAttribute)
|
||||
ActorToolboxAttribute attribute = null;
|
||||
foreach (var e in actorType.GetAttributes(false))
|
||||
{
|
||||
attribute = actorToolboxAttribute;
|
||||
break;
|
||||
if (e is ActorToolboxAttribute actorToolboxAttribute)
|
||||
{
|
||||
attribute = actorToolboxAttribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name;
|
||||
var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name;
|
||||
|
||||
// Display all actors on no search
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
_groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType));
|
||||
|
||||
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
|
||||
continue;
|
||||
|
||||
var item = CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType);
|
||||
SearchFilterHighlights(item, text, ranges);
|
||||
}
|
||||
|
||||
// Hack primitive models into the search results
|
||||
foreach (var child in groupPrimitives.Children)
|
||||
{
|
||||
if (child is Item primitiveAssetItem)
|
||||
{
|
||||
var text = primitiveAssetItem.Text;
|
||||
// Display all actors on no search
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
_groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType));
|
||||
|
||||
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
|
||||
continue;
|
||||
|
||||
// Rebuild the path based on item name (it would be better to convert the drag data back to a string somehow)
|
||||
string path = $"Primitives/{text}.flax";
|
||||
|
||||
var item = CreateEditorAssetItem(text, path);
|
||||
var item = CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType);
|
||||
SearchFilterHighlights(item, text, ranges);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
_groupSearch.SortChildren();
|
||||
if (((int)SearchFilter.Primitives & _searchFilterMask) != 0)
|
||||
{
|
||||
// Hack primitive models into the search results
|
||||
foreach (var child in groupPrimitives.Children)
|
||||
{
|
||||
if (child is Item primitiveAssetItem)
|
||||
{
|
||||
var text = primitiveAssetItem.Text;
|
||||
|
||||
// Rebuild the path based on item name (it would be better to convert the drag data back to a string somehow)
|
||||
string path = $"Primitives/{text}.flax";
|
||||
|
||||
// Display all primitives on no search
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
_groupSearch.AddChild(CreateEditorAssetItem(text, path));
|
||||
|
||||
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
|
||||
continue;
|
||||
|
||||
var item = CreateEditorAssetItem(text, path);
|
||||
SearchFilterHighlights(item, text, ranges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (((int)SearchFilter.UI & _searchFilterMask) != 0)
|
||||
{
|
||||
foreach (var controlType in Editor.Instance.CodeEditing.Controls.Get())
|
||||
{
|
||||
if (controlType.IsAbstract)
|
||||
continue;
|
||||
|
||||
ActorToolboxAttribute attribute = null;
|
||||
foreach (var e in controlType.GetAttributes(false))
|
||||
{
|
||||
if (e is ActorToolboxAttribute actorToolboxAttribute)
|
||||
{
|
||||
attribute = actorToolboxAttribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var text = (attribute == null) ? controlType.Name : string.IsNullOrEmpty(attribute.Name) ? controlType.Name : attribute.Name;
|
||||
|
||||
// Display all controls on no search
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
_groupSearch.AddChild(CreateControlItem(Utilities.Utils.GetPropertyNameUI(controlType.Name), controlType));
|
||||
|
||||
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
|
||||
continue;
|
||||
|
||||
var item = CreateControlItem(Utilities.Utils.GetPropertyNameUI(controlType.Name), controlType);
|
||||
SearchFilterHighlights(item, text, ranges);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the search results alphabetically
|
||||
_groupSearch.SortChildren();
|
||||
|
||||
_groupSearch.UnlockChildrenRecursive();
|
||||
PerformLayout();
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Show a text to hint the user that either no filter is active or the search does not return any results
|
||||
bool noSearchResults = _groupSearch.Children.Count == 0 && !string.IsNullOrEmpty(_searchBox.Text);
|
||||
bool showHint = _searchFilterMask == 0 || noSearchResults;
|
||||
if (showHint)
|
||||
{
|
||||
string hint = noSearchResults ? "No results" : "No search filter active, please enable at least one filter";
|
||||
var textRect = _groupSearch.Parent.Parent.Bounds;
|
||||
var style = Style.Current;
|
||||
Render2D.DrawText(style.FontMedium, hint, textRect, style.ForegroundGrey, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchFilterHighlights(Item item, string text, QueryFilterHelper.Range[] ranges)
|
||||
{
|
||||
_groupSearch.AddChild(item);
|
||||
|
||||
@@ -229,18 +229,12 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
auto& context = *Context.Get();
|
||||
float eventTimeMin = animPrevPos;
|
||||
float eventTimeMax = animPos;
|
||||
if (loop && context.DeltaTime * speed < 0)
|
||||
|
||||
if (eventTimeMin > eventTimeMax)
|
||||
{
|
||||
// Check if animation looped (for anim events shooting during backwards playback)
|
||||
//const float posNotLooped = startTimePos + oldTimePos;
|
||||
//if (posNotLooped < 0.0f || posNotLooped > length)
|
||||
//const int32 animPosCycle = Math::CeilToInt(animPos / anim->GetDuration());
|
||||
//const int32 animPrevPosCycle = Math::CeilToInt(animPrevPos / anim->GetDuration());
|
||||
//if (animPosCycle != animPrevPosCycle)
|
||||
{
|
||||
Swap(eventTimeMin, eventTimeMax);
|
||||
}
|
||||
Swap(eventTimeMin, eventTimeMax);
|
||||
}
|
||||
|
||||
const float eventTime = (float)(animPos / anim->Data.FramesPerSecond);
|
||||
const float eventDeltaTime = (float)((animPos - animPrevPos) / anim->Data.FramesPerSecond);
|
||||
for (const auto& track : anim->Events)
|
||||
@@ -251,7 +245,13 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
continue;
|
||||
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
|
||||
if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration)
|
||||
if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration
|
||||
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
|
||||
// Handle the edge case of an event on 0 or on max animation duration during looping
|
||||
|| (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration())
|
||||
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
)
|
||||
{
|
||||
int32 stateIndex = -1;
|
||||
if (duration > 1)
|
||||
|
||||
@@ -532,7 +532,7 @@ bool Animation::SaveHeader(const ModelData& modelData, WriteStream& stream, int3
|
||||
// Nested animations
|
||||
stream.WriteInt32(0); // Empty list
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
void Animation::GetReferences(Array<Guid>& assets, Array<String>& files) const
|
||||
|
||||
@@ -398,7 +398,7 @@ bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -457,7 +457,7 @@ bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
bool Model::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk) const
|
||||
|
||||
@@ -332,7 +332,7 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
stream.Read(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
|
||||
@@ -661,7 +661,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -691,7 +691,7 @@ bool SkinnedModel::SaveHeader(WriteStream& stream) const
|
||||
const int32 blendShapes = mesh.BlendShapes.Count();
|
||||
stream.Write((uint16)blendShapes);
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
blendShape.SaveHeader(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,27 @@ bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try model prefab
|
||||
String pathPrefab = String(StringUtils::GetPathWithoutExtension(path)) + DEFAULT_PREFAB_EXTENSION_DOT;
|
||||
if (FileSystem::FileExists(pathPrefab))
|
||||
{
|
||||
auto prefab = Content::Load<Prefab>(pathPrefab);
|
||||
if (prefab)
|
||||
{
|
||||
for (const auto& e : prefab->ObjectsDataCache)
|
||||
{
|
||||
auto importOptionsMember = e.Value->FindMember("ImportOptions");
|
||||
if (importOptionsMember != e.Value->MemberEnd() && importOptionsMember->value.IsObject())
|
||||
{
|
||||
options.Deserialize(*(ISerializable::DeserializeStream*)&importOptionsMember->value, nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,13 +32,53 @@ namespace FlaxEngine
|
||||
/// <summary>
|
||||
/// Determines whether the specified layer is set in the mask.
|
||||
/// </summary>
|
||||
/// <param name="layerName">Name of the layer (from layers settings).</param>
|
||||
/// <param name="layerName">Name of the layer (from Layers settings).</param>
|
||||
/// <returns><c>true</c> if the specified layer is set; otherwise, <c>false</c>.</returns>
|
||||
public bool HasLayer(string layerName)
|
||||
{
|
||||
return HasLayer(Level.GetLayerIndex(layerName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a layer mask based on a specific layer names.
|
||||
/// </summary>
|
||||
/// <param name="layerNames">The names of the layers (from Layers settings).</param>
|
||||
/// <returns>A layer mask with the mask set to the layers found. Returns a mask with 0 if not found.</returns>
|
||||
public static LayersMask GetMask(params string[] layerNames)
|
||||
{
|
||||
LayersMask mask = new LayersMask();
|
||||
foreach (var layerName in layerNames)
|
||||
{
|
||||
// Ignore blank entries
|
||||
if (string.IsNullOrEmpty(layerName))
|
||||
continue;
|
||||
int index = Level.GetLayerIndex(layerName);
|
||||
if (index != -1)
|
||||
mask.Mask |= (uint)(1 << index);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer index based on the layer name.
|
||||
/// </summary>
|
||||
/// <param name="layerName">The name of the layer.</param>
|
||||
/// <returns>The index if found, otherwise, returns -1.</returns>
|
||||
public static int GetLayerIndex(string layerName)
|
||||
{
|
||||
return Level.GetLayerIndex(layerName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer name based on the layer index.
|
||||
/// </summary>
|
||||
/// <param name="layerIndex">The index of the layer.</param>
|
||||
/// <returns>The name of the layer if found, otherwise, a blank string.</returns>
|
||||
public static string GetLayerName(int layerIndex)
|
||||
{
|
||||
return Level.GetLayerName(layerIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds two masks.
|
||||
/// </summary>
|
||||
|
||||
@@ -27,13 +27,30 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified layer index is set in the mask.
|
||||
/// </summary>
|
||||
/// <param name="layerIndex">Index of the layer (zero-based).</param>
|
||||
/// <returns><c>true</c> if the specified layer is set; otherwise, <c>false</c>.</returns>
|
||||
FORCE_INLINE bool HasLayer(int32 layerIndex) const
|
||||
{
|
||||
return (Mask & (1 << layerIndex)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified layer name is set in the mask.
|
||||
/// </summary>
|
||||
/// <param name="layerName">Name of the layer (from Layers settings).</param>
|
||||
/// <returns><c>true</c> if the specified layer is set; otherwise, <c>false</c>.</returns>
|
||||
bool HasLayer(const StringView& layerName) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a layers mask from a specific layer name.
|
||||
/// </summary>
|
||||
/// <param name="layerNames">The names of the layers (from Layers settings).</param>
|
||||
/// <returns>A layers mask with the Mask set to the same Mask as the layer name passed in. Returns a LayersMask with a mask of 0 if no layer found.</returns>
|
||||
static LayersMask GetMask(Span<StringView> layerNames);
|
||||
|
||||
operator uint32() const
|
||||
{
|
||||
return Mask;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "StringView.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
String String::Empty;
|
||||
const String String::Empty;
|
||||
|
||||
String::String(const StringAnsi& str)
|
||||
{
|
||||
|
||||
@@ -548,7 +548,7 @@ public:
|
||||
/// <summary>
|
||||
/// Instance of the empty string.
|
||||
/// </summary>
|
||||
static String Empty;
|
||||
static const String Empty;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -9,7 +9,7 @@ StringView StringBuilder::ToStringView() const
|
||||
return StringView(_data.Get(), _data.Count());
|
||||
}
|
||||
|
||||
StringView StringView::Empty;
|
||||
const StringView StringView::Empty;
|
||||
|
||||
StringView::StringView(const String& str)
|
||||
: StringViewBase<Char>(str.Get(), str.Length())
|
||||
|
||||
@@ -227,7 +227,7 @@ public:
|
||||
/// <summary>
|
||||
/// Instance of the empty string.
|
||||
/// </summary>
|
||||
static StringView Empty;
|
||||
static const StringView Empty;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
PACK_STRUCT(struct DecalMaterialShaderData {
|
||||
Matrix WorldMatrix;
|
||||
Matrix InvWorld;
|
||||
Matrix SVPositionToWorld;
|
||||
Matrix SvPositionToWorld;
|
||||
});
|
||||
|
||||
DrawPass DecalMaterialShader::GetDrawModes() const
|
||||
@@ -47,7 +47,9 @@ void DecalMaterialShader::Bind(BindParameters& params)
|
||||
MaterialParams::Bind(params.ParamsLink, bindMeta);
|
||||
|
||||
// Decals use depth buffer to draw on top of the objects
|
||||
context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer));
|
||||
GPUTexture* depthBuffer = params.RenderContext.Buffers->DepthBuffer;
|
||||
GPUTextureView* depthBufferView = EnumHasAnyFlags(depthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View();
|
||||
context->BindSR(0, depthBufferView);
|
||||
|
||||
// Setup material constants
|
||||
{
|
||||
@@ -65,7 +67,7 @@ void DecalMaterialShader::Bind(BindParameters& params)
|
||||
0, 0, 1, 0,
|
||||
-1.0f, 1.0f, 0, 1);
|
||||
const Matrix svPositionToWorld = offsetMatrix * view.IVP;
|
||||
Matrix::Transpose(svPositionToWorld, materialData->SVPositionToWorld);
|
||||
Matrix::Transpose(svPositionToWorld, materialData->SvPositionToWorld);
|
||||
}
|
||||
|
||||
// Bind constants
|
||||
@@ -90,16 +92,20 @@ void DecalMaterialShader::Unload()
|
||||
bool DecalMaterialShader::Load()
|
||||
{
|
||||
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc0.VS = _shader->GetVS("VS_Decal");
|
||||
psDesc0.VS = _shader->GetVS("VS_Decal"); // TODO: move VS_Decal to be shared (eg. in GBuffer.shader)
|
||||
if (psDesc0.VS == nullptr)
|
||||
return true;
|
||||
psDesc0.PS = _shader->GetPS("PS_Decal");
|
||||
psDesc0.CullMode = CullMode::Normal;
|
||||
if (GPUDevice::Instance->Limits.HasReadOnlyDepth)
|
||||
{
|
||||
psDesc0.DepthEnable = true;
|
||||
psDesc0.DepthWriteEnable = false;
|
||||
}
|
||||
|
||||
switch (_info.DecalBlendingMode)
|
||||
{
|
||||
case MaterialDecalBlendingMode::Translucent:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -107,9 +113,7 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Stain:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::DestColor;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -117,9 +121,7 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Normal:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -127,13 +129,10 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Emissive:
|
||||
{
|
||||
psDesc0.BlendMode = BlendingMode::Additive;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_cache.Outside = GPUDevice::Instance->CreatePipelineState();
|
||||
if (_cache.Outside->Init(psDesc0))
|
||||
@@ -143,6 +142,7 @@ bool DecalMaterialShader::Load()
|
||||
}
|
||||
|
||||
psDesc0.CullMode = CullMode::Inverted;
|
||||
psDesc0.DepthEnable = false;
|
||||
_cache.Inside = GPUDevice::Instance->CreatePipelineState();
|
||||
if (_cache.Inside->Init(psDesc0))
|
||||
{
|
||||
|
||||
@@ -23,6 +23,8 @@ PACK_STRUCT(struct GUIMaterialShaderData {
|
||||
Float4 ViewInfo;
|
||||
Float4 ScreenSize;
|
||||
Float4 ViewSize;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
void GUIMaterialShader::Bind(BindParameters& params)
|
||||
@@ -55,7 +57,8 @@ void GUIMaterialShader::Bind(BindParameters& params)
|
||||
materialData->ViewPos = Float3::Zero;
|
||||
materialData->ViewFar = 0.0f;
|
||||
materialData->ViewDir = Float3::Forward;
|
||||
materialData->TimeParam = params.TimeParam;
|
||||
materialData->TimeParam = params.Time;
|
||||
materialData->UnscaledTimeParam = params.UnscaledTime;
|
||||
materialData->ViewInfo = Float4::Zero;
|
||||
auto& viewport = Render2D::GetViewport();
|
||||
materialData->ScreenSize = Float4(viewport.Width, viewport.Height, 1.0f / viewport.Width, 1.0f / viewport.Height);
|
||||
|
||||
@@ -148,7 +148,7 @@ public:
|
||||
const ::DrawCall* DrawCall = nullptr;
|
||||
MaterialParamsLink* ParamsLink = nullptr;
|
||||
void* CustomData = nullptr;
|
||||
float TimeParam;
|
||||
float Time, UnscaledTime;
|
||||
bool Instanced = false;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,12 +37,15 @@ GPU_CB_STRUCT(MaterialShaderDataPerView {
|
||||
Float4 TemporalAAJitter;
|
||||
Float3 LargeWorldsChunkIndex;
|
||||
float LargeWorldsChunkSize;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderContext& renderContext)
|
||||
: GPUContext(context)
|
||||
, RenderContext(renderContext)
|
||||
, TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Time(Time::Draw.Time.GetTotalSeconds())
|
||||
, UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -50,7 +53,8 @@ IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderC
|
||||
: GPUContext(context)
|
||||
, RenderContext(renderContext)
|
||||
, DrawCall(&drawCall)
|
||||
, TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Time(Time::Draw.Time.GetTotalSeconds())
|
||||
, UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Instanced(instanced)
|
||||
{
|
||||
}
|
||||
@@ -78,7 +82,8 @@ void IMaterial::BindParameters::BindViewData()
|
||||
cb.ViewPos = view.Position;
|
||||
cb.ViewFar = view.Far;
|
||||
cb.ViewDir = view.Direction;
|
||||
cb.TimeParam = TimeParam;
|
||||
cb.TimeParam = Time;
|
||||
cb.UnscaledTimeParam = UnscaledTime;
|
||||
cb.ViewInfo = view.ViewInfo;
|
||||
cb.ScreenSize = view.ScreenSize;
|
||||
cb.TemporalAAJitter = view.TemporalAAJitter;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// <summary>
|
||||
/// Current materials shader version.
|
||||
/// </summary>
|
||||
#define MATERIAL_GRAPH_VERSION 173
|
||||
#define MATERIAL_GRAPH_VERSION 174
|
||||
|
||||
class Material;
|
||||
class GPUShader;
|
||||
|
||||
@@ -21,6 +21,8 @@ PACK_STRUCT(struct PostFxMaterialShaderData {
|
||||
Float4 ScreenSize;
|
||||
Float4 TemporalAAJitter;
|
||||
Matrix InverseViewProjectionMatrix;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
void PostFxMaterialShader::Bind(BindParameters& params)
|
||||
@@ -51,7 +53,8 @@ void PostFxMaterialShader::Bind(BindParameters& params)
|
||||
materialData->ViewPos = view.Position;
|
||||
materialData->ViewFar = view.Far;
|
||||
materialData->ViewDir = view.Direction;
|
||||
materialData->TimeParam = params.TimeParam;
|
||||
materialData->TimeParam = params.Time;
|
||||
materialData->UnscaledTimeParam = params.UnscaledTime;
|
||||
materialData->ViewInfo = view.ViewInfo;
|
||||
materialData->ScreenSize = view.ScreenSize;
|
||||
materialData->TemporalAAJitter = view.TemporalAAJitter;
|
||||
|
||||
@@ -175,6 +175,12 @@ public:
|
||||
return (const T*)FindCustomBuffer(name, withLinked);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
const T* FindLinkedBuffer(const StringView& name) const
|
||||
{
|
||||
return LinkedCustomBuffers ? (const T*)LinkedCustomBuffers->FindCustomBuffer(name, true) : nullptr;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T* GetCustomBuffer(const StringView& name, bool withLinked = true)
|
||||
{
|
||||
|
||||
@@ -184,6 +184,14 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no
|
||||
GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
|
||||
}
|
||||
|
||||
void AnimatedModel::GetNodeTransformation(Array<NodeTransformation>& nodeTransformations, bool worldSpace) const
|
||||
{
|
||||
for (NodeTransformation& item : nodeTransformations)
|
||||
{
|
||||
GetNodeTransformation(item.NodeIndex, item.NodeMatrix, worldSpace);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
@@ -201,6 +209,33 @@ void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTra
|
||||
OnAnimationUpdated();
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(const Array<NodeTransformation>& nodeTransformations, bool worldSpace)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
const_cast<AnimatedModel*>(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return
|
||||
|
||||
// Calculate it once, outside loop
|
||||
Matrix invWorld;
|
||||
if (worldSpace)
|
||||
{
|
||||
Matrix world;
|
||||
GetLocalToWorldMatrix(world);
|
||||
Matrix::Invert(world, invWorld);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeTransformations.Count(); i++)
|
||||
{
|
||||
int nodeIndex = nodeTransformations[i].NodeIndex;
|
||||
CHECK(nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count());
|
||||
GraphInstance.NodesPose[nodeIndex] = nodeTransformations[i].NodeMatrix;
|
||||
if (worldSpace)
|
||||
{
|
||||
GraphInstance.NodesPose[nodeIndex] = GraphInstance.NodesPose[nodeIndex] * invWorld;
|
||||
}
|
||||
}
|
||||
OnAnimationUpdated();
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace)
|
||||
{
|
||||
SetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
|
||||
@@ -830,7 +865,10 @@ void AnimatedModel::OnAnimationUpdated_Async()
|
||||
_skinningData.OnDataChanged(!PerBoneMotionBlur);
|
||||
}
|
||||
|
||||
UpdateBounds();
|
||||
if (UpdateWhenOffscreen)
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::OnAnimationUpdated_Sync()
|
||||
|
||||
@@ -18,6 +18,24 @@ class FLAXENGINE_API AnimatedModel : public ModelInstanceActor, IAssetReference
|
||||
DECLARE_SCENE_OBJECT(AnimatedModel);
|
||||
friend class AnimationsSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps the data of a Node and its relevant Transform Matrix together when passing it between functions.
|
||||
/// </summary>
|
||||
API_STRUCT() struct NodeTransformation
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NodeTransformation);
|
||||
|
||||
/// <summary>
|
||||
/// The index of the node in the node hierarchy.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 NodeIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The transformation matrix of the node
|
||||
/// </summary>
|
||||
API_FIELD() Matrix NodeMatrix;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes the animation graph updates frequency for the animated model.
|
||||
/// </summary>
|
||||
@@ -242,6 +260,14 @@ public:
|
||||
/// <param name="worldSpace">True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.</param>
|
||||
API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node final transformation for a series of nodes.
|
||||
/// </summary>
|
||||
/// <param name="nodeTransformations">The series of nodes that will be returned</param>
|
||||
/// <param name="worldSpace">True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.</param>
|
||||
/// <returns></returns>
|
||||
API_FUNCTION() void GetNodeTransformation(API_PARAM(Ref) Array<NodeTransformation>& nodeTransformations, bool worldSpace = false) const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices.
|
||||
/// </summary>
|
||||
@@ -258,6 +284,14 @@ public:
|
||||
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
|
||||
API_FUNCTION() void SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace = false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a group of nodes final transformation.
|
||||
/// </summary>
|
||||
/// <param name="nodeTransformations">Array of the final node transformation matrix.</param>
|
||||
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
|
||||
/// <returns></returns>
|
||||
API_FUNCTION() void SetNodeTransformation(const Array<NodeTransformation>& nodeTransformations, bool worldSpace = false);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the closest node to a given location.
|
||||
/// </summary>
|
||||
|
||||
@@ -65,6 +65,21 @@ bool LayersMask::HasLayer(const StringView& layerName) const
|
||||
return HasLayer(Level::GetLayerIndex(layerName));
|
||||
}
|
||||
|
||||
LayersMask LayersMask::GetMask(Span<StringView> layerNames)
|
||||
{
|
||||
LayersMask mask(0);
|
||||
for (StringView& layerName : layerNames)
|
||||
{
|
||||
// Ignore blank entries
|
||||
if (layerName.Length() == 0)
|
||||
continue;
|
||||
int32 index = Level::GetLayerIndex(layerName);
|
||||
if (index != -1)
|
||||
mask.Mask |= (uint32)(1 << index);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
enum class SceneEventType
|
||||
{
|
||||
OnSceneSaving = 0,
|
||||
@@ -824,6 +839,18 @@ int32 Level::GetLayerIndex(const StringView& layer)
|
||||
return result;
|
||||
}
|
||||
|
||||
StringView Level::GetLayerName(const int32 layerIndex)
|
||||
{
|
||||
for (int32 i = 0; i < 32; i++)
|
||||
{
|
||||
if (i == layerIndex)
|
||||
{
|
||||
return Layers[i];
|
||||
}
|
||||
}
|
||||
return TEXT("");
|
||||
}
|
||||
|
||||
void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
@@ -539,6 +539,13 @@ public:
|
||||
/// </summary>
|
||||
API_FUNCTION() static int32 GetLayerIndex(const StringView& layer);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the layer based on the index.
|
||||
/// </summary>
|
||||
/// <param name="layerIndex">The index to find the layer string. 0 - 32.</param>
|
||||
/// <returns>The layer string. Returns a blank string if index not found.</returns>
|
||||
API_FUNCTION() static StringView GetLayerName(const int32 layerIndex);
|
||||
|
||||
private:
|
||||
// Actor API
|
||||
enum class ActorEventType
|
||||
|
||||
@@ -549,6 +549,19 @@ void ParticleEffect::OnAssetChanged(Asset* asset, void* caller)
|
||||
}
|
||||
|
||||
void ParticleEffect::OnAssetLoaded(Asset* asset, void* caller)
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
#if USE_EDITOR
|
||||
// When one of the emitters gets edited, cached parameters need to be applied
|
||||
auto& emitters = ParticleSystem.Get()->Emitters;
|
||||
for (auto& emitter : emitters)
|
||||
{
|
||||
emitter.Loaded.BindUnique<ParticleEffect, &ParticleEffect::OnParticleEmitterLoaded>(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ParticleEffect::OnParticleEmitterLoaded()
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
}
|
||||
@@ -844,6 +857,10 @@ void ParticleEffect::OnActiveInTreeChanged()
|
||||
CacheModifiedParameters();
|
||||
Instance.ClearState();
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffect::OnTransformChanged()
|
||||
|
||||
@@ -394,6 +394,7 @@ private:
|
||||
void ApplyModifiedParameters();
|
||||
void OnParticleSystemModified();
|
||||
void OnParticleSystemLoaded();
|
||||
void OnParticleEmitterLoaded();
|
||||
|
||||
// [IAssetReference]
|
||||
void OnAssetChanged(Asset* asset, void* caller) override;
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -18,281 +14,9 @@
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
const DateTime UnixEpoch(1970, 1, 1);
|
||||
|
||||
bool AppleFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeletePathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeletePathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
return DeletePathTree(pathANSI.Get());
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsANSI<> searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool AppleFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exist or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 AppleFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(StringAsANSI<>(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(StringAsANSI<>(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::CopyFile(const StringView& dst, const StringView& src)
|
||||
{
|
||||
const StringAsANSI<> srcANSI(*src, src.Length());
|
||||
@@ -352,156 +76,6 @@ out_error:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
void AppleFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
|
||||
{
|
||||
String home;
|
||||
|
||||
@@ -4,33 +4,17 @@
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
#include "Engine/Platform/Unix/UnixFileSystem.h"
|
||||
|
||||
/// <summary>
|
||||
/// Apple platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AppleFileSystem : public FileSystemBase
|
||||
class FLAXENGINE_API AppleFileSystem : public UnixFileSystem
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static bool CopyFile(const StringView& dst, const StringView& src);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,22 +4,16 @@
|
||||
|
||||
#include "LinuxFileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Platform/StringUtils.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringBuilder.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
@@ -165,280 +159,6 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeleteUnixPathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeleteUnixPathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
{
|
||||
return DeleteUnixPathTree(pathANSI.Get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 LinuxFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(StringAsUTF8<>(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(StringAsUTF8<>(*src, src.Length()).Get(), StringAsUTF8<>(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(StringAsUTF8<>(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src)
|
||||
{
|
||||
const StringAsUTF8<> srcANSI(*src, src.Length());
|
||||
@@ -612,156 +332,6 @@ bool LinuxFileSystem::UrnEncodePath(const char *path, char *result, const int ma
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
void LinuxFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
|
||||
{
|
||||
const String& home = LinuxPlatform::GetHomeDirectory();
|
||||
|
||||
@@ -4,38 +4,24 @@
|
||||
|
||||
#if PLATFORM_LINUX
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
#include "Engine/Platform/Unix/UnixFileSystem.h"
|
||||
|
||||
/// <summary>
|
||||
/// Linux platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API LinuxFileSystem : public FileSystemBase
|
||||
class FLAXENGINE_API LinuxFileSystem : public UnixFileSystem
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames);
|
||||
static bool ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path);
|
||||
static bool ShowFileExplorer(const StringView& path);
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static bool CopyFile(const StringView& dst, const StringView& src);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
static bool UrnEncodePath(const char *path, char *result, int maxLength);
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static String getBaseName(const StringView& path);
|
||||
static String getNameWithoutExtension(const StringView& path);
|
||||
};
|
||||
|
||||
470
Source/Engine/Platform/Unix/UnixFileSystem.cpp
Normal file
470
Source/Engine/Platform/Unix/UnixFileSystem.cpp
Normal file
@@ -0,0 +1,470 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#if PLATFORM_UNIX
|
||||
|
||||
#include "UnixFileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
typedef StringAsANSI<> UnixString;
|
||||
#else
|
||||
typedef StringAsUTF8<> UnixString;
|
||||
#endif
|
||||
|
||||
const DateTime UnixEpoch(1970, 1, 1);
|
||||
|
||||
bool UnixFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const UnixString pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeleteUnixPathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeleteUnixPathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
{
|
||||
return DeleteUnixPathTree(pathANSI.Get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
const UnixString searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool UnixFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 UnixFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(UnixString(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(UnixString(*src, src.Length()).Get(), UnixString(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(UnixString(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
const int32 pathLength = strlen(entry->d_name);
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 ||
|
||||
StringUtils::Compare(searchPattern, "*") == 0 ||
|
||||
StringUtils::Compare(searchPattern, "*.*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength > 2 && searchPattern[searchPatternLength-1] == '*')
|
||||
{
|
||||
// Contains pattern
|
||||
bool match = false;
|
||||
for (int32 i = 0; i < pathLength - searchPatternLength - 1; i++)
|
||||
{
|
||||
int32 len = Math::Min(searchPatternLength - 2, pathLength - i);
|
||||
if (StringUtils::Compare(&entry->d_name[i], &searchPattern[1], len) == 0)
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
LOG(Warning, "DirectoryGetFiles: Wildcard filter is not implemented");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime UnixFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
#endif
|
||||
35
Source/Engine/Platform/Unix/UnixFileSystem.h
Normal file
35
Source/Engine/Platform/Unix/UnixFileSystem.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_UNIX
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
|
||||
/// <summary>
|
||||
/// Unix platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API UnixFileSystem : public FileSystemBase
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
|
||||
private:
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -434,6 +434,7 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light
|
||||
PROFILE_GPU_CPU("Decals");
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
auto buffers = renderContext.Buffers;
|
||||
GPUTextureView* depthBuffer = EnumHasAnyFlags(buffers->DepthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? buffers->DepthBuffer->ViewReadOnlyDepth() : nullptr;
|
||||
|
||||
// Sort decals from the lowest order to the highest order
|
||||
Sorting::QuickSort(decals.Get(), decals.Count(), &SortDecal);
|
||||
@@ -484,22 +485,22 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light
|
||||
count++;
|
||||
targetBuffers[2] = buffers->GBuffer1->View();
|
||||
}
|
||||
context->SetRenderTarget(nullptr, ToSpan(targetBuffers, count));
|
||||
context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, count));
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Stain:
|
||||
{
|
||||
context->SetRenderTarget(buffers->GBuffer0->View());
|
||||
context->SetRenderTarget(depthBuffer, buffers->GBuffer0->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Normal:
|
||||
{
|
||||
context->SetRenderTarget(buffers->GBuffer1->View());
|
||||
context->SetRenderTarget(depthBuffer, buffers->GBuffer1->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Emissive:
|
||||
{
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
context->SetRenderTarget(depthBuffer, lightBuffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ struct ShadowAtlasLightTile
|
||||
{
|
||||
ShadowsAtlasRectTile* RectTile;
|
||||
ShadowsAtlasRectTile* StaticRectTile;
|
||||
const ShadowsAtlasRectTile* LinkedRectTile;
|
||||
Matrix WorldToShadow;
|
||||
float FramesToUpdate; // Amount of frames (with fraction) until the next shadow update can happen
|
||||
bool SkipUpdate;
|
||||
@@ -94,6 +95,7 @@ struct ShadowAtlasLightTile
|
||||
void ClearStatic()
|
||||
{
|
||||
StaticRectTile = nullptr;
|
||||
LinkedRectTile = nullptr;
|
||||
FramesToUpdate = 0;
|
||||
SkipUpdate = false;
|
||||
}
|
||||
@@ -301,6 +303,7 @@ public:
|
||||
GPUTexture* StaticShadowMapAtlas = nullptr;
|
||||
DynamicTypedBuffer ShadowsBuffer;
|
||||
GPUBufferView* ShadowsBufferView = nullptr;
|
||||
const ShadowsCustomBuffer* LinkedShadows = nullptr;
|
||||
RectPackAtlas<ShadowsAtlasRectTile> Atlas;
|
||||
RectPackAtlas<ShadowsAtlasRectTile> StaticAtlas;
|
||||
Dictionary<Guid, ShadowAtlasLight> Lights;
|
||||
@@ -1044,6 +1047,32 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowsPass::ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData) const
|
||||
{
|
||||
// Color.r is used by PS_DepthClear in Quad shader to clear depth
|
||||
quadShaderData.Color = Float4::One;
|
||||
context->UpdateCB(quadShaderCB, &quadShaderData);
|
||||
context->BindCB(0, quadShaderCB);
|
||||
|
||||
// Clear tile depth
|
||||
context->SetState(_psDepthClear);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
void ShadowsPass::CopyShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData, const GPUTexture* srcShadowMap, const ShadowsAtlasRectTile* srcTile) const
|
||||
{
|
||||
// Color.xyzw is used by PS_DepthCopy in Quad shader to scale input texture UVs
|
||||
const float staticAtlasResolutionInv = 1.0f / (float)srcShadowMap->Width();
|
||||
quadShaderData.Color = Float4(srcTile->Width, srcTile->Height, srcTile->X, srcTile->Y) * staticAtlasResolutionInv;
|
||||
context->UpdateCB(quadShaderCB, &quadShaderData);
|
||||
context->BindCB(0, quadShaderCB);
|
||||
|
||||
// Copy tile depth
|
||||
context->BindSR(0, srcShadowMap->View());
|
||||
context->SetState(_psDepthCopy);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
void ShadowsPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
@@ -1068,26 +1097,26 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch&
|
||||
// Early out and skip shadows setup if no lights is actively casting shadows
|
||||
// RenderBuffers will automatically free any old ShadowsCustomBuffer after a few frames if we don't update LastFrameUsed
|
||||
Array<RenderLightData*, RendererAllocation> shadowedLights;
|
||||
for (auto& light : renderContext.List->DirectionalLights)
|
||||
if (_shadowMapFormat != PixelFormat::Unknown && EnumHasAllFlags(renderContext.View.Flags, ViewFlags::Shadows) && !checkIfSkipPass())
|
||||
{
|
||||
if (light.CanRenderShadow(renderContext.View))
|
||||
shadowedLights.Add(&light);
|
||||
}
|
||||
for (auto& light : renderContext.List->SpotLights)
|
||||
{
|
||||
if (light.CanRenderShadow(renderContext.View))
|
||||
shadowedLights.Add(&light);
|
||||
}
|
||||
for (auto& light : renderContext.List->PointLights)
|
||||
{
|
||||
if (light.CanRenderShadow(renderContext.View))
|
||||
shadowedLights.Add(&light);
|
||||
for (auto& light : renderContext.List->DirectionalLights)
|
||||
{
|
||||
if (light.CanRenderShadow(renderContext.View))
|
||||
shadowedLights.Add(&light);
|
||||
}
|
||||
for (auto& light : renderContext.List->SpotLights)
|
||||
{
|
||||
if (light.CanRenderShadow(renderContext.View))
|
||||
shadowedLights.Add(&light);
|
||||
}
|
||||
for (auto& light : renderContext.List->PointLights)
|
||||
{
|
||||
if (light.CanRenderShadow(renderContext.View))
|
||||
shadowedLights.Add(&light);
|
||||
}
|
||||
}
|
||||
const auto currentFrame = Engine::FrameCount;
|
||||
if (_shadowMapFormat == PixelFormat::Unknown ||
|
||||
EnumHasNoneFlags(renderContext.View.Flags, ViewFlags::Shadows) ||
|
||||
checkIfSkipPass() ||
|
||||
shadowedLights.IsEmpty())
|
||||
if (shadowedLights.IsEmpty())
|
||||
{
|
||||
// Invalidate any existing custom buffer that could have been used by the same task (eg. when rendering 6 sides of env probe)
|
||||
if (auto* old = (ShadowsCustomBuffer*)renderContext.Buffers->FindCustomBuffer<ShadowsCustomBuffer>(TEXT("Shadows"), false))
|
||||
@@ -1100,11 +1129,14 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch&
|
||||
|
||||
// Initialize shadow atlas
|
||||
auto& shadows = *renderContext.Buffers->GetCustomBuffer<ShadowsCustomBuffer>(TEXT("Shadows"), false);
|
||||
shadows.LinkedShadows = renderContext.Buffers->FindLinkedBuffer<ShadowsCustomBuffer>(TEXT("Shadows"));
|
||||
if (shadows.LinkedShadows && (shadows.LinkedShadows->LastFrameUsed != currentFrame || shadows.LinkedShadows->ViewOrigin != renderContext.View.Origin))
|
||||
shadows.LinkedShadows = nullptr; // Don't use incompatible linked shadows buffer
|
||||
if (shadows.LastFrameUsed == currentFrame)
|
||||
shadows.Reset();
|
||||
shadows.LastFrameUsed = currentFrame;
|
||||
shadows.MaxShadowsQuality = Math::Clamp(Math::Min<int32>((int32)Graphics::ShadowsQuality, (int32)renderContext.View.MaxShadowsQuality), 0, (int32)Quality::MAX - 1);
|
||||
shadows.EnableStaticShadows = !renderContext.View.IsOfflinePass && !renderContext.View.IsSingleFrame;
|
||||
shadows.EnableStaticShadows = !renderContext.View.IsOfflinePass && !renderContext.View.IsSingleFrame && !shadows.LinkedShadows;
|
||||
int32 atlasResolution;
|
||||
switch (Graphics::ShadowMapsQuality)
|
||||
{
|
||||
@@ -1323,6 +1355,29 @@ RETRY_ATLAS_SETUP:
|
||||
SetupLight(shadows, renderContext, renderContextBatch, *(RenderSpotLightData*)light, atlasLight);
|
||||
else //if (light->IsDirectionalLight)
|
||||
SetupLight(shadows, renderContext, renderContextBatch, *(RenderDirectionalLightData*)light, atlasLight);
|
||||
|
||||
// Check if that light exists in linked shadows buffer to reuse shadow maps
|
||||
const ShadowAtlasLight* linkedAtlasLight;
|
||||
if (shadows.LinkedShadows && ((linkedAtlasLight = shadows.LinkedShadows->Lights.TryGet(light->ID))) && linkedAtlasLight->TilesCount == atlasLight.TilesCount)
|
||||
{
|
||||
for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
|
||||
{
|
||||
auto& tile = atlasLight.Tiles[tileIndex];
|
||||
tile.LinkedRectTile = nullptr;
|
||||
auto& linkedTile = linkedAtlasLight->Tiles[tileIndex];
|
||||
|
||||
// Check if both lights use the same projections
|
||||
if (tile.WorldToShadow == linkedTile.WorldToShadow && linkedTile.RectTile)
|
||||
{
|
||||
tile.LinkedRectTile = linkedTile.RectTile;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& tile : atlasLight.Tiles)
|
||||
tile.LinkedRectTile = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shadows.StaticAtlas.IsInitialized())
|
||||
@@ -1493,29 +1548,21 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
|
||||
|
||||
// Set viewport for tile
|
||||
context->SetViewportAndScissors(tile.CachedViewport);
|
||||
if (tile.StaticRectTile && atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow)
|
||||
if (tile.LinkedRectTile)
|
||||
{
|
||||
// Color.xyzw is used by PS_DepthCopy in Quad shader to scale input texture UVs
|
||||
const float staticAtlasResolutionInv = 1.0f / shadows.StaticShadowMapAtlas->Width();
|
||||
quadShaderData.Color = Float4(tile.StaticRectTile->Width, tile.StaticRectTile->Height, tile.StaticRectTile->X, tile.StaticRectTile->Y) * staticAtlasResolutionInv;
|
||||
context->UpdateCB(quadShaderCB, &quadShaderData);
|
||||
context->BindCB(0, quadShaderCB);
|
||||
|
||||
// Copy tile depth
|
||||
context->BindSR(0, shadows.StaticShadowMapAtlas->View());
|
||||
context->SetState(_psDepthCopy);
|
||||
context->DrawFullscreenTriangle();
|
||||
// Copy linked shadow
|
||||
ASSERT(shadows.LinkedShadows);
|
||||
CopyShadowMapTile(context, quadShaderCB, quadShaderData, shadows.LinkedShadows->ShadowMapAtlas, tile.LinkedRectTile);
|
||||
}
|
||||
else if (tile.StaticRectTile && atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow)
|
||||
{
|
||||
// Copy static shadow
|
||||
CopyShadowMapTile(context, quadShaderCB, quadShaderData, shadows.StaticShadowMapAtlas, tile.StaticRectTile);
|
||||
}
|
||||
else if (!shadows.ClearShadowMapAtlas)
|
||||
{
|
||||
// Color.r is used by PS_DepthClear in Quad shader to clear depth
|
||||
quadShaderData.Color = Float4::One;
|
||||
context->UpdateCB(quadShaderCB, &quadShaderData);
|
||||
context->BindCB(0, quadShaderCB);
|
||||
|
||||
// Clear tile depth
|
||||
context->SetState(_psDepthClear);
|
||||
context->DrawFullscreenTriangle();
|
||||
// Clear shadow
|
||||
ClearShadowMapTile(context, quadShaderCB, quadShaderData);
|
||||
}
|
||||
|
||||
// Draw objects depth
|
||||
|
||||
@@ -60,6 +60,8 @@ private:
|
||||
static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight);
|
||||
static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight);
|
||||
static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight);
|
||||
void ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, struct QuadShaderData& quadShaderData) const;
|
||||
void CopyShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, struct QuadShaderData& quadShaderData, const GPUTexture* srcShadowMap, const struct ShadowsAtlasRectTile* srcTile) const;
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user