Merge remote-tracking branch 'origin/master' into 1.9

This commit is contained in:
Wojtek Figat
2024-06-13 17:07:37 +02:00
68 changed files with 2222 additions and 374 deletions

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

Binary file not shown.

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

Binary file not shown.

View File

@@ -111,6 +111,11 @@ namespace FlaxEditor.CustomEditors
/// </summary>
public PropertyNameLabel LinkedLabel;
/// <summary>
/// Gets the layout for this editor. Used to calculate bounds.
/// </summary>
public LayoutElementsContainer Layout => _layout;
internal virtual void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
{
_layout = layout;

View File

@@ -41,6 +41,13 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (XElement.ValueBox.Parent is UniformGridPanel ug)
{
ug.Height += 2;
ug.SlotSpacing = new Float2(4);
ug.SlotPadding = new Margin(0, 0, 1, 1);
}
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
@@ -66,6 +73,13 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (XElement.ValueBox.Parent is UniformGridPanel ug)
{
ug.Height += 2;
ug.SlotSpacing = new Float2(4);
ug.SlotPadding = new Margin(0, 0, 1, 1);
}
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
@@ -122,6 +136,13 @@ namespace FlaxEditor.CustomEditors.Editors
menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling");
};
}
if (XElement.ValueBox.Parent is UniformGridPanel ug)
{
ug.Height += 2;
ug.SlotSpacing = new Float2(4);
ug.SlotPadding = new Margin(0, 0, 1, 1);
}
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;

View File

@@ -38,6 +38,9 @@ namespace FlaxEditor.CustomEditors.Editors
/// </summary>
public readonly int Index;
private Rectangle _arrangeButtonRect;
private bool _arrangeButtonInUse;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionItemLabel"/> class.
/// </summary>
@@ -50,6 +53,12 @@ namespace FlaxEditor.CustomEditors.Editors
Index = index;
SetupContextMenu += OnSetupContextMenu;
_arrangeButtonRect = new Rectangle(2, 3, 12, 12);
// Extend margin of the label to support a dragging handle.
Margin m = Margin;
m.Left += 16;
Margin = m;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
@@ -71,6 +80,107 @@ namespace FlaxEditor.CustomEditors.Editors
b.Enabled = !Editor._readOnly;
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
base.OnEndMouseCapture();
_arrangeButtonInUse = false;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var style = FlaxEngine.GUI.Style.Current;
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
{
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
}
}
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
{
var child = Editor.ChildrenEditors[0];
var container = child.Layout.ContainerControl;
var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
var barSidesExtend = 20.0f;
var barHeight = 5.0f;
var barCheckAreaHeight = 40.0f;
var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
for (int i = 0; i < container.Children.Count / 2; i++)
{
var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
{
index = i;
var p1 = containerChild.UpperLeft;
rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
return true;
}
}
var p2 = container.Children[((container.Children.Count / 2) - 1) * 2].BottomLeft;
if (pos > p2.Y)
{
index = (container.Children.Count / 2) - 1;
rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
return true;
}
index = -1;
rect = Rectangle.Empty;
return false;
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
{
_arrangeButtonInUse = true;
Focus();
StartMouseCapture();
return true;
}
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _arrangeButtonInUse)
{
_arrangeButtonInUse = false;
EndMouseCapture();
if (ArrangeAreaCheck(out var index, out _))
{
Editor.Shift(Index, index);
}
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override void OnLostFocus()
{
if (_arrangeButtonInUse)
{
_arrangeButtonInUse = false;
EndMouseCapture();
}
base.OnLostFocus();
}
private void OnMoveUpClicked()
{
Editor.Move(Index, Index - 1);
@@ -106,6 +216,9 @@ namespace FlaxEditor.CustomEditors.Editors
private bool _canReorder = true;
private Rectangle _arrangeButtonRect;
private bool _arrangeButtonInUse;
public void Setup(CollectionEditor editor, int index, bool canReorder = true)
{
HeaderHeight = 18;
@@ -123,10 +236,92 @@ namespace FlaxEditor.CustomEditors.Editors
MouseButtonRightClicked += OnMouseButtonRightClicked;
if (_canReorder)
{
// TODO: Drag drop
HeaderTextMargin = new Margin(18, 0, 0, 0);
_arrangeButtonRect = new Rectangle(16, 3, 12, 12);
}
}
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
{
var container = Parent;
var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
var barSidesExtend = 20.0f;
var barHeight = 5.0f;
var barCheckAreaHeight = 40.0f;
var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
for (int i = 0; i < (container.Children.Count + 1) / 2; i++) // Add 1 to pretend there is a spacer at the end.
{
var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
{
index = i;
var p1 = containerChild.UpperLeft;
rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
return true;
}
}
var p2 = container.Children[container.Children.Count - 1].BottomLeft;
if (pos > p2.Y)
{
index = ((container.Children.Count + 1) / 2) - 1;
rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
return true;
}
index = -1;
rect = Rectangle.Empty;
return false;
}
public override void Draw()
{
base.Draw();
if (_canReorder)
{
var style = FlaxEngine.GUI.Style.Current;
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
{
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
}
}
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
{
_arrangeButtonInUse = true;
Focus();
StartMouseCapture();
return true;
}
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _arrangeButtonInUse)
{
_arrangeButtonInUse = false;
EndMouseCapture();
if (ArrangeAreaCheck(out var index, out _))
{
Editor.Shift(Index, index);
}
}
return base.OnMouseUp(location, button);
}
private void OnMouseButtonRightClicked(DropPanel panel, Float2 location)
{
if (LinkedEditor == null)
@@ -324,7 +519,6 @@ namespace FlaxEditor.CustomEditors.Editors
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
elementType.Equals(new ScriptType(typeof(SettingsBase)));
for (int i = 0; i < size; i++)
{
// Apply spacing
@@ -440,6 +634,39 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(cloned);
}
/// <summary>
/// Shifts the specified item at the given index and moves it through the list to the other item. It supports undo.
/// </summary>
/// <param name="srcIndex">Index of the source item.</param>
/// <param name="dstIndex">Index of the destination to move to.</param>
private void Shift(int srcIndex, int dstIndex)
{
if (IsSetBlocked)
return;
var cloned = CloneValues();
if (dstIndex > srcIndex)
{
for (int i = srcIndex; i < dstIndex; i++)
{
var tmp = cloned[i + 1];
cloned[i + 1] = cloned[i];
cloned[i] = tmp;
}
}
else
{
for (int i = srcIndex; i > dstIndex; i--)
{
var tmp = cloned[i - 1];
cloned[i - 1] = cloned[i];
cloned[i] = tmp;
}
}
SetValue(cloned);
}
/// <summary>
/// Removes the item at the specified index. It supports undo.
/// </summary>

View File

@@ -1378,6 +1378,7 @@ namespace FlaxEditor
public byte AutoReloadScriptsOnMainWindowFocus;
public byte ForceScriptCompilationOnStartup;
public byte UseAssetImportPathRelative;
public byte EnableParticlesPreview;
public byte AutoRebuildCSG;
public float AutoRebuildCSGTimeoutMs;
public byte AutoRebuildNavMesh;

View File

@@ -130,7 +130,7 @@ namespace FlaxEditor.GUI.Input
base.Draw();
var style = Style.Current;
var r = new Rectangle(2, 2, Width - 4, Height - 4);
var r = new Rectangle(0, 0, Width, Height);
Render2D.FillRectangle(r, _value);
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);

View File

@@ -23,6 +23,9 @@ namespace FlaxEditor.GUI
[HideInEditor]
public class Item : Control
{
private bool _isStartsWithMatch;
private bool _isFullMatch;
/// <summary>
/// The is mouse down flag.
/// </summary>
@@ -43,6 +46,11 @@ namespace FlaxEditor.GUI
/// </summary>
public string Category;
/// <summary>
/// A computed score for the context menu order
/// </summary>
public float SortScore;
/// <summary>
/// Occurs when items gets clicked by the user.
/// </summary>
@@ -61,44 +69,69 @@ namespace FlaxEditor.GUI
{
}
/// <summary>
/// Updates the <see cref="SortScore"/>
/// </summary>
public void UpdateScore()
{
SortScore = 0;
if (!Visible)
return;
if (_highlights is { Count: > 0 })
SortScore += 1;
if (_isStartsWithMatch)
SortScore += 2;
if (_isFullMatch)
SortScore += 5;
}
/// <summary>
/// Updates the filter.
/// </summary>
/// <param name="filterText">The filter text.</param>
public void UpdateFilter(string filterText)
{
_isStartsWithMatch = _isFullMatch = false;
if (string.IsNullOrWhiteSpace(filterText))
{
// Clear filter
_highlights?.Clear();
Visible = true;
return;
}
else
if (QueryFilterHelper.Match(filterText, Name, out var ranges))
{
if (QueryFilterHelper.Match(filterText, Name, out var ranges))
{
// Update highlights
if (_highlights == null)
_highlights = new List<Rectangle>(ranges.Length);
else
_highlights.Clear();
var style = Style.Current;
var font = style.FontSmall;
for (int i = 0; i < ranges.Length; i++)
{
var start = font.GetCharPosition(Name, ranges[i].StartIndex);
var end = font.GetCharPosition(Name, ranges[i].EndIndex);
_highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height));
}
Visible = true;
}
// Update highlights
if (_highlights == null)
_highlights = new List<Rectangle>(ranges.Length);
else
_highlights.Clear();
var style = Style.Current;
var font = style.FontSmall;
for (int i = 0; i < ranges.Length; i++)
{
// Hide
_highlights?.Clear();
Visible = false;
var start = font.GetCharPosition(Name, ranges[i].StartIndex);
var end = font.GetCharPosition(Name, ranges[i].EndIndex);
_highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height));
if (ranges[i].StartIndex <= 0)
{
_isStartsWithMatch = true;
if (ranges[i].Length == Name.Length)
_isFullMatch = true;
}
}
Visible = true;
return;
}
// Hide
_highlights?.Clear();
Visible = false;
}
/// <summary>
@@ -178,7 +211,14 @@ namespace FlaxEditor.GUI
public override int Compare(Control other)
{
if (other is Item otherItem)
return string.Compare(Name, otherItem.Name, StringComparison.Ordinal);
{
int order = -1 * SortScore.CompareTo(otherItem.SortScore);
if (order == 0)
{
order = string.Compare(Name, otherItem.Name, StringComparison.Ordinal);
}
return order;
}
return base.Compare(other);
}
}
@@ -249,7 +289,10 @@ namespace FlaxEditor.GUI
for (int i = 0; i < items.Count; i++)
{
if (items[i] is Item item)
{
item.UpdateFilter(_searchBox.Text);
item.UpdateScore();
}
}
if (_categoryPanels != null)
{
@@ -262,6 +305,7 @@ namespace FlaxEditor.GUI
if (category.Children[j] is Item item2)
{
item2.UpdateFilter(_searchBox.Text);
item2.UpdateScore();
anyVisible |= item2.Visible;
}
}
@@ -273,6 +317,8 @@ namespace FlaxEditor.GUI
}
}
SortItems();
UnlockChildrenRecursive();
PerformLayout(true);
_searchBox.Focus();

View File

@@ -363,6 +363,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <inheritdoc />
public override void OnDestroy()
{
if (_previewValue != null)
Timeline.ShowPreviewValuesChanged -= OnTimelineShowPreviewValuesChanged;
_previewValue = null;
_rightKey = null;
_addKey = null;

View File

@@ -2,6 +2,7 @@
using System;
using FlaxEngine;
using System.Runtime.InteropServices;
namespace FlaxEditor.Gizmo
{
@@ -15,91 +16,120 @@ namespace FlaxEditor.Gizmo
[HideInEditor]
private sealed class Renderer : PostProcessEffect
{
private IntPtr _debugDrawContext;
[StructLayout(LayoutKind.Sequential)]
private struct Data
{
public Matrix WorldMatrix;
public Matrix ViewProjectionMatrix;
public Float4 GridColor;
public Float3 ViewPos;
public float Far;
public Float3 Padding;
public float GridSize;
}
private static readonly uint[] _triangles =
{
0, 2, 1, // Face front
1, 3, 0,
};
private GPUBuffer[] _vbs = new GPUBuffer[1];
private GPUBuffer _vertexBuffer;
private GPUBuffer _indexBuffer;
private GPUPipelineState _psGrid;
private Shader _shader;
public Renderer()
{
Order = -100;
UseSingleTarget = true;
Location = PostProcessEffectLocation.BeforeForwardPass;
Location = PostProcessEffectLocation.Default;
_shader = FlaxEngine.Content.LoadAsyncInternal<Shader>("Shaders/Editor/Grid");
}
~Renderer()
{
if (_debugDrawContext != IntPtr.Zero)
{
DebugDraw.FreeContext(_debugDrawContext);
_debugDrawContext = IntPtr.Zero;
}
Destroy(ref _psGrid);
Destroy(ref _vertexBuffer);
Destroy(ref _indexBuffer);
_shader = null;
}
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
public override unsafe void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
{
if (_shader == null)
return;
Profiler.BeginEventGPU("Editor Grid");
if (_debugDrawContext == IntPtr.Zero)
_debugDrawContext = DebugDraw.AllocateContext();
DebugDraw.SetContext(_debugDrawContext);
DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1));
var viewPos = (Vector3)renderContext.View.Position;
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
var options = Editor.Instance.Options.Options;
float space = options.Viewport.ViewportGridScale, size;
if (dst <= 500.0f)
Float3 camPos = renderContext.View.WorldPosition;
float gridSize = renderContext.View.Far + 20000;
// Lazy-init resources
if (_vertexBuffer == null)
{
size = 8000;
_vertexBuffer = new GPUBuffer();
var desc = GPUBufferDescription.Vertex(sizeof(Float3), 4);
_vertexBuffer.Init(ref desc);
}
else if (dst <= 2000.0f)
if (_indexBuffer == null)
{
space *= 2;
size = 8000;
_indexBuffer = new GPUBuffer();
fixed (uint* ptr = _triangles)
{
var desc = GPUBufferDescription.Index(sizeof(uint), _triangles.Length, new IntPtr(ptr));
_indexBuffer.Init(ref desc);
}
}
else
if (_psGrid == null)
{
space *= 20;
size = 100000;
_psGrid = new GPUPipelineState();
var desc = GPUPipelineState.Description.Default;
desc.BlendMode = BlendingMode.AlphaBlend;
desc.CullMode = CullMode.TwoSided;
desc.VS = _shader.GPU.GetVS("VS_Grid");
desc.PS = _shader.GPU.GetPS("PS_Grid");
_psGrid.Init(ref desc);
}
float bigLineIntensity = 0.8f;
Color bigColor = Color.Gray * bigLineIntensity;
Color color = bigColor * 0.8f;
int count = (int)(size / space);
int midLine = count / 2;
int bigLinesMod = count / 8;
Vector3 start = new Vector3(0, 0, size * -0.5f);
Vector3 end = new Vector3(0, 0, size * 0.5f);
for (int i = 0; i <= count; i++)
// Update vertices of the plane
// TODO: perf this operation in a Vertex Shader
float y = 1.5f; // Add small bias to reduce Z-fighting with geometry at scene origin
var vertices = new Float3[]
{
start.X = end.X = i * space + start.Z;
Color lineColor = color;
if (i == midLine)
lineColor = Color.Blue * bigLineIntensity;
else if (i % bigLinesMod == 0)
lineColor = bigColor;
DebugDraw.DrawLine(start, end, lineColor);
new Float3(-gridSize + camPos.X, y, -gridSize + camPos.Z),
new Float3(gridSize + camPos.X, y, gridSize + camPos.Z),
new Float3(-gridSize + camPos.X, y, gridSize + camPos.Z),
new Float3(gridSize + camPos.X, y, -gridSize + camPos.Z),
};
fixed (Float3* ptr = vertices)
{
context.UpdateBuffer(_vertexBuffer, new IntPtr(ptr), (uint)(sizeof(Float3) * vertices.Length));
}
start = new Vector3(size * -0.5f, 0, 0);
end = new Vector3(size * 0.5f, 0, 0);
for (int i = 0; i <= count; i++)
// Update constant buffer data
var cb = _shader.GPU.GetCB(0);
if (cb != IntPtr.Zero)
{
start.Z = end.Z = i * space + start.X;
Color lineColor = color;
if (i == midLine)
lineColor = Color.Red * bigLineIntensity;
else if (i % bigLinesMod == 0)
lineColor = bigColor;
DebugDraw.DrawLine(start, end, lineColor);
var data = new Data();
Matrix.Multiply(ref renderContext.View.View, ref renderContext.View.Projection, out var viewProjection);
data.WorldMatrix = Matrix.Identity;
Matrix.Transpose(ref viewProjection, out data.ViewProjectionMatrix);
data.ViewPos = renderContext.View.WorldPosition;
data.GridColor = options.Viewport.ViewportGridColor;
data.Far = renderContext.View.Far;
data.GridSize = options.Viewport.ViewportGridViewDistance;
context.UpdateCB(cb, new IntPtr(&data));
}
DebugDraw.Draw(ref renderContext, input.View(), null, true);
DebugDraw.SetContext(IntPtr.Zero);
// Draw geometry using custom Pixel Shader and Vertex Shader
context.BindCB(0, cb);
context.BindIB(_indexBuffer);
_vbs[0] = _vertexBuffer;
context.BindVB(_vbs);
context.SetState(_psGrid);
context.SetRenderTarget(renderContext.Buffers.DepthBuffer.View(), input.View());
context.DrawIndexed((uint)_triangles.Length);
Profiler.EndEventGPU();
}

View File

@@ -223,6 +223,19 @@ void ManagedEditor::Init()
{
LOG(Info, "Loading managed assemblies (due to disabled compilation on startup)");
Scripting::Load();
const auto endInitMethod = mclass->GetMethod("EndInit");
if (endInitMethod == nullptr)
{
LOG(Fatal, "Invalid Editor assembly! Missing EndInit method.");
}
endInitMethod->Invoke(instance, nullptr, &exception);
if (exception)
{
MException ex(exception);
ex.Log(LogType::Warning, TEXT("ManagedEditor::EndInit"));
LOG_STR(Fatal, TEXT("Failed to initialize editor during EndInit! ") + ex.Message);
}
}
// Call building if need to (based on CL)

View File

@@ -27,6 +27,7 @@ API_CLASS(Namespace="FlaxEditor", Name="Editor", NoSpawn, NoConstructor) class M
byte AutoReloadScriptsOnMainWindowFocus = 1;
byte ForceScriptCompilationOnStartup = 1;
byte UseAssetImportPathRelative = 1;
byte EnableParticlesPreview = 1;
byte AutoRebuildCSG = 1;
float AutoRebuildCSGTimeoutMs = 50;
byte AutoRebuildNavMesh = 1;

View File

@@ -228,7 +228,7 @@ namespace FlaxEditor.Modules
new SearchResult { Name = item.ShortName, Type = assetItem.TypeName, Item = item }
};
}
var actor = FlaxEngine.Object.Find<Actor>(ref id);
var actor = FlaxEngine.Object.Find<Actor>(ref id, true);
if (actor != null)
{
return new List<SearchResult>
@@ -236,6 +236,16 @@ namespace FlaxEditor.Modules
new SearchResult { Name = actor.Name, Type = actor.TypeName, Item = actor }
};
}
var script = FlaxEngine.Object.Find<Script>(ref id, true);
if (script != null && script.Actor != null)
{
string actorPathStart = $"{script.Actor.Name}/";
return new List<SearchResult>
{
new SearchResult { Name = $"{actorPathStart}{script.TypeName}", Type = script.TypeName, Item = script }
};
}
}
Profiler.BeginEvent("ContentFinding.Search");
@@ -388,6 +398,13 @@ namespace FlaxEditor.Modules
Editor.Instance.SceneEditing.Select(actor);
Editor.Instance.Windows.EditWin.Viewport.FocusSelection();
break;
case Script script:
if (script.Actor != null)
{
Editor.Instance.SceneEditing.Select(script.Actor);
Editor.Instance.Windows.EditWin.Viewport.FocusSelection();
}
break;
}
}

View File

@@ -175,13 +175,6 @@ namespace FlaxEditor.Options
[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 timestamps prefix mode for debug log messages.
/// </summary>
[DefaultValue(TimestampsFormats.None)]
[EditorDisplay("Interface"), EditorOrder(210), Tooltip("The timestamps prefix mode for debug log messages.")]
public TimestampsFormats DebugLogTimestampsFormat { get; set; } = TimestampsFormats.None;
/// <summary>
/// Gets or sets the editor icons scale. Editor restart required.
/// </summary>
@@ -220,21 +213,70 @@ namespace FlaxEditor.Options
/// <summary>
/// Gets or sets the timestamps prefix mode for output log messages.
/// </summary>
[DefaultValue(TimestampsFormats.TimeSinceStartup)]
[EditorDisplay("Output Log", "Timestamps Format"), EditorOrder(300), Tooltip("The timestamps prefix mode for output log messages.")]
public TimestampsFormats OutputLogTimestampsFormat { get; set; } = TimestampsFormats.TimeSinceStartup;
[DefaultValue(TimestampsFormats.None)]
[EditorDisplay("Debug Log"), EditorOrder(350), Tooltip("The timestamps prefix mode for debug log messages.")]
public TimestampsFormats DebugLogTimestampsFormat { get; set; } = TimestampsFormats.None;
/// <summary>
/// Gets or sets the clear on play for debug log messages.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Debug Log", "Clear on Play"), EditorOrder(360), Tooltip("Clears all log entries on enter playmode.")]
public bool DebugLogClearOnPlay { get; set; } = true;
/// <summary>
/// Gets or sets the collapse mode for debug log messages.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Debug Log"), EditorOrder(361), Tooltip("Collapses similar or repeating log entries.")]
public bool DebugLogCollapse { get; set; } = true;
/// <summary>
/// Gets or sets the automatic pause on error for debug log messages.
/// </summary>
[DefaultValue(false)]
[EditorDisplay("Debug Log", "Pause on Error"), EditorOrder(362), Tooltip("Performs auto pause on error.")]
public bool DebugLogPauseOnError { get; set; } = false;
/// <summary>
/// Gets or sets the automatic pause on error for debug log messages.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Debug Log", "Show error messages"), EditorOrder(370), Tooltip("Shows/hides error messages.")]
public bool DebugLogShowErrorMessages { get; set; } = true;
/// <summary>
/// Gets or sets the automatic pause on error for debug log messages.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Debug Log", "Show warning messages"), EditorOrder(371), Tooltip("Shows/hides warning messages.")]
public bool DebugLogShowWarningMessages { get; set; } = true;
/// <summary>
/// Gets or sets the automatic pause on error for debug log messages.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Debug Log", "Show info messages"), EditorOrder(372), Tooltip("Shows/hides info messages.")]
public bool DebugLogShowInfoMessages { get; set; } = true;
/// <summary>
/// Gets or sets the timestamps prefix mode for output log messages.
/// </summary>
[DefaultValue(TimestampsFormats.TimeSinceStartup)]
[EditorDisplay("Output Log", "Timestamps Format"), EditorOrder(400), Tooltip("The timestamps prefix mode for output log messages.")]
public TimestampsFormats OutputLogTimestampsFormat { get; set; } = TimestampsFormats.TimeSinceStartup;
/// <summary>
/// Gets or sets the log type prefix mode for output log messages.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Output Log", "Show Log Type"), EditorOrder(310), Tooltip("Determines whether show log type prefix in output log messages.")]
[EditorDisplay("Output Log", "Show Log Type"), EditorOrder(410), Tooltip("Determines whether show log type prefix in output log messages.")]
public bool OutputLogShowLogType { get; set; } = true;
/// <summary>
/// Gets or sets the output log text font.
/// </summary>
[EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")]
[EditorDisplay("Output Log", "Text Font"), EditorOrder(420), Tooltip("The output log text font.")]
public FontReference OutputLogTextFont
{
get => _outputLogFont;
@@ -253,63 +295,70 @@ namespace FlaxEditor.Options
/// Gets or sets the output log text color.
/// </summary>
[DefaultValue(typeof(Color), "1,1,1,1")]
[EditorDisplay("Output Log", "Text Color"), EditorOrder(330), Tooltip("The output log text color.")]
[EditorDisplay("Output Log", "Text Color"), EditorOrder(430), Tooltip("The output log text color.")]
public Color OutputLogTextColor { get; set; } = Color.White;
/// <summary>
/// Gets or sets the output log text shadow color.
/// </summary>
[DefaultValue(typeof(Color), "0,0,0,0.5")]
[EditorDisplay("Output Log", "Text Shadow Color"), EditorOrder(340), Tooltip("The output log text shadow color.")]
[EditorDisplay("Output Log", "Text Shadow Color"), EditorOrder(440), Tooltip("The output log text shadow color.")]
public Color OutputLogTextShadowColor { get; set; } = new Color(0, 0, 0, 0.5f);
/// <summary>
/// Gets or sets the output log text shadow offset. Set to 0 to disable this feature.
/// </summary>
[DefaultValue(typeof(Float2), "1,1")]
[EditorDisplay("Output Log", "Text Shadow Offset"), EditorOrder(340), Tooltip("The output log text shadow offset. Set to 0 to disable this feature.")]
[EditorDisplay("Output Log", "Text Shadow Offset"), EditorOrder(445), Tooltip("The output log text shadow offset. Set to 0 to disable this feature.")]
public Float2 OutputLogTextShadowOffset { get; set; } = new Float2(1);
/// <summary>
/// Gets or sets a value indicating whether auto-focus output log window on code compilation error.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Output Log", "Focus Output Log On Compilation Error"), EditorOrder(350), Tooltip("Determines whether auto-focus output log window on code compilation error.")]
[EditorDisplay("Output Log", "Focus Output Log On Compilation Error"), EditorOrder(450), Tooltip("Determines whether auto-focus output log window on code compilation error.")]
public bool FocusOutputLogOnCompilationError { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether auto-focus output log window on game build error.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Output Log", "Focus Output Log On Game Build Error"), EditorOrder(360), Tooltip("Determines whether auto-focus output log window on game build error.")]
[EditorDisplay("Output Log", "Focus Output Log On Game Build Error"), EditorOrder(460), Tooltip("Determines whether auto-focus output log window on game build error.")]
public bool FocusOutputLogOnGameBuildError { get; set; } = true;
/// <summary>
/// Gets or sets the value for automatic scroll to bottom in output log.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Output Log", "Scroll to bottom"), EditorOrder(470), Tooltip("Scroll the output log view to bottom automatically after new lines are added.")]
public bool OutputLogScrollToBottom { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether auto-focus game window on play mode start.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Play In-Editor", "Focus Game Window On Play"), EditorOrder(400), Tooltip("Determines whether auto-focus game window on play mode start.")]
[EditorDisplay("Play In-Editor", "Focus Game Window On Play"), EditorOrder(500), Tooltip("Determines whether auto-focus game window on play mode start.")]
public bool FocusGameWinOnPlay { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating what action should be taken upon pressing the play button.
/// </summary>
[DefaultValue(PlayAction.PlayScenes)]
[EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)]
[EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(510)]
public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes;
/// <summary>
/// Gets or sets a value indicating how the game window should be displayed when the game is launched.
/// </summary>
[DefaultValue(GameWindowMode.Docked)]
[EditorDisplay("Play In-Editor", "Game Window Mode"), EditorOrder(420), Tooltip("Determines how the game window is displayed when the game is launched.")]
[EditorDisplay("Play In-Editor", "Game Window Mode"), EditorOrder(520), Tooltip("Determines how the game window is displayed when the game is launched.")]
public GameWindowMode DefaultGameWindowMode { get; set; } = GameWindowMode.Docked;
/// <summary>
/// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game.
/// </summary>
[DefaultValue(1), Range(1, 4)]
[EditorDisplay("Cook & Run"), EditorOrder(500)]
[EditorDisplay("Cook & Run"), EditorOrder(600)]
public int NumberOfGameClientsToLaunch = 1;
/// <summary>
@@ -331,13 +380,13 @@ namespace FlaxEditor.Options
/// <summary>
/// The list of fallback fonts to use when main text font is missing certain characters. Empty to use fonts from GraphicsSettings.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(650)]
[EditorDisplay("Fonts"), EditorOrder(750)]
public FontAsset[] FallbackFonts = new FontAsset[1] { FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.FallbackFont) };
/// <summary>
/// Gets or sets the title font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(700), Tooltip("The title font for editor UI.")]
public FontReference TitleFont
{
get => _titleFont;
@@ -355,7 +404,7 @@ namespace FlaxEditor.Options
/// <summary>
/// Gets or sets the large font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(710), Tooltip("The large font for editor UI.")]
public FontReference LargeFont
{
get => _largeFont;
@@ -373,7 +422,7 @@ namespace FlaxEditor.Options
/// <summary>
/// Gets or sets the medium font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(720), Tooltip("The medium font for editor UI.")]
public FontReference MediumFont
{
get => _mediumFont;
@@ -391,7 +440,7 @@ namespace FlaxEditor.Options
/// <summary>
/// Gets or sets the small font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(730), Tooltip("The small font for editor UI.")]
public FontReference SmallFont
{
get => _smallFont;

View File

@@ -192,6 +192,7 @@ namespace FlaxEditor.Options
internalOptions.AutoReloadScriptsOnMainWindowFocus = (byte)(Options.General.AutoReloadScriptsOnMainWindowFocus ? 1 : 0);
internalOptions.ForceScriptCompilationOnStartup = (byte)(Options.General.ForceScriptCompilationOnStartup ? 1 : 0);
internalOptions.UseAssetImportPathRelative = (byte)(Options.General.UseAssetImportPathRelative ? 1 : 0);
internalOptions.EnableParticlesPreview = (byte)(Options.Visual.EnableParticlesPreview ? 1 : 0);
internalOptions.AutoRebuildCSG = (byte)(Options.General.AutoRebuildCSG ? 1 : 0);
internalOptions.AutoRebuildCSGTimeoutMs = Options.General.AutoRebuildCSGTimeoutMs;
internalOptions.AutoRebuildNavMesh = (byte)(Options.General.AutoRebuildNavMesh ? 1 : 0);

View File

@@ -129,5 +129,19 @@ namespace FlaxEditor.Options
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
[EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")]
public float ViewportGridScale { get; set; } = 50.0f;
/// <summary>
/// Gets or sets the view distance you can see the grid.
/// </summary>
[DefaultValue(2500.0f)]
[EditorDisplay("Grid"), EditorOrder(300), Tooltip("The maximum distance you will be able to see the grid.")]
public float ViewportGridViewDistance { get; set; } = 2500.0f;
/// <summary>
/// Gets or sets the grid color.
/// </summary>
[DefaultValue(typeof(Color), "0.5,0.5,0.5,1.0")]
[EditorDisplay("Grid"), EditorOrder(310), Tooltip("The color for the viewport grid.")]
public Color ViewportGridColor { get; set; } = new Color(0.5f, 0.5f, 0.5f, 1.0f);
}
}

View File

@@ -59,5 +59,12 @@ namespace FlaxEditor.Options
[DefaultValue(true)]
[EditorDisplay("Quality", "Enable MSAA For Debug Draw"), EditorOrder(500), Tooltip("Determines whether enable MSAA for DebugDraw primitives rendering. Helps with pixel aliasing but reduces performance.")]
public bool EnableMSAAForDebugDraw { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether show looping particle effects in Editor viewport to simulate in-game look.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Preview"), EditorOrder(1000)]
public bool EnableParticlesPreview { get; set; } = true;
}
}

View File

@@ -71,7 +71,6 @@ namespace FlaxEditor.States
{
// Skip compilation on startup
OnCompilationEnd(true);
Editor.EndInit();
}
}

View File

@@ -2198,21 +2198,23 @@ namespace FlaxEditor.Surface.Archetypes
_combobox.ClearItems();
_tooltips.Clear();
_functionNodesIds.Clear();
var nodes = Surface.Nodes;
var count = _signature != null ? nodes.Count : 0;
for (int i = 0; i < count; i++)
if (Surface != null && _signature != null)
{
if (nodes[i] is VisualScriptFunctionNode functionNode)
var nodes = Surface.Nodes;
for (int i = 0; i < nodes.Count; i++)
{
// Get if function signature matches the event signature
functionNode.GetSignature(out var functionSig);
if (IsValidFunctionSignature(ref functionSig))
if (nodes[i] is VisualScriptFunctionNode functionNode)
{
if (functionNode.ID == handlerFunctionNodeId)
toSelect = _functionNodesIds.Count;
_functionNodesIds.Add(functionNode.ID);
_tooltips.Add(functionNode.TooltipText);
_combobox.AddItem(functionSig.ToString());
// Get if function signature matches the event signature
functionNode.GetSignature(out var functionSig);
if (IsValidFunctionSignature(ref functionSig))
{
if (functionNode.ID == handlerFunctionNodeId)
toSelect = _functionNodesIds.Count;
_functionNodesIds.Add(functionNode.ID);
_tooltips.Add(functionNode.TooltipText);
_combobox.AddItem(functionSig.ToString());
}
}
}
}

View File

@@ -936,6 +936,142 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
}
},
new NodeArchetype
{
TypeID = 43,
Title = "Rotate UV",
Description = "Rotates 2D vector by given angle around (0,0) origin",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(250, 40),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues =
[
0.0f,
],
Elements =
[
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 2),
]
},
new NodeArchetype
{
TypeID = 44,
Title = "Cone Gradient",
Description = "Creates cone gradient around normalized UVs (range [-1; 1]), angle is in radians (range [0; TwoPi])",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 40),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues =
[
0.0f,
],
Elements =
[
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
]
},
new NodeArchetype
{
TypeID = 45,
Title = "Cycle Gradient",
Description = "Creates 2D sphere mask gradient around normalized UVs (range [-1; 1])",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 20),
ConnectionsHints = ConnectionsHint.Vector,
Elements =
[
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 1),
]
},
new NodeArchetype
{
TypeID = 46,
Title = "Falloff and Offset",
Description = "",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 60),
ConnectionsHints = ConnectionsHint.Numeric,
DefaultValues =
[
0.0f,
0.9f
],
Elements =
[
NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0),
NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 3),
]
},
new NodeArchetype
{
TypeID = 47,
Title = "Linear Gradient",
Description = "x = Gradient along X axis, y = Gradient along Y axis",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 60),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues =
[
0.0f,
false
],
Elements =
[
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2, 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 3),
]
},
new NodeArchetype
{
TypeID = 48,
Title = "Radial Gradient",
Description = "",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 40),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues =
[
0.0f,
],
Elements =
[
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
]
},
new NodeArchetype
{
TypeID = 49,
Title = "Ring Gradient",
Description = "x = InnerMask,y = OuterMask,z = Mask",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 80),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues =
[
1.0f,
0.8f,
0.05f,
],
Elements =
[
NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3, 2),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4),
]
},
};
}
}

View File

@@ -1266,7 +1266,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc />
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
{
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2);
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, OutputBox.ConnectingConnectionThickness);
}
/// <inheritdoc />

View File

@@ -738,7 +738,7 @@ namespace FlaxEditor.Surface.Elements
/// <inheritdoc />
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
{
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2);
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, OutputBox.ConnectingConnectionThickness);
}
/// <inheritdoc />

View File

@@ -35,7 +35,7 @@ namespace FlaxEditor.Surface.Elements
ParentNode.ValuesChanged += OnNodeValuesChanged;
// Disable slider if surface doesn't allow it
if (!ParentNode.Surface.CanLivePreviewValueChanges)
if (ParentNode.Surface != null && !ParentNode.Surface.CanLivePreviewValueChanges)
_slideSpeed = 0.0f;
}

View File

@@ -14,6 +14,26 @@ namespace FlaxEditor.Surface.Elements
[HideInEditor]
public class OutputBox : Box
{
/// <summary>
/// The default thickness of the connection line
/// </summary>
public const float DefaultConnectionThickness = 1.5f;
/// <summary>
/// The thickness of a highlighted connection line
/// </summary>
public const float ConnectingConnectionThickness = 2.5f;
/// <summary>
/// The thickness of the selected connection line
/// </summary>
public const float SelectedConnectionThickness = 3f;
/// <summary>
/// The offset between the connection line and the box
/// </summary>
public const float DefaultConnectionOffset = 24f;
/// <summary>
/// Distance for the mouse to be considered above the connection
/// </summary>
@@ -33,7 +53,7 @@ namespace FlaxEditor.Surface.Elements
/// <param name="end">The end location.</param>
/// <param name="color">The connection color.</param>
/// <param name="thickness">The connection thickness.</param>
public static void DrawConnection(SurfaceStyle style, ref Float2 start, ref Float2 end, ref Color color, float thickness = 1)
public static void DrawConnection(SurfaceStyle style, ref Float2 start, ref Float2 end, ref Color color, float thickness = DefaultConnectionThickness)
{
if (style.DrawConnection != null)
{
@@ -41,11 +61,22 @@ namespace FlaxEditor.Surface.Elements
return;
}
float connectionOffset = Mathf.Max(0f, DefaultConnectionOffset * (1 - Editor.Instance.Options.Options.Interface.ConnectionCurvature));
Float2 offsetStart = new Float2(start.X + connectionOffset, start.Y);
Float2 offsetEnd = new Float2(end.X - connectionOffset, end.Y);
// Calculate control points
CalculateBezierControlPoints(start, end, out var control1, out var control2);
CalculateBezierControlPoints(offsetStart, offsetEnd, out var control1, out var control2);
// Draw offset lines only if necessary
if (connectionOffset >= float.Epsilon)
{
Render2D.DrawLine(start, offsetStart, color, thickness);
Render2D.DrawLine(offsetEnd, end, color, thickness);
}
// Draw line
Render2D.DrawBezier(start, control1, control2, end, color, thickness);
Render2D.DrawBezier(offsetStart, control1, control2, offsetEnd, color, thickness);
/*
// Debug drawing control points
@@ -80,9 +111,10 @@ namespace FlaxEditor.Surface.Elements
/// <param name="mousePosition">The mouse position</param>
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition)
{
var startPos = ConnectionOrigin;
var endPos = targetBox.ConnectionOrigin;
return IntersectsConnection(ref startPos, ref endPos, ref mousePosition, MouseOverConnectionDistance);
float connectionOffset = Mathf.Max(0f, DefaultConnectionOffset * (1 - Editor.Instance.Options.Options.Interface.ConnectionCurvature));
Float2 start = new Float2(ConnectionOrigin.X + connectionOffset, ConnectionOrigin.Y);
Float2 end = new Float2(targetBox.ConnectionOrigin.X - connectionOffset, targetBox.ConnectionOrigin.Y);
return IntersectsConnection(ref start, ref end, ref mousePosition, MouseOverConnectionDistance);
}
/// <summary>
@@ -102,22 +134,26 @@ namespace FlaxEditor.Surface.Elements
if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0)
return false;
float squaredDistance = distance;
CalculateBezierControlPoints(start, end, out var control1, out var control2);
float connectionOffset = Mathf.Max(0f, DefaultConnectionOffset * (1 - Editor.Instance.Options.Options.Interface.ConnectionCurvature));
Float2 offsetStart = new Float2(start.X + connectionOffset, start.Y);
Float2 offsetEnd = new Float2(end.X - connectionOffset, end.Y);
var d1 = control1 - start;
float squaredDistance = distance;
CalculateBezierControlPoints(offsetStart, offsetEnd, out var control1, out var control2);
var d1 = control1 - offsetStart;
var d2 = control2 - control1;
var d3 = end - control2;
var d3 = offsetEnd - control2;
float len = d1.Length + d2.Length + d3.Length;
int segmentCount = Math.Min(Math.Max(Mathf.CeilToInt(len * 0.05f), 1), 100);
float segmentCountInv = 1.0f / segmentCount;
Bezier(ref start, ref control1, ref control2, ref end, 0, out var p);
Bezier(ref offsetStart, ref control1, ref control2, ref offsetEnd, 0, out var p);
for (int i = 1; i <= segmentCount; i++)
{
var oldp = p;
float t = i * segmentCountInv;
Bezier(ref start, ref control1, ref control2, ref end, t, out p);
Bezier(ref offsetStart, ref control1, ref control2, ref offsetEnd, t, out p);
// Maybe it would be reasonable to return the point?
CollisionsHelper.ClosestPointPointLine(ref point, ref oldp, ref p, out var result);
@@ -153,16 +189,21 @@ namespace FlaxEditor.Surface.Elements
{
Box targetBox = Connections[i];
var endPos = targetBox.ConnectionOrigin;
var highlight = 1 + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity);
var highlight = DefaultConnectionThickness + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity);
var alpha = targetBox.Enabled && targetBox.IsActive ? 1.0f : 0.6f;
var color = _currentTypeColor * highlight * alpha;
// We have to calculate an offset here to preserve the original color for when the default connection thickness is larger than 1
var highlightOffset = (highlight - (DefaultConnectionThickness - 1));
var color = _currentTypeColor * highlightOffset * alpha;
// TODO: Figure out how to only draw the topmost connection
if (IntersectsConnection(ref startPos, ref endPos, ref mousePosition, mouseOverDistance))
{
highlight += 0.5f;
}
highlight += DefaultConnectionThickness * 0.5f;
// Increase thickness on impulse/ execution lines
if (targetBox.CurrentType.IsVoid)
highlight *= 2;
DrawConnection(style, ref startPos, ref endPos, ref color, highlight);
}
}
@@ -177,7 +218,7 @@ namespace FlaxEditor.Surface.Elements
var endPos = targetBox.ConnectionOrigin;
var alpha = targetBox.Enabled && targetBox.IsActive ? 1.0f : 0.6f;
var color = _currentTypeColor * alpha;
DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2.5f);
DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, SelectedConnectionThickness);
}
/// <inheritdoc />

View File

@@ -129,7 +129,12 @@ namespace FlaxEditor.Surface
/// <param name="args">The drag drop arguments data.</param>
protected virtual void HandleDragDropParameters(List<string> objects, DragDropEventArgs args)
{
var arch = GetParameterGetterNodeArchetype(out var groupId);
// Try to get the setter node when holding the ALT key, otherwise get the getter node
if (!Input.GetKey(KeyboardKeys.Alt) || !TryGetParameterSetterNodeArchetype(out var groupId, out var arch))
{
arch = GetParameterGetterNodeArchetype(out groupId);
}
for (int i = 0; i < objects.Count; i++)
{
var parameter = GetParameter(objects[i]);
@@ -156,5 +161,18 @@ namespace FlaxEditor.Surface
groupId = 6;
return Archetypes.Parameters.Nodes[0];
}
/// <summary>
/// Tries to get the parameter setter node archetype to use.
/// </summary>
/// <param name="groupId">The group ID.</param>
/// <param name="archetype">The node archetype.</param>
/// <returns>True if a setter node exists.</returns>
protected virtual bool TryGetParameterSetterNodeArchetype(out ushort groupId, out NodeArchetype archetype)
{
groupId = 0;
archetype = null;
return false;
}
}
}

View File

@@ -35,6 +35,12 @@ namespace FlaxEditor.Surface
return RootContext.GetParameter(name);
}
/// <inheritdoc />
public void OnParamReordered()
{
MarkAsEdited();
}
/// <inheritdoc />
public void OnParamCreated(SurfaceParameter param)
{

View File

@@ -350,6 +350,213 @@ namespace FlaxEditor.Surface
}
}
sealed class ReorderParamAction : IUndoAction
{
/// <summary>
/// The window reference.
/// </summary>
public IVisjectSurfaceWindow Window;
/// <summary>
/// The parameters editor for this action.
/// </summary>
public ParametersEditor Editor;
/// <summary>
/// The old index the parameter was at.
/// </summary>
public int OldIndex;
/// <summary>
/// The new index the parameter will be at.
/// </summary>
public int NewIndex;
/// <inheritdoc />
public string ActionString => "Reorder Parameter";
/// <inheritdoc />
public void Dispose()
{
Window = null;
Editor = null;
}
public void Swap(int oldIdx, int newIdx)
{
if (oldIdx == newIdx)
return; // ?
var parameters = Window.VisjectSurface.Parameters;
if (newIdx > oldIdx)
{
for (int i = oldIdx; i < newIdx; i++)
{
SurfaceParameter old = parameters[i + 1];
parameters[i + 1] = parameters[i];
parameters[i] = old;
}
}
else
{
for (int i = oldIdx; i > newIdx; i--)
{
SurfaceParameter old = parameters[i - 1];
parameters[i - 1] = parameters[i];
parameters[i] = old;
}
}
}
/// <inheritdoc />
public void Do()
{
Swap(OldIndex, NewIndex);
Window.VisjectSurface.OnParamReordered();
}
/// <inheritdoc />
public void Undo()
{
Swap(NewIndex, OldIndex);
Window.VisjectSurface.OnParamReordered();
}
}
/// <summary>
/// Custom draggable property name label that handles reordering visject parameters.
/// </summary>
/// <seealso cref="FlaxEditor.CustomEditors.GUI.DraggablePropertyNameLabel" />
[HideInEditor]
public class ParameterPropertyNameLabel : DraggablePropertyNameLabel
{
private ParametersEditor _editor;
private IVisjectSurfaceWindow _window;
private Rectangle _arrangeButtonRect;
private bool _arrangeButtonInUse;
/// <inheritdoc />
public ParameterPropertyNameLabel(string name, ParametersEditor editor)
: base(name)
{
_editor = editor;
_window = _editor.Values[0] as IVisjectSurfaceWindow;
_arrangeButtonRect = new Rectangle(2, 3, 12, 12);
// Extend margin of the label to support a dragging handle
Margin m = Margin;
m.Left += 16;
Margin = m;
}
private bool ArrangeAreaCheck(out int index, out Rectangle rect)
{
var child = _editor.ChildrenEditors[0];
var container = child.Layout.ContainerControl;
var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
var barSidesExtend = 20.0f;
var barHeight = 5.0f;
var barCheckAreaHeight = 40.0f;
var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
for (int i = 0; i < container.Children.Count / 2; i++)
{
var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
{
index = i;
var p1 = containerChild.UpperLeft;
rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
return true;
}
}
var p2 = container.Children[((container.Children.Count / 2) - 1) * 2].BottomLeft;
if (pos > p2.Y)
{
index = (container.Children.Count / 2) - 1;
rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
return true;
}
index = -1;
rect = Rectangle.Empty;
return false;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var style = Style.Current;
var mousePosition = PointFromScreen(Input.MouseScreenPosition);
var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
Render2D.DrawSprite(Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
{
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
}
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
{
_arrangeButtonInUse = true;
Focus();
StartMouseCapture();
return true;
}
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _arrangeButtonInUse)
{
_arrangeButtonInUse = false;
EndMouseCapture();
ArrangeAreaCheck(out var index, out _);
var action = new ReorderParamAction
{
OldIndex = (int)Tag,
NewIndex = index,
Window = _window,
Editor = _editor
};
action.Do();
_window.Undo.AddAction(action);
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override void OnLostFocus()
{
if (_arrangeButtonInUse)
{
_arrangeButtonInUse = false;
EndMouseCapture();
}
base.OnLostFocus();
}
/// <inheritdoc />
protected override void OnSizeChanged()
{
base.OnSizeChanged();
// Center the drag button vertically
_arrangeButtonRect = new Rectangle(2, Mathf.Ceil((Height - 12) * 0.5f) + 1, 12, 12);
}
}
/// <summary>
/// Custom editor for editing Visject Surface parameters collection.
/// </summary>
@@ -431,10 +638,10 @@ namespace FlaxEditor.Surface
attributes
);
var propertyLabel = new DraggablePropertyNameLabel(name)
var propertyLabel = new ParameterPropertyNameLabel(name, this)
{
Tag = pIndex,
Drag = OnDragParameter
Drag = OnDragParameter,
};
if (!p.IsPublic)
propertyLabel.TextColor = propertyLabel.TextColor.RGBMultiplied(0.7f);

View File

@@ -187,6 +187,14 @@ namespace FlaxEditor.Surface
return Archetypes.Parameters.Nodes[2];
}
/// <inheritdoc />
protected override bool TryGetParameterSetterNodeArchetype(out ushort groupId, out NodeArchetype archetype)
{
groupId = 6;
archetype = Archetypes.Parameters.Nodes[3];
return true;
}
/// <inheritdoc />
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
{

View File

@@ -318,7 +318,6 @@ namespace FlaxEditor.Windows
{
Title = "Debug Log";
Icon = IconInfo;
OnEditorOptionsChanged(Editor.Options.Options);
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
// Toolstrip
@@ -327,17 +326,42 @@ namespace FlaxEditor.Windows
Parent = this,
};
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries");
_clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode");
bool collapse = true;
if (Editor.ProjectCache.TryGetCustomData("DebugLogCollapse", out bool setCollapse))
collapse = setCollapse;
_collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse", () => Editor.ProjectCache.SetCustomData("DebugLogCollapse", _collapseLogsButton.Checked.ToString())).SetAutoCheck(true).SetChecked(collapse).LinkTooltip("Collapses similar logs.");
_pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
_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");
_collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse", () =>
{
editor.Options.Options.Interface.DebugLogCollapse = _collapseLogsButton.Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Collapses similar logs.");
_pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error", () =>
{
editor.Options.Options.Interface.DebugLogPauseOnError = _pauseOnErrorButton.Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
toolstrip.AddSeparator();
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages");
_groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides warning messages");
_groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides info messages");
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () =>
{
UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked);
editor.Options.Options.Interface.DebugLogShowErrorMessages = _groupButtons[0].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides error messages");
_groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () =>
{
UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked);
editor.Options.Options.Interface.DebugLogShowWarningMessages = _groupButtons[1].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides warning messages");
_groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () =>
{
UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked);
editor.Options.Options.Interface.DebugLogShowInfoMessages = _groupButtons[2].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides info messages");
UpdateCount();
OnEditorOptionsChanged(Editor.Options.Options);
// Split panel
_split = new SplitPanel(Orientation.Vertical, ScrollBars.Vertical, ScrollBars.Both)
@@ -383,6 +407,12 @@ namespace FlaxEditor.Windows
private void OnEditorOptionsChanged(EditorOptions options)
{
_timestampsFormats = options.Interface.DebugLogTimestampsFormat;
_clearOnPlayButton.Checked = options.Interface.DebugLogClearOnPlay;
_collapseLogsButton.Checked = options.Interface.DebugLogCollapse;
_pauseOnErrorButton.Checked = options.Interface.DebugLogPauseOnError;
_groupButtons[0].Checked = options.Interface.DebugLogShowErrorMessages;
_groupButtons[1].Checked = options.Interface.DebugLogShowWarningMessages;
_groupButtons[2].Checked = options.Interface.DebugLogShowInfoMessages;
}
/// <summary>

View File

@@ -162,7 +162,7 @@ namespace FlaxEditor.Windows
Editor.Options.Apply(_options);
ClearDirtyFlag();
GatherData();
}
private void SetupCustomTabs()
@@ -234,6 +234,18 @@ namespace FlaxEditor.Windows
base.OnDestroy();
}
/// <inheritdoc />
protected override void OnShow()
{
if (!_isDataDirty)
{
// Refresh the data, skip when data is modified during window docking
GatherData();
}
base.OnShow();
}
/// <inheritdoc />
protected override bool OnClosing(ClosingReason reason)
{

View File

@@ -145,6 +145,8 @@ namespace FlaxEditor.Windows.Profiler
#endif
Type = gpuResource.ResourceType,
};
if (resource.Name == null)
resource.Name = string.Empty;
// Create tooltip
sb.Clear();

View File

@@ -212,7 +212,7 @@ bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const
return false;
}
Variant BehaviorKnowledge::GetGoal(ScriptingTypeHandle type)
const Variant& BehaviorKnowledge::GetGoal(ScriptingTypeHandle type) const
{
for (const Variant& goal : Goals)
{

View File

@@ -101,7 +101,7 @@ public:
/// </summary>
/// <param name="type">The goal type.</param>
/// <returns>The goal value or null if not found.</returns>
API_FUNCTION() Variant GetGoal(ScriptingTypeHandle type);
API_FUNCTION() const Variant& GetGoal(ScriptingTypeHandle type) const;
/// <summary>
/// Adds the goal to the knowledge. If goal of that type already exists then it's value is updated.

View File

@@ -3,6 +3,7 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using FlaxEngine.Interop;
namespace FlaxEngine
{

View File

@@ -105,7 +105,7 @@ public:
OnSet(other._asset);
}
AssetReference(AssetReference&& other)
AssetReference(AssetReference&& other) noexcept
{
OnSet(other._asset);
other.OnSet(nullptr);

View File

@@ -138,7 +138,7 @@ public:
/// <summary>
/// Binds a static function.
/// </summary>
template<ReturnType (*Method)(Params...)>
template<ReturnType(*Method)(Params...)>
void Bind()
{
if (_lambda)
@@ -288,6 +288,7 @@ protected:
// Single allocation for list of binded functions. Thread-safe access via atomic operations. Removing binded function simply clears the entry to handle function unregister during invocation.
intptr volatile _ptr = 0;
intptr volatile _size = 0;
typedef void (*StubSignature)(void*, Params...);
#else
struct Data
{
@@ -297,7 +298,6 @@ protected:
// Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations.
intptr volatile _data = 0;
#endif
typedef void (*StubSignature)(void*, Params...);
public:
Delegate()
@@ -811,7 +811,7 @@ public:
auto function = (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings->_function);
auto callee = (void*)Platform::AtomicRead((intptr volatile*)&bindings->_callee);
if (function != nullptr && function == (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings->_function))
function(callee, Forward<Params>(params)...);
function(callee, params...);
++bindings;
}
#else
@@ -823,7 +823,7 @@ public:
{
const FunctionType& item = i->Item;
ASSERT_LOW_LAYER(item._function);
item._function(item._callee, Forward<Params>(params)...);
item._function(item._callee, params...);
}
#endif
}

View File

@@ -0,0 +1,67 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "LogContext.h"
#include "Engine/Core/Types/Guid.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Threading/ThreadLocal.h"
struct LogContextThreadData
{
LogContextData* Ptr;
uint32 Count;
uint32 Capacity;
void Push(LogContextData&& item)
{
if (Count == Capacity)
{
Capacity = Capacity == 0 ? 32 : Capacity * 2;
auto ptr = (LogContextData*)Allocator::Allocate(Capacity * sizeof(LogContextData));
Platform::MemoryCopy(ptr, Ptr, Count * sizeof(LogContextData));
Allocator::Free(Ptr);
Ptr = ptr;
}
Ptr[Count++] = MoveTemp(item);
}
void Pop()
{
Count--;
}
LogContextData Peek()
{
return Count > 0 ? Ptr[Count - 1] : LogContextData();
}
};
ThreadLocal<LogContextThreadData> GlobalLogContexts;
String LogContext::GetInfo()
{
LogContextData context = LogContext::Get();
if (context.ObjectID != Guid::Empty)
return String::Format(TEXT("(Loading source was {0})"), context.ObjectID);
return String::Empty;
}
void LogContext::Push(const Guid& id)
{
LogContextData context;
context.ObjectID = id;
auto& stack = GlobalLogContexts.Get();
stack.Push(MoveTemp(context));
}
void LogContext::Pop()
{
auto& stack = GlobalLogContexts.Get();
stack.Pop();
}
LogContextData LogContext::Get()
{
auto& stack = GlobalLogContexts.Get();
return stack.Peek();
}

View File

@@ -0,0 +1,77 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Config.h"
#include "Engine/Scripting/ScriptingType.h"
class String;
struct Guid;
/// <summary>
/// Log context data structure. Contains different kinds of context data for different situtations.
/// </summary>
API_STRUCT(NoDefault) struct FLAXENGINE_API LogContextData
{
DECLARE_SCRIPTING_TYPE_STRUCTURE(LogContextData)
/// <summary>
/// A GUID for an object which this context applies to.
/// </summary>
API_FIELD() Guid ObjectID;
};
template<>
struct TIsPODType<LogContextData>
{
enum { Value = true };
};
/// <summary>
/// Log context interaction class. Methods are thread local, and as such, the context is as well.
/// This system is used to pass down important information to be logged through large callstacks
/// which don't have any reason to be passing down the information otherwise.
/// </summary>
API_CLASS(Static) class FLAXENGINE_API LogContext
{
DECLARE_SCRIPTING_TYPE_MINIMAL(LogContext)
/// <summary>
/// Adds a log context element to the stack to be displayed in warning and error logs.
/// </summary>
/// <param name="id">The ID of the object this context applies to.</param>
API_FUNCTION() static void Push(const Guid& id);
/// <summary>
/// Pops a log context element off of the stack and discards it.
/// </summary>
API_FUNCTION() static void Pop();
/// <summary>
/// Gets the log context element off the top of stack.
/// </summary>
/// <returns>The log context element at the top of the stack.</returns>
API_FUNCTION() static LogContextData Get();
/// <summary>
/// Returns a string which represents the current log context on the stack.
/// </summary>
/// <returns>The formatted string representing the current log context.</returns>
API_FUNCTION() static String GetInfo();
};
/// <summary>
/// Helper structure used to call LogContext Push/Pop within single code block.
/// </summary>
struct LogContextScope
{
FORCE_INLINE LogContextScope(const Guid& id)
{
LogContext::Push(id);
}
FORCE_INLINE ~LogContextScope()
{
LogContext::Pop();
}
};

View File

@@ -79,6 +79,26 @@ namespace FlaxEngine
/// </summary>
public Float2 UpperRight => new Float2(Location.X + Size.X, Location.Y);
/// <summary>
/// Gets position of the bottom right corner of the rectangle
/// </summary>
public Float2 LowerRight => Location + Size;
/// <summary>
/// Gets position of the bottom left corner of the rectangle
/// </summary>
public Float2 LowerLeft => new Float2(Location.X, Location.Y + Size.Y);
/// <summary>
/// Gets position of the upper left corner of the rectangle
/// </summary>
public Float2 TopLeft => Location;
/// <summary>
/// Gets position of the upper right corner of the rectangle
/// </summary>
public Float2 TopRight => new Float2(Location.X + Size.X, Location.Y);
/// <summary>
/// Gets position of the bottom right corner of the rectangle
/// </summary>

View File

@@ -117,6 +117,30 @@ public:
return Location + Float2(Size.X, 0);
}
// Gets position of the bottom right corner of the rectangle
Float2 GetLowerRight() const
{
return Location + Size;
}
// Gets position of the bottom left corner of the rectangle
Float2 GetLowerLeft() const
{
return Location + Float2(0, Size.Y);
}
// Gets position of the upper left corner of the rectangle
Float2 GetTopLeft() const
{
return Location;
}
// Gets position of the upper right corner of the rectangle
Float2 GetTopRight() const
{
return Location + Float2(Size.X, 0);
}
// Gets position of the bottom right corner of the rectangle
Float2 GetBottomRight() const
{

View File

@@ -319,7 +319,6 @@ inline T&& Forward(typename TRemoveReference<T>::Type& t) noexcept
template<typename T>
inline T&& Forward(typename TRemoveReference<T>::Type&& t) noexcept
{
static_assert(!TIsLValueReference<T>::Value, "Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}

View File

@@ -124,7 +124,7 @@ void GameplayGlobals::SetDefaultValues(const Dictionary<String, Variant>& values
}
}
Variant GameplayGlobals::GetValue(const StringView& name) const
const Variant& GameplayGlobals::GetValue(const StringView& name) const
{
ScopeLock lock(Locker);
auto e = Variables.TryGet(name);

View File

@@ -66,7 +66,7 @@ public:
/// </summary>
/// <param name="name">The variable name.</param>
/// <returns>The value.</returns>
API_FUNCTION() Variant GetValue(const StringView& name) const;
API_FUNCTION() const Variant& GetValue(const StringView& name) const;
/// <summary>
/// Sets the value of the global variable (it must be added first).

View File

@@ -1019,9 +1019,6 @@ namespace FlaxEngine.Interop
{
#if FLAX_EDITOR
// Clear all caches which might hold references to assemblies in collectible ALC
typeCache.Clear();
// Release all references in collectible ALC
cachedDelegatesCollectible.Clear();
foreach (var pair in managedTypesCollectible)
pair.Value.handle.Free();

View File

@@ -31,8 +31,6 @@ namespace FlaxEngine.Interop
private static bool firstAssemblyLoaded = false;
private static Dictionary<string, Type> typeCache = new();
private static IntPtr boolTruePtr = ManagedHandle.ToIntPtr(ManagedHandle.Alloc((int)1, GCHandleType.Pinned));
private static IntPtr boolFalsePtr = ManagedHandle.ToIntPtr(ManagedHandle.Alloc((int)0, GCHandleType.Pinned));
@@ -279,44 +277,6 @@ namespace FlaxEngine.Interop
return dst;
}
private static Type FindType(string typeName)
{
if (typeCache.TryGetValue(typeName, out Type type))
return type;
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
type = ResolveSlow(typeName);
if (type == null)
{
string fullTypeName = typeName;
typeName = typeName.Substring(0, typeName.IndexOf(','));
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
type = ResolveSlow(typeName);
typeName = fullTypeName;
}
typeCache.Add(typeName, type);
return type;
static Type ResolveSlow(string typeName)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
var type = assembly.GetType(typeName);
if (type != null)
return type;
}
return null;
}
static Assembly ResolveAssembly(AssemblyName name) => ResolveScriptingAssemblyByName(name, allowPartial: false);
}
/// <summary>Find <paramref name="assemblyName"/> among the scripting assemblies.</summary>
/// <param name="assemblyName">The name to find</param>
/// <param name="allowPartial">If true, partial names should be allowed to be resolved.</param>
@@ -378,18 +338,15 @@ namespace FlaxEngine.Interop
/// </summary>
internal static Type GetInternalType(Type type)
{
string[] splits = type.AssemblyQualifiedName.Split(',');
string @namespace = string.Join('.', splits[0].Split('.').SkipLast(1));
string className = @namespace.Length > 0 ? splits[0].Substring(@namespace.Length + 1) : splits[0];
string parentClassName = "";
if (className.Contains('+'))
{
parentClassName = className.Substring(0, className.LastIndexOf('+') + 1);
className = className.Substring(parentClassName.Length);
}
string marshallerName = className + "Marshaller";
string internalAssemblyQualifiedName = $"{@namespace}.{parentClassName}{marshallerName}+{className}Internal,{String.Join(',', splits.Skip(1))}";
return FindType(internalAssemblyQualifiedName);
Type marshallerType = type.GetCustomAttribute<System.Runtime.InteropServices.Marshalling.NativeMarshallingAttribute>()?.NativeType;
if (marshallerType == null)
return null;
Type internalType = marshallerType.GetNestedType($"{type.Name}Internal");
if (internalType == null)
return null;
return internalType;
}
internal class ReferenceTypePlaceholder { }
@@ -1338,7 +1295,7 @@ namespace FlaxEngine.Interop
if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
// Thread-safe creation
lock (typeCache)
lock (method)
{
if (invokeDelegate == null)
{

View File

@@ -273,7 +273,7 @@ AnimGraphParameter* AnimatedModel::GetParameter(const StringView& name)
return nullptr;
}
Variant AnimatedModel::GetParameterValue(const StringView& name)
const Variant& AnimatedModel::GetParameterValue(const StringView& name) const
{
CHECK_ANIM_GRAPH_PARAM_ACCESS_RESULT(Variant::Null);
for (auto& param : GraphInstance.Parameters)
@@ -299,7 +299,7 @@ void AnimatedModel::SetParameterValue(const StringView& name, const Variant& val
LOG(Warning, "Failed to set animated model '{0}' missing parameter '{1}'", ToString(), name);
}
Variant AnimatedModel::GetParameterValue(const Guid& id)
const Variant& AnimatedModel::GetParameterValue(const Guid& id) const
{
CHECK_ANIM_GRAPH_PARAM_ACCESS_RESULT(Variant::Null);
for (auto& param : GraphInstance.Parameters)

View File

@@ -308,7 +308,7 @@ public:
/// </summary>
/// <param name="name">The parameter name.</param>
/// <returns>The value.</returns>
API_FUNCTION() Variant GetParameterValue(const StringView& name);
API_FUNCTION() const Variant& GetParameterValue(const StringView& name) const;
/// <summary>
/// Sets the anim graph instance parameter value.
@@ -322,7 +322,7 @@ public:
/// </summary>
/// <param name="id">The parameter id.</param>
/// <returns>The value.</returns>
API_FUNCTION() Variant GetParameterValue(const Guid& id);
API_FUNCTION() const Variant& GetParameterValue(const Guid& id) const;
/// <summary>
/// Sets the anim graph instance parameter value.

View File

@@ -6,6 +6,7 @@
#include "Engine/Content/Content.h"
#include "Engine/Core/Cache.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Serialization/ISerializeModifier.h"
@@ -247,6 +248,7 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
CHECK(obj);
#endif
ISerializeModifier* modifier = context.GetModifier();
LogContextScope logContext(obj->GetID());
// Check for prefab instance
Guid prefabObjectId;

View File

@@ -91,14 +91,14 @@ Variant ParticleEffectParameter::GetDefaultValue() const
return paramValue;
}
Variant ParticleEffectParameter::GetDefaultEmitterValue() const
const Variant& ParticleEffectParameter::GetDefaultEmitterValue() const
{
CHECK_RETURN(IsValid(), Variant::False);
const ParticleSystemParameter& param = _effect->ParticleSystem->Emitters[_emitterIndex]->Graph.Parameters[_paramIndex];
return param.Value;
}
Variant ParticleEffectParameter::GetValue() const
const Variant& ParticleEffectParameter::GetValue() const
{
CHECK_RETURN(IsValid(), Variant::False);
const Variant& paramValue = _effect->Instance.Emitters[_emitterIndex].Parameters[_paramIndex];
@@ -205,7 +205,7 @@ ParticleEffectParameter* ParticleEffect::GetParameter(const StringView& emitterT
return nullptr;
}
Variant ParticleEffect::GetParameterValue(const StringView& emitterTrackName, const StringView& paramName)
const Variant& ParticleEffect::GetParameterValue(const StringView& emitterTrackName, const StringView& paramName)
{
const auto param = GetParameter(emitterTrackName, paramName);
CHECK_RETURN(param, Variant::Null);
@@ -474,15 +474,21 @@ void ParticleEffect::Update()
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
void ParticleEffect::UpdateExecuteInEditor()
{
// Auto-play in Editor
if (!Editor::IsPlayMode && !_isStopped)
if (!Editor::IsPlayMode && !_isStopped && IsLooping && PlayOnStart && Editor::Managed->ManagedEditorOptions.EnableParticlesPreview)
{
_isPlaying = true;
Update();
}
else if (!Editor::IsPlayMode && _isPlaying)
{
_isPlaying = false;
ResetSimulation();
}
}
#endif

View File

@@ -109,13 +109,13 @@ public:
/// Gets the default value of the parameter (set in particle emitter asset).
/// </summary>
/// <returns>The default value.</returns>
API_PROPERTY() Variant GetDefaultEmitterValue() const;
API_PROPERTY() const Variant& GetDefaultEmitterValue() const;
/// <summary>
/// Gets the value of the parameter.
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY() Variant GetValue() const;
API_PROPERTY() const Variant& GetValue() const;
/// <summary>
/// Sets the value of the parameter.
@@ -293,7 +293,7 @@ public:
/// <param name="emitterTrackName">The emitter track name (in particle system asset).</param>
/// <param name="paramName">The emitter parameter name (in particle emitter asset).</param>
/// <returns>The value.</returns>
API_FUNCTION() Variant GetParameterValue(const StringView& emitterTrackName, const StringView& paramName);
API_FUNCTION() const Variant& GetParameterValue(const StringView& emitterTrackName, const StringView& paramName);
/// <summary>
/// Set the particle parameter value.

View File

@@ -21,6 +21,8 @@ Collider::Collider(const SpawnParams& params)
, _cachedScale(1.0f)
, _contactOffset(2.0f)
{
Material.Loaded.Bind<Collider, &Collider::OnMaterialChanged>(this);
Material.Unload.Bind<Collider, &Collider::OnMaterialChanged>(this);
Material.Changed.Bind<Collider, &Collider::OnMaterialChanged>(this);
}

View File

@@ -4474,6 +4474,16 @@ void PhysicsBackend::DestroyController(void* controller)
controllerPhysX->release();
}
void PhysicsBackend::DestroyMaterial(void* material)
{
ASSERT_LOW_LAYER(material);
auto materialPhysX = (PxMaterial*)material;
materialPhysX->userData = nullptr;
FlushLocker.Lock();
DeleteObjects.Add(materialPhysX);
FlushLocker.Unlock();
}
void PhysicsBackend::DestroyObject(void* object)
{
ASSERT_LOW_LAYER(object);

View File

@@ -81,7 +81,7 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier*
PhysicalMaterial::~PhysicalMaterial()
{
if (_material)
PhysicsBackend::DestroyObject(_material);
PhysicsBackend::DestroyMaterial(_material);
}
bool PhysicsService::Init()

View File

@@ -314,6 +314,7 @@ public:
static void DestroyShape(void* shape);
static void DestroyJoint(void* joint);
static void DestroyController(void* controller);
static void DestroyMaterial(void* material);
static void DestroyObject(void* object);
static void RemoveCollider(PhysicsColliderActor* collider);
static void RemoveJoint(Joint* joint);

View File

@@ -865,6 +865,10 @@ void PhysicsBackend::DestroyController(void* controller)
{
}
void PhysicsBackend::DestroyMaterial(void* material)
{
}
void PhysicsBackend::DestroyObject(void* object)
{
}

View File

@@ -778,7 +778,16 @@ void WindowsWindow::UpdateCursor()
if (!_lastCursorHidden)
{
_lastCursorHidden = true;
::ShowCursor(FALSE);
while(::ShowCursor(FALSE) >= 0)
{
if (_cursorHiddenSafetyCount >= 100)
{
LOG(Warning, "Cursor has failed to hide.");
break;
}
_cursorHiddenSafetyCount += 1;
}
_cursorHiddenSafetyCount = 0;
}
::SetCursor(nullptr);
return;
@@ -786,7 +795,16 @@ void WindowsWindow::UpdateCursor()
else if (_lastCursorHidden)
{
_lastCursorHidden = false;
::ShowCursor(TRUE);
while(::ShowCursor(TRUE) < 0)
{
if (_cursorHiddenSafetyCount >= 100)
{
LOG(Warning, "Cursor has failed to show.");
break;
}
_cursorHiddenSafetyCount += 1;
}
_cursorHiddenSafetyCount = 0;
}
int32 index = 0;

View File

@@ -29,6 +29,7 @@ private:
bool _trackingMouse = false;
bool _clipCursorSet = false;
bool _lastCursorHidden = false;
int _cursorHiddenSafetyCount = 0;
bool _isDuringMaximize = false;
Windows::HANDLE _monitor = nullptr;
Windows::LONG _clipCursorRect[4];

View File

@@ -126,10 +126,11 @@ namespace FlaxEngine
/// </summary>
/// <param name="id">Unique ID of the object.</param>
/// <typeparam name="T">Type of the object.</typeparam>
/// <param name="skipLog">Whether or not to log warnings when objects aren't found.</param>
/// <returns>Found object or null if missing.</returns>
public static T Find<T>(ref Guid id) where T : Object
public static T Find<T>(ref Guid id, bool skipLog = false) where T : Object
{
return Internal_FindObject(ref id, typeof(T)) as T;
return Internal_FindObject(ref id, typeof(T), skipLog) as T;
}
/// <summary>
@@ -137,10 +138,11 @@ namespace FlaxEngine
/// </summary>
/// <param name="id">Unique ID of the object.</param>
/// <param name="type">Type of the object.</param>
/// <param name="skipLog">Whether or not to log warnings when objects aren't found.</param>
/// <returns>Found object or null if missing.</returns>
public static Object Find(ref Guid id, Type type)
public static Object Find(ref Guid id, Type type, bool skipLog = false)
{
return Internal_FindObject(ref id, type);
return Internal_FindObject(ref id, type, skipLog);
}
/// <summary>
@@ -335,7 +337,7 @@ namespace FlaxEngine
internal static partial string Internal_GetTypeName(IntPtr obj);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_FindObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial Object Internal_FindObject(ref Guid id, [MarshalUsing(typeof(Interop.SystemTypeMarshaller))] Type type);
internal static partial Object Internal_FindObject(ref Guid id, [MarshalUsing(typeof(Interop.SystemTypeMarshaller))] Type type, [MarshalAs(UnmanagedType.U1)] bool skipLog = false);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_TryFindObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial Object Internal_TryFindObject(ref Guid id, [MarshalUsing(typeof(Interop.SystemTypeMarshaller))] Type type);

View File

@@ -21,6 +21,7 @@
#include "ManagedCLR/MCore.h"
#include "ManagedCLR/MException.h"
#include "Internal/StdTypesContainer.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Types/Stopwatch.h"
@@ -880,7 +881,7 @@ ScriptingObject* Scripting::FindObject(Guid id, const MClass* type)
// Check type
if (!type || result->Is(type))
return result;
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}.", id, String(result->GetType().Fullname), String(type->GetFullName()));
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}. {3}", id, String(result->GetType().Fullname), String(type->GetFullName()), LogContext::GetInfo());
return nullptr;
}
@@ -899,7 +900,7 @@ ScriptingObject* Scripting::FindObject(Guid id, const MClass* type)
return asset;
}
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}.", id, String(type->GetFullName()));
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}. {2}", id, String(type->GetFullName()), LogContext::GetInfo());
return nullptr;
}

View File

@@ -6,6 +6,7 @@
#include "BinaryModule.h"
#include "Engine/Level/Actor.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Core/Types/Pair.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Content/Asset.h"
@@ -718,7 +719,7 @@ DEFINE_INTERNAL_CALL(MString*) ObjectInternal_GetTypeName(ScriptingObject* obj)
return MUtils::ToString(obj->GetType().Fullname);
}
DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* type)
DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject* type, bool skipLog = false)
{
if (!id->IsValid())
return nullptr;
@@ -735,15 +736,20 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MTypeObject*
{
if (klass && !obj->Is(klass))
{
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}.", *id, String(obj->GetType().Fullname), String(klass->GetFullName()));
if (!skipLog)
LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}. {3}", *id, String(obj->GetType().Fullname), String(klass->GetFullName()), LogContext::GetInfo());
return nullptr;
}
return obj->GetOrCreateManagedInstance();
}
if (klass)
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}.", *id, String(klass->GetFullName()));
else
LOG(Warning, "Unable to find scripting object with ID={0}", *id);
if (!skipLog)
{
if (klass)
LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}. {2}", *id, String(klass->GetFullName()), LogContext::GetInfo());
else
LOG(Warning, "Unable to find scripting object with ID={0}", *id);
}
return nullptr;
}

View File

@@ -569,6 +569,106 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
// Do the inverse interpolation and saturate it
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate((({0} - {1}) / ({2} - {1})))"), gradient.Value, lowerEdge.Value, upperEdge.Value), node);
}
// Rotate UV [Rotator Simple]
case 43:
{
//cosine = cos(rotation);
//sine = sin(rotation);
//float2 out = float2(cosine * uv.x + sine * uv.y,cosine * uv.y - sine * uv.x);
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
break;
}
// Cone Gradient
case 44:
{
//float gradient = angle - abs(atan2(uv.x,uv.y));
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x, {0}.y))"), uv.Value, rotationAngle.Value), node);
break;
}
// Cycle Gradient
case 45:
{
//float gradient = 1 - length(uv * 2);
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node);
break;
}
// Falloff and Offset
case 46:
{
//float out = clamp((((Value - (1 - Offset)) + Falloff) / Falloff),0,1)
const auto in = tryGetValue(node->GetBox(0), ShaderGraphValue::Zero);
const auto graphValue = tryGetValue(node->GetBox(1), node->Values[0].AsFloat);
const auto falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat);
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - {1})) + {2}) / {2}))"), in.Value, graphValue.Value, falloff.Value), node);
break;
}
// Linear Gradient
case 47:
{
// float2 uv = Input0.xy;
// float r = Input0.z;
// float2 A = 1.0 - float2(cos(r) * uv.x + sin(r) * uv.y, cos(r) * uv.y - sin(r) * uv.x);
// float2 out = float2(Mirror ? abs(A.x < 1.0 ? (A.x - 0.5) * 2 : (2 - ((A.x - 0.5) * 2)) * -1) : A.x < 1.0 ? (A.x - 0.5) * 2 : 1,Mirror ? abs(A.y < 1.0 ? (A.y - 0.5) * 2 : (2 - ((A.y - 0.5) * 2)) * -1) : A.y < 1.0 ? (A.y - 0.5) * 2 : 1);
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
const auto mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool();
auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
value = writeLocal(
ValueType::Float2, String::Format(TEXT
(
"float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)"
), mirror.Value, a.Value),
node);
break;
}
// Radial Gradient
case 48:
{
//float gradient = clamp(atan2(uv.x,uv.y) - angle,0.0,1.0);
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(atan2({0}.x, {0}.y) - {1})"), uv.Value, rotationAngle.Value), node);
break;
}
// Ring Gradient
case 49:
{
// Nodes:
// float c = CycleGradient(uv)
// float InnerMask = FalloffAndOffset(c,(OuterBounds - Falloff),Falloff)
// float OuterMask = FalloffAndOffset(1-c,1-InnerBounds,Falloff)
// float Mask = OuterMask * InnerMask;
// TODO: check if there is some useless operators
//expanded
//float cycleGradient = 1 - length(uv * 2);
//float InnerMask = clamp((((c - (1 - (OuterBounds - Falloff))) + Falloff) / Falloff),0,1)
//float OuterMask = clamp(((((1-c) - (1 - (1-InnerBounds))) + Falloff) / Falloff),0,1)
//float Mask = OuterMask * InnerMask;
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto outerBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
const auto innerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat();
const auto falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat();
auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node);
auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - ({1} - {2}))) + {2}) / {2}))"), c.Value, outerBounds.Value, falloff.Value), node);
auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((((1.0 - {0}) - (1.0 - (1.0 - {1}))) + {2}) / {2}))"), c.Value, innerBounds.Value, falloff.Value), node);
auto mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), innerMask.Value, outerMask.Value), node);
value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, {1}, {2})"), innerMask.Value, outerMask.Value, mask.Value), node);
break;
}
default:
break;
}

View File

@@ -306,7 +306,7 @@ HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
LOG_STR(Warning, String(err));
FreeEXRErrorMessage(err);
}
return S_FALSE;
return E_FAIL;
}
// Setup image
@@ -326,7 +326,7 @@ HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
return result;
#else
LOG(Warning, "EXR format is not supported.");
return S_FALSE;
return E_FAIL;
#endif
}

View File

@@ -220,7 +220,7 @@ namespace FlaxEngine.GUI
{
base.DrawSelf();
float progressNormalized = (_current - _minimum) / _maximum;
float progressNormalized = Mathf.InverseLerp(_minimum, _maximum, _current);
if (progressNormalized > 0.001f)
{
Rectangle barRect = new Rectangle(0, 0, Width * progressNormalized, Height);

View File

@@ -100,6 +100,7 @@ namespace FlaxEngine.GUI
if (value > _maximum)
throw new ArgumentOutOfRangeException();
_minimum = value;
UpdateThumb();
if (Value < _minimum)
Value = _minimum;
}
@@ -116,6 +117,7 @@ namespace FlaxEngine.GUI
if (value < _minimum)
throw new ArgumentOutOfRangeException();
_maximum = value;
UpdateThumb();
if (Value > _maximum)
Value = _maximum;
}

View File

@@ -11,6 +11,7 @@ namespace FlaxEngine.GUI
{
private Margin _slotPadding;
private int _slotsV, _slotsH;
private Float2 _slotSpacing;
/// <summary>
/// Gets or sets the padding given to each slot.
@@ -62,11 +63,25 @@ namespace FlaxEngine.GUI
}
}
/// <summary>
/// Gets or sets grid slot spacing.
/// </summary>
[EditorOrder(30), Limit(0), Tooltip("The Grid slot spacing.")]
public Float2 SlotSpacing
{
get => _slotSpacing;
set
{
_slotSpacing = value;
PerformLayout();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="UniformGridPanel"/> class.
/// </summary>
public UniformGridPanel()
: this(2)
: this(0)
{
}
@@ -74,10 +89,11 @@ namespace FlaxEngine.GUI
/// Initializes a new instance of the <see cref="UniformGridPanel"/> class.
/// </summary>
/// <param name="slotPadding">The slot padding.</param>
public UniformGridPanel(float slotPadding = 2)
public UniformGridPanel(float slotPadding = 0)
{
AutoFocus = false;
SlotPadding = new Margin(slotPadding);
SlotSpacing = new Float2(2);
_slotsH = _slotsV = 5;
}
@@ -122,6 +138,42 @@ namespace FlaxEngine.GUI
var slotBounds = new Rectangle(slotSize.X * x, slotSize.Y * y, slotSize.X, slotSize.Y);
_slotPadding.ShrinkRectangle(ref slotBounds);
if (slotsV > 1)
{
if (y == 0)
{
slotBounds.Height -= _slotSpacing.Y * 0.5f;
}
else if (y == slotsV - 1)
{
slotBounds.Height -= _slotSpacing.Y * 0.5f;
slotBounds.Y += _slotSpacing.Y * 0.5f;
}
else
{
slotBounds.Height -= _slotSpacing.Y;
slotBounds.Y += _slotSpacing.Y * 0.5f;
}
}
if (slotsH > 1)
{
if (x == 0)
{
slotBounds.Width -= _slotSpacing.X * 0.5f;
}
else if (x == slotsH - 1)
{
slotBounds.Width -= _slotSpacing.X * 0.5f;
slotBounds.X += _slotSpacing.X * 0.5f;
}
else
{
slotBounds.Width -= _slotSpacing.X;
slotBounds.X += _slotSpacing.X * 0.5f;
}
}
var c = _children[i++];
c.Bounds = slotBounds;
}

View File

@@ -0,0 +1,389 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine.GUI
{
/// <summary>
/// Radial menu control that arranges child controls (of type Image) in a circle.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
public class RadialMenu : ContainerControl
{
private bool _materialIsDirty = true;
private float _angle;
private float _selectedSegment;
private int _highlightSegment = -1;
private MaterialBase _material;
private MaterialInstance _materialInstance;
private int _segmentCount;
private Color _highlightColor;
private Color _foregroundColor;
private Color _selectionColor;
private float _edgeOffset;
private float _thickness = 0.2f;
private float USize => Size.X < Size.Y ? Size.X : Size.Y;
private bool ShowMatProp => _material != null;
/// <summary>
/// The material to use for menu background drawing.
/// </summary>
[EditorOrder(1)]
public MaterialBase Material
{
get => _material;
set
{
if (_material == value)
return;
_material = value;
Object.Destroy(ref _materialInstance);
_materialIsDirty = true;
}
}
/// <summary>
/// Gets or sets the edge offset.
/// </summary>
[EditorOrder(2), Range(0, 1)]
public float EdgeOffset
{
get => _edgeOffset;
set
{
_edgeOffset = Math.Clamp(value, 0, 1);
_materialIsDirty = true;
PerformLayout();
}
}
/// <summary>
/// Gets or sets the thickness.
/// </summary>
[EditorOrder(3), Range(0, 1), VisibleIf(nameof(ShowMatProp))]
public float Thickness
{
get => _thickness;
set
{
_thickness = Math.Clamp(value, 0, 1);
_materialIsDirty = true;
PerformLayout();
}
}
/// <summary>
/// Gets or sets control background color (transparent color (alpha=0) means no background rendering).
/// </summary>
[VisibleIf(nameof(ShowMatProp))]
public new Color BackgroundColor
{
get => base.BackgroundColor;
set
{
_materialIsDirty = true;
base.BackgroundColor = value;
}
}
/// <summary>
/// Gets or sets the color of the highlight.
/// </summary>
[VisibleIf(nameof(ShowMatProp))]
public Color HighlightColor
{
get => _highlightColor;
set
{
_materialIsDirty = true;
_highlightColor = value;
}
}
/// <summary>
/// Gets or sets the color of the foreground.
/// </summary>
[VisibleIf(nameof(ShowMatProp))]
public Color ForegroundColor
{
get => _foregroundColor;
set
{
_materialIsDirty = true;
_foregroundColor = value;
}
}
/// <summary>
/// Gets or sets the color of the selection.
/// </summary>
[VisibleIf(nameof(ShowMatProp))]
public Color SelectionColor
{
get => _selectionColor;
set
{
_materialIsDirty = true;
_selectionColor = value;
}
}
/// <summary>
/// The selected callback
/// </summary>
[HideInEditor]
public Action<int> Selected;
/// <summary>
/// The allow change selection when inside
/// </summary>
[VisibleIf(nameof(ShowMatProp))]
public bool AllowChangeSelectionWhenInside;
/// <summary>
/// The center as button
/// </summary>
[VisibleIf(nameof(ShowMatProp))]
public bool CenterAsButton;
/// <summary>
/// Initializes a new instance of the <see cref="RadialMenu"/> class.
/// </summary>
public RadialMenu()
: this(0, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RadialMenu"/> class.
/// </summary>
/// <param name="x">Position X coordinate</param>
/// <param name="y">Position Y coordinate</param>
/// <param name="width">Width</param>
/// <param name="height">Height</param>
public RadialMenu(float x, float y, float width = 100, float height = 100)
: base(x, y, width, height)
{
var style = Style.Current;
if (style != null)
{
BackgroundColor = style.BackgroundNormal;
HighlightColor = style.BackgroundSelected;
ForegroundColor = style.BackgroundHighlighted;
SelectionColor = style.BackgroundSelected;
}
_material = Content.LoadAsyncInternal<MaterialBase>("Engine/DefaultRadialMenu");
}
/// <summary>
/// Initializes a new instance of the <see cref="RadialMenu"/> class.
/// </summary>
/// <param name="location">Position</param>
/// <param name="size">Size</param>
public RadialMenu(Float2 location, Float2 size)
: this(location.X, location.Y, size.X, size.Y)
{
}
/// <inheritdoc/>
public override void DrawSelf()
{
if (_materialInstance == null && Material != null)
{
_materialInstance = Material.CreateVirtualInstance();
_materialIsDirty = true;
}
if (_materialInstance != null)
{
if (_materialIsDirty)
{
_materialInstance.SetParameterValue("RadialMenu_EdgeOffset", Math.Clamp(1 - _edgeOffset, 0, 1));
_materialInstance.SetParameterValue("RadialMenu_Thickness", Math.Clamp(_thickness, 0, 1));
_materialInstance.SetParameterValue("RadialMenu_Angle", (1.0f / _segmentCount) * Mathf.Pi);
_materialInstance.SetParameterValue("RadialMenu_SegmentCount", _segmentCount);
_materialInstance.SetParameterValue("RadialMenu_HighlightColor", _highlightColor);
_materialInstance.SetParameterValue("RadialMenu_ForegroundColor", _foregroundColor);
_materialInstance.SetParameterValue("RadialMenu_BackgroundColor", BackgroundColor);
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
UpdateSelectionColor();
_materialIsDirty = false;
}
Render2D.DrawMaterial(_materialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y)));
}
}
/// <inheritdoc/>
public override void OnMouseMove(Float2 location)
{
if (_materialInstance != null)
{
if (_highlightSegment == -1)
{
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
var max = (1 - _edgeOffset) * USize * 0.5f;
var val = ((USize * 0.5f) - location).Length;
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
{
UpdateAngle(ref location);
}
}
}
base.OnMouseMove(location);
}
/// <inheritdoc/>
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (_materialInstance == null)
return base.OnMouseDown(location, button);
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
var max = (1 - _edgeOffset) * USize * 0.5f;
var val = ((USize * 0.5f) - location).Length;
var c = val < min && CenterAsButton;
var selected = (int)_selectedSegment;
selected++;
if (Mathf.IsInRange(val, min, max) || c)
{
_highlightSegment = c ? 0 : selected;
UpdateSelectionColor();
return true;
}
else
{
_highlightSegment = -1;
UpdateSelectionColor();
}
return base.OnMouseDown(location, button);
}
/// <inheritdoc/>
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (_materialInstance == null)
return base.OnMouseDown(location, button);
if (_highlightSegment >= 0)
{
Selected?.Invoke(_highlightSegment);
_highlightSegment = -1;
UpdateSelectionColor();
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
var max = (1 - _edgeOffset) * USize * 0.5f;
var val = ((USize * 0.5f) - location).Length;
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
{
UpdateAngle(ref location);
}
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc/>
public override void OnMouseLeave()
{
if (_materialInstance == null)
return;
_selectedSegment = 0;
_highlightSegment = -1;
Selected?.Invoke(_highlightSegment);
UpdateSelectionColor();
}
/// <inheritdoc/>
public override void OnChildrenChanged()
{
_segmentCount = 0;
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Image)
{
_segmentCount++;
}
}
_materialIsDirty = true;
base.OnChildrenChanged();
}
/// <inheritdoc/>
public override void PerformLayout(bool force = false)
{
var sa = -1.0f / _segmentCount * Mathf.TwoPi;
var midp = USize * 0.5f;
var mp = ((1 - _edgeOffset) - (_thickness * 0.5f)) * midp;
float f = 0;
if (_segmentCount % 2 != 0)
{
f += sa * 0.5f;
}
if (_materialInstance == null)
{
for (int i = 0; i < Children.Count; i++)
{
Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
f += sa;
}
}
else
{
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Image)
{
Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
f += sa;
}
}
}
base.PerformLayout(force);
}
private void UpdateSelectionColor()
{
Color color;
if (_highlightSegment == -1)
{
color = _foregroundColor;
}
else
{
if (CenterAsButton)
{
color = _highlightSegment > 0 ? SelectionColor : _foregroundColor;
}
else
{
color = SelectionColor;
}
}
_materialInstance.SetParameterValue("RadialMenu_SelectionColor", color);
}
private void UpdateAngle(ref Float2 location)
{
var size = new Float2(USize);
var p = (size * 0.5f) - location;
var sa = (1.0f / _segmentCount) * Mathf.TwoPi;
_angle = Mathf.Atan2(p.X, p.Y);
_angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa;
_selectedSegment = _angle;
_selectedSegment = Mathf.RoundToInt((_selectedSegment < 0 ? Mathf.TwoPi + _selectedSegment : _selectedSegment) / sa);
if (float.IsNaN(_angle) || float.IsInfinity(_angle))
_angle = 0;
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
}
private static Float2 Rotate2D(Float2 point, float angle)
{
return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y,
Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X);
}
}
}

View File

@@ -0,0 +1,151 @@
// Implementation based on:
// "The Best Darn Grid Shader (Yet)", Medium, Oct 2023
// Ben Golus
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#3e73
#define USE_FORWARD true;
#include "./Flax/Common.hlsl"
META_CB_BEGIN(0, Data)
float4x4 WorldMatrix;
float4x4 ViewProjectionMatrix;
float4 GridColor;
float3 ViewPos;
float Far;
float3 Padding;
float GridSize;
META_CB_END
// Geometry data passed to the vertex shader
struct ModelInput
{
float3 Position : POSITION;
};
// Interpolants passed from the vertex shader
struct VertexOutput
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
float3 WorldPosition : TEXCOORD1;
};
// Interpolants passed to the pixel shader
struct PixelInput
{
float4 Position : SV_Position;
noperspective float2 TexCoord : TEXCOORD0;
float3 WorldPosition : TEXCOORD1;
};
// Vertex shader function for grid rendering
META_VS(true, FEATURE_LEVEL_ES2)
META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true)
VertexOutput VS_Grid(ModelInput input)
{
VertexOutput output;
output.WorldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz;
output.Position = mul(float4(input.Position.xyz, 1), ViewProjectionMatrix);
return output;
}
float invLerp(float from, float to, float value)
{
return (value - from) / (to - from);
}
float remap(float origFrom, float origTo, float targetFrom, float targetTo, float value)
{
float rel = invLerp(origFrom, origTo, value);
return lerp(targetFrom, targetTo, rel);
}
float ddLength(float a)
{
return length(float2(ddx(a), ddy(a)));
}
float GetLine(float pos, float scale, float thickness)
{
float lineWidth = thickness;
float coord = (pos * 0.01) * scale;
float2 uvDDXY = float2(ddx(coord), ddy(coord));
float deriv = float(length(uvDDXY.xy));
float drawWidth = clamp(lineWidth, deriv, 0.5);
float lineAA = deriv * 1.5;
float gridUV = abs(coord);
float grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
grid2 *= saturate(lineWidth / drawWidth);
grid2 = lerp(grid2, lineWidth, saturate(deriv * 2.0 - 1.0));
float grid = lerp(grid2, 1.0, grid2);
return grid;
}
float GetGrid(float3 pos, float scale, float thickness)
{
float lineWidth = thickness;
float2 coord = (pos.xz * 0.01) * scale;
float4 uvDDXY = float4(ddx(coord), ddy(coord));
float2 deriv = float2(length(uvDDXY.xz), length(uvDDXY.yw));
float2 drawWidth = clamp(lineWidth, deriv, 0.5);
float2 lineAA = deriv * 1.5;
float2 gridUV = 1.0 - abs(frac(coord) * 2.0 - 1.0);
float2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
grid2 *= saturate(lineWidth / drawWidth);
grid2 = lerp(grid2, lineWidth, saturate(deriv * 2.0 - 1.0));
float grid = lerp(grid2.x, 1.0, grid2.y);
return grid;
}
float4 GetColor(float3 pos, float scale)
{
float dist = 1 - saturate(distance(float3(ViewPos.x, 0, ViewPos.z), pos) / GridSize);
// Line width
float g1LW = 0.01;
// Major line Z
float l1 = GetLine(pos.x, 1, g1LW * 2);
// Major line X
float l2 = GetLine(pos.z, 1, g1LW);
// Main grid
float g1 = GetGrid(pos, 1, g1LW * 0.8);
float g2 = GetGrid(pos, 2, g1LW * 0.4);
float g3 = GetGrid(pos, 0.1, g1LW * 2);
float camFadeLarge = clamp(invLerp(2500, 4000, abs(ViewPos.y)),0, g3);
g3 *= camFadeLarge;
float g4 = GetGrid(pos, 10, g1LW);
float camFadeTiny = clamp(invLerp(150, 100, abs(ViewPos.y)), 0, g4);
g4 *= camFadeTiny;
float grid = 0;
grid = max(l1, l2);
grid = max(grid, g1);
grid = max(grid, g2);
grid = max(grid, g3);
grid = max(grid, g4);
float4 color = grid * GridColor;
color = lerp(color, float4(1,0,0,1), l2);
color = lerp(color, float4(0,0,1,1), l1);
color *= dist;
return color;
}
// Pixel shader function for grid rendering
META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Grid(PixelInput input) : SV_Target
{
float4 color = GetColor(input.WorldPosition, 1);
return color;
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
//#define AUTO_DOC_TOOLTIPS
//#define MARSHALLER_FULL_NAME
using System;
using System.Collections.Generic;
@@ -582,8 +583,13 @@ namespace Flax.Build.Bindings
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))";
else if (FindApiTypeInfo(buildData, functionInfo.ReturnType, caller)?.IsInterface ?? false)
{
var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller);
var fullReturnValueType = returnValueType;
if (!string.IsNullOrEmpty(apiType?.Namespace))
fullReturnValueType = $"{apiType.Namespace}.Interop.{returnValueType}";
// Interfaces are not supported by NativeMarshallingAttribute, marshal the parameter
returnMarshalType = $"MarshalUsing(typeof({returnValueType}Marshaller))";
returnMarshalType = $"MarshalUsing(typeof({fullReturnValueType}Marshaller))";
}
else if (functionInfo.ReturnType.Type == "MonoArray" || functionInfo.ReturnType.Type == "MArray")
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))";
@@ -941,10 +947,11 @@ namespace Flax.Build.Bindings
contents.AppendLine();
// Namespace begin
string interopNamespace = "";
if (!string.IsNullOrEmpty(classInfo.Namespace))
{
contents.AppendFormat("namespace ");
contents.AppendLine(classInfo.Namespace);
interopNamespace = $"{classInfo.Namespace}.Interop";
contents.AppendLine($"namespace {classInfo.Namespace}");
contents.AppendLine("{");
indent += " ";
}
@@ -956,10 +963,18 @@ namespace Flax.Build.Bindings
GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged);
#if USE_NETCORE
string marshallerName = "";
string marshallerFullName = "";
if (!classInfo.IsStatic)
{
marshallerName = classInfo.Name + "Marshaller";
contents.Append(indent).AppendLine($"[NativeMarshalling(typeof({marshallerName}))]");
#if MARSHALLER_FULL_NAME
marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName;
#else
if (!string.IsNullOrEmpty(interopNamespace))
CSharpUsedNamespaces.Add(interopNamespace);
marshallerFullName = marshallerName;
#endif
contents.Append(indent).AppendLine($"[NativeMarshalling(typeof({marshallerFullName}))]");
}
#endif
contents.Append(indent).Append(GenerateCSharpAccessLevel(classInfo.Access));
@@ -1384,6 +1399,13 @@ namespace Flax.Build.Bindings
#if USE_NETCORE
if (!string.IsNullOrEmpty(marshallerName))
{
if (!string.IsNullOrEmpty(interopNamespace))
{
contents.AppendLine("}");
contents.AppendLine($"namespace {interopNamespace}");
contents.AppendLine("{");
}
contents.AppendLine();
contents.AppendLine(string.Join("\n" + indent, (indent + $$"""
/// <summary>
@@ -1392,15 +1414,15 @@ namespace Flax.Build.Bindings
#if FLAX_EDITOR
[HideInEditor]
#endif
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerName}}.ManagedToNative))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerName}}.ManagedToNative))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerName}}.ManagedToNative))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerName}}.NativeToManaged))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerName}}.NativeToManaged))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerName}}.NativeToManaged))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerName}}.Bidirectional))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerName}}.Bidirectional))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerName}}))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))]
{{GenerateCSharpAccessLevel(classInfo.Access)}}static class {{marshallerName}}
{
#pragma warning disable 1591
@@ -1454,64 +1476,57 @@ namespace Flax.Build.Bindings
{
contents.AppendLine();
// Namespace begin
if (!string.IsNullOrEmpty(structureInfo.Namespace))
{
contents.AppendFormat("namespace ");
contents.AppendLine(structureInfo.Namespace);
contents.AppendLine("{");
indent += " ";
}
#if USE_NETCORE
// Generate blittable structure
string structNativeMarshaling = "";
if (UseCustomMarshalling(buildData, structureInfo, structureInfo))
{
// Namespace begin
string interopNamespace = "";
if (!string.IsNullOrEmpty(structureInfo.Namespace))
{
interopNamespace = $"{structureInfo.Namespace}.Interop";
contents.AppendLine($"namespace {interopNamespace}");
contents.AppendLine("{");
indent += " ";
}
// NOTE: Permanent FlaxEngine.Object GCHandles must not be released when marshalling from native to managed.
string marshallerName = structureInfo.Name + "Marshaller";
structNativeMarshaling = $"[NativeMarshalling(typeof({marshallerName}))]";
contents.Append(indent).AppendLine($"/// <summary>");
contents.Append(indent).AppendLine($"/// Marshaller for type <see cref=\"{structureInfo.Name}\"/>.");
contents.Append(indent).AppendLine($"/// </summary>");
if (buildData.Target != null & buildData.Target.IsEditor)
contents.Append(indent).AppendLine("[HideInEditor]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedIn, typeof({marshallerName}.ManagedToNative))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedOut, typeof({marshallerName}.ManagedToNative))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementIn, typeof({marshallerName}.ManagedToNative))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedOut, typeof({marshallerName}.NativeToManaged))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedIn, typeof({marshallerName}.NativeToManaged))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementOut, typeof({marshallerName}.NativeToManaged))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedRef, typeof({marshallerName}.Bidirectional))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedRef, typeof({marshallerName}.Bidirectional))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementRef, typeof({marshallerName}))]");
contents.Append(indent).AppendLine($"{GenerateCSharpAccessLevel(structureInfo.Access)}static unsafe class {marshallerName}");
contents.Append(indent).AppendLine("{");
contents.AppendLine("#pragma warning disable 1591");
indent += " ";
#if MARSHALLER_FULL_NAME
string marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName;
#else
if (!string.IsNullOrEmpty(interopNamespace))
CSharpUsedNamespaces.Add(interopNamespace);
string marshallerFullName = marshallerName;
#endif
structNativeMarshaling = $"[NativeMarshalling(typeof({marshallerFullName}))]";
var toManagedContent = GetStringBuilder();
var toNativeContent = GetStringBuilder();
var freeContents = GetStringBuilder();
var freeContents2 = GetStringBuilder();
var structContents = GetStringBuilder();
{
// Native struct begin
// TODO: skip using this utility structure if the auto-generated C# struct is already the same as XXXInternal here below
var structIndent = "";
if (buildData.Target != null & buildData.Target.IsEditor)
contents.Append(indent).AppendLine("[HideInEditor]");
contents.Append(indent).AppendLine("[StructLayout(LayoutKind.Sequential)]");
contents.Append(indent).Append("public struct ").Append(structureInfo.Name).Append("Internal");
structContents.Append(structIndent).AppendLine("[HideInEditor]");
structContents.Append(structIndent).AppendLine("[StructLayout(LayoutKind.Sequential)]");
structContents.Append(structIndent).Append("public struct ").Append(structureInfo.Name).Append("Internal");
if (structureInfo.BaseType != null && structureInfo.IsPod)
contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo));
contents.AppendLine();
contents.Append(indent + "{");
indent += " ";
structContents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo));
structContents.AppendLine();
structContents.Append(structIndent + "{");
structIndent += " ";
toNativeContent.Append($"var unmanaged = new {structureInfo.Name}Internal();").AppendLine();
toManagedContent.Append($"var managed = new {structureInfo.Name}();").AppendLine();
contents.AppendLine();
structContents.AppendLine();
foreach (var fieldInfo in structureInfo.Fields)
{
if (fieldInfo.IsStatic || fieldInfo.IsConstexpr)
@@ -1528,7 +1543,7 @@ namespace Flax.Build.Bindings
else
originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo);
contents.Append(indent).Append("public ");
structContents.Append(structIndent).Append("public ");
var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo);
bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo);
@@ -1540,7 +1555,7 @@ namespace Flax.Build.Bindings
if (GenerateCSharpUseFixedBuffer(originalType))
{
// Use fixed statement with primitive types of buffers
contents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine();
structContents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine();
// Copy fixed-size array
toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);");
@@ -1550,12 +1565,12 @@ namespace Flax.Build.Bindings
#endif
{
// Padding in structs for fixed-size array
contents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine();
structContents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine();
for (int i = 1; i < fieldInfo.Type.ArraySize; i++)
{
GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic);
contents.Append(indent).Append("public ");
contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine();
GenerateCSharpAttributes(buildData, structContents, structIndent, structureInfo, fieldInfo, fieldInfo.IsStatic);
structContents.Append(structIndent).Append("public ");
structContents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine();
}
// Copy fixed-size array item one-by-one
@@ -1601,8 +1616,7 @@ namespace Flax.Build.Bindings
}
//else if (type == "Guid")
// type = "GuidNative";
contents.Append(type).Append(' ').Append(fieldInfo.Name).Append(';').AppendLine();
structContents.Append($"{type} {fieldInfo.Name};").Append(type == "IntPtr" ? $" // {originalType}" : "").AppendLine();
}
// Generate struct constructor/getter and deconstructor/setter function
@@ -1649,13 +1663,13 @@ namespace Flax.Build.Bindings
if (internalType)
{
// Marshal blittable array elements back to original non-blittable elements
string originalElementTypeMarshaller = $"{originalElementType}Marshaller";
string originalElementTypeMarshaller = (!string.IsNullOrEmpty(apiType?.Namespace) ? $"{apiType?.Namespace}.Interop." : "") + $"{originalElementType}Marshaller";
string originalElementTypeName = originalElementType.Substring(originalElementType.LastIndexOf('.') + 1); // Strip namespace
string internalElementType = $"{originalElementTypeMarshaller}.{originalElementTypeName}Internal";
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.NativeToManaged.Free(value); }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
}
else if (fieldInfo.Type.GenericArgs[0].IsObjectRef)
{
@@ -1706,7 +1720,7 @@ namespace Flax.Build.Bindings
toManagedContent.AppendLine($"{internalTypeMarshaller}.ToManaged(unmanaged.{fieldInfo.Name});");
toNativeContent.AppendLine($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name});");
freeContents.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});");
freeContents2.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});");
freeContents2.AppendLine($"{internalTypeMarshaller}.NativeToManaged.Free(unmanaged.{fieldInfo.Name});");
}
/*else if (originalType == "Guid")
{
@@ -1721,73 +1735,109 @@ namespace Flax.Build.Bindings
}
// Native struct end
indent = indent.Substring(0, indent.Length - 4);
contents.AppendLine(indent + "}").AppendLine();
structIndent = structIndent.Substring(0, structIndent.Length - 4);
structContents.AppendLine(structIndent + "}").AppendLine();
toNativeContent.Append("return unmanaged;");
toManagedContent.Append("return managed;");
}
var indent2 = indent + " ";
var indent3 = indent2 + " ";
contents.AppendLine(string.Join("\n" + indent, (indent + $$"""
/// <summary>
/// Marshaller for type <see cref="{{structureInfo.Name}}"/>.
/// </summary>
{{InsertHideInEditorSection()}}
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))]
{{GenerateCSharpAccessLevel(structureInfo.Access)}}static unsafe class {{marshallerName}}
{
#pragma warning disable 1591
{{structContents.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
// NativeToManaged stateless shape
// NOTE: GCHandles of FlaxEngine.Object must not be released in this case
if (buildData.Target != null && buildData.Target.IsEditor)
contents.Append(indent).AppendLine("[HideInEditor]");
contents.Append(indent).AppendLine("public static class NativeToManaged").Append(indent).AppendLine("{");
contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);");
contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);");
contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged)");
contents.Append(indent2).AppendLine("{").Append(indent3).AppendLine(freeContents2.Replace("\n", "\n" + indent3).ToString().TrimEnd()).Append(indent2).AppendLine("}");
contents.Append(indent).AppendLine("}");
{{InsertHideInEditorSection()}}
public static class NativeToManaged
{
public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{structureInfo.Name}}Internal unmanaged)
{
{{freeContents2.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
}
{{InsertHideInEditorSection()}}
public static class ManagedToNative
{
public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.Free(unmanaged);
}
{{InsertHideInEditorSection()}}
public struct Bidirectional
{
{{structureInfo.Name}} managed;
{{structureInfo.Name}}Internal unmanaged;
public void FromManaged({{structureInfo.Name}} managed) => this.managed = managed;
public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; }
//public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) { {{marshallerFullName}}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; }
public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) => this.unmanaged = unmanaged;
public {{structureInfo.Name}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; }
public void Free() => NativeToManaged.Free(unmanaged);
}
internal static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => ToManaged(unmanaged);
internal static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => ToNative(managed);
internal static void Free({{structureInfo.Name}}Internal unmanaged)
{
{{freeContents.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
// ManagedToNative stateless shape
if (buildData.Target != null && buildData.Target.IsEditor)
contents.Append(indent).AppendLine("[HideInEditor]");
contents.Append(indent).AppendLine($"public static class ManagedToNative").Append(indent).AppendLine("{");
contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);");
contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);");
contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged) => {marshallerName}.Free(unmanaged);");
contents.Append(indent).AppendLine("}");
internal static {{structureInfo.Name}} ToManaged({{structureInfo.Name}}Internal unmanaged)
{
{{toManagedContent.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
internal static {{structureInfo.Name}}Internal ToNative({{structureInfo.Name}} managed)
{
{{toNativeContent.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
#pragma warning restore 1591
}
""").Split(new char[] { '\n' })));
// Bidirectional stateful shape
// NOTE: GCHandles of FlaxEngine.Object must not be released unless they were allocated by this marshaller
if (buildData.Target != null && buildData.Target.IsEditor)
contents.Append(indent).AppendLine("[HideInEditor]");
contents.Append(indent).AppendLine($"public struct Bidirectional").Append(indent).AppendLine("{");
contents.Append(indent2).AppendLine($"{structureInfo.Name} managed;");
contents.Append(indent2).AppendLine($"{structureInfo.Name}Internal unmanaged;");
contents.Append(indent2).AppendLine($"public void FromManaged({structureInfo.Name} managed) => this.managed = managed;");
contents.Append(indent2).AppendLine($"public {structureInfo.Name}Internal ToUnmanaged() {{ unmanaged = {marshallerName}.ToNative(managed); return unmanaged; }}");
//contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) {{ {marshallerName}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; }}");
contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) => this.unmanaged = unmanaged;");
contents.Append(indent2).AppendLine($"public {structureInfo.Name} ToManaged() {{ managed = {marshallerName}.ToManaged(unmanaged); return managed; }}");
contents.Append(indent2).AppendLine($"public void Free() => NativeToManaged.Free(unmanaged);");
contents.Append(indent).AppendLine("}");
// Bidirectional stateless shape
contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => ToManaged(unmanaged);");
contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => ToNative(managed);");
contents.Append(indent).AppendLine($"internal static void Free({structureInfo.Name}Internal unmanaged)");
contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(freeContents.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}");
// Managed/native converters
contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal unmanaged)");
contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toManagedContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}");
contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ToNative({structureInfo.Name} managed)");
contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toNativeContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}");
contents.AppendLine("#pragma warning restore 1591");
indent = indent.Substring(0, indent.Length - 4);
contents.Append(indent).AppendLine("}").AppendLine();
string InsertHideInEditorSection()
{
return (buildData.Target != null & buildData.Target.IsEditor) ? $$"""
[HideInEditor]
""" : "";
}
PutStringBuilder(toManagedContent);
PutStringBuilder(toNativeContent);
PutStringBuilder(freeContents);
PutStringBuilder(freeContents2);
PutStringBuilder(structContents);
// Namespace end
if (!string.IsNullOrEmpty(structureInfo.Namespace))
{
contents.AppendLine("}");
indent = indent.Substring(0, indent.Length - 4);
}
}
#endif
// Namespace begin
if (!string.IsNullOrEmpty(structureInfo.Namespace))
{
contents.AppendLine($"namespace {structureInfo.Namespace}");
contents.AppendLine("{");
indent += " ";
}
// Struct docs
GenerateCSharpComment(contents, indent, structureInfo.Comment);
@@ -1979,8 +2029,7 @@ namespace Flax.Build.Bindings
// Namespace begin
if (!string.IsNullOrEmpty(enumInfo.Namespace))
{
contents.AppendFormat("namespace ");
contents.AppendLine(enumInfo.Namespace);
contents.AppendLine($"namespace {enumInfo.Namespace}");
contents.AppendLine("{");
indent += " ";
}
@@ -2026,10 +2075,11 @@ namespace Flax.Build.Bindings
{
// Begin
contents.AppendLine();
string interopNamespace = "";
if (!string.IsNullOrEmpty(interfaceInfo.Namespace))
{
contents.AppendFormat("namespace ");
contents.AppendLine(interfaceInfo.Namespace);
interopNamespace = $"{interfaceInfo.Namespace}.Interop";
contents.AppendLine($"namespace {interfaceInfo.Namespace}");
contents.AppendLine("{");
indent += " ";
}
@@ -2096,14 +2146,22 @@ namespace Flax.Build.Bindings
#if USE_NETCORE
{
if (!string.IsNullOrEmpty(interopNamespace))
{
contents.AppendLine("}");
contents.AppendLine($"namespace {interopNamespace}");
contents.AppendLine("{");
}
string marshallerName = interfaceInfo.Name + "Marshaller";
string marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName;
contents.AppendLine();
contents.Append(indent).AppendLine("/// <summary>");
contents.Append(indent).AppendLine($"/// Marshaller for type <see cref=\"{interfaceInfo.Name}\"/>.");
contents.Append(indent).AppendLine("/// </summary>");
if (buildData.Target != null & buildData.Target.IsEditor)
contents.Append(indent).AppendLine("[HideInEditor]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({interfaceInfo.Name}), MarshalMode.Default, typeof({marshallerName}))]");
contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({interfaceInfo.Name}), MarshalMode.Default, typeof({marshallerFullName}))]");
contents.Append(indent).AppendLine($"public static class {marshallerName}");
contents.Append(indent).AppendLine("{");
contents.AppendLine("#pragma warning disable 1591");