Merge branch 'FlaxEngine:master' into material_import
This commit is contained in:
BIN
Content/Editor/IconsAtlas.flax
LFS
BIN
Content/Editor/IconsAtlas.flax
LFS
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -31,7 +32,7 @@ namespace FlaxEditor.Content.GUI
|
||||
: base(x, y, height)
|
||||
{
|
||||
TargetNode = targetNode;
|
||||
Text = targetNode.NavButtonLabel + "/";
|
||||
Text = targetNode.NavButtonLabel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -72,7 +73,6 @@ namespace FlaxEditor.Content.GUI
|
||||
|
||||
if (_dragOverItems == null)
|
||||
_dragOverItems = new DragItems(ValidateDragItem);
|
||||
|
||||
_dragOverItems.OnDragEnter(data);
|
||||
var result = GetDragEffect(data);
|
||||
_validDragOver = result != DragDropEffect.None;
|
||||
@@ -122,4 +122,70 @@ namespace FlaxEditor.Content.GUI
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ContentNavigationSeparator : ComboBox
|
||||
{
|
||||
public ContentNavigationButton Target;
|
||||
|
||||
public ContentNavigationSeparator(ContentNavigationButton target, float x, float y, float height)
|
||||
{
|
||||
Target = target;
|
||||
Bounds = new Rectangle(x, y, 16, height);
|
||||
Offsets = new Margin(Bounds.X, Bounds.Width, Bounds.Y, Bounds.Height);
|
||||
UpdateTransform();
|
||||
|
||||
MaximumItemsInViewCount = 20;
|
||||
var style = Style.Current;
|
||||
BackgroundColor = style.BackgroundNormal;
|
||||
BackgroundColorHighlighted = BackgroundColor;
|
||||
BackgroundColorSelected = BackgroundColor;
|
||||
}
|
||||
|
||||
protected override ContextMenu OnCreatePopup()
|
||||
{
|
||||
// Update items
|
||||
ClearItems();
|
||||
foreach (var child in Target.TargetNode.Children)
|
||||
{
|
||||
if (child is ContentTreeNode node)
|
||||
{
|
||||
if (node.Folder.VisibleInHierarchy) // Respect the filter set by ContentFilterConfig.Filter(...)
|
||||
AddItem(node.Folder.ShortName);
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnCreatePopup();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var style = Style.Current;
|
||||
var rect = new Rectangle(Float2.Zero, Size);
|
||||
var color = IsDragOver ? style.BackgroundSelected * 0.6f : (_mouseDown ? style.BackgroundSelected : (IsMouseOver ? style.BackgroundHighlighted : Color.Transparent));
|
||||
Render2D.FillRectangle(rect, color);
|
||||
Render2D.DrawSprite(Editor.Instance.Icons.ArrowRight12, new Rectangle(rect.Location.X, rect.Y + rect.Size.Y * 0.25f, rect.Size.X, rect.Size.X), EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled);
|
||||
}
|
||||
|
||||
protected override void OnLayoutMenuButton(ContextMenuButton button, int index, bool construct = false)
|
||||
{
|
||||
button.Icon = Editor.Instance.Icons.FolderClosed32;
|
||||
if (_tooltips != null && _tooltips.Length > index)
|
||||
button.TooltipText = _tooltips[index];
|
||||
}
|
||||
|
||||
protected override void OnItemClicked(int index)
|
||||
{
|
||||
base.OnItemClicked(index);
|
||||
|
||||
var item = _items[index];
|
||||
foreach (var child in Target.TargetNode.Children)
|
||||
{
|
||||
if (child is ContentTreeNode node && node.Folder.ShortName == item)
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Navigate(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,11 +261,14 @@ namespace FlaxEditor.Content.GUI
|
||||
ClearItems();
|
||||
|
||||
// Add references and link items
|
||||
_items.AddRange(items);
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
items[i].Parent = this;
|
||||
items[i].AddReference(this);
|
||||
if (items[i].Visible)
|
||||
{
|
||||
items[i].Parent = this;
|
||||
items[i].AddReference(this);
|
||||
_items.Add(items[i]);
|
||||
}
|
||||
}
|
||||
if (selection != null)
|
||||
{
|
||||
@@ -711,7 +714,7 @@ namespace FlaxEditor.Content.GUI
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
float width = GetClientArea().Width;
|
||||
float x = 0, y = 0;
|
||||
float x = 0, y = 1;
|
||||
float viewScale = _viewScale * 0.97f;
|
||||
|
||||
switch (ViewType)
|
||||
@@ -722,21 +725,22 @@ namespace FlaxEditor.Content.GUI
|
||||
int itemsToFit = Mathf.FloorToInt(width / defaultItemsWidth) - 1;
|
||||
if (itemsToFit < 1)
|
||||
itemsToFit = 1;
|
||||
float itemsWidth = width / Mathf.Max(itemsToFit, 1);
|
||||
int xSpace = 4;
|
||||
float itemsWidth = width / Mathf.Max(itemsToFit, 1) - xSpace;
|
||||
float itemsHeight = itemsWidth / defaultItemsWidth * (ContentItem.DefaultHeight * viewScale);
|
||||
var flooredItemsWidth = Mathf.Floor(itemsWidth);
|
||||
var flooredItemsHeight = Mathf.Floor(itemsHeight);
|
||||
x = itemsToFit == 1 ? 0 : itemsWidth / itemsToFit;
|
||||
x = itemsToFit == 1 ? 1 : itemsWidth / itemsToFit + xSpace;
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
var c = _children[i];
|
||||
c.Bounds = new Rectangle(Mathf.Floor(x), Mathf.Floor(y), flooredItemsWidth, flooredItemsHeight);
|
||||
|
||||
x += itemsWidth + itemsWidth / itemsToFit;
|
||||
x += (itemsWidth + xSpace) + (itemsWidth + xSpace) / itemsToFit;
|
||||
if (x + itemsWidth > width)
|
||||
{
|
||||
x = itemsToFit == 1 ? 0 : itemsWidth / itemsToFit;
|
||||
y += itemsHeight + 5;
|
||||
x = itemsToFit == 1 ? 1 : itemsWidth / itemsToFit + xSpace;
|
||||
y += itemsHeight + 7;
|
||||
}
|
||||
}
|
||||
if (x > 0)
|
||||
@@ -751,7 +755,7 @@ namespace FlaxEditor.Content.GUI
|
||||
{
|
||||
var c = _children[i];
|
||||
c.Bounds = new Rectangle(x, y, width, itemsHeight);
|
||||
y += itemsHeight + 5;
|
||||
y += itemsHeight + 1;
|
||||
}
|
||||
y += 40.0f;
|
||||
|
||||
|
||||
@@ -441,6 +441,9 @@ namespace FlaxEditor.Content
|
||||
{
|
||||
get
|
||||
{
|
||||
// Skip when hidden
|
||||
if (!Visible)
|
||||
return Rectangle.Empty;
|
||||
var view = Parent as ContentView;
|
||||
var size = Size;
|
||||
switch (view?.ViewType ?? ContentViewType.Tiles)
|
||||
@@ -483,6 +486,30 @@ namespace FlaxEditor.Content
|
||||
else
|
||||
Render2D.FillRectangle(rectangle, Color.Black);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the item thumbnail.
|
||||
/// </summary>
|
||||
/// <param name="rectangle">The thumbnail rectangle.</param>
|
||||
/// /// <param name="shadow">Whether or not to draw the shadow. Overrides DrawShadow.</param>
|
||||
public void DrawThumbnail(ref Rectangle rectangle, bool shadow)
|
||||
{
|
||||
// Draw shadow
|
||||
if (shadow)
|
||||
{
|
||||
const float thumbnailInShadowSize = 50.0f;
|
||||
var shadowRect = rectangle.MakeExpanded((DefaultThumbnailSize - thumbnailInShadowSize) * rectangle.Width / DefaultThumbnailSize * 1.3f);
|
||||
if (!_shadowIcon.IsValid)
|
||||
_shadowIcon = Editor.Instance.Icons.AssetShadow128;
|
||||
Render2D.DrawSprite(_shadowIcon, shadowRect);
|
||||
}
|
||||
|
||||
// Draw thumbnail
|
||||
if (_thumbnail.IsValid)
|
||||
Render2D.DrawSprite(_thumbnail, rectangle);
|
||||
else
|
||||
Render2D.FillRectangle(rectangle, Color.Black);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of references to that item.
|
||||
@@ -642,7 +669,6 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
// Cache data
|
||||
var size = Size;
|
||||
var style = Style.Current;
|
||||
var view = Parent as ContentView;
|
||||
@@ -655,9 +681,51 @@ namespace FlaxEditor.Content
|
||||
{
|
||||
case ContentViewType.Tiles:
|
||||
{
|
||||
var thumbnailSize = size.X - 2 * DefaultMarginSize;
|
||||
thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize);
|
||||
var thumbnailSize = size.X;
|
||||
thumbnailRect = new Rectangle(0, 0, thumbnailSize, thumbnailSize);
|
||||
nameAlignment = TextAlignment.Center;
|
||||
|
||||
if (this is ContentFolder)
|
||||
{
|
||||
// Small shadow
|
||||
var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1);
|
||||
var color = Color.Black.AlphaMultiplied(0.2f);
|
||||
Render2D.FillRectangle(shadowRect, color);
|
||||
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
|
||||
|
||||
if (isSelected)
|
||||
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
|
||||
else if (IsMouseOver)
|
||||
Render2D.FillRectangle(clientRect, style.BackgroundHighlighted);
|
||||
|
||||
DrawThumbnail(ref thumbnailRect, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Small shadow
|
||||
var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1);
|
||||
var color = Color.Black.AlphaMultiplied(0.2f);
|
||||
Render2D.FillRectangle(shadowRect, color);
|
||||
|
||||
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
|
||||
Render2D.FillRectangle(TextRectangle, style.LightBackground);
|
||||
|
||||
var accentHeight = 2 * view.ViewScale;
|
||||
var barRect = new Rectangle(0, thumbnailRect.Height - accentHeight, clientRect.Width, accentHeight);
|
||||
Render2D.FillRectangle(barRect, Color.DimGray);
|
||||
|
||||
DrawThumbnail(ref thumbnailRect, false);
|
||||
if (isSelected)
|
||||
{
|
||||
Render2D.FillRectangle(textRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
|
||||
Render2D.DrawRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
|
||||
}
|
||||
else if (IsMouseOver)
|
||||
{
|
||||
Render2D.FillRectangle(textRect, style.BackgroundHighlighted);
|
||||
Render2D.DrawRectangle(clientRect, style.BackgroundHighlighted);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ContentViewType.List:
|
||||
@@ -665,23 +733,21 @@ namespace FlaxEditor.Content
|
||||
var thumbnailSize = size.Y - 2 * DefaultMarginSize;
|
||||
thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize);
|
||||
nameAlignment = TextAlignment.Near;
|
||||
|
||||
if (isSelected)
|
||||
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
|
||||
else if (IsMouseOver)
|
||||
Render2D.FillRectangle(clientRect, style.BackgroundHighlighted);
|
||||
|
||||
DrawThumbnail(ref thumbnailRect);
|
||||
break;
|
||||
}
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// Draw background
|
||||
if (isSelected)
|
||||
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
|
||||
else if (IsMouseOver)
|
||||
Render2D.FillRectangle(clientRect, style.BackgroundHighlighted);
|
||||
|
||||
// Draw preview
|
||||
DrawThumbnail(ref thumbnailRect);
|
||||
|
||||
|
||||
// Draw short name
|
||||
Render2D.PushClip(ref textRect);
|
||||
Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 0.75f, 0.95f);
|
||||
Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace FlaxEditor.Content
|
||||
ShowFileExtension = true;
|
||||
}
|
||||
|
||||
private static string FilterScriptName(string input)
|
||||
internal static string FilterScriptName(string input)
|
||||
{
|
||||
var length = input.Length;
|
||||
var sb = new StringBuilder(length);
|
||||
|
||||
@@ -17,6 +17,6 @@ namespace FlaxEditor.Content
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string NavButtonLabel => string.Empty;
|
||||
public override string NavButtonLabel => " /";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
@@ -14,54 +17,853 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
[CustomEditor(typeof(Spline)), DefaultEditor]
|
||||
public class SplineEditor : ActorEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage undo spline data
|
||||
/// </summary>
|
||||
private struct UndoData
|
||||
{
|
||||
public Spline Spline;
|
||||
public BezierCurve<Transform>.Keyframe[] BeforeKeyframes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basis for creating tangent manipulation types for bezier curves.
|
||||
/// </summary>
|
||||
private class EditTangentOptionBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when user set selected tangent mode.
|
||||
/// </summary>
|
||||
/// <param name="spline">Current spline selected on editor viewport.</param>
|
||||
/// <param name="index">Index of current keyframe selected on spline.</param>
|
||||
public virtual void OnSetMode(Spline spline, int index)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when user select a keyframe (spline point) of current selected spline on editor viewport.
|
||||
/// </summary>
|
||||
/// <param name="spline">Current spline selected on editor viewport.</param>
|
||||
/// <param name="index">Index of current keyframe selected on spline.</param>
|
||||
public virtual void OnSelectKeyframe(Spline spline, int index)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when user select a tangent of current keyframe selected from spline.
|
||||
/// </summary>
|
||||
/// <param name="spline">Current spline selected on editor viewport.</param>
|
||||
/// <param name="index">Index of current keyframe selected on spline.</param>
|
||||
public virtual void OnSelectTangent(Spline spline, int index)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the tangent in from current keyframe selected from spline is moved on editor viewport.
|
||||
/// </summary>
|
||||
/// <param name="spline">Current spline selected on editor viewport.</param>
|
||||
/// <param name="index">Index of current keyframe selected on spline.</param>
|
||||
public virtual void OnMoveTangentIn(Spline spline, int index)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the tangent out from current keyframe selected from spline is moved on editor viewport.
|
||||
/// </summary>
|
||||
/// <param name="spline">Current spline selected on editor viewport.</param>
|
||||
/// <param name="index">Current spline selected on editor viewport.</param>
|
||||
public virtual void OnMoveTangentOut(Spline spline, int index)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit curve options manipulate the curve as free mode
|
||||
/// </summary>
|
||||
private sealed class FreeTangentMode : EditTangentOptionBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void OnSetMode(Spline spline, int index)
|
||||
{
|
||||
if (IsLinearTangentMode(spline, index) || IsSmoothInTangentMode(spline, index) || IsSmoothOutTangentMode(spline, index))
|
||||
{
|
||||
SetPointSmooth(spline, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit curve options to set tangents to linear
|
||||
/// </summary>
|
||||
private sealed class LinearTangentMode : EditTangentOptionBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void OnSetMode(Spline spline, int index)
|
||||
{
|
||||
SetKeyframeLinear(spline, index);
|
||||
|
||||
// change the selection to tangent parent (a spline point / keyframe)
|
||||
SetSelectSplinePointNode(spline, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit curve options to align tangents of selected spline
|
||||
/// </summary>
|
||||
private sealed class AlignedTangentMode : EditTangentOptionBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void OnSetMode(Spline spline, int index)
|
||||
{
|
||||
SmoothIfNotAligned(spline, index);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnSelectKeyframe(Spline spline, int index)
|
||||
{
|
||||
SmoothIfNotAligned(spline, index);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnMoveTangentIn(Spline spline, int index)
|
||||
{
|
||||
SetPointAligned(spline, index, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnMoveTangentOut(Spline spline, int index)
|
||||
{
|
||||
SetPointAligned(spline, index, false);
|
||||
}
|
||||
|
||||
private void SmoothIfNotAligned(Spline spline, int index)
|
||||
{
|
||||
if (!IsAlignedTangentMode(spline, index))
|
||||
{
|
||||
SetPointSmooth(spline, index);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPointAligned(Spline spline, int index, bool alignWithIn)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
var referenceTangent = alignWithIn ? keyframe.TangentIn : keyframe.TangentOut;
|
||||
var otherTangent = !alignWithIn ? keyframe.TangentIn : keyframe.TangentOut;
|
||||
|
||||
// inverse of reference tangent
|
||||
otherTangent.Translation = -referenceTangent.Translation.Normalized * otherTangent.Translation.Length;
|
||||
|
||||
if (alignWithIn)
|
||||
keyframe.TangentOut = otherTangent;
|
||||
if (!alignWithIn)
|
||||
keyframe.TangentIn = otherTangent;
|
||||
|
||||
spline.SetSplineKeyframe(index, keyframe);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit curve options manipulate the curve setting selected point
|
||||
/// tangent in as smoothed but tangent out as linear
|
||||
/// </summary>
|
||||
private sealed class SmoothInTangentMode : EditTangentOptionBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void OnSetMode(Spline spline, int index)
|
||||
{
|
||||
SetTangentSmoothIn(spline, index);
|
||||
SetSelectTangentIn(spline, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit curve options manipulate the curve setting selected point
|
||||
/// tangent in as linear but tangent out as smoothed
|
||||
/// </summary>
|
||||
private sealed class SmoothOutTangentMode : EditTangentOptionBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void OnSetMode(Spline spline, int index)
|
||||
{
|
||||
SetTangentSmoothOut(spline, index);
|
||||
SetSelectTangentOut(spline, index);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class IconTab : Tab
|
||||
{
|
||||
private sealed class IconTabHeader : Tabs.TabHeader
|
||||
{
|
||||
public IconTabHeader(Tabs tabs, Tab tab)
|
||||
: base(tabs, tab)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (EnabledInHierarchy && Tab.Enabled)
|
||||
((IconTab)Tab)._action();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var tab = (IconTab)Tab;
|
||||
var enabled = EnabledInHierarchy && tab.EnabledInHierarchy;
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
var size = Size;
|
||||
var textHeight = 16.0f;
|
||||
var iconSize = size.Y - textHeight;
|
||||
var iconRect = new Rectangle((Width - iconSize) / 2, 0, iconSize, iconSize);
|
||||
if (tab._mirrorIcon)
|
||||
{
|
||||
iconRect.Location.X += iconRect.Size.X;
|
||||
iconRect.Size.X *= -1;
|
||||
}
|
||||
var color = style.Foreground;
|
||||
if (!enabled)
|
||||
color *= 0.6f;
|
||||
Render2D.DrawSprite(tab._customIcon, iconRect, color);
|
||||
Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Action _action;
|
||||
private readonly string _customText;
|
||||
private readonly SpriteHandle _customIcon;
|
||||
private readonly bool _mirrorIcon;
|
||||
|
||||
public IconTab(Action action, string text, SpriteHandle icon, bool mirrorIcon = false)
|
||||
: base(string.Empty, SpriteHandle.Invalid)
|
||||
{
|
||||
_action = action;
|
||||
_customText = text;
|
||||
_customIcon = icon;
|
||||
_mirrorIcon = mirrorIcon;
|
||||
}
|
||||
|
||||
public override Tabs.TabHeader CreateHeader()
|
||||
{
|
||||
return new IconTabHeader((Tabs)Parent, this);
|
||||
}
|
||||
}
|
||||
|
||||
private EditTangentOptionBase _currentTangentMode;
|
||||
|
||||
private Tabs _selectedPointsTabs, _allPointsTabs;
|
||||
private Tab _freeTangentTab;
|
||||
private Tab _linearTangentTab;
|
||||
private Tab _alignedTangentTab;
|
||||
private Tab _smoothInTangentTab;
|
||||
private Tab _smoothOutTangentTab;
|
||||
private Tab _setLinearAllTangentsTab;
|
||||
private Tab _setSmoothAllTangentsTab;
|
||||
|
||||
private bool _tanInChanged;
|
||||
private bool _tanOutChanged;
|
||||
private Vector3 _lastTanInPos;
|
||||
private Vector3 _lastTanOutPos;
|
||||
private Spline _selectedSpline;
|
||||
private SplineNode.SplinePointNode _selectedPoint;
|
||||
private SplineNode.SplinePointNode _lastPointSelected;
|
||||
private SplineNode.SplinePointTangentNode _selectedTangentIn;
|
||||
private SplineNode.SplinePointTangentNode _selectedTangentOut;
|
||||
|
||||
private UndoData[] _selectedSplinesUndoData;
|
||||
|
||||
private bool HasPointSelected => _selectedPoint != null;
|
||||
private bool HasTangentsSelected => _selectedTangentIn != null || _selectedTangentOut != null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
if (Values.HasDifferentTypes == false)
|
||||
_currentTangentMode = new FreeTangentMode();
|
||||
if (Values.HasDifferentTypes || !(Values[0] is Spline spline))
|
||||
return;
|
||||
_selectedSpline = spline;
|
||||
|
||||
layout.Space(10);
|
||||
var tabSize = 46;
|
||||
var icons = Editor.Instance.Icons;
|
||||
|
||||
layout.Header("Selected spline point");
|
||||
_selectedPointsTabs = new Tabs
|
||||
{
|
||||
layout.Space(10);
|
||||
var grid = layout.CustomContainer<UniformGridPanel>();
|
||||
grid.CustomControl.SlotsHorizontally = 2;
|
||||
grid.CustomControl.SlotsVertically = 1;
|
||||
grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear;
|
||||
grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth;
|
||||
Height = tabSize,
|
||||
TabsSize = new Float2(tabSize),
|
||||
AutoTabsSize = true,
|
||||
Parent = layout.ContainerControl,
|
||||
};
|
||||
_linearTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedLinear, "Linear", icons.SplineLinear64));
|
||||
_freeTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedFree, "Free", icons.SplineFree64));
|
||||
_alignedTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedAligned, "Aligned", icons.SplineAligned64));
|
||||
_smoothInTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothIn, "Smooth In", icons.SplineSmoothIn64));
|
||||
_smoothOutTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothOut, "Smooth Out", icons.SplineSmoothIn64, true));
|
||||
_selectedPointsTabs.SelectedTabIndex = -1;
|
||||
|
||||
layout.Header("All spline points");
|
||||
_allPointsTabs = new Tabs
|
||||
{
|
||||
Height = tabSize,
|
||||
TabsSize = new Float2(tabSize),
|
||||
AutoTabsSize = true,
|
||||
Parent = layout.ContainerControl,
|
||||
};
|
||||
_setLinearAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsLinear, "Set Linear Tangents", icons.SplineLinear64));
|
||||
_setSmoothAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsSmooth, "Set Smooth Tangents", icons.SplineAligned64));
|
||||
_allPointsTabs.SelectedTabIndex = -1;
|
||||
|
||||
if (_selectedSpline)
|
||||
_selectedSpline.SplineUpdated += OnSplineEdited;
|
||||
|
||||
SetSelectedTangentTypeAsCurrent();
|
||||
UpdateEditTabsSelection();
|
||||
UpdateButtonsEnabled();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
if (_selectedSpline)
|
||||
_selectedSpline.SplineUpdated -= OnSplineEdited;
|
||||
}
|
||||
|
||||
private void OnSplineEdited()
|
||||
{
|
||||
UpdateEditTabsSelection();
|
||||
UpdateButtonsEnabled();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
UpdateSelectedPoint();
|
||||
UpdateSelectedTangent();
|
||||
|
||||
if (!CanEditTangent())
|
||||
return;
|
||||
|
||||
var index = _lastPointSelected.Index;
|
||||
var currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation;
|
||||
var currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation;
|
||||
|
||||
if (_selectedTangentIn != null)
|
||||
{
|
||||
_tanInChanged = _lastTanInPos != currentTangentInPosition;
|
||||
_lastTanInPos = currentTangentInPosition;
|
||||
}
|
||||
|
||||
if (_selectedTangentOut != null)
|
||||
{
|
||||
_tanOutChanged = _lastTanOutPos != currentTangentOutPosition;
|
||||
_lastTanOutPos = currentTangentOutPosition;
|
||||
}
|
||||
|
||||
if (_tanInChanged)
|
||||
_currentTangentMode.OnMoveTangentIn(_selectedSpline, index);
|
||||
if (_tanOutChanged)
|
||||
_currentTangentMode.OnMoveTangentOut(_selectedSpline, index);
|
||||
|
||||
currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation;
|
||||
currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation;
|
||||
|
||||
// Update last tangents position after changes
|
||||
if (_selectedSpline)
|
||||
_lastTanInPos = currentTangentInPosition;
|
||||
if (_selectedSpline)
|
||||
_lastTanOutPos = currentTangentOutPosition;
|
||||
_tanInChanged = false;
|
||||
_tanOutChanged = false;
|
||||
}
|
||||
|
||||
private void SetSelectedTangentTypeAsCurrent()
|
||||
{
|
||||
if (_lastPointSelected == null || _selectedPoint == null)
|
||||
return;
|
||||
if (IsLinearTangentMode(_selectedSpline, _lastPointSelected.Index))
|
||||
SetModeLinear();
|
||||
else if (IsAlignedTangentMode(_selectedSpline, _lastPointSelected.Index))
|
||||
SetModeAligned();
|
||||
else if (IsSmoothInTangentMode(_selectedSpline, _lastPointSelected.Index))
|
||||
SetModeSmoothIn();
|
||||
else if (IsSmoothOutTangentMode(_selectedSpline, _lastPointSelected.Index))
|
||||
SetModeSmoothOut();
|
||||
else if (IsFreeTangentMode(_selectedSpline, _lastPointSelected.Index))
|
||||
SetModeFree();
|
||||
}
|
||||
|
||||
private void UpdateEditTabsSelection()
|
||||
{
|
||||
_selectedPointsTabs.Enabled = CanEditTangent();
|
||||
if (!_selectedPointsTabs.Enabled)
|
||||
{
|
||||
_selectedPointsTabs.SelectedTabIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
var isFree = _currentTangentMode is FreeTangentMode;
|
||||
var isLinear = _currentTangentMode is LinearTangentMode;
|
||||
var isAligned = _currentTangentMode is AlignedTangentMode;
|
||||
var isSmoothIn = _currentTangentMode is SmoothInTangentMode;
|
||||
var isSmoothOut = _currentTangentMode is SmoothOutTangentMode;
|
||||
|
||||
if (isFree)
|
||||
_selectedPointsTabs.SelectedTab = _freeTangentTab;
|
||||
else if (isLinear)
|
||||
_selectedPointsTabs.SelectedTab = _linearTangentTab;
|
||||
else if (isAligned)
|
||||
_selectedPointsTabs.SelectedTab = _alignedTangentTab;
|
||||
else if (isSmoothIn)
|
||||
_selectedPointsTabs.SelectedTab = _smoothInTangentTab;
|
||||
else if (isSmoothOut)
|
||||
_selectedPointsTabs.SelectedTab = _smoothOutTangentTab;
|
||||
else
|
||||
_selectedPointsTabs.SelectedTabIndex = -1;
|
||||
}
|
||||
|
||||
private void UpdateButtonsEnabled()
|
||||
{
|
||||
_linearTangentTab.Enabled = CanEditTangent();
|
||||
_freeTangentTab.Enabled = CanEditTangent();
|
||||
_alignedTangentTab.Enabled = CanEditTangent();
|
||||
_smoothInTangentTab.Enabled = CanSetTangentSmoothIn();
|
||||
_smoothOutTangentTab.Enabled = CanSetTangentSmoothOut();
|
||||
_setLinearAllTangentsTab.Enabled = CanSetAllTangentsLinear();
|
||||
_setSmoothAllTangentsTab.Enabled = CanSetAllTangentsSmooth();
|
||||
}
|
||||
|
||||
private bool CanEditTangent()
|
||||
{
|
||||
return !HasDifferentTypes && !HasDifferentValues && (HasPointSelected || HasTangentsSelected);
|
||||
}
|
||||
|
||||
private bool CanSetTangentSmoothIn()
|
||||
{
|
||||
if (!CanEditTangent())
|
||||
return false;
|
||||
return _lastPointSelected.Index != 0;
|
||||
}
|
||||
|
||||
private bool CanSetTangentSmoothOut()
|
||||
{
|
||||
if (!CanEditTangent())
|
||||
return false;
|
||||
return _lastPointSelected.Index < _selectedSpline.SplinePointsCount - 1;
|
||||
}
|
||||
|
||||
private bool CanSetAllTangentsSmooth()
|
||||
{
|
||||
return _selectedSpline != null;
|
||||
}
|
||||
|
||||
private bool CanSetAllTangentsLinear()
|
||||
{
|
||||
return _selectedSpline != null;
|
||||
}
|
||||
|
||||
private void SetModeLinear()
|
||||
{
|
||||
if (_currentTangentMode is LinearTangentMode)
|
||||
return;
|
||||
_currentTangentMode = new LinearTangentMode();
|
||||
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
|
||||
}
|
||||
|
||||
private void SetModeFree()
|
||||
{
|
||||
if (_currentTangentMode is FreeTangentMode)
|
||||
return;
|
||||
_currentTangentMode = new FreeTangentMode();
|
||||
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
|
||||
}
|
||||
|
||||
private void SetModeAligned()
|
||||
{
|
||||
if (_currentTangentMode is AlignedTangentMode)
|
||||
return;
|
||||
_currentTangentMode = new AlignedTangentMode();
|
||||
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
|
||||
}
|
||||
|
||||
private void SetModeSmoothIn()
|
||||
{
|
||||
if (_currentTangentMode is SmoothInTangentMode)
|
||||
return;
|
||||
_currentTangentMode = new SmoothInTangentMode();
|
||||
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
|
||||
}
|
||||
|
||||
private void SetModeSmoothOut()
|
||||
{
|
||||
if (_currentTangentMode is SmoothOutTangentMode)
|
||||
return;
|
||||
_currentTangentMode = new SmoothOutTangentMode();
|
||||
_currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index);
|
||||
}
|
||||
|
||||
private void UpdateSelectedPoint()
|
||||
{
|
||||
// works only if select one spline
|
||||
if (_selectedSpline)
|
||||
{
|
||||
var currentSelected = Editor.Instance.SceneEditing.Selection[0];
|
||||
|
||||
if (currentSelected == _selectedPoint)
|
||||
return;
|
||||
if (currentSelected is SplineNode.SplinePointNode selectedPoint)
|
||||
{
|
||||
_selectedPoint = selectedPoint;
|
||||
_lastPointSelected = _selectedPoint;
|
||||
_currentTangentMode.OnSelectKeyframe(_selectedSpline, _lastPointSelected.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedPoint = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedPoint = null;
|
||||
}
|
||||
|
||||
SetSelectedTangentTypeAsCurrent();
|
||||
UpdateEditTabsSelection();
|
||||
UpdateButtonsEnabled();
|
||||
}
|
||||
|
||||
private void UpdateSelectedTangent()
|
||||
{
|
||||
// works only if select one spline
|
||||
if (_lastPointSelected == null || Editor.Instance.SceneEditing.SelectionCount != 1)
|
||||
{
|
||||
_selectedTangentIn = null;
|
||||
_selectedTangentOut = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var currentSelected = Editor.Instance.SceneEditing.Selection[0];
|
||||
|
||||
if (currentSelected is not SplineNode.SplinePointTangentNode selectedPoint)
|
||||
{
|
||||
_selectedTangentIn = null;
|
||||
_selectedTangentOut = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentSelected == _selectedTangentIn)
|
||||
return;
|
||||
if (currentSelected == _selectedTangentOut)
|
||||
return;
|
||||
|
||||
var index = _lastPointSelected.Index;
|
||||
|
||||
if (currentSelected.Transform == _selectedSpline.GetSplineTangent(index, true))
|
||||
{
|
||||
_selectedTangentIn = selectedPoint;
|
||||
_selectedTangentOut = null;
|
||||
_currentTangentMode.OnSelectTangent(_selectedSpline, index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentSelected.Transform == _selectedSpline.GetSplineTangent(index, false))
|
||||
{
|
||||
_selectedTangentOut = selectedPoint;
|
||||
_selectedTangentIn = null;
|
||||
_currentTangentMode.OnSelectTangent(_selectedSpline, index);
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedTangentIn = null;
|
||||
_selectedTangentOut = null;
|
||||
}
|
||||
|
||||
private void StartEditSpline()
|
||||
{
|
||||
if (Presenter.Undo != null && Presenter.Undo.Enabled)
|
||||
{
|
||||
// Capture 'before' state for undo
|
||||
var splines = new List<UndoData>();
|
||||
for (int i = 0; i < Values.Count; i++)
|
||||
{
|
||||
if (Values[i] is Spline spline)
|
||||
{
|
||||
splines.Add(new UndoData
|
||||
{
|
||||
Spline = spline,
|
||||
BeforeKeyframes = spline.SplineKeyframes.Clone() as BezierCurve<Transform>.Keyframe[]
|
||||
});
|
||||
}
|
||||
}
|
||||
_selectedSplinesUndoData = splines.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void EndEditSpline()
|
||||
{
|
||||
// Update buttons state
|
||||
UpdateEditTabsSelection();
|
||||
|
||||
if (Presenter.Undo != null && Presenter.Undo.Enabled)
|
||||
{
|
||||
// Add undo
|
||||
foreach (var splineUndoData in _selectedSplinesUndoData)
|
||||
{
|
||||
Presenter.Undo.AddAction(new EditSplineAction(_selectedSpline, splineUndoData.BeforeKeyframes));
|
||||
SplineNode.OnSplineEdited(splineUndoData.Spline);
|
||||
Editor.Instance.Scene.MarkSceneEdited(splineUndoData.Spline.Scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSetSelectedLinear()
|
||||
{
|
||||
StartEditSpline();
|
||||
SetModeLinear();
|
||||
EndEditSpline();
|
||||
}
|
||||
|
||||
private void OnSetSelectedFree()
|
||||
{
|
||||
StartEditSpline();
|
||||
SetModeFree();
|
||||
EndEditSpline();
|
||||
}
|
||||
|
||||
private void OnSetSelectedAligned()
|
||||
{
|
||||
StartEditSpline();
|
||||
SetModeAligned();
|
||||
EndEditSpline();
|
||||
}
|
||||
|
||||
private void OnSetSelectedSmoothIn()
|
||||
{
|
||||
StartEditSpline();
|
||||
SetModeSmoothIn();
|
||||
EndEditSpline();
|
||||
}
|
||||
|
||||
private void OnSetSelectedSmoothOut()
|
||||
{
|
||||
StartEditSpline();
|
||||
SetModeSmoothOut();
|
||||
EndEditSpline();
|
||||
}
|
||||
|
||||
private void OnSetTangentsLinear()
|
||||
{
|
||||
var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled;
|
||||
for (int i = 0; i < Values.Count; i++)
|
||||
{
|
||||
if (Values[i] is Spline spline)
|
||||
{
|
||||
var before = enableUndo ? (BezierCurve<Transform>.Keyframe[])spline.SplineKeyframes.Clone() : null;
|
||||
spline.SetTangentsLinear();
|
||||
if (enableUndo)
|
||||
Presenter.Undo.AddAction(new EditSplineAction(spline, before));
|
||||
SplineNode.OnSplineEdited(spline);
|
||||
Editor.Instance.Scene.MarkSceneEdited(spline.Scene);
|
||||
}
|
||||
}
|
||||
StartEditSpline();
|
||||
_selectedSpline.SetTangentsLinear();
|
||||
_selectedSpline.UpdateSpline();
|
||||
EndEditSpline();
|
||||
}
|
||||
|
||||
private void OnSetTangentsSmooth()
|
||||
{
|
||||
var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled;
|
||||
for (int i = 0; i < Values.Count; i++)
|
||||
StartEditSpline();
|
||||
_selectedSpline.SetTangentsSmooth();
|
||||
_selectedSpline.UpdateSpline();
|
||||
EndEditSpline();
|
||||
}
|
||||
|
||||
private static bool IsFreeTangentMode(Spline spline, int index)
|
||||
{
|
||||
if (IsLinearTangentMode(spline, index) ||
|
||||
IsAlignedTangentMode(spline, index) ||
|
||||
IsSmoothInTangentMode(spline, index) ||
|
||||
IsSmoothOutTangentMode(spline, index))
|
||||
{
|
||||
if (Values[i] is Spline spline)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsLinearTangentMode(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
return keyframe.TangentIn.Translation.Length == 0 && keyframe.TangentOut.Translation.Length == 0;
|
||||
}
|
||||
|
||||
private static bool IsAlignedTangentMode(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
var tangentIn = keyframe.TangentIn.Translation;
|
||||
var tangentOut = keyframe.TangentOut.Translation;
|
||||
if (tangentIn.Length == 0 || tangentOut.Length == 0)
|
||||
return false;
|
||||
|
||||
var angleBetweenTwoTangents = Vector3.Dot(tangentIn.Normalized, tangentOut.Normalized);
|
||||
if (angleBetweenTwoTangents < -0.99f)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsSmoothInTangentMode(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
return keyframe.TangentIn.Translation.Length > 0 && keyframe.TangentOut.Translation.Length == 0;
|
||||
}
|
||||
|
||||
private static bool IsSmoothOutTangentMode(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
return keyframe.TangentOut.Translation.Length > 0 && keyframe.TangentIn.Translation.Length == 0;
|
||||
}
|
||||
|
||||
private static void SetKeyframeLinear(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
keyframe.TangentIn.Translation = Vector3.Zero;
|
||||
keyframe.TangentOut.Translation = Vector3.Zero;
|
||||
|
||||
var lastSplineIndex = spline.SplinePointsCount - 1;
|
||||
if (index == lastSplineIndex && spline.IsLoop)
|
||||
{
|
||||
var lastPoint = spline.GetSplineKeyframe(lastSplineIndex);
|
||||
lastPoint.TangentIn.Translation = Vector3.Zero;
|
||||
lastPoint.TangentOut.Translation = Vector3.Zero;
|
||||
spline.SetSplineKeyframe(lastSplineIndex, lastPoint);
|
||||
}
|
||||
|
||||
spline.SetSplineKeyframe(index, keyframe);
|
||||
spline.UpdateSpline();
|
||||
}
|
||||
|
||||
private static void SetTangentSmoothIn(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
|
||||
// Auto smooth tangent if's linear
|
||||
if (keyframe.TangentIn.Translation.Length == 0)
|
||||
{
|
||||
var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f);
|
||||
var previousKeyframe = spline.GetSplineKeyframe(index - 1);
|
||||
var tangentDirection = keyframe.Value.WorldToLocalVector(previousKeyframe.Value.Translation - keyframe.Value.Translation);
|
||||
tangentDirection = tangentDirection.Normalized * smoothRange;
|
||||
keyframe.TangentIn.Translation = tangentDirection;
|
||||
}
|
||||
|
||||
keyframe.TangentOut.Translation = Vector3.Zero;
|
||||
spline.SetSplineKeyframe(index, keyframe);
|
||||
spline.UpdateSpline();
|
||||
}
|
||||
|
||||
private static void SetTangentSmoothOut(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
|
||||
// Auto smooth tangent if's linear
|
||||
if (keyframe.TangentOut.Translation.Length == 0)
|
||||
{
|
||||
var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f);
|
||||
var nextKeyframe = spline.GetSplineKeyframe(index + 1);
|
||||
var tangentDirection = keyframe.Value.WorldToLocalVector(nextKeyframe.Value.Translation - keyframe.Value.Translation);
|
||||
tangentDirection = tangentDirection.Normalized * smoothRange;
|
||||
keyframe.TangentOut.Translation = tangentDirection;
|
||||
}
|
||||
|
||||
keyframe.TangentIn.Translation = Vector3.Zero;
|
||||
|
||||
spline.SetSplineKeyframe(index, keyframe);
|
||||
spline.UpdateSpline();
|
||||
}
|
||||
|
||||
private static void SetPointSmooth(Spline spline, int index)
|
||||
{
|
||||
var keyframe = spline.GetSplineKeyframe(index);
|
||||
var tangentInSize = keyframe.TangentIn.Translation.Length;
|
||||
var tangentOutSize = keyframe.TangentOut.Translation.Length;
|
||||
|
||||
var isLastKeyframe = index >= spline.SplinePointsCount - 1;
|
||||
var isFirstKeyframe = index <= 0;
|
||||
var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplinePoint(index), 10f);
|
||||
|
||||
// Force smooth it's linear point
|
||||
if (tangentInSize == 0f)
|
||||
tangentInSize = smoothRange;
|
||||
if (tangentOutSize == 0f)
|
||||
tangentOutSize = smoothRange;
|
||||
|
||||
// Try get next / last keyframe
|
||||
var nextKeyframe = !isLastKeyframe ? spline.GetSplineKeyframe(index + 1) : keyframe;
|
||||
var previousKeyframe = !isFirstKeyframe ? spline.GetSplineKeyframe(index - 1) : keyframe;
|
||||
|
||||
// calc form from Spline.cpp -> SetTangentsSmooth
|
||||
// get tangent direction
|
||||
var tangentDirection = (keyframe.Value.Translation - previousKeyframe.Value.Translation + nextKeyframe.Value.Translation - keyframe.Value.Translation).Normalized;
|
||||
|
||||
keyframe.TangentIn.Translation = -tangentDirection;
|
||||
keyframe.TangentOut.Translation = tangentDirection;
|
||||
|
||||
keyframe.TangentIn.Translation *= tangentInSize;
|
||||
keyframe.TangentOut.Translation *= tangentOutSize;
|
||||
|
||||
spline.SetSplineKeyframe(index, keyframe);
|
||||
spline.UpdateSpline();
|
||||
}
|
||||
|
||||
private static SplineNode.SplinePointNode GetSplinePointNode(Spline spline, int index)
|
||||
{
|
||||
return (SplineNode.SplinePointNode)SceneGraphFactory.FindNode(spline.ID).ChildNodes[index];
|
||||
}
|
||||
|
||||
private static SplineNode.SplinePointTangentNode GetSplineTangentInNode(Spline spline, int index)
|
||||
{
|
||||
var point = GetSplinePointNode(spline, index);
|
||||
var tangentIn = spline.GetSplineTangent(index, true);
|
||||
var tangentNodes = point.ChildNodes;
|
||||
|
||||
// find tangent in node comparing all child nodes position
|
||||
for (int i = 0; i < tangentNodes.Count; i++)
|
||||
{
|
||||
if (tangentNodes[i].Transform.Translation == tangentIn.Translation)
|
||||
{
|
||||
var before = enableUndo ? (BezierCurve<Transform>.Keyframe[])spline.SplineKeyframes.Clone() : null;
|
||||
spline.SetTangentsSmooth();
|
||||
if (enableUndo)
|
||||
Presenter.Undo.AddAction(new EditSplineAction(spline, before));
|
||||
SplineNode.OnSplineEdited(spline);
|
||||
Editor.Instance.Scene.MarkSceneEdited(spline.Scene);
|
||||
return (SplineNode.SplinePointTangentNode)tangentNodes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static SplineNode.SplinePointTangentNode GetSplineTangentOutNode(Spline spline, int index)
|
||||
{
|
||||
var point = GetSplinePointNode(spline, index);
|
||||
var tangentOut = spline.GetSplineTangent(index, false);
|
||||
var tangentNodes = point.ChildNodes;
|
||||
|
||||
// find tangent out node comparing all child nodes position
|
||||
for (int i = 0; i < tangentNodes.Count; i++)
|
||||
{
|
||||
if (tangentNodes[i].Transform.Translation == tangentOut.Translation)
|
||||
{
|
||||
return (SplineNode.SplinePointTangentNode)tangentNodes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void SetSelectSplinePointNode(Spline spline, int index)
|
||||
{
|
||||
Editor.Instance.SceneEditing.Select(GetSplinePointNode(spline, index));
|
||||
}
|
||||
|
||||
private static void SetSelectTangentIn(Spline spline, int index)
|
||||
{
|
||||
Editor.Instance.SceneEditing.Select(GetSplineTangentInNode(spline, index));
|
||||
}
|
||||
|
||||
private static void SetSelectTangentOut(Spline spline, int index)
|
||||
{
|
||||
Editor.Instance.SceneEditing.Select(GetSplineTangentOutNode(spline, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +123,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
if (tree.IsLayoutLocked)
|
||||
return;
|
||||
root.LockChildrenRecursive();
|
||||
tree.LockChildrenRecursive();
|
||||
Utilities.Utils.UpdateSearchPopupFilter(root, searchBox.Text);
|
||||
root.UnlockChildrenRecursive();
|
||||
tree.UnlockChildrenRecursive();
|
||||
menu.PerformLayout();
|
||||
};
|
||||
root.ExpandAll(true);
|
||||
|
||||
122
Source/Editor/CustomEditors/Editors/InputEditor.cs
Normal file
122
Source/Editor/CustomEditors/Editors/InputEditor.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit input event properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(InputEvent)), DefaultEditor]
|
||||
public class InputEventEditor : CustomEditor
|
||||
{
|
||||
private Dropdown _dropdown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var dropdownElement = layout.Custom<Dropdown>();
|
||||
_dropdown = dropdownElement.CustomControl;
|
||||
var names = new List<LocalizedString>();
|
||||
foreach (var mapping in Input.ActionMappings)
|
||||
{
|
||||
if (!names.Contains(mapping.Name))
|
||||
names.Add(mapping.Name);
|
||||
}
|
||||
_dropdown.Items = names;
|
||||
if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name))
|
||||
_dropdown.SelectedItem = inputEvent.Name;
|
||||
_dropdown.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(Dropdown dropdown)
|
||||
{
|
||||
SetValue(new InputEvent(dropdown.SelectedItem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Values[0] is InputEvent inputEvent && _dropdown.Items.Contains(inputEvent.Name))
|
||||
_dropdown.SelectedItem = inputEvent.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
if (_dropdown != null)
|
||||
_dropdown.SelectedIndexChanged -= OnSelectedIndexChanged;
|
||||
_dropdown = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit input axis properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(InputAxis)), DefaultEditor]
|
||||
public class InputAxisEditor : CustomEditor
|
||||
{
|
||||
private Dropdown _dropdown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
var dropdownElement = layout.Custom<Dropdown>();
|
||||
_dropdown = dropdownElement.CustomControl;
|
||||
var names = new List<LocalizedString>();
|
||||
foreach (var mapping in Input.AxisMappings)
|
||||
{
|
||||
if (!names.Contains(mapping.Name))
|
||||
names.Add(mapping.Name);
|
||||
}
|
||||
_dropdown.Items = names;
|
||||
if (Values[0] is InputAxis inputAxis && names.Contains(inputAxis.Name))
|
||||
_dropdown.SelectedItem = inputAxis.Name;
|
||||
_dropdown.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(Dropdown dropdown)
|
||||
{
|
||||
SetValue(new InputAxis(dropdown.SelectedItem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (HasDifferentValues)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Values[0] is InputAxis inputAxis && _dropdown.Items.Contains(inputAxis.Name))
|
||||
_dropdown.SelectedItem = inputAxis.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
if (_dropdown != null)
|
||||
_dropdown.SelectedIndexChanged -= OnSelectedIndexChanged;
|
||||
_dropdown = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
_entryIndex = entryIndex;
|
||||
_modelInstance = modelInstance;
|
||||
var slots = modelInstance.MaterialSlots;
|
||||
if (slots == null || entryIndex >= slots.Length)
|
||||
return;
|
||||
if (entry.Material == slots[entryIndex].Material)
|
||||
{
|
||||
// Ensure that entry with default material set is set back to null
|
||||
|
||||
@@ -593,9 +593,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
if (tree.IsLayoutLocked)
|
||||
return;
|
||||
root.LockChildrenRecursive();
|
||||
tree.LockChildrenRecursive();
|
||||
Utilities.Utils.UpdateSearchPopupFilter(root, searchBox.Text);
|
||||
root.UnlockChildrenRecursive();
|
||||
tree.UnlockChildrenRecursive();
|
||||
menu.PerformLayout();
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ public class Editor : EditorModule
|
||||
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
|
||||
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
|
||||
options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
|
||||
options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile");
|
||||
|
||||
// Enable optimizations for Editor, disable this for debugging the editor
|
||||
if (options.Configuration == TargetConfiguration.Development)
|
||||
|
||||
@@ -97,6 +97,10 @@ namespace FlaxEditor
|
||||
public SpriteHandle Build64;
|
||||
public SpriteHandle Add64;
|
||||
public SpriteHandle ShipIt64;
|
||||
public SpriteHandle SplineFree64;
|
||||
public SpriteHandle SplineLinear64;
|
||||
public SpriteHandle SplineAligned64;
|
||||
public SpriteHandle SplineSmoothIn64;
|
||||
|
||||
// 96px
|
||||
public SpriteHandle Toolbox96;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -396,14 +397,24 @@ namespace FlaxEditor.GUI
|
||||
_popupMenu.ButtonClicked += btn =>
|
||||
{
|
||||
OnItemClicked((int)btn.Tag);
|
||||
_popupMenu?.Hide();
|
||||
if (SupportMultiSelect)
|
||||
{
|
||||
// Don't hide in multi-select, so user can edit multiple elements instead of just one
|
||||
UpdateButtons();
|
||||
_popupMenu?.PerformLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
_popupMenu?.Hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check if menu hs been already shown
|
||||
if (_popupMenu.Visible)
|
||||
{
|
||||
_popupMenu.Hide();
|
||||
if (!SupportMultiSelect)
|
||||
_popupMenu.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -412,27 +423,7 @@ namespace FlaxEditor.GUI
|
||||
// Check if has any items
|
||||
if (_items.Count > 0)
|
||||
{
|
||||
// Setup items list
|
||||
var itemControls = _popupMenu.Items.ToArray();
|
||||
foreach (var e in itemControls)
|
||||
e.Dispose();
|
||||
if (Sorted)
|
||||
_items.Sort();
|
||||
var style = Style.Current;
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
{
|
||||
var btn = _popupMenu.AddButton(_items[i]);
|
||||
if (_selectedIndices.Contains(i))
|
||||
{
|
||||
btn.Icon = style.CheckBoxTick;
|
||||
}
|
||||
if (_tooltips != null && _tooltips.Length > i)
|
||||
{
|
||||
btn.TooltipText = _tooltips[i];
|
||||
}
|
||||
|
||||
btn.Tag = i;
|
||||
}
|
||||
UpdateButtons();
|
||||
|
||||
// Show dropdown list
|
||||
_popupMenu.MinimumWidth = Width;
|
||||
@@ -440,6 +431,54 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates buttons layout.
|
||||
/// </summary>
|
||||
private void UpdateButtons()
|
||||
{
|
||||
if (_popupMenu.Items.Count() != _items.Count)
|
||||
{
|
||||
var itemControls = _popupMenu.Items.ToArray();
|
||||
foreach (var e in itemControls)
|
||||
e.Dispose();
|
||||
if (Sorted)
|
||||
_items.Sort();
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
{
|
||||
var btn = _popupMenu.AddButton(_items[i]);
|
||||
OnLayoutMenuButton(btn, i, true);
|
||||
btn.Tag = i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var itemControls = _popupMenu.Items.ToArray();
|
||||
if (Sorted)
|
||||
_items.Sort();
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
{
|
||||
if (itemControls[i] is ContextMenuButton btn)
|
||||
{
|
||||
btn.Text = _items[i];
|
||||
OnLayoutMenuButton(btn, i, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when button is created or updated. Can be used to customize the visuals.
|
||||
/// </summary>
|
||||
/// <param name="button">The button.</param>
|
||||
/// <param name="index">The item index.</param>
|
||||
/// <param name="construct">true if button is created else it is repainting the button</param>
|
||||
protected virtual void OnLayoutMenuButton(ContextMenuButton button, int index, bool construct = false)
|
||||
{
|
||||
button.Checked = _selectedIndices.Contains(index);
|
||||
if (_tooltips != null && _tooltips.Length > index)
|
||||
button.TooltipText = _tooltips[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the popup menu.
|
||||
/// </summary>
|
||||
@@ -460,6 +499,8 @@ namespace FlaxEditor.GUI
|
||||
_popupMenu = null;
|
||||
}
|
||||
|
||||
if (IsDisposing)
|
||||
return;
|
||||
_selectedIndices.Clear();
|
||||
_selectedIndices = null;
|
||||
_items.Clear();
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
|
||||
// Draw icon
|
||||
const float iconSize = 14;
|
||||
var icon = Checked ? Style.Current.CheckBoxTick : Icon;
|
||||
var icon = Checked ? style.CheckBoxTick : Icon;
|
||||
if (icon.IsValid)
|
||||
Render2D.DrawSprite(icon, new Rectangle(-iconSize - 1, (Height - iconSize) / 2, iconSize, iconSize), textColor);
|
||||
}
|
||||
|
||||
@@ -304,6 +304,17 @@ namespace FlaxEditor.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
// Fallback to the edit window for shortcuts
|
||||
var editor = Editor.Instance;
|
||||
return editor.Windows.EditWin.InputActions.Process(editor, this, key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutAfterChildren()
|
||||
{
|
||||
|
||||
@@ -50,14 +50,9 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
if (toolstrip == null)
|
||||
return;
|
||||
|
||||
var lastToolstripButton = toolstrip.LastButton;
|
||||
var parentSize = Parent.Size;
|
||||
Bounds = new Rectangle
|
||||
(
|
||||
new Float2(lastToolstripButton.Right + 8.0f, 0),
|
||||
new Float2(parentSize.X - X - 8.0f, toolstrip.Height)
|
||||
);
|
||||
Bounds = new Rectangle(lastToolstripButton.Right + 8.0f, 0, parentSize.X - X - 8.0f, toolstrip.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,5 +77,14 @@ namespace FlaxEditor.GUI.Tabs
|
||||
{
|
||||
Deselected?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory method for tabs header control (UI for the tabs strip to represent this tab).
|
||||
/// </summary>
|
||||
/// <returns>The tab header control.</returns>
|
||||
public virtual Tabs.TabHeader CreateHeader()
|
||||
{
|
||||
return new Tabs.TabHeader((Tabs)Parent, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,6 @@ namespace FlaxEditor.GUI.Tabs
|
||||
/// </summary>
|
||||
protected Tabs Tabs;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the tab.
|
||||
/// </summary>
|
||||
protected int Index;
|
||||
|
||||
/// <summary>
|
||||
/// The tab.
|
||||
/// </summary>
|
||||
@@ -38,21 +33,22 @@ namespace FlaxEditor.GUI.Tabs
|
||||
/// Initializes a new instance of the <see cref="TabHeader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tabs">The tabs.</param>
|
||||
/// <param name="index">The tab index.</param>
|
||||
/// <param name="tab">The tab.</param>
|
||||
public TabHeader(Tabs tabs, int index, Tab tab)
|
||||
public TabHeader(Tabs tabs, Tab tab)
|
||||
: base(Float2.Zero, tabs._tabsSize)
|
||||
{
|
||||
Tabs = tabs;
|
||||
Index = index;
|
||||
Tab = tab;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
Tabs.SelectedTabIndex = Index;
|
||||
Tabs.Focus();
|
||||
if (EnabledInHierarchy && Tab.Enabled)
|
||||
{
|
||||
Tabs.SelectedTab = Tab;
|
||||
Tabs.Focus();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,19 +57,20 @@ namespace FlaxEditor.GUI.Tabs
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Cache data
|
||||
var style = Style.Current;
|
||||
var enabled = EnabledInHierarchy && Tab.EnabledInHierarchy;
|
||||
var tabRect = new Rectangle(Float2.Zero, Size);
|
||||
bool isTabSelected = Tabs._selectedIndex == Index;
|
||||
bool isMouseOverTab = IsMouseOver;
|
||||
var textOffset = Tabs._orientation == Orientation.Horizontal ? 0 : 8;
|
||||
|
||||
// Draw bar
|
||||
if (isTabSelected)
|
||||
if (Tabs.SelectedTab == Tab)
|
||||
{
|
||||
var color = style.BackgroundSelected;
|
||||
if (!enabled)
|
||||
color *= 0.6f;
|
||||
if (Tabs._orientation == Orientation.Horizontal)
|
||||
{
|
||||
Render2D.FillRectangle(tabRect, style.BackgroundSelected);
|
||||
Render2D.FillRectangle(tabRect, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -84,10 +81,10 @@ namespace FlaxEditor.GUI.Tabs
|
||||
fillRect.Size.X -= lefEdgeWidth;
|
||||
fillRect.Location.X += lefEdgeWidth;
|
||||
Render2D.FillRectangle(fillRect, style.Background);
|
||||
Render2D.FillRectangle(leftEdgeRect, style.BackgroundSelected);
|
||||
Render2D.FillRectangle(leftEdgeRect, color);
|
||||
}
|
||||
}
|
||||
else if (isMouseOverTab)
|
||||
else if (IsMouseOver && enabled)
|
||||
{
|
||||
Render2D.FillRectangle(tabRect, style.BackgroundHighlighted);
|
||||
}
|
||||
@@ -131,20 +128,15 @@ namespace FlaxEditor.GUI.Tabs
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
|
||||
// Cache data
|
||||
var tabsSize = Tabs._tabsSize;
|
||||
var clientSize = GetClientArea();
|
||||
tabsSize = Float2.Min(tabsSize, clientSize.Size);
|
||||
var tabRect = new Rectangle(Float2.Zero, tabsSize);
|
||||
var tabStripOffset = Tabs._orientation == Orientation.Horizontal ? new Float2(tabsSize.X, 0) : new Float2(0, tabsSize.Y);
|
||||
|
||||
// Arrange tab header controls
|
||||
var pos = Float2.Zero;
|
||||
var sizeMask = Tabs._orientation == Orientation.Horizontal ? Float2.UnitX : Float2.UnitY;
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is TabHeader tabHeader)
|
||||
{
|
||||
tabHeader.Bounds = tabRect;
|
||||
tabRect.Offset(tabStripOffset);
|
||||
tabHeader.Location = pos;
|
||||
pos += tabHeader.Size * sizeMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +152,11 @@ namespace FlaxEditor.GUI.Tabs
|
||||
/// </summary>
|
||||
protected Float2 _tabsSize;
|
||||
|
||||
/// <summary>
|
||||
/// Automatic tab size based on the fill axis.
|
||||
/// </summary>
|
||||
protected bool _autoTabsSizeAuto;
|
||||
|
||||
/// <summary>
|
||||
/// The orientation.
|
||||
/// </summary>
|
||||
@@ -174,15 +171,31 @@ namespace FlaxEditor.GUI.Tabs
|
||||
set
|
||||
{
|
||||
_tabsSize = value;
|
||||
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
|
||||
if (!_autoTabsSizeAuto)
|
||||
{
|
||||
if (TabsPanel.Children[i] is TabHeader tabHeader)
|
||||
tabHeader.Size = value;
|
||||
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
|
||||
{
|
||||
if (TabsPanel.Children[i] is TabHeader tabHeader)
|
||||
tabHeader.Size = value;
|
||||
}
|
||||
}
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables automatic tabs size to fill the space.
|
||||
/// </summary>
|
||||
public bool AutoTabsSize
|
||||
{
|
||||
get => _autoTabsSizeAuto;
|
||||
set
|
||||
{
|
||||
_autoTabsSizeAuto = value;
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the orientation.
|
||||
/// </summary>
|
||||
@@ -339,14 +352,10 @@ namespace FlaxEditor.GUI.Tabs
|
||||
|
||||
// Update tabs headers
|
||||
TabsPanel.DisposeChildren();
|
||||
int index = 0;
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is Tab tab)
|
||||
{
|
||||
var tabHeader = new TabHeader(this, index++, tab);
|
||||
TabsPanel.AddChild(tabHeader);
|
||||
}
|
||||
TabsPanel.AddChild(tab.CreateHeader());
|
||||
}
|
||||
|
||||
TabsPanel.IsLayoutLocked = wasLocked;
|
||||
@@ -371,23 +380,46 @@ namespace FlaxEditor.GUI.Tabs
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
var tabsSize = _tabsSize;
|
||||
if (_autoTabsSizeAuto)
|
||||
{
|
||||
// Horizontal is default for Toolbox so tabs go to the right
|
||||
int tabsCount = 0;
|
||||
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
|
||||
{
|
||||
if (TabsPanel.Children[i] is TabHeader)
|
||||
tabsCount++;
|
||||
}
|
||||
if (tabsCount == 0)
|
||||
tabsCount = 1;
|
||||
if (_orientation == Orientation.Horizontal)
|
||||
tabsSize.X = Width / tabsCount;
|
||||
else
|
||||
tabsSize.Y = Height / tabsCount;
|
||||
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
|
||||
{
|
||||
if (TabsPanel.Children[i] is TabHeader tabHeader)
|
||||
tabHeader.Size = tabsSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Fit the tabs panel
|
||||
TabsPanel.Size = _orientation == Orientation.Horizontal ? new Float2(Width, _tabsSize.Y) : new Float2(_tabsSize.X, Height);
|
||||
TabsPanel.Size = _orientation == Orientation.Horizontal
|
||||
? new Float2(Width, tabsSize.Y)
|
||||
: new Float2(tabsSize.X, Height);
|
||||
|
||||
// Hide all pages except selected one
|
||||
var clientArea = _orientation == Orientation.Horizontal
|
||||
? new Rectangle(0, _tabsSize.Y, Width, Height - _tabsSize.Y)
|
||||
: new Rectangle(_tabsSize.X, 0, Width - _tabsSize.X, Height);
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
if (Children[i] is Tab tab)
|
||||
{
|
||||
// Check if is selected or not
|
||||
if (i - 1 == _selectedIndex)
|
||||
{
|
||||
// Show and fit size
|
||||
tab.Visible = true;
|
||||
tab.Bounds = clientArea;
|
||||
tab.Bounds = _orientation == Orientation.Horizontal
|
||||
? new Rectangle(0, tabsSize.Y, Width, Height - tabsSize.Y)
|
||||
: new Rectangle(tabsSize.X, 0, Width - tabsSize.X, Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -181,5 +182,25 @@ namespace FlaxEditor.GUI
|
||||
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
// Fallback to the owning window for shortcuts
|
||||
EditorWindow editorWindow = null;
|
||||
ContainerControl c = Parent;
|
||||
while (c != null && editorWindow == null)
|
||||
{
|
||||
editorWindow = c as EditorWindow;
|
||||
c = c.Parent;
|
||||
}
|
||||
var editor = Editor.Instance;
|
||||
if (editorWindow == null)
|
||||
editorWindow = editor.Windows.EditWin; // Fallback to main editor window
|
||||
return editorWindow.InputActions.Process(editor, this, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,6 +762,10 @@ namespace FlaxEditor.GUI.Tree
|
||||
// Add/Remove
|
||||
tree.AddOrRemoveSelection(this);
|
||||
}
|
||||
else if (button == MouseButton.Right && tree.Selection.Contains(this))
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select
|
||||
|
||||
@@ -902,6 +902,23 @@ namespace FlaxEditor.Modules
|
||||
}
|
||||
if (sortChildren)
|
||||
node.SortChildren();
|
||||
|
||||
// Ignore some special folders
|
||||
if (node is MainContentTreeNode mainNode && mainNode.Folder.ShortName == "Source")
|
||||
{
|
||||
var mainNodeChild = mainNode.Folder.Find(StringUtils.CombinePaths(mainNode.Path, "obj")) as ContentFolder;
|
||||
if (mainNodeChild != null)
|
||||
{
|
||||
mainNodeChild.Visible = false;
|
||||
mainNodeChild.Node.Visible = false;
|
||||
}
|
||||
mainNodeChild = mainNode.Folder.Find(StringUtils.CombinePaths(mainNode.Path, "Properties")) as ContentFolder;
|
||||
if (mainNodeChild != null)
|
||||
{
|
||||
mainNodeChild.Visible = false;
|
||||
mainNodeChild.Node.Visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadScripts(ContentTreeNode parent, string[] files)
|
||||
|
||||
@@ -166,6 +166,8 @@ namespace FlaxEditor.Modules
|
||||
public void ShowFinder(Control control)
|
||||
{
|
||||
var finder = _finder ?? (_finder = new ContentFinder());
|
||||
if (control == null)
|
||||
control = Editor.Instance.Windows.MainWindow.GUI;
|
||||
var position = (control.Size - new Float2(finder.Width, 300.0f)) * 0.5f;
|
||||
finder.Show(control, position);
|
||||
}
|
||||
|
||||
@@ -591,7 +591,7 @@ namespace FlaxEditor.Modules
|
||||
private void OnActorNameChanged(Actor actor)
|
||||
{
|
||||
ActorNode node = GetActorNode(actor);
|
||||
node?.TreeNode.OnNameChanged();
|
||||
node?.TreeNode.UpdateText();
|
||||
}
|
||||
|
||||
private void OnActorActiveChanged(Actor actor)
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
[HideInEditor]
|
||||
public sealed class SplineNode : ActorNode
|
||||
{
|
||||
private sealed class SplinePointNode : ActorChildNode<SplineNode>
|
||||
internal sealed class SplinePointNode : ActorChildNode<SplineNode>
|
||||
{
|
||||
public unsafe SplinePointNode(SplineNode node, Guid id, int index)
|
||||
: base(node, id, index)
|
||||
@@ -167,10 +167,11 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
|
||||
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
|
||||
{
|
||||
normal = -ray.Ray.Direction;
|
||||
var actor = (Spline)_node.Actor;
|
||||
var pos = actor.GetSplinePoint(Index);
|
||||
normal = -ray.Ray.Direction;
|
||||
return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance);
|
||||
var nodeSize = NodeSizeByDistance(Transform.Translation, PointNodeSize);
|
||||
return new BoundingSphere(pos, nodeSize).Intersects(ref ray.Ray, out distance);
|
||||
}
|
||||
|
||||
public override void OnDebugDraw(ViewportDebugDrawData data)
|
||||
@@ -179,23 +180,26 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
var pos = actor.GetSplinePoint(Index);
|
||||
var tangentIn = actor.GetSplineTangent(Index, true).Translation;
|
||||
var tangentOut = actor.GetSplineTangent(Index, false).Translation;
|
||||
var pointSize = NodeSizeByDistance(pos, PointNodeSize);
|
||||
var tangentInSize = NodeSizeByDistance(tangentIn, TangentNodeSize);
|
||||
var tangentOutSize = NodeSizeByDistance(tangentOut, TangentNodeSize);
|
||||
|
||||
// Draw spline path
|
||||
ParentNode.OnDebugDraw(data);
|
||||
|
||||
// Draw selected point highlight
|
||||
DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false);
|
||||
DebugDraw.DrawSphere(new BoundingSphere(pos, pointSize), Color.Yellow, 0, false);
|
||||
|
||||
// Draw tangent points
|
||||
if (tangentIn != pos)
|
||||
{
|
||||
DebugDraw.DrawLine(pos, tangentIn, Color.Blue.AlphaMultiplied(0.6f), 0, false);
|
||||
DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.Blue, 0, false);
|
||||
DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, tangentInSize), Color.Blue, 0, false);
|
||||
}
|
||||
if (tangentOut != pos)
|
||||
{
|
||||
DebugDraw.DrawLine(pos, tangentOut, Color.Red.AlphaMultiplied(0.6f), 0, false);
|
||||
DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.Red, 0, false);
|
||||
DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, tangentOutSize), Color.Red, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +223,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SplinePointTangentNode : ActorChildNode
|
||||
internal sealed class SplinePointTangentNode : ActorChildNode
|
||||
{
|
||||
private SplineNode _node;
|
||||
private int _index;
|
||||
@@ -249,10 +253,11 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
|
||||
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
|
||||
{
|
||||
normal = -ray.Ray.Direction;
|
||||
var actor = (Spline)_node.Actor;
|
||||
var pos = actor.GetSplineTangent(_index, _isIn).Translation;
|
||||
normal = -ray.Ray.Direction;
|
||||
return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance);
|
||||
var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize);
|
||||
return new BoundingSphere(pos, tangentSize).Intersects(ref ray.Ray, out distance);
|
||||
}
|
||||
|
||||
public override void OnDebugDraw(ViewportDebugDrawData data)
|
||||
@@ -263,7 +268,8 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
// Draw selected tangent highlight
|
||||
var actor = (Spline)_node.Actor;
|
||||
var pos = actor.GetSplineTangent(_index, _isIn).Translation;
|
||||
DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false);
|
||||
var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize);
|
||||
DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false);
|
||||
}
|
||||
|
||||
public override void OnContextMenu(ContextMenu contextMenu)
|
||||
@@ -279,6 +285,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
private const Real PointNodeSize = 1.5f;
|
||||
private const Real TangentNodeSize = 1.0f;
|
||||
|
||||
/// <inheritdoc />
|
||||
public SplineNode(Actor actor)
|
||||
: base(actor)
|
||||
@@ -398,6 +407,13 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
}
|
||||
|
||||
internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize)
|
||||
{
|
||||
var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform;
|
||||
var distance = Vector3.Distance(cameraTransform.Translation, nodePosition) / 100;
|
||||
return distance * nodeSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
|
||||
{
|
||||
|
||||
@@ -97,11 +97,6 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnNameChanged()
|
||||
{
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tree node text.
|
||||
/// </summary>
|
||||
|
||||
@@ -553,6 +553,18 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 112,
|
||||
Title = "Particle Scale",
|
||||
Description = "Particle scale.",
|
||||
Flags = NodeFlags.ParticleEmitterGraph,
|
||||
Size = new Float2(200, 30),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0),
|
||||
}
|
||||
},
|
||||
|
||||
// Simulation data access nodes
|
||||
new NodeArchetype
|
||||
|
||||
@@ -430,7 +430,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
{
|
||||
if (actorNode.Actor)
|
||||
{
|
||||
actorNode.TreeNode.OnNameChanged();
|
||||
actorNode.TreeNode.UpdateText();
|
||||
actorNode.TreeNode.OnOrderInParentChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -202,6 +202,13 @@ namespace FlaxEditor.Windows
|
||||
button.PerformLayout();
|
||||
x += button.Width + NavigationBar.DefaultButtonsMargin;
|
||||
_navigationBar.AddChild(button);
|
||||
if (i > 0)
|
||||
{
|
||||
var separator = new ContentNavigationSeparator(button, x, ToolStrip.DefaultMarginV, h);
|
||||
separator.PerformLayout();
|
||||
x += separator.Width + NavigationBar.DefaultButtonsMargin;
|
||||
_navigationBar.AddChild(separator);
|
||||
}
|
||||
}
|
||||
nodes.Clear();
|
||||
|
||||
@@ -218,21 +225,13 @@ namespace FlaxEditor.Windows
|
||||
/// <summary>
|
||||
/// Gets the current view folder.
|
||||
/// </summary>
|
||||
public ContentFolder CurrentViewFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
var node = SelectedNode;
|
||||
return node?.Folder;
|
||||
}
|
||||
}
|
||||
public ContentFolder CurrentViewFolder => SelectedNode?.Folder;
|
||||
|
||||
/// <summary>
|
||||
/// Shows the root folder.
|
||||
/// </summary>
|
||||
public void ShowRoot()
|
||||
{
|
||||
// Show root folder
|
||||
_tree.Select(_root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
|
||||
// Search by filter only
|
||||
bool showAllFiles = _showAllFiles;
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
if (SelectedNode == _root)
|
||||
@@ -199,12 +200,12 @@ namespace FlaxEditor.Windows
|
||||
for (int i = 0; i < _root.ChildrenCount; i++)
|
||||
{
|
||||
if (_root.GetChild(i) is ContentTreeNode node)
|
||||
UpdateItemsSearchFilter(node.Folder, items, filters);
|
||||
UpdateItemsSearchFilter(node.Folder, items, filters, showAllFiles);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateItemsSearchFilter(CurrentViewFolder, items, filters);
|
||||
UpdateItemsSearchFilter(CurrentViewFolder, items, filters, showAllFiles);
|
||||
}
|
||||
}
|
||||
// Search by asset ID
|
||||
@@ -221,12 +222,12 @@ namespace FlaxEditor.Windows
|
||||
for (int i = 0; i < _root.ChildrenCount; i++)
|
||||
{
|
||||
if (_root.GetChild(i) is ContentTreeNode node)
|
||||
UpdateItemsSearchFilter(node.Folder, items, filters, query);
|
||||
UpdateItemsSearchFilter(node.Folder, items, filters, showAllFiles, query);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateItemsSearchFilter(CurrentViewFolder, items, filters, query);
|
||||
UpdateItemsSearchFilter(CurrentViewFolder, items, filters, showAllFiles, query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,42 +235,34 @@ namespace FlaxEditor.Windows
|
||||
_view.ShowItems(items, _sortType);
|
||||
}
|
||||
|
||||
private void UpdateItemsSearchFilter(ContentFolder folder, List<ContentItem> items, bool[] filters)
|
||||
private void UpdateItemsSearchFilter(ContentFolder folder, List<ContentItem> items, bool[] filters, bool showAllFiles)
|
||||
{
|
||||
for (int i = 0; i < folder.Children.Count; i++)
|
||||
{
|
||||
var child = folder.Children[i];
|
||||
|
||||
if (child is ContentFolder childFolder)
|
||||
{
|
||||
UpdateItemsSearchFilter(childFolder, items, filters);
|
||||
UpdateItemsSearchFilter(childFolder, items, filters, showAllFiles);
|
||||
}
|
||||
else
|
||||
else if (filters[(int)child.SearchFilter] && (showAllFiles || !(child is FileItem)))
|
||||
{
|
||||
if (filters[(int)child.SearchFilter])
|
||||
{
|
||||
items.Add(child);
|
||||
}
|
||||
items.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateItemsSearchFilter(ContentFolder folder, List<ContentItem> items, bool[] filters, string filterText)
|
||||
private void UpdateItemsSearchFilter(ContentFolder folder, List<ContentItem> items, bool[] filters, bool showAllFiles, string filterText)
|
||||
{
|
||||
for (int i = 0; i < folder.Children.Count; i++)
|
||||
{
|
||||
var child = folder.Children[i];
|
||||
|
||||
if (child is ContentFolder childFolder)
|
||||
{
|
||||
UpdateItemsSearchFilter(childFolder, items, filters, filterText);
|
||||
UpdateItemsSearchFilter(childFolder, items, filters, showAllFiles, filterText);
|
||||
}
|
||||
else if (filters[(int)child.SearchFilter])
|
||||
else if (filters[(int)child.SearchFilter] && (showAllFiles || !(child is FileItem)) && QueryFilterHelper.Match(filterText, child.ShortName))
|
||||
{
|
||||
if (filters[(int)child.SearchFilter] && QueryFilterHelper.Match(filterText, child.ShortName))
|
||||
{
|
||||
items.Add(child);
|
||||
}
|
||||
items.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Content.GUI;
|
||||
@@ -45,6 +46,7 @@ namespace FlaxEditor.Windows
|
||||
private TextBox _itemsSearchBox;
|
||||
private ViewDropdown _viewDropdown;
|
||||
private SortType _sortType;
|
||||
private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true;
|
||||
|
||||
private RootContentTreeNode _root;
|
||||
|
||||
@@ -64,6 +66,59 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
public ContentView View => _view;
|
||||
|
||||
internal bool ShowEngineFiles
|
||||
{
|
||||
get => _showEngineFiles;
|
||||
set
|
||||
{
|
||||
if (_showEngineFiles != value)
|
||||
{
|
||||
_showEngineFiles = value;
|
||||
if (Editor.ContentDatabase.Engine != null)
|
||||
{
|
||||
Editor.ContentDatabase.Engine.Visible = value;
|
||||
Editor.ContentDatabase.Engine.Folder.Visible = value;
|
||||
RefreshView();
|
||||
_tree.PerformLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool ShowPluginsFiles
|
||||
{
|
||||
get => _showPluginsFiles;
|
||||
set
|
||||
{
|
||||
if (_showPluginsFiles != value)
|
||||
{
|
||||
_showPluginsFiles = value;
|
||||
foreach (var project in Editor.ContentDatabase.Projects)
|
||||
{
|
||||
if (project == Editor.ContentDatabase.Game || project == Editor.ContentDatabase.Engine)
|
||||
continue;
|
||||
project.Visible = value;
|
||||
project.Folder.Visible = value;
|
||||
RefreshView();
|
||||
_tree.PerformLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool ShowAllFiles
|
||||
{
|
||||
get => _showAllFiles;
|
||||
set
|
||||
{
|
||||
if (_showAllFiles != value)
|
||||
{
|
||||
_showAllFiles = value;
|
||||
RefreshView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentWindow"/> class.
|
||||
/// </summary>
|
||||
@@ -107,11 +162,12 @@ namespace FlaxEditor.Windows
|
||||
_navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward");
|
||||
_navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward");
|
||||
_navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up");
|
||||
_toolStrip.AddSeparator();
|
||||
|
||||
// Navigation bar
|
||||
_navigationBar = new NavigationBar
|
||||
{
|
||||
Parent = this,
|
||||
Parent = _toolStrip,
|
||||
};
|
||||
|
||||
// Split panel
|
||||
@@ -216,10 +272,6 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
|
||||
var showFileExtensionsButton = menu.AddButton("Show file extensions", () => View.ShowFileExtensions = !View.ShowFileExtensions);
|
||||
showFileExtensionsButton.Checked = View.ShowFileExtensions;
|
||||
showFileExtensionsButton.AutoCheck = true;
|
||||
|
||||
var viewScale = menu.AddButton("View Scale");
|
||||
viewScale.CloseMenuOnClick = false;
|
||||
var scaleValue = new FloatValueBox(1, 75, 2, 50.0f, 0.3f, 3.0f, 0.01f)
|
||||
@@ -243,6 +295,33 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
};
|
||||
|
||||
var show = menu.AddChildMenu("Show");
|
||||
{
|
||||
var b = show.ContextMenu.AddButton("File extensions", () => View.ShowFileExtensions = !View.ShowFileExtensions);
|
||||
b.TooltipText = "Shows all files with extensions";
|
||||
b.Checked = View.ShowFileExtensions;
|
||||
b.CloseMenuOnClick = false;
|
||||
b.AutoCheck = true;
|
||||
|
||||
b = show.ContextMenu.AddButton("Engine files", () => ShowEngineFiles = !ShowEngineFiles);
|
||||
b.TooltipText = "Shows in-built engine content";
|
||||
b.Checked = ShowEngineFiles;
|
||||
b.CloseMenuOnClick = false;
|
||||
b.AutoCheck = true;
|
||||
|
||||
b = show.ContextMenu.AddButton("Plugins files", () => ShowPluginsFiles = !ShowPluginsFiles);
|
||||
b.TooltipText = "Shows plugin projects content";
|
||||
b.Checked = ShowPluginsFiles;
|
||||
b.CloseMenuOnClick = false;
|
||||
b.AutoCheck = true;
|
||||
|
||||
b = show.ContextMenu.AddButton("All files", () => ShowAllFiles = !ShowAllFiles);
|
||||
b.TooltipText = "Shows all files including other than assets and source code";
|
||||
b.Checked = ShowAllFiles;
|
||||
b.CloseMenuOnClick = false;
|
||||
b.AutoCheck = true;
|
||||
}
|
||||
|
||||
var filters = menu.AddChildMenu("Filters");
|
||||
for (int i = 0; i < _viewDropdown.Items.Count; i++)
|
||||
{
|
||||
@@ -351,7 +430,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
if (!item.CanRename)
|
||||
return;
|
||||
|
||||
|
||||
// Show element in the view
|
||||
Select(item, true);
|
||||
|
||||
@@ -428,10 +507,7 @@ namespace FlaxEditor.Windows
|
||||
if (!Editor.ContentEditing.IsValidAssetName(item, newShortName, out string hint))
|
||||
{
|
||||
// Invalid name
|
||||
MessageBox.Show("Given asset name is invalid. " + hint,
|
||||
"Invalid name",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
MessageBox.Show("Given asset name is invalid. " + hint, "Invalid name", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -877,7 +953,7 @@ namespace FlaxEditor.Windows
|
||||
if (target == _root)
|
||||
{
|
||||
// Special case for root folder
|
||||
List<ContentItem> items = new List<ContentItem>(8);
|
||||
var items = new List<ContentItem>(8);
|
||||
for (int i = 0; i < _root.ChildrenCount; i++)
|
||||
{
|
||||
if (_root.GetChild(i) is ContentTreeNode node)
|
||||
@@ -890,7 +966,10 @@ namespace FlaxEditor.Windows
|
||||
else
|
||||
{
|
||||
// Show folder contents
|
||||
_view.ShowItems(target.Folder.Children, _sortType, false, true);
|
||||
var items = target.Folder.Children;
|
||||
if (!_showAllFiles)
|
||||
items = items.Where(x => !(x is FileItem)).ToList();
|
||||
_view.ShowItems(items, _sortType, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,12 +992,6 @@ namespace FlaxEditor.Windows
|
||||
_navigateUpButton.Enabled = folder != null && _tree.SelectedNode != _root;
|
||||
}
|
||||
|
||||
private void RemoveFolder2Root(ContentTreeNode node)
|
||||
{
|
||||
// Remove from the root
|
||||
_root.RemoveChild(node);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInit()
|
||||
{
|
||||
@@ -931,13 +1004,18 @@ namespace FlaxEditor.Windows
|
||||
|
||||
// Add game project on top, plugins in the middle and engine at bottom
|
||||
_root.AddChild(Editor.ContentDatabase.Game);
|
||||
Editor.ContentDatabase.Projects.Sort();
|
||||
foreach (var project in Editor.ContentDatabase.Projects)
|
||||
{
|
||||
project.SortChildrenRecursive();
|
||||
if (project == Editor.ContentDatabase.Game || project == Editor.ContentDatabase.Engine)
|
||||
continue;
|
||||
project.Visible = _showPluginsFiles;
|
||||
project.Folder.Visible = _showPluginsFiles;
|
||||
_root.AddChild(project);
|
||||
}
|
||||
Editor.ContentDatabase.Engine.Visible = _showEngineFiles;
|
||||
Editor.ContentDatabase.Engine.Folder.Visible = _showEngineFiles;
|
||||
_root.AddChild(Editor.ContentDatabase.Engine);
|
||||
|
||||
Editor.ContentDatabase.Game?.Expand(true);
|
||||
@@ -1009,7 +1087,6 @@ namespace FlaxEditor.Windows
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
// Check if it's a right mouse button
|
||||
if (button == MouseButton.Right)
|
||||
{
|
||||
// Find control that is under the mouse
|
||||
@@ -1065,6 +1142,9 @@ namespace FlaxEditor.Windows
|
||||
LayoutSerializeSplitter(writer, "Split", _split);
|
||||
writer.WriteAttributeString("Scale", _view.ViewScale.ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("ShowFileExtensions", _view.ShowFileExtensions.ToString());
|
||||
writer.WriteAttributeString("ShowEngineFiles", ShowEngineFiles.ToString());
|
||||
writer.WriteAttributeString("ShowPluginsFiles", ShowPluginsFiles.ToString());
|
||||
writer.WriteAttributeString("ShowAllFiles", ShowAllFiles.ToString());
|
||||
writer.WriteAttributeString("ViewType", _view.ViewType.ToString());
|
||||
}
|
||||
|
||||
@@ -1076,6 +1156,12 @@ namespace FlaxEditor.Windows
|
||||
_view.ViewScale = value1;
|
||||
if (bool.TryParse(node.GetAttribute("ShowFileExtensions"), out bool value2))
|
||||
_view.ShowFileExtensions = value2;
|
||||
if (bool.TryParse(node.GetAttribute("ShowEngineFiles"), out value2))
|
||||
ShowEngineFiles = value2;
|
||||
if (bool.TryParse(node.GetAttribute("ShowPluginsFiles"), out value2))
|
||||
ShowPluginsFiles = value2;
|
||||
if (bool.TryParse(node.GetAttribute("ShowAllFiles"), out value2))
|
||||
ShowAllFiles = value2;
|
||||
if (Enum.TryParse(node.GetAttribute("ViewType"), out ContentViewType viewType))
|
||||
_view.ViewType = viewType;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,18 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Json;
|
||||
|
||||
namespace FlaxEditor.Windows
|
||||
{
|
||||
@@ -17,6 +24,8 @@ namespace FlaxEditor.Windows
|
||||
public sealed class PluginsWindow : EditorWindow
|
||||
{
|
||||
private Tabs _tabs;
|
||||
private Button _addPluginProjectButton;
|
||||
private Button _cloneProjectButton;
|
||||
private readonly List<CategoryEntry> _categories = new List<CategoryEntry>();
|
||||
private readonly Dictionary<Plugin, PluginEntry> _entries = new Dictionary<Plugin, PluginEntry>();
|
||||
|
||||
@@ -174,19 +183,576 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Title = "Plugins";
|
||||
|
||||
var vp = new Panel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Parent = this,
|
||||
};
|
||||
_addPluginProjectButton = new Button
|
||||
{
|
||||
Text = "Create Plugin Project",
|
||||
TooltipText = "Add new plugin project.",
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
LocalLocation = new Float2(70, 18),
|
||||
Size = new Float2(150, 25),
|
||||
Parent = vp,
|
||||
};
|
||||
_addPluginProjectButton.Clicked += OnAddButtonClicked;
|
||||
|
||||
_cloneProjectButton = new Button
|
||||
{
|
||||
Text = "Clone Plugin Project",
|
||||
TooltipText = "Git Clone a plugin project.",
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
LocalLocation = new Float2(70 + _addPluginProjectButton.Size.X + 8, 18),
|
||||
Size = new Float2(150, 25),
|
||||
Parent = vp,
|
||||
};
|
||||
_cloneProjectButton.Clicked += OnCloneProjectButtonClicked;
|
||||
|
||||
_tabs = new Tabs
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
Offsets = new Margin(0, 0, _addPluginProjectButton.Bottom + 8, 0),
|
||||
TabsSize = new Float2(120, 32),
|
||||
Parent = this
|
||||
Parent = vp
|
||||
};
|
||||
|
||||
OnPluginsChanged();
|
||||
PluginManager.PluginsChanged += OnPluginsChanged;
|
||||
}
|
||||
|
||||
private void OnCloneProjectButtonClicked()
|
||||
{
|
||||
var popup = new ContextMenuBase
|
||||
{
|
||||
Size = new Float2(300, 125),
|
||||
ClipChildren = false,
|
||||
CullChildren = false,
|
||||
};
|
||||
popup.Show(_cloneProjectButton, new Float2(_cloneProjectButton.Width, 0));
|
||||
|
||||
var nameLabel = new Label
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
AutoWidth = true,
|
||||
Text = "Name",
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
};
|
||||
nameLabel.LocalY += 10;
|
||||
|
||||
var nameTextBox = new TextBox
|
||||
{
|
||||
Parent = popup,
|
||||
WatermarkText = "Plugin Name",
|
||||
TooltipText = "If left blank, this will take the git name.",
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
IsMultiline = false,
|
||||
};
|
||||
nameTextBox.LocalX += (300 - (10)) * 0.5f;
|
||||
nameTextBox.LocalY += 10;
|
||||
nameLabel.LocalX += (300 - (nameLabel.Width + nameTextBox.Width)) * 0.5f + 10;
|
||||
|
||||
var defaultTextBoxBorderColor = nameTextBox.BorderColor;
|
||||
var defaultTextBoxBorderSelectedColor = nameTextBox.BorderSelectedColor;
|
||||
nameTextBox.TextChanged += () =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(nameTextBox.Text))
|
||||
{
|
||||
nameTextBox.BorderColor = defaultTextBoxBorderColor;
|
||||
nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor;
|
||||
return;
|
||||
}
|
||||
|
||||
var pluginPath = Path.Combine(Globals.ProjectFolder, "Plugins", nameTextBox.Text);
|
||||
if (Directory.Exists(pluginPath))
|
||||
{
|
||||
nameTextBox.BorderColor = Color.Red;
|
||||
nameTextBox.BorderSelectedColor = Color.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
nameTextBox.BorderColor = defaultTextBoxBorderColor;
|
||||
nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor;
|
||||
}
|
||||
};
|
||||
|
||||
var gitPathLabel = new Label
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
AutoWidth = true,
|
||||
Text = "Git Path",
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
};
|
||||
gitPathLabel.LocalX += (300 - gitPathLabel.Width) * 0.5f;
|
||||
gitPathLabel.LocalY += 35;
|
||||
|
||||
var gitPathTextBox = new TextBox
|
||||
{
|
||||
Parent = popup,
|
||||
WatermarkText = "https://github.com/FlaxEngine/ExamplePlugin.git",
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Size = new Float2(280, TextBox.DefaultHeight),
|
||||
IsMultiline = false,
|
||||
};
|
||||
gitPathTextBox.LocalY += 60;
|
||||
gitPathTextBox.LocalX += 10;
|
||||
|
||||
var submitButton = new Button
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Text = "Clone",
|
||||
Width = 70,
|
||||
};
|
||||
submitButton.LocalX += 300 * 0.5f - submitButton.Width - 10;
|
||||
submitButton.LocalY += 90;
|
||||
|
||||
submitButton.Clicked += () =>
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Plugins", nameTextBox.Text)) && !string.IsNullOrEmpty(nameTextBox.Text))
|
||||
{
|
||||
Editor.LogWarning("Cannot create plugin due to name conflict.");
|
||||
return;
|
||||
}
|
||||
OnCloneButtonClicked(nameTextBox.Text, gitPathTextBox.Text);
|
||||
nameTextBox.Clear();
|
||||
gitPathTextBox.Clear();
|
||||
popup.Hide();
|
||||
};
|
||||
|
||||
var cancelButton = new Button
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Text = "Cancel",
|
||||
Width = 70,
|
||||
};
|
||||
cancelButton.LocalX += 300 * 0.5f + 10;
|
||||
cancelButton.LocalY += 90;
|
||||
|
||||
cancelButton.Clicked += () =>
|
||||
{
|
||||
nameTextBox.Clear();
|
||||
gitPathTextBox.Clear();
|
||||
popup.Hide();
|
||||
};
|
||||
}
|
||||
|
||||
private async void OnCloneButtonClicked(string pluginName, string gitPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(gitPath))
|
||||
{
|
||||
Editor.LogError("Failed to create plugin project due to no GIT path.");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(pluginName))
|
||||
{
|
||||
var split = gitPath.Split('/');
|
||||
if (string.IsNullOrEmpty(split[^1]))
|
||||
{
|
||||
var name = split[^2].Replace(".git", "");
|
||||
pluginName = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = split[^1].Replace(".git", "");
|
||||
pluginName = name;
|
||||
}
|
||||
}
|
||||
|
||||
var clonePath = Path.Combine(Globals.ProjectFolder, "Plugins", pluginName);
|
||||
if (!Directory.Exists(clonePath))
|
||||
Directory.CreateDirectory(clonePath);
|
||||
else
|
||||
{
|
||||
Editor.LogError("Plugin Name is already used. Pick a different Name.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
// Start git clone
|
||||
var settings = new CreateProcessSettings
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = $"clone {gitPath} \"{clonePath}\"",
|
||||
ShellExecute = false,
|
||||
LogOutput = true,
|
||||
};
|
||||
Platform.CreateProcess(ref settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Editor.LogError($"Failed Git process. {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
Editor.Log("Plugin project has been cloned.");
|
||||
|
||||
// Find project config file. Could be different then what the user named the folder.
|
||||
var files = Directory.GetFiles(clonePath);
|
||||
string pluginProjectName = "";
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file.Contains(".flaxproj", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
pluginProjectName = Path.GetFileNameWithoutExtension(file);
|
||||
Debug.Log(pluginProjectName);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(pluginProjectName))
|
||||
Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually.");
|
||||
else
|
||||
{
|
||||
await AddReferenceToProject(pluginName, pluginProjectName);
|
||||
MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddButtonClicked()
|
||||
{
|
||||
var popup = new ContextMenuBase
|
||||
{
|
||||
Size = new Float2(230, 125),
|
||||
ClipChildren = false,
|
||||
CullChildren = false,
|
||||
};
|
||||
popup.Show(_addPluginProjectButton, new Float2(_addPluginProjectButton.Width, 0));
|
||||
|
||||
var nameLabel = new Label
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Text = "Name",
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
};
|
||||
nameLabel.LocalX += 10;
|
||||
nameLabel.LocalY += 10;
|
||||
|
||||
var nameTextBox = new TextBox
|
||||
{
|
||||
Parent = popup,
|
||||
WatermarkText = "Plugin Name",
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
IsMultiline = false,
|
||||
};
|
||||
nameTextBox.LocalX += 100;
|
||||
nameTextBox.LocalY += 10;
|
||||
var defaultTextBoxBorderColor = nameTextBox.BorderColor;
|
||||
var defaultTextBoxBorderSelectedColor = nameTextBox.BorderSelectedColor;
|
||||
nameTextBox.TextChanged += () =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(nameTextBox.Text))
|
||||
{
|
||||
nameTextBox.BorderColor = defaultTextBoxBorderColor;
|
||||
nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor;
|
||||
return;
|
||||
}
|
||||
|
||||
var pluginPath = Path.Combine(Globals.ProjectFolder, "Plugins", nameTextBox.Text);
|
||||
if (Directory.Exists(pluginPath))
|
||||
{
|
||||
nameTextBox.BorderColor = Color.Red;
|
||||
nameTextBox.BorderSelectedColor = Color.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
nameTextBox.BorderColor = defaultTextBoxBorderColor;
|
||||
nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor;
|
||||
}
|
||||
};
|
||||
|
||||
var versionLabel = new Label
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Text = "Version",
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
};
|
||||
versionLabel.LocalX += 10;
|
||||
versionLabel.LocalY += 35;
|
||||
|
||||
var versionTextBox = new TextBox
|
||||
{
|
||||
Parent = popup,
|
||||
WatermarkText = "1.0.0",
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
IsMultiline = false,
|
||||
};
|
||||
versionTextBox.LocalY += 35;
|
||||
versionTextBox.LocalX += 100;
|
||||
|
||||
var companyLabel = new Label
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Text = "Company",
|
||||
HorizontalAlignment = TextAlignment.Near,
|
||||
};
|
||||
companyLabel.LocalX += 10;
|
||||
companyLabel.LocalY += 60;
|
||||
|
||||
var companyTextBox = new TextBox
|
||||
{
|
||||
Parent = popup,
|
||||
WatermarkText = "Company Name",
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
IsMultiline = false,
|
||||
};
|
||||
companyTextBox.LocalY += 60;
|
||||
companyTextBox.LocalX += 100;
|
||||
|
||||
var submitButton = new Button
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Text = "Create",
|
||||
Width = 70,
|
||||
};
|
||||
submitButton.LocalX += 40;
|
||||
submitButton.LocalY += 90;
|
||||
|
||||
submitButton.Clicked += () =>
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Plugins", nameTextBox.Text)))
|
||||
{
|
||||
Editor.LogWarning("Cannot create plugin due to name conflict.");
|
||||
return;
|
||||
}
|
||||
OnCreateButtonClicked(nameTextBox.Text, versionTextBox.Text, companyTextBox.Text);
|
||||
nameTextBox.Clear();
|
||||
versionTextBox.Clear();
|
||||
companyTextBox.Clear();
|
||||
popup.Hide();
|
||||
};
|
||||
|
||||
var cancelButton = new Button
|
||||
{
|
||||
Parent = popup,
|
||||
AnchorPreset = AnchorPresets.TopLeft,
|
||||
Text = "Cancel",
|
||||
Width = 70,
|
||||
};
|
||||
cancelButton.LocalX += 120;
|
||||
cancelButton.LocalY += 90;
|
||||
|
||||
cancelButton.Clicked += () =>
|
||||
{
|
||||
nameTextBox.Clear();
|
||||
versionTextBox.Clear();
|
||||
companyTextBox.Clear();
|
||||
popup.Hide();
|
||||
};
|
||||
}
|
||||
|
||||
private async void OnCreateButtonClicked(string pluginName, string pluginVersion, string companyName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pluginName))
|
||||
{
|
||||
Editor.LogError("Failed to create plugin project due to no plugin name.");
|
||||
return;
|
||||
}
|
||||
|
||||
var templateUrl = "https://github.com/FlaxEngine/ExamplePlugin/archive/refs/heads/master.zip";
|
||||
var localTemplateFolderLocation = Path.Combine(Editor.LocalCachePath, "TemplatePluginCache");
|
||||
if (!Directory.Exists(localTemplateFolderLocation))
|
||||
Directory.CreateDirectory(localTemplateFolderLocation);
|
||||
var localTemplatePath = Path.Combine(localTemplateFolderLocation, "TemplatePlugin.zip");
|
||||
|
||||
try
|
||||
{
|
||||
// Download example plugin
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
byte[] zipBytes = await client.GetByteArrayAsync(templateUrl);
|
||||
await File.WriteAllBytesAsync(!File.Exists(localTemplatePath) ? Path.Combine(localTemplatePath) : Path.Combine(Editor.LocalCachePath, "TemplatePluginCache", "TemplatePlugin1.zip"), zipBytes);
|
||||
|
||||
Editor.Log("Template for plugin project has downloaded");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Editor.LogError($"Failed to download template project. Trying to use local file. {e}");
|
||||
if (!File.Exists(localTemplatePath))
|
||||
{
|
||||
Editor.LogError("Failed to use local file. Does not exist.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any changes in new downloaded file
|
||||
if (File.Exists(Path.Combine(Editor.LocalCachePath, "TemplatePluginCache", "TemplatePlugin1.zip")))
|
||||
{
|
||||
var localTemplatePath2 = Path.Combine(Editor.LocalCachePath, "TemplatePluginCache", "TemplatePlugin1.zip");
|
||||
bool areDifferent = false;
|
||||
using (var zip1 = ZipFile.OpenRead(localTemplatePath))
|
||||
{
|
||||
using (var zip2 = ZipFile.OpenRead(localTemplatePath2))
|
||||
{
|
||||
if (zip1.Entries.Count != zip2.Entries.Count)
|
||||
{
|
||||
areDifferent = true;
|
||||
}
|
||||
|
||||
foreach (ZipArchiveEntry entry1 in zip1.Entries)
|
||||
{
|
||||
ZipArchiveEntry entry2 = zip2.GetEntry(entry1.FullName);
|
||||
if (entry2 == null)
|
||||
{
|
||||
areDifferent = true;
|
||||
break;
|
||||
}
|
||||
if (entry1.Length != entry2.Length || entry1.CompressedLength != entry2.CompressedLength || entry1.Crc32 != entry2.Crc32)
|
||||
{
|
||||
areDifferent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (areDifferent)
|
||||
{
|
||||
File.Delete(localTemplatePath);
|
||||
File.Move(localTemplatePath2, localTemplatePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(localTemplatePath2);
|
||||
}
|
||||
}
|
||||
|
||||
var extractPath = Path.Combine(Globals.ProjectFolder, "Plugins");
|
||||
if (!Directory.Exists(extractPath))
|
||||
Directory.CreateDirectory(extractPath);
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => ZipFile.ExtractToDirectory(localTemplatePath, extractPath));
|
||||
Editor.Log("Template for plugin project successfully moved to project.");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Editor.LogError($"Failed to add plugin to project. {e}");
|
||||
}
|
||||
|
||||
// Format plugin name into a valid name for code (C#/C++ typename)
|
||||
var pluginCodeName = Content.ScriptItem.FilterScriptName(pluginName);
|
||||
if (string.IsNullOrEmpty(pluginCodeName))
|
||||
pluginCodeName = "MyPlugin";
|
||||
Editor.Log($"Using plugin code type name: {pluginCodeName}");
|
||||
|
||||
var oldPluginPath = Path.Combine(extractPath, "ExamplePlugin-master");
|
||||
var newPluginPath = Path.Combine(extractPath, pluginName);
|
||||
Directory.Move(oldPluginPath, newPluginPath);
|
||||
|
||||
var oldFlaxProjFile = Path.Combine(newPluginPath, "ExamplePlugin.flaxproj");
|
||||
var newFlaxProjFile = Path.Combine(newPluginPath, $"{pluginName}.flaxproj");
|
||||
File.Move(oldFlaxProjFile, newFlaxProjFile);
|
||||
|
||||
var readme = Path.Combine(newPluginPath, "README.md");
|
||||
if (File.Exists(readme))
|
||||
File.Delete(readme);
|
||||
var license = Path.Combine(newPluginPath, "LICENSE");
|
||||
if (File.Exists(license))
|
||||
File.Delete(license);
|
||||
|
||||
// Flax plugin project file
|
||||
var flaxPluginProjContents = JsonSerializer.Deserialize<ProjectInfo>(await File.ReadAllTextAsync(newFlaxProjFile));
|
||||
flaxPluginProjContents.Name = pluginName;
|
||||
if (!string.IsNullOrEmpty(pluginVersion))
|
||||
flaxPluginProjContents.Version = new Version(pluginVersion);
|
||||
if (!string.IsNullOrEmpty(companyName))
|
||||
flaxPluginProjContents.Company = companyName;
|
||||
flaxPluginProjContents.GameTarget = $"{pluginCodeName}Target";
|
||||
flaxPluginProjContents.EditorTarget = $"{pluginCodeName}EditorTarget";
|
||||
await File.WriteAllTextAsync(newFlaxProjFile, JsonSerializer.Serialize(flaxPluginProjContents, typeof(ProjectInfo)), Encoding.UTF8);
|
||||
|
||||
// Format game settings
|
||||
var gameSettingsPath = Path.Combine(newPluginPath, "Content", "GameSettings.json");
|
||||
if (File.Exists(gameSettingsPath))
|
||||
{
|
||||
var contents = await File.ReadAllTextAsync(gameSettingsPath);
|
||||
contents = contents.Replace("Example Plugin", pluginName);
|
||||
contents = contents.Replace("\"CompanyName\": \"Flax\"", $"\"CompanyName\": \"{companyName}\"");
|
||||
contents = contents.Replace("1.0", pluginVersion);
|
||||
await File.WriteAllTextAsync(gameSettingsPath, contents, Encoding.UTF8);
|
||||
}
|
||||
|
||||
// Rename source directories
|
||||
var sourcePath = Path.Combine(newPluginPath, "Source");
|
||||
var sourceDirectories = Directory.GetDirectories(sourcePath);
|
||||
foreach (var directory in sourceDirectories)
|
||||
{
|
||||
var files = Directory.GetFiles(directory);
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file.Contains("MyPluginEditor.cs"))
|
||||
{
|
||||
File.Delete(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileName(file).Replace("ExamplePlugin", pluginCodeName);
|
||||
var fileText = await File.ReadAllTextAsync(file);
|
||||
fileText = fileText.Replace("ExamplePlugin", pluginCodeName);
|
||||
if (file.Contains("MyPlugin.cs"))
|
||||
{
|
||||
fileName = "ExamplePlugin.cs";
|
||||
fileText = fileText.Replace("MyPlugin", pluginCodeName);
|
||||
fileText = fileText.Replace("My Plugin", pluginName);
|
||||
fileText = fileText.Replace("Flax Engine", companyName);
|
||||
fileText = fileText.Replace("new Version(1, 0)", $"new Version({pluginVersion.Trim().Replace(".", ", ")})");
|
||||
}
|
||||
await File.WriteAllTextAsync(file, fileText);
|
||||
File.Move(file, Path.Combine(directory, fileName));
|
||||
}
|
||||
|
||||
var newName = directory.Replace("ExamplePlugin", pluginCodeName);
|
||||
Directory.Move(directory, newName);
|
||||
}
|
||||
|
||||
// Rename targets
|
||||
var targetFiles = Directory.GetFiles(sourcePath);
|
||||
foreach (var file in targetFiles)
|
||||
{
|
||||
var fileText = await File.ReadAllTextAsync(file);
|
||||
await File.WriteAllTextAsync(file, fileText.Replace("ExamplePlugin", pluginCodeName), Encoding.UTF8);
|
||||
var newName = file.Replace("ExamplePlugin", pluginCodeName);
|
||||
File.Move(file, newName);
|
||||
}
|
||||
Editor.Log($"Plugin project {pluginName} has successfully been created.");
|
||||
|
||||
await AddReferenceToProject(pluginName, pluginName);
|
||||
MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
|
||||
}
|
||||
|
||||
private async Task AddReferenceToProject(string pluginFolderName, string pluginName)
|
||||
{
|
||||
// Project flax config file
|
||||
var flaxProjPath = Editor.GameProject.ProjectPath;
|
||||
if (File.Exists(flaxProjPath))
|
||||
{
|
||||
var flaxProjContents = JsonSerializer.Deserialize<ProjectInfo>(await File.ReadAllTextAsync(flaxProjPath));
|
||||
var oldReferences = flaxProjContents.References;
|
||||
var references = new List<ProjectInfo.Reference>(oldReferences);
|
||||
var newPath = $"$(ProjectPath)/Plugins/{pluginFolderName}/{pluginName}.flaxproj";
|
||||
if (!references.Exists(x => string.Equals(x.Name, newPath)))
|
||||
{
|
||||
var newReference = new ProjectInfo.Reference
|
||||
{
|
||||
Name = newPath,
|
||||
};
|
||||
references.Add(newReference);
|
||||
}
|
||||
flaxProjContents.References = references.ToArray();
|
||||
await File.WriteAllTextAsync(flaxProjPath, JsonSerializer.Serialize(flaxProjContents, typeof(ProjectInfo)), Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPluginsChanged()
|
||||
{
|
||||
List<PluginEntry> toRemove = null;
|
||||
@@ -299,19 +865,21 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
if (pluginType == null)
|
||||
return null;
|
||||
|
||||
foreach (var e in _entries.Keys)
|
||||
{
|
||||
if (e.GetType() == pluginType && _entries.ContainsKey(e))
|
||||
return _entries[e];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (_addPluginProjectButton != null)
|
||||
_addPluginProjectButton.Clicked -= OnAddButtonClicked;
|
||||
if (_cloneProjectButton != null)
|
||||
_cloneProjectButton.Clicked -= OnCloneProjectButtonClicked;
|
||||
PluginManager.PluginsChanged -= OnPluginsChanged;
|
||||
|
||||
base.OnDestroy();
|
||||
|
||||
@@ -1251,7 +1251,7 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
|
||||
boxBase = node->GetBox(3);
|
||||
if (boxBase->HasConnection())
|
||||
eatBox(node, boxBase->FirstConnection());
|
||||
Dictionary<Variant, Variant>::Iterator it(dictionary, iteratorValue.Value.AsInt);
|
||||
Dictionary<Variant, Variant>::Iterator it(&dictionary, iteratorValue.Value.AsInt);
|
||||
++it;
|
||||
iteratorValue.Value.AsInt = it.Index();
|
||||
}
|
||||
@@ -1269,12 +1269,12 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
|
||||
// Key
|
||||
case 1:
|
||||
if (iteratorIndex != scope->ReturnedValues.Count() && dictionaryIndex != scope->ReturnedValues.Count())
|
||||
value = Dictionary<Variant, Variant>::Iterator(*scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Key;
|
||||
value = Dictionary<Variant, Variant>::Iterator(scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Key;
|
||||
break;
|
||||
// Value
|
||||
case 2:
|
||||
if (iteratorIndex != scope->ReturnedValues.Count() && dictionaryIndex != scope->ReturnedValues.Count())
|
||||
value = Dictionary<Variant, Variant>::Iterator(*scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Value;
|
||||
value = Dictionary<Variant, Variant>::Iterator(scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Value;
|
||||
break;
|
||||
// Break
|
||||
case 5:
|
||||
|
||||
@@ -938,12 +938,12 @@ public:
|
||||
|
||||
FORCE_INLINE bool IsEnd() const
|
||||
{
|
||||
return _index == _array->Count();
|
||||
return _index == _array->_count;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsNotEnd() const
|
||||
{
|
||||
return _index != _array->Count();
|
||||
return _index != _array->_count;
|
||||
}
|
||||
|
||||
FORCE_INLINE T& operator*() const
|
||||
@@ -975,7 +975,7 @@ public:
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
if (_index != _array->Count())
|
||||
if (_index != _array->_count)
|
||||
_index++;
|
||||
return *this;
|
||||
}
|
||||
@@ -983,7 +983,7 @@ public:
|
||||
Iterator operator++(int)
|
||||
{
|
||||
Iterator temp = *this;
|
||||
if (_index != _array->Count())
|
||||
if (_index != _array->_count)
|
||||
_index++;
|
||||
return temp;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ public:
|
||||
struct Iterator
|
||||
{
|
||||
friend ChunkedArray;
|
||||
|
||||
private:
|
||||
ChunkedArray* _collection;
|
||||
int32 _chunkIndex;
|
||||
|
||||
@@ -237,22 +237,28 @@ public:
|
||||
{
|
||||
friend Dictionary;
|
||||
private:
|
||||
Dictionary& _collection;
|
||||
Dictionary* _collection;
|
||||
int32 _index;
|
||||
|
||||
public:
|
||||
Iterator(Dictionary& collection, const int32 index)
|
||||
Iterator(Dictionary* collection, const int32 index)
|
||||
: _collection(collection)
|
||||
, _index(index)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(Dictionary const& collection, const int32 index)
|
||||
: _collection((Dictionary&)collection)
|
||||
Iterator(Dictionary const* collection, const int32 index)
|
||||
: _collection(const_cast<Dictionary*>(collection))
|
||||
, _index(index)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator()
|
||||
: _collection(nullptr)
|
||||
, _index(-1)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator& i)
|
||||
: _collection(i._collection)
|
||||
, _index(i._index)
|
||||
@@ -273,27 +279,27 @@ public:
|
||||
|
||||
FORCE_INLINE bool IsEnd() const
|
||||
{
|
||||
return _index == _collection._size;
|
||||
return _index == _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsNotEnd() const
|
||||
{
|
||||
return _index != _collection._size;
|
||||
return _index != _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE Bucket& operator*() const
|
||||
{
|
||||
return _collection._allocation.Get()[_index];
|
||||
return _collection->_allocation.Get()[_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE Bucket* operator->() const
|
||||
{
|
||||
return &_collection._allocation.Get()[_index];
|
||||
return &_collection->_allocation.Get()[_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE explicit operator bool() const
|
||||
{
|
||||
return _index >= 0 && _index < _collection._size;
|
||||
return _index >= 0 && _index < _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!() const
|
||||
@@ -308,7 +314,7 @@ public:
|
||||
|
||||
FORCE_INLINE bool operator!=(const Iterator& v) const
|
||||
{
|
||||
return _index != v._index || &_collection != &v._collection;
|
||||
return _index != v._index || _collection != v._collection;
|
||||
}
|
||||
|
||||
Iterator& operator=(const Iterator& v)
|
||||
@@ -320,10 +326,10 @@ public:
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
const int32 capacity = _collection.Capacity();
|
||||
const int32 capacity = _collection->_size;
|
||||
if (_index != capacity)
|
||||
{
|
||||
const Bucket* data = _collection._allocation.Get();
|
||||
const Bucket* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
_index++;
|
||||
@@ -343,7 +349,7 @@ public:
|
||||
{
|
||||
if (_index > 0)
|
||||
{
|
||||
const Bucket* data = _collection._allocation.Get();
|
||||
const Bucket* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
_index--;
|
||||
@@ -633,7 +639,7 @@ public:
|
||||
/// <param name="i">Iterator with key and value.</param>
|
||||
void Add(const Iterator& i)
|
||||
{
|
||||
ASSERT(&i._collection != this && i);
|
||||
ASSERT(i._collection != this && i);
|
||||
const Bucket& bucket = *i;
|
||||
Add(bucket.Key, bucket.Value);
|
||||
}
|
||||
@@ -667,7 +673,7 @@ public:
|
||||
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
|
||||
bool Remove(const Iterator& i)
|
||||
{
|
||||
ASSERT(&i._collection == this);
|
||||
ASSERT(i._collection == this);
|
||||
if (i)
|
||||
{
|
||||
ASSERT(_allocation.Get()[i._index].IsOccupied());
|
||||
@@ -711,7 +717,7 @@ public:
|
||||
return End();
|
||||
FindPositionResult pos;
|
||||
FindPosition(key, pos);
|
||||
return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End();
|
||||
return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -812,38 +818,38 @@ public:
|
||||
public:
|
||||
Iterator Begin() const
|
||||
{
|
||||
Iterator i(*this, -1);
|
||||
Iterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
Iterator End() const
|
||||
{
|
||||
return Iterator(*this, _size);
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
Iterator begin()
|
||||
{
|
||||
Iterator i(*this, -1);
|
||||
Iterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
FORCE_INLINE Iterator end()
|
||||
{
|
||||
return Iterator(*this, _size);
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
const Iterator begin() const
|
||||
{
|
||||
Iterator i(*this, -1);
|
||||
Iterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
FORCE_INLINE const Iterator end() const
|
||||
{
|
||||
return Iterator(*this, _size);
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -213,17 +213,17 @@ public:
|
||||
{
|
||||
friend HashSet;
|
||||
private:
|
||||
HashSet& _collection;
|
||||
HashSet* _collection;
|
||||
int32 _index;
|
||||
|
||||
Iterator(HashSet& collection, const int32 index)
|
||||
Iterator(HashSet* collection, const int32 index)
|
||||
: _collection(collection)
|
||||
, _index(index)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(HashSet const& collection, const int32 index)
|
||||
: _collection((HashSet&)collection)
|
||||
Iterator(HashSet const* collection, const int32 index)
|
||||
: _collection(const_cast<HashSet*>(collection))
|
||||
, _index(index)
|
||||
{
|
||||
}
|
||||
@@ -244,27 +244,27 @@ public:
|
||||
public:
|
||||
FORCE_INLINE bool IsEnd() const
|
||||
{
|
||||
return _index == _collection.Capacity();
|
||||
return _index == _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsNotEnd() const
|
||||
{
|
||||
return _index != _collection.Capacity();
|
||||
return _index != _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE Bucket& operator*() const
|
||||
{
|
||||
return _collection._allocation.Get()[_index];
|
||||
return _collection->_allocation.Get()[_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE Bucket* operator->() const
|
||||
{
|
||||
return &_collection._allocation.Get()[_index];
|
||||
return &_collection->_allocation.Get()[_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE explicit operator bool() const
|
||||
{
|
||||
return _index >= 0 && _index < _collection._size;
|
||||
return _index >= 0 && _index < _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator !() const
|
||||
@@ -274,12 +274,12 @@ public:
|
||||
|
||||
FORCE_INLINE bool operator==(const Iterator& v) const
|
||||
{
|
||||
return _index == v._index && &_collection == &v._collection;
|
||||
return _index == v._index && _collection == v._collection;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const Iterator& v) const
|
||||
{
|
||||
return _index != v._index || &_collection != &v._collection;
|
||||
return _index != v._index || _collection != v._collection;
|
||||
}
|
||||
|
||||
Iterator& operator=(const Iterator& v)
|
||||
@@ -291,10 +291,10 @@ public:
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
const int32 capacity = _collection.Capacity();
|
||||
const int32 capacity = _collection->_size;
|
||||
if (_index != capacity)
|
||||
{
|
||||
const Bucket* data = _collection._allocation.Get();
|
||||
const Bucket* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
_index++;
|
||||
@@ -314,7 +314,7 @@ public:
|
||||
{
|
||||
if (_index > 0)
|
||||
{
|
||||
const Bucket* data = _collection._allocation.Get();
|
||||
const Bucket* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
_index--;
|
||||
@@ -464,7 +464,7 @@ public:
|
||||
/// <param name="i">Iterator with item to add</param>
|
||||
void Add(const Iterator& i)
|
||||
{
|
||||
ASSERT(&i._collection != this && i);
|
||||
ASSERT(i._collection != this && i);
|
||||
const Bucket& bucket = *i;
|
||||
Add(bucket.Item);
|
||||
}
|
||||
@@ -498,7 +498,7 @@ public:
|
||||
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
|
||||
bool Remove(const Iterator& i)
|
||||
{
|
||||
ASSERT(&i._collection == this);
|
||||
ASSERT(i._collection == this);
|
||||
if (i)
|
||||
{
|
||||
ASSERT(_allocation.Get()[i._index].IsOccupied());
|
||||
@@ -523,7 +523,7 @@ public:
|
||||
return End();
|
||||
FindPositionResult pos;
|
||||
FindPosition(item, pos);
|
||||
return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End();
|
||||
return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -559,38 +559,38 @@ public:
|
||||
public:
|
||||
Iterator Begin() const
|
||||
{
|
||||
Iterator i(*this, -1);
|
||||
Iterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
Iterator End() const
|
||||
{
|
||||
return Iterator(*this, _size);
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
Iterator begin()
|
||||
{
|
||||
Iterator i(*this, -1);
|
||||
Iterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
FORCE_INLINE Iterator end()
|
||||
{
|
||||
return Iterator(*this, _size);
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
const Iterator begin() const
|
||||
{
|
||||
Iterator i(*this, -1);
|
||||
Iterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
FORCE_INLINE const Iterator end() const
|
||||
{
|
||||
return Iterator(*this, _size);
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -920,6 +920,11 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo
|
||||
}
|
||||
}
|
||||
|
||||
void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest)
|
||||
{
|
||||
DrawLine(origin, origin + direction, color, duration, depthTest);
|
||||
}
|
||||
|
||||
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest)
|
||||
{
|
||||
const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
|
||||
|
||||
@@ -31,6 +31,18 @@ namespace FlaxEngine
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the line in a direction.
|
||||
/// </summary>
|
||||
/// <param name="origin">The origin of the line.</param>
|
||||
/// <param name="direction">The direction of the line.</param>
|
||||
/// <param name="color">The color.</param>
|
||||
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
|
||||
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
|
||||
public static void DrawRay(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the line.
|
||||
/// </summary>
|
||||
|
||||
@@ -69,6 +69,16 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
|
||||
/// <param name="drawScenes">True if draw all debug shapes from scenes too or false if draw just from specified actor list.</param>
|
||||
API_FUNCTION() static void DrawActors(Actor** selectedActors, int32 selectedActorsCount, bool drawScenes);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the line in a direction.
|
||||
/// </summary>
|
||||
/// <param name="origin">The origin of the line.</param>
|
||||
/// <param name="direction">The direction of the line.</param>
|
||||
/// <param name="color">The color.</param>
|
||||
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
|
||||
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
|
||||
API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration = 0.0f, bool depthTest = true);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the line.
|
||||
/// </summary>
|
||||
@@ -604,6 +614,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
|
||||
API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color, int32 size = 32, float duration = 0.0f);
|
||||
};
|
||||
|
||||
#define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, duration, depthTest)
|
||||
#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) DebugDraw::DrawLine(start, end, color, duration, depthTest)
|
||||
#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) DebugDraw::DrawLines(lines, transform, color, duration, depthTest)
|
||||
#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) DebugDraw::DrawBezier(p1, p2, p3, p4, color, duration, depthTest)
|
||||
|
||||
@@ -371,7 +371,6 @@ namespace FlaxEngine.Interop
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
ManagedHandlePool.FreeHandle(handle);
|
||||
handle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
@@ -183,6 +183,9 @@ namespace FlaxEngine.Interop
|
||||
public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.Free(unmanaged);
|
||||
}
|
||||
|
||||
#if FLAX_EDITOR
|
||||
[HideInEditor]
|
||||
#endif
|
||||
[CustomMarshaller(typeof(Array), MarshalMode.ManagedToUnmanagedIn, typeof(SystemArrayMarshaller.ManagedToNative))]
|
||||
[CustomMarshaller(typeof(Array), MarshalMode.UnmanagedToManagedOut, typeof(SystemArrayMarshaller.ManagedToNative))]
|
||||
[CustomMarshaller(typeof(Array), MarshalMode.ManagedToUnmanagedOut, typeof(SystemArrayMarshaller.NativeToManaged))]
|
||||
@@ -251,6 +254,74 @@ namespace FlaxEngine.Interop
|
||||
}
|
||||
}
|
||||
|
||||
#if FLAX_EDITOR
|
||||
[HideInEditor]
|
||||
#endif
|
||||
[CustomMarshaller(typeof(object[]), MarshalMode.ManagedToUnmanagedIn, typeof(SystemObjectArrayMarshaller.ManagedToNative))]
|
||||
[CustomMarshaller(typeof(object[]), MarshalMode.UnmanagedToManagedOut, typeof(SystemObjectArrayMarshaller.ManagedToNative))]
|
||||
[CustomMarshaller(typeof(object[]), MarshalMode.ManagedToUnmanagedOut, typeof(SystemObjectArrayMarshaller.NativeToManaged))]
|
||||
[CustomMarshaller(typeof(object[]), MarshalMode.UnmanagedToManagedIn, typeof(SystemObjectArrayMarshaller.NativeToManaged))]
|
||||
public static unsafe class SystemObjectArrayMarshaller
|
||||
{
|
||||
#if FLAX_EDITOR
|
||||
[HideInEditor]
|
||||
#endif
|
||||
public struct ManagedToNative
|
||||
{
|
||||
ManagedHandle handle;
|
||||
|
||||
public void FromManaged(object[] managed)
|
||||
{
|
||||
if (managed != null)
|
||||
{
|
||||
var managedArray = NativeInterop.ManagedArrayToGCHandleWrappedArray(managed);
|
||||
handle = ManagedHandle.Alloc(managedArray, GCHandleType.Weak);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr ToUnmanaged()
|
||||
{
|
||||
return ManagedHandle.ToIntPtr(handle);
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
#if FLAX_EDITOR
|
||||
[HideInEditor]
|
||||
#endif
|
||||
public struct NativeToManaged
|
||||
{
|
||||
ManagedHandle handle;
|
||||
|
||||
public void FromUnmanaged(IntPtr unmanaged)
|
||||
{
|
||||
if (unmanaged == IntPtr.Zero)
|
||||
return;
|
||||
handle = ManagedHandle.FromIntPtr(unmanaged);
|
||||
}
|
||||
|
||||
public object[] ToManaged()
|
||||
{
|
||||
object[] result = null;
|
||||
if (handle.IsAllocated)
|
||||
{
|
||||
var managedArray = Unsafe.As<ManagedArray>(handle.Target);
|
||||
result = NativeInterop.GCHandleArrayToManagedArray<object>(managedArray);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if FLAX_EDITOR
|
||||
[HideInEditor]
|
||||
#endif
|
||||
|
||||
@@ -581,9 +581,9 @@ namespace FlaxEngine.Interop
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
internal static IntPtr NewStringLength(sbyte* text, int length)
|
||||
internal static IntPtr NewStringUTF8(sbyte* text, int length)
|
||||
{
|
||||
return ManagedString.ToNativeWeak(new string(text, 0, length));
|
||||
return ManagedString.ToNativeWeak(new string(text, 0, length, System.Text.Encoding.UTF8));
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
|
||||
@@ -456,6 +456,19 @@ void Actor::SetLayerName(const StringView& value)
|
||||
LOG(Warning, "Unknown layer name '{0}'", value);
|
||||
}
|
||||
|
||||
void Actor::SetLayerNameRecursive(const StringView& value)
|
||||
{
|
||||
for (int32 i = 0; i < 32; i++)
|
||||
{
|
||||
if (Level::Layers[i] == value)
|
||||
{
|
||||
SetLayerRecursive(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG(Warning, "Unknown layer name '{0}'", value);
|
||||
}
|
||||
|
||||
bool Actor::HasTag() const
|
||||
{
|
||||
return Tags.Count() != 0;
|
||||
@@ -476,6 +489,18 @@ void Actor::AddTag(const Tag& tag)
|
||||
Tags.AddUnique(tag);
|
||||
}
|
||||
|
||||
void Actor::AddTagRecursive(const Tag& tag)
|
||||
{
|
||||
for (const auto& child : Children)
|
||||
child->AddTagRecursive(tag);
|
||||
Tags.AddUnique(tag);
|
||||
}
|
||||
|
||||
void Actor::RemoveTag(const Tag& tag)
|
||||
{
|
||||
Tags.Remove(tag);
|
||||
}
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
|
||||
const String& Actor::GetTag() const
|
||||
@@ -500,6 +525,17 @@ void Actor::SetLayer(int32 layerIndex)
|
||||
OnLayerChanged();
|
||||
}
|
||||
|
||||
void Actor::SetLayerRecursive(int32 layerIndex)
|
||||
{
|
||||
layerIndex = Math::Clamp(layerIndex, 0, 31);
|
||||
for (const auto& child : Children)
|
||||
child->SetLayerRecursive(layerIndex);
|
||||
if (layerIndex == _layer)
|
||||
return;
|
||||
_layer = layerIndex;
|
||||
OnLayerChanged();
|
||||
}
|
||||
|
||||
void Actor::SetName(const StringView& value)
|
||||
{
|
||||
if (_name == value)
|
||||
|
||||
@@ -102,6 +102,12 @@ public:
|
||||
/// <param name="layerIndex">The index of the layer.</param>
|
||||
API_PROPERTY() void SetLayer(int32 layerIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the layer recursively for all underlying children.
|
||||
/// </summary>
|
||||
/// <param name="layerIndex">The index of the layer.</param>
|
||||
API_FUNCTION() void SetLayerRecursive(int32 layerIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the layer.
|
||||
/// </summary>
|
||||
@@ -113,6 +119,11 @@ public:
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetLayerName(const StringView& value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the layer recursively for actor and for all underlying child actors.
|
||||
/// </summary>
|
||||
API_FUNCTION() void SetLayerNameRecursive(const StringView& value);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this actor has any tag assigned.
|
||||
/// </summary>
|
||||
@@ -136,6 +147,18 @@ public:
|
||||
/// <param name="tag">The tag to add.</param>
|
||||
API_FUNCTION() void AddTag(const Tag& tag);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a tag to the actor and for all underlying child actors.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag to add.</param>
|
||||
API_FUNCTION() void AddTagRecursive(const Tag& tag);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a tag to the actor
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag to remove.</param>
|
||||
API_FUNCTION() void RemoveTag(const Tag& tag);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the tag.
|
||||
/// [Deprecated in v1.5]
|
||||
|
||||
@@ -484,8 +484,7 @@ namespace
|
||||
|
||||
void Spline::OnDebugDraw()
|
||||
{
|
||||
const Color color = GetSplineColor();
|
||||
DrawSpline(this, color.AlphaMultiplied(0.7f), _transform, true);
|
||||
DrawSpline(this, GetSplineColor().AlphaMultiplied(0.7f), _transform, true);
|
||||
|
||||
// Base
|
||||
Actor::OnDebugDraw();
|
||||
@@ -493,8 +492,7 @@ void Spline::OnDebugDraw()
|
||||
|
||||
void Spline::OnDebugDrawSelected()
|
||||
{
|
||||
const Color color = GetSplineColor();
|
||||
DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false);
|
||||
DrawSpline(this, Color::White, _transform, false);
|
||||
|
||||
// Base
|
||||
Actor::OnDebugDrawSelected();
|
||||
|
||||
@@ -354,6 +354,7 @@ public:
|
||||
|
||||
protected:
|
||||
#if USE_EDITOR
|
||||
// Spline color getter for debug drawing, can be overriden by custom spline types.
|
||||
virtual Color GetSplineColor()
|
||||
{
|
||||
return Color::White;
|
||||
|
||||
@@ -1255,20 +1255,19 @@ void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<Prefab
|
||||
auto nestedPrefab = allPrefabs[i].Get();
|
||||
if (nestedPrefab)
|
||||
{
|
||||
if (WaitForLoaded())
|
||||
if (nestedPrefab->WaitForLoaded())
|
||||
{
|
||||
LOG(Warning, "Waiting for prefab asset load failed.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sync only if prefab is used by this prefab (directly) and it has been captured before
|
||||
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
|
||||
if (nestedPrefabIndex != -1)
|
||||
{
|
||||
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[nestedPrefabIndex]))
|
||||
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
|
||||
continue;
|
||||
|
||||
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
|
||||
|
||||
ObjectsRemovalService::Flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +331,12 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node
|
||||
value = GET_PARTICLE_ATTRIBUTE(0, float);
|
||||
break;
|
||||
}
|
||||
// Particle Scale
|
||||
case 112:
|
||||
{
|
||||
value = GET_PARTICLE_ATTRIBUTE(0, Float3);
|
||||
break;
|
||||
}
|
||||
// Effect Position
|
||||
case 200:
|
||||
{
|
||||
|
||||
@@ -331,6 +331,10 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
|
||||
case 111:
|
||||
value = AccessParticleAttribute(node, TEXT("Radius"), ParticleAttribute::ValueTypes::Float, AccessMode::Read);
|
||||
break;
|
||||
// Particle Scale
|
||||
case 112:
|
||||
value = AccessParticleAttribute(node, TEXT("Scale"), ParticleAttribute::ValueTypes::Float3, AccessMode::Read);
|
||||
break;
|
||||
// Effect Position
|
||||
case 200:
|
||||
value = Value(VariantType::Float3, TEXT("EffectPosition"));
|
||||
|
||||
@@ -269,13 +269,20 @@ public:
|
||||
USE_ATTRIBUTE(Lifetime, Float, 1);
|
||||
break;
|
||||
}
|
||||
// Particle Mass
|
||||
// Particle Radius
|
||||
case GRAPH_NODE_MAKE_TYPE(14, 111):
|
||||
{
|
||||
node->UsesParticleData = true;
|
||||
USE_ATTRIBUTE(Radius, Float, 0);
|
||||
break;
|
||||
}
|
||||
// Particle Scale
|
||||
case GRAPH_NODE_MAKE_TYPE(14, 112):
|
||||
{
|
||||
node->UsesParticleData = true;
|
||||
USE_ATTRIBUTE(Scale, Float3, 0);
|
||||
break;
|
||||
}
|
||||
// Random
|
||||
case GRAPH_NODE_MAKE_TYPE(14, 208):
|
||||
case GRAPH_NODE_MAKE_TYPE(14, 209):
|
||||
|
||||
@@ -223,9 +223,10 @@ void WheeledVehicle::Setup()
|
||||
return;
|
||||
|
||||
// Release previous
|
||||
void* scene = GetPhysicsScene()->GetPhysicsScene();
|
||||
if (_vehicle)
|
||||
{
|
||||
PhysicsBackend::RemoveVehicle(GetPhysicsScene()->GetPhysicsScene(), this);
|
||||
PhysicsBackend::RemoveVehicle(scene, this);
|
||||
PhysicsBackend::DestroyVehicle(_vehicle, (int32)_driveTypeCurrent);
|
||||
_vehicle = nullptr;
|
||||
}
|
||||
@@ -236,7 +237,7 @@ void WheeledVehicle::Setup()
|
||||
if (!_vehicle)
|
||||
return;
|
||||
_driveTypeCurrent = _driveType;
|
||||
PhysicsBackend::AddVehicle(GetPhysicsScene()->GetPhysicsScene(), this);
|
||||
PhysicsBackend::AddVehicle(scene, this);
|
||||
PhysicsBackend::SetRigidDynamicActorSolverIterationCounts(_actor, 12, 4);
|
||||
#else
|
||||
LOG(Fatal, "Vehicles are not supported.");
|
||||
@@ -358,8 +359,23 @@ void WheeledVehicle::OnColliderChanged(Collider* c)
|
||||
{
|
||||
RigidBody::OnColliderChanged(c);
|
||||
|
||||
// Rebuild vehicle when someone adds/removed wheels
|
||||
Setup();
|
||||
if (_useWheelsUpdates)
|
||||
{
|
||||
// Rebuild vehicle when someone adds/removed wheels
|
||||
Setup();
|
||||
}
|
||||
}
|
||||
|
||||
void WheeledVehicle::OnActiveInTreeChanged()
|
||||
{
|
||||
// Skip rebuilds from per-wheel OnColliderChanged when whole vehicle is toggled
|
||||
_useWheelsUpdates = false;
|
||||
RigidBody::OnActiveInTreeChanged();
|
||||
_useWheelsUpdates = true;
|
||||
|
||||
// Perform whole rebuild when it gets activated
|
||||
if (IsActiveInHierarchy())
|
||||
Setup();
|
||||
}
|
||||
|
||||
void WheeledVehicle::OnPhysicsSceneChanged(PhysicsScene* previous)
|
||||
|
||||
@@ -329,6 +329,7 @@ private:
|
||||
DifferentialSettings _differential;
|
||||
GearboxSettings _gearbox;
|
||||
bool _fixInvalidForwardDir = false; // [Deprecated on 13.06.2023, expires on 13.06.2025]
|
||||
bool _useWheelsUpdates = true;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -484,6 +485,7 @@ public:
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
void OnColliderChanged(Collider* c) override;
|
||||
void OnActiveInTreeChanged() override;
|
||||
|
||||
protected:
|
||||
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
|
||||
#define CC_MIN_SIZE 0.001f
|
||||
|
||||
CharacterController::CharacterController(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
, _controller(nullptr)
|
||||
@@ -100,10 +102,9 @@ void CharacterController::SetStepOffset(float value)
|
||||
{
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float contactOffset = Math::Max(_contactOffset, ZeroTolerance);
|
||||
const float minSize = 0.001f;
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, minSize);
|
||||
PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - minSize));
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, CC_MIN_SIZE);
|
||||
PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - CC_MIN_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +176,8 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis
|
||||
void CharacterController::DrawPhysicsDebug(RenderView& view)
|
||||
{
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
|
||||
const Vector3 position = _transform.LocalToWorld(_center);
|
||||
if (view.Mode == ViewMode::PhysicsColliders)
|
||||
DEBUG_DRAW_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true);
|
||||
@@ -188,9 +188,8 @@ void CharacterController::DrawPhysicsDebug(RenderView& view)
|
||||
void CharacterController::OnDebugDrawSelected()
|
||||
{
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
|
||||
const Vector3 position = _transform.LocalToWorld(_center);
|
||||
DEBUG_DRAW_WIRE_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false);
|
||||
|
||||
@@ -231,9 +230,8 @@ void CharacterController::UpdateSize() const
|
||||
if (_controller)
|
||||
{
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), CC_MIN_SIZE);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
|
||||
PhysicsBackend::SetControllerSize(_controller, radius, height);
|
||||
}
|
||||
}
|
||||
@@ -245,11 +243,12 @@ void CharacterController::CreateShape()
|
||||
|
||||
void CharacterController::UpdateBounds()
|
||||
{
|
||||
void* actor = _shape ? PhysicsBackend::GetShapeActor(_shape) : nullptr;
|
||||
if (actor)
|
||||
PhysicsBackend::GetActorBounds(actor, _box);
|
||||
else
|
||||
_box = BoundingBox(_transform.Translation);
|
||||
const float scaling = GetScale().GetAbsolute().MaxValue();
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
|
||||
const Vector3 position = _transform.LocalToWorld(_center);
|
||||
const Vector3 extent(radius, height * 0.5f + radius, radius);
|
||||
_box = BoundingBox(position - extent, position + extent);
|
||||
BoundingSphere::FromBox(_box, _sphere);
|
||||
}
|
||||
|
||||
|
||||
@@ -889,18 +889,42 @@ void* PhysicsBackend::CreateScene(const PhysicsSettings& settings)
|
||||
PxSceneDesc sceneDesc(ToleranceScale);
|
||||
sceneDesc.gravity = C2P(settings.DefaultGravity);
|
||||
sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS;
|
||||
sceneDesc.flags |= PxSceneFlag::eENABLE_PCM;
|
||||
if (!settings.DisableCCD)
|
||||
sceneDesc.flags |= PxSceneFlag::eENABLE_CCD;
|
||||
sceneDesc.simulationEventCallback = &scenePhysX->EventsCallback;
|
||||
sceneDesc.filterShader = FilterShader;
|
||||
sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity;
|
||||
sceneDesc.solverType = PxSolverType::ePGS;
|
||||
switch (settings.SolverType)
|
||||
{
|
||||
case PhysicsSolverType::ProjectedGaussSeidelIterativeSolver:
|
||||
sceneDesc.solverType = PxSolverType::ePGS;
|
||||
break;
|
||||
case PhysicsSolverType::TemporalGaussSeidelSolver:
|
||||
sceneDesc.solverType = PxSolverType::eTGS;
|
||||
break;
|
||||
}
|
||||
if (sceneDesc.cpuDispatcher == nullptr)
|
||||
{
|
||||
scenePhysX->CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp<uint32>(Platform::GetCPUInfo().ProcessorCoreCount - 1, 1, 4));
|
||||
CHECK_INIT(scenePhysX->CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!");
|
||||
sceneDesc.cpuDispatcher = scenePhysX->CpuDispatcher;
|
||||
}
|
||||
switch (settings.BroadPhaseType)
|
||||
{
|
||||
case PhysicsBroadPhaseType::SweepAndPrune:
|
||||
sceneDesc.broadPhaseType = PxBroadPhaseType::eSAP;
|
||||
break;
|
||||
case PhysicsBroadPhaseType::MultiBoxPruning:
|
||||
sceneDesc.broadPhaseType = PxBroadPhaseType::eMBP;
|
||||
break;
|
||||
case PhysicsBroadPhaseType::AutomaticBoxPruning:
|
||||
sceneDesc.broadPhaseType = PxBroadPhaseType::eABP;
|
||||
break;
|
||||
case PhysicsBroadPhaseType::ParallelAutomaticBoxPruning:
|
||||
sceneDesc.broadPhaseType = PxBroadPhaseType::ePABP;
|
||||
break;
|
||||
}
|
||||
|
||||
// Create scene
|
||||
scenePhysX->Scene = PhysX->createScene(sceneDesc);
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
void ClearColliderFromCollection(PhysicsColliderActor* collider, Array<SimulationEventCallback::CollidersPair>& collection)
|
||||
void ClearColliderFromCollection(const PhysicsColliderActor* collider, Array<SimulationEventCallback::CollidersPair>& collection)
|
||||
{
|
||||
if (collection.IsEmpty())
|
||||
return;
|
||||
for (int32 i = 0; i < collection.Count(); i++)
|
||||
{
|
||||
if (collection[i].First == collider || collection[i].Second == collider)
|
||||
@@ -24,8 +26,10 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void ClearColliderFromCollection(PhysicsColliderActor* collider, SimulationEventCallback::CollisionsPool& collection)
|
||||
void ClearColliderFromCollection(const PhysicsColliderActor* collider, SimulationEventCallback::CollisionsPool& collection)
|
||||
{
|
||||
if (collection.IsEmpty())
|
||||
return;
|
||||
for (auto i = collection.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (i->Key.First == collider || i->Key.Second == collider)
|
||||
|
||||
@@ -57,6 +57,8 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier*
|
||||
DESERIALIZE(FrictionCombineMode);
|
||||
DESERIALIZE(RestitutionCombineMode);
|
||||
DESERIALIZE(DisableCCD);
|
||||
DESERIALIZE(BroadPhaseType);
|
||||
DESERIALIZE(SolverType);
|
||||
DESERIALIZE(MaxDeltaTime);
|
||||
DESERIALIZE(EnableSubstepping);
|
||||
DESERIALIZE(SubstepDeltaTime);
|
||||
|
||||
@@ -6,6 +6,50 @@
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Types.h"
|
||||
|
||||
/// <summary>
|
||||
/// Broad phase algorithm used in the simulation.
|
||||
/// <see href="https://nvidia-omniverse.github.io/PhysX/physx/5.1.0/_build/physx/latest/struct_px_broad_phase_type.html"/>
|
||||
/// </summary>
|
||||
API_ENUM() enum class PhysicsBroadPhaseType
|
||||
{
|
||||
/// <summary>
|
||||
/// 3-axes sweep-and-prune. Good generic choice with great performance when many objects are sleeping.
|
||||
/// </summary>
|
||||
SweepAndPrune = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Alternative broad phase algorithm that does not suffer from the same performance issues as SAP when all objects are moving or when inserting large numbers of objects.
|
||||
/// </summary>
|
||||
MultiBoxPruning = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Revisited implementation of MBP, which automatically manages broad-phase regions.
|
||||
/// </summary>
|
||||
AutomaticBoxPruning = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Parallel implementation of ABP. It can often be the fastest (CPU) broadphase, but it can use more memory than ABP.
|
||||
/// </summary>
|
||||
ParallelAutomaticBoxPruning = 3,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The type of solver used in the simulation.
|
||||
/// <see href="https://nvidia-omniverse.github.io/PhysX/physx/5.1.0/_build/physx/latest/struct_px_solver_type.html"/>
|
||||
/// </summary>
|
||||
API_ENUM() enum class PhysicsSolverType
|
||||
{
|
||||
/// <summary>
|
||||
/// The iterative sequential impulse solver.
|
||||
/// </summary>
|
||||
ProjectedGaussSeidelIterativeSolver = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Non linear iterative solver. This kind of solver can lead to improved convergence and handle large mass ratios, long chains and jointed systems better. It is slightly more expensive than the default solver and can introduce more energy to correct joint and contact errors.
|
||||
/// </summary>
|
||||
TemporalGaussSeidelSolver = 1,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Physics simulation settings container.
|
||||
/// </summary>
|
||||
@@ -43,6 +87,18 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Simulation\")")
|
||||
bool DisableCCD = false;
|
||||
|
||||
/// <summary>
|
||||
/// Broad phase algorithm to use in the simulation.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(71), EditorDisplay(\"Simulation\")")
|
||||
PhysicsBroadPhaseType BroadPhaseType = PhysicsBroadPhaseType::ParallelAutomaticBoxPruning;
|
||||
|
||||
/// <summary>
|
||||
/// The solver type to use in the simulation.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(72), EditorDisplay(\"Simulation\")")
|
||||
PhysicsSolverType SolverType = PhysicsSolverType::ProjectedGaussSeidelIterativeSolver;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum allowed delta time (in seconds) for the physics simulation step.
|
||||
/// </summary>
|
||||
|
||||
@@ -405,6 +405,7 @@ void WindowBase::OnResize(int32 width, int32 height)
|
||||
_swapChain->Resize(width, height);
|
||||
if (RenderTask)
|
||||
RenderTask->Resize(width, height);
|
||||
Resized({ static_cast<float>(width), static_cast<float>(height) });
|
||||
INVOKE_EVENT_PARAMS_2(OnResize, &width, &height);
|
||||
}
|
||||
|
||||
|
||||
@@ -315,6 +315,11 @@ public:
|
||||
/// </summary>
|
||||
Action Closed;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when window gets resized.
|
||||
/// </summary>
|
||||
Delegate<Float2> Resized;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when window gets focused.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "MTypes.h"
|
||||
#include "MClass.h"
|
||||
#include "MCore.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Types/DataContainer.h"
|
||||
@@ -354,7 +355,7 @@ struct MConverter<Array<T>>
|
||||
{
|
||||
if (!klass)
|
||||
return nullptr;
|
||||
MArray* result = MCore::Array::New(klass, data.Count());
|
||||
MArray* result = MCore::Array::New(klass->GetElementClass(), data.Count());
|
||||
MConverter<T> converter;
|
||||
converter.ToManagedArray(result, Span<T>(data.Get(), data.Count()));
|
||||
return (MObject*)result;
|
||||
|
||||
@@ -365,8 +365,8 @@ MString* MCore::String::GetEmpty(MDomain* domain)
|
||||
|
||||
MString* MCore::String::New(const char* str, int32 length, MDomain* domain)
|
||||
{
|
||||
static void* NewStringLengthPtr = GetStaticMethodPointer(TEXT("NewStringLength"));
|
||||
return (MString*)CallStaticMethod<void*, const char*, int>(NewStringLengthPtr, str, length);
|
||||
static void* NewStringUTF8Ptr = GetStaticMethodPointer(TEXT("NewStringUTF8"));
|
||||
return (MString*)CallStaticMethod<void*, const char*, int>(NewStringUTF8Ptr, str, length);
|
||||
}
|
||||
|
||||
MString* MCore::String::New(const Char* str, int32 length, MDomain* domain)
|
||||
|
||||
@@ -205,6 +205,13 @@ namespace FlaxEngine
|
||||
|
||||
internal static void AddDictionaryItem(IDictionary dictionary, object key, object value)
|
||||
{
|
||||
// TODO: more generic approach to properly add value that is of custom boxed type? (eg. via NativeInterop.MarshalToManaged)
|
||||
if (value is ManagedArray managedArray)
|
||||
{
|
||||
var managedArrayHandle = ManagedHandle.Alloc(managedArray, GCHandleType.Normal);
|
||||
value = NativeInterop.MarshalToManaged((IntPtr)managedArrayHandle, managedArray.ArrayType);
|
||||
managedArrayHandle.Free();
|
||||
}
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -384,6 +384,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
|
||||
SERIALIZE(BaseLOD);
|
||||
SERIALIZE(LODCount);
|
||||
SERIALIZE(TriangleReduction);
|
||||
SERIALIZE(SloppyOptimization);
|
||||
SERIALIZE(LODTargetError);
|
||||
SERIALIZE(ImportMaterials);
|
||||
SERIALIZE(ImportMaterialsAsInstances);
|
||||
SERIALIZE(InstanceToImportAs);
|
||||
@@ -427,6 +429,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
|
||||
DESERIALIZE(BaseLOD);
|
||||
DESERIALIZE(LODCount);
|
||||
DESERIALIZE(TriangleReduction);
|
||||
DESERIALIZE(SloppyOptimization);
|
||||
DESERIALIZE(LODTargetError);
|
||||
DESERIALIZE(ImportMaterials);
|
||||
DESERIALIZE(ImportMaterialsAsInstances);
|
||||
DESERIALIZE(InstanceToImportAs);
|
||||
@@ -1556,7 +1560,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
|
||||
int32 dstMeshIndexCountTarget = int32(srcMeshIndexCount * triangleReduction) / 3 * 3;
|
||||
Array<unsigned int> indices;
|
||||
indices.Resize(dstMeshIndexCountTarget);
|
||||
int32 dstMeshIndexCount = (int32)meshopt_simplifySloppy(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget);
|
||||
int32 dstMeshIndexCount = {};
|
||||
if (options.SloppyOptimization)
|
||||
dstMeshIndexCount = (int32)meshopt_simplifySloppy(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget);
|
||||
else
|
||||
dstMeshIndexCount = (int32)meshopt_simplify(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError);
|
||||
indices.Resize(dstMeshIndexCount);
|
||||
if (dstMeshIndexCount == 0)
|
||||
continue;
|
||||
|
||||
@@ -324,6 +324,12 @@ public:
|
||||
// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.
|
||||
API_FIELD(Attributes="EditorOrder(1130), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 1, 0.001f)")
|
||||
float TriangleReduction = 0.5f;
|
||||
// Whether to do a sloppy mesh optimization. This is faster but does not follow the topology of the original mesh.
|
||||
API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))")
|
||||
bool SloppyOptimization = true;
|
||||
// Only used if Sloppy is false. Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0..1] range (e.g. 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents).
|
||||
API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), Limit(0.01f, 1, 0.001f)")
|
||||
float LODTargetError = 0.1f;
|
||||
|
||||
public: // Materials
|
||||
|
||||
|
||||
@@ -199,11 +199,11 @@ namespace FlaxEngine.GUI
|
||||
// UI navigation
|
||||
if (_canvas.ReceivesEvents)
|
||||
{
|
||||
UpdateNavigation(deltaTime, _canvas.NavigationInputActionUp, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigationInputActionDown, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigationInputActionLeft, NavDirection.Left, ref _navigationHeldTimeLeft, ref _navigationRateTimeLeft);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigationInputActionRight, NavDirection.Right, ref _navigationHeldTimeRight, ref _navigationRateTimeRight);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigationInputActionSubmit, ref _navigationHeldTimeSubmit, ref _navigationRateTimeSubmit, SubmitFocused);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigateLeft.Name, NavDirection.Left, ref _navigationHeldTimeLeft, ref _navigationRateTimeLeft);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigateRight.Name, NavDirection.Right, ref _navigationHeldTimeRight, ref _navigationRateTimeRight);
|
||||
UpdateNavigation(deltaTime, _canvas.NavigateSubmit.Name, ref _navigationHeldTimeSubmit, ref _navigationRateTimeSubmit, SubmitFocused);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
365
Source/Engine/UI/GUI/Common/Slider.cs
Normal file
365
Source/Engine/UI/GUI/Common/Slider.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine.GUI;
|
||||
|
||||
/// <summary>
|
||||
/// The slider control.
|
||||
/// </summary>
|
||||
public class Slider : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum value.
|
||||
/// </summary>
|
||||
protected float _minimum;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum value.
|
||||
/// </summary>
|
||||
protected float _maximum = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum value.
|
||||
/// </summary>
|
||||
[EditorOrder(20), Tooltip("The minimum value.")]
|
||||
public float Minimum
|
||||
{
|
||||
get => _minimum;
|
||||
set
|
||||
{
|
||||
if (value > _maximum)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (WholeNumbers)
|
||||
value = Mathf.RoundToInt(value);
|
||||
_minimum = value;
|
||||
if (Value < _minimum)
|
||||
Value = _minimum;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum value.
|
||||
/// </summary>
|
||||
[EditorOrder(30), Tooltip("The maximum value.")]
|
||||
public float Maximum
|
||||
{
|
||||
get => _maximum;
|
||||
set
|
||||
{
|
||||
if (value < _minimum || Mathf.IsZero(value))
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (WholeNumbers)
|
||||
value = Mathf.RoundToInt(value);
|
||||
_maximum = value;
|
||||
if (Value > _maximum)
|
||||
Value = _maximum;
|
||||
}
|
||||
}
|
||||
|
||||
private float _value = 100f;
|
||||
private Rectangle _thumbRect;
|
||||
private float _thumbCenter;
|
||||
private Float2 _thumbSize = new Float2(16, 16);
|
||||
private bool _isSliding;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value (normalized to range 0-100).
|
||||
/// </summary>
|
||||
[EditorOrder(10), Tooltip("The current value.")]
|
||||
public float Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
value = Mathf.Clamp(value, Minimum, Maximum);
|
||||
if (WholeNumbers)
|
||||
value = Mathf.RoundToInt(value);
|
||||
if (!Mathf.NearEqual(value, _value))
|
||||
{
|
||||
_value = value;
|
||||
|
||||
// Update
|
||||
UpdateThumb();
|
||||
ValueChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The local position of the thumb center
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Float2 ThumbCenter => new(_thumbCenter, Height / 2);
|
||||
|
||||
/// <summary>
|
||||
/// The local position of the beginning of the track.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Float2 TrackBeginning => new(_thumbSize.X / 2, Height / 2);
|
||||
|
||||
/// <summary>
|
||||
/// The local position of the end of the track.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Float2 TrackEnd => new(Width - _thumbSize.X / 2, Height / 2);
|
||||
|
||||
/// <summary>
|
||||
/// The height of the track.
|
||||
/// </summary>
|
||||
[EditorOrder(40), Tooltip("The track height.")]
|
||||
public int TrackHeight { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The thumb size.
|
||||
/// </summary>
|
||||
[EditorOrder(41), Tooltip("The size of the thumb.")]
|
||||
public Float2 ThumbSize {
|
||||
get => _thumbSize;
|
||||
set
|
||||
{
|
||||
_thumbSize = value;
|
||||
UpdateThumb();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether to fill the track.
|
||||
/// </summary>
|
||||
[EditorOrder(42), Tooltip("Fill the track.")]
|
||||
public bool FillTrack = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use whole numbers.
|
||||
/// </summary>
|
||||
[EditorOrder(43), Tooltip("Use whole numbers.")]
|
||||
public bool WholeNumbers = false;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the slider track line
|
||||
/// </summary>
|
||||
[EditorDisplay("Track Style"), EditorOrder(2010), Tooltip("The color of the slider track line."), ExpandGroups]
|
||||
public Color TrackLineColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color of the slider fill track line
|
||||
/// </summary>
|
||||
[EditorDisplay("Track Style"), EditorOrder(2011), VisibleIf(nameof(FillTrack)), Tooltip("The color of the slider fill track line.")]
|
||||
public Color TrackFillLineColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the track.
|
||||
/// </summary>
|
||||
private float TrackWidth => Width;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush used for slider track drawing.
|
||||
/// </summary>
|
||||
[EditorDisplay("Track Style"), EditorOrder(2012), Tooltip("The brush used for slider track drawing.")]
|
||||
public IBrush TrackBrush { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush used for slider fill track drawing.
|
||||
/// </summary>
|
||||
[EditorDisplay("Track Style"), EditorOrder(2013), VisibleIf(nameof(FillTrack)), Tooltip("The brush used for slider fill track drawing.")]
|
||||
public IBrush FillTrackBrush { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color of the slider thumb when it's not selected
|
||||
/// </summary>
|
||||
[EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups]
|
||||
public Color ThumbColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color of the slider thumb when it's selected
|
||||
/// </summary>
|
||||
[EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's selected.")]
|
||||
public Color ThumbColorSelected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush used for slider thumb drawing.
|
||||
/// </summary>
|
||||
[EditorDisplay("Thumb Style"), EditorOrder(2032), Tooltip("The brush of the slider thumb.")]
|
||||
public IBrush ThumbBrush { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether user is using a slider.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public bool IsSliding => _isSliding;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when sliding starts.
|
||||
/// </summary>
|
||||
public event Action SlidingStart;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when sliding ends.
|
||||
/// </summary>
|
||||
public event Action SlidingEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when value gets changed.
|
||||
/// </summary>
|
||||
public event Action ValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Slider"/> class.
|
||||
/// </summary>
|
||||
public Slider()
|
||||
: this(120, 30)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Slider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="width">The width.</param>
|
||||
/// <param name="height">The height.</param>
|
||||
public Slider(float width, float height)
|
||||
: base(0, 0, width, height)
|
||||
{
|
||||
var style = Style.Current;
|
||||
TrackLineColor = style.BackgroundHighlighted;
|
||||
TrackFillLineColor = style.LightBackground;
|
||||
ThumbColor = style.BackgroundNormal;
|
||||
ThumbColorSelected = style.BackgroundSelected;
|
||||
UpdateThumb();
|
||||
}
|
||||
|
||||
private void UpdateThumb()
|
||||
{
|
||||
// Cache data
|
||||
float trackSize = TrackWidth;
|
||||
float range = Maximum - Minimum;
|
||||
float pixelRange = trackSize - _thumbSize.X;
|
||||
float perc = (_value - Minimum) / range;
|
||||
float thumbPosition = (int)(perc * pixelRange);
|
||||
_thumbCenter = thumbPosition + _thumbSize.X / 2;
|
||||
_thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y);
|
||||
}
|
||||
|
||||
private void EndSliding()
|
||||
{
|
||||
_isSliding = false;
|
||||
EndMouseCapture();
|
||||
SlidingEnd?.Invoke();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Draw track line
|
||||
//var lineRect = new Rectangle(4, (Height - TrackHeight) / 2, Width - 8, TrackHeight);
|
||||
var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight) / 2, Width - _thumbSize.X, TrackHeight);
|
||||
if (TrackBrush != null)
|
||||
TrackBrush.Draw(lineRect, TrackLineColor);
|
||||
else
|
||||
Render2D.FillRectangle(lineRect, TrackLineColor);
|
||||
|
||||
// Draw track fill
|
||||
if (FillTrack)
|
||||
{
|
||||
var fillLineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2);
|
||||
Render2D.PushClip(ref fillLineRect);
|
||||
if (FillTrackBrush != null)
|
||||
FillTrackBrush.Draw(lineRect, TrackFillLineColor);
|
||||
else
|
||||
Render2D.FillRectangle(lineRect, TrackFillLineColor);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
|
||||
// Draw thumb
|
||||
var thumbColor = _isSliding ? ThumbColorSelected : ThumbColor;
|
||||
if (ThumbBrush != null)
|
||||
ThumbBrush.Draw(_thumbRect, thumbColor);
|
||||
else
|
||||
Render2D.FillRectangle(_thumbRect, thumbColor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
if (_isSliding)
|
||||
{
|
||||
EndSliding();
|
||||
}
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
Focus();
|
||||
float mousePosition = location.X;
|
||||
|
||||
if (_thumbRect.Contains(ref location))
|
||||
{
|
||||
// Start sliding
|
||||
_isSliding = true;
|
||||
StartMouseCapture();
|
||||
SlidingStart?.Invoke();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Click change
|
||||
Value += (mousePosition < _thumbCenter ? -1 : 1) * 10;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Float2 location)
|
||||
{
|
||||
if (_isSliding)
|
||||
{
|
||||
// Update sliding
|
||||
var slidePosition = location + Root.TrackingMouseOffset;
|
||||
Value = Mathf.Remap(slidePosition.X, 4, TrackWidth - 4, Minimum, Maximum);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isSliding)
|
||||
{
|
||||
EndSliding();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
// Check if was sliding
|
||||
if (_isSliding)
|
||||
{
|
||||
EndSliding();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnEndMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnSizeChanged()
|
||||
{
|
||||
base.OnSizeChanged();
|
||||
|
||||
UpdateThumb();
|
||||
}
|
||||
}
|
||||
@@ -263,39 +263,39 @@ namespace FlaxEngine
|
||||
public float NavigationInputRepeatRate { get; set; } = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the input action for performing UI navigation Up (from Input Settings).
|
||||
/// The input action for performing UI navigation Up (from Input Settings).
|
||||
/// </summary>
|
||||
[EditorOrder(510), EditorDisplay("Navigation", "Navigate Up")]
|
||||
[Tooltip("The name of the input action for performing UI navigation Up (from Input Settings).")]
|
||||
public string NavigationInputActionUp { get; set; } = "NavigateUp";
|
||||
[Tooltip("The input action for performing UI navigation Up (from Input Settings).")]
|
||||
public InputEvent NavigateUp { get; set; } = new InputEvent("NavigateUp");
|
||||
|
||||
/// <summary>
|
||||
/// The name of the input action for performing UI navigation Down (from Input Settings).
|
||||
/// The input action for performing UI navigation Down (from Input Settings).
|
||||
/// </summary>
|
||||
[EditorOrder(520), EditorDisplay("Navigation", "Navigate Down")]
|
||||
[Tooltip("The name of the input action for performing UI navigation Down (from Input Settings).")]
|
||||
public string NavigationInputActionDown { get; set; } = "NavigateDown";
|
||||
[Tooltip("The input action for performing UI navigation Down (from Input Settings).")]
|
||||
public InputEvent NavigateDown { get; set; } = new InputEvent("NavigateDown");
|
||||
|
||||
/// <summary>
|
||||
/// The name of the input action for performing UI navigation Left (from Input Settings).
|
||||
/// The input action for performing UI navigation Left (from Input Settings).
|
||||
/// </summary>
|
||||
[EditorOrder(530), EditorDisplay("Navigation", "Navigate Left")]
|
||||
[Tooltip("The name of the input action for performing UI navigation Left (from Input Settings).")]
|
||||
public string NavigationInputActionLeft { get; set; } = "NavigateLeft";
|
||||
[Tooltip("The input action for performing UI navigation Left (from Input Settings).")]
|
||||
public InputEvent NavigateLeft { get; set; } = new InputEvent("NavigateLeft");
|
||||
|
||||
/// <summary>
|
||||
/// The name of the input action for performing UI navigation Right (from Input Settings).
|
||||
/// The input action for performing UI navigation Right (from Input Settings).
|
||||
/// </summary>
|
||||
[EditorOrder(540), EditorDisplay("Navigation", "Navigate Right")]
|
||||
[Tooltip("The name of the input action for performing UI navigation Right (from Input Settings).")]
|
||||
public string NavigationInputActionRight { get; set; } = "NavigateRight";
|
||||
[Tooltip("The input action for performing UI navigation Right (from Input Settings).")]
|
||||
public InputEvent NavigateRight { get; set; } = new InputEvent("NavigateRight");
|
||||
|
||||
/// <summary>
|
||||
/// The name of the input action for performing UI navigation Submit (from Input Settings).
|
||||
/// The input action for performing UI navigation Submit (from Input Settings).
|
||||
/// </summary>
|
||||
[EditorOrder(540), EditorDisplay("Navigation", "Navigate Submit")]
|
||||
[Tooltip("The name of the input action for performing UI navigation Submit (from Input Settings).")]
|
||||
public string NavigationInputActionSubmit { get; set; } = "NavigateSubmit";
|
||||
[EditorOrder(550), EditorDisplay("Navigation", "Navigate Submit")]
|
||||
[Tooltip("The input action for performing UI navigation Submit (from Input Settings).")]
|
||||
public InputEvent NavigateSubmit { get; set; } = new InputEvent("NavigateSubmit");
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -620,14 +620,36 @@ namespace FlaxEngine
|
||||
jsonWriter.WriteValue(NavigationInputRepeatDelay);
|
||||
jsonWriter.WritePropertyName("NavigationInputRepeatRate");
|
||||
jsonWriter.WriteValue(NavigationInputRepeatRate);
|
||||
jsonWriter.WritePropertyName("NavigationInputActionUp");
|
||||
jsonWriter.WriteValue(NavigationInputActionUp);
|
||||
jsonWriter.WritePropertyName("NavigationInputActionDown");
|
||||
jsonWriter.WriteValue(NavigationInputActionDown);
|
||||
jsonWriter.WritePropertyName("NavigationInputActionLeft");
|
||||
jsonWriter.WriteValue(NavigationInputActionLeft);
|
||||
jsonWriter.WritePropertyName("NavigationInputActionRight");
|
||||
jsonWriter.WriteValue(NavigationInputActionRight);
|
||||
|
||||
jsonWriter.WritePropertyName("NavigateUp");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateUp.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
|
||||
jsonWriter.WritePropertyName("NavigateDown");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateDown.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
|
||||
jsonWriter.WritePropertyName("NavigateLeft");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateLeft.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
|
||||
jsonWriter.WritePropertyName("NavigateRight");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateRight.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
|
||||
jsonWriter.WritePropertyName("NavigateSubmit");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateSubmit.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
@@ -713,25 +735,45 @@ namespace FlaxEngine
|
||||
jsonWriter.WritePropertyName("NavigationInputRepeatRate");
|
||||
jsonWriter.WriteValue(NavigationInputRepeatRate);
|
||||
}
|
||||
if (NavigationInputActionUp != other.NavigationInputActionUp)
|
||||
if (NavigateUp.Name != other.NavigateUp.Name)
|
||||
{
|
||||
jsonWriter.WritePropertyName("NavigationInputActionUp");
|
||||
jsonWriter.WriteValue(NavigationInputActionUp);
|
||||
jsonWriter.WritePropertyName("NavigateUp");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateUp.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
if (NavigationInputActionDown != other.NavigationInputActionDown)
|
||||
if (NavigateDown.Name != other.NavigateDown.Name)
|
||||
{
|
||||
jsonWriter.WritePropertyName("NavigationInputActionDown");
|
||||
jsonWriter.WriteValue(NavigationInputActionDown);
|
||||
jsonWriter.WritePropertyName("NavigateDown");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateDown.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
if (NavigationInputActionLeft != other.NavigationInputActionLeft)
|
||||
if (NavigateLeft.Name != other.NavigateLeft.Name)
|
||||
{
|
||||
jsonWriter.WritePropertyName("NavigationInputActionLeft");
|
||||
jsonWriter.WriteValue(NavigationInputActionLeft);
|
||||
jsonWriter.WritePropertyName("NavigateLeft");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateLeft.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
if (NavigationInputActionRight != other.NavigationInputActionRight)
|
||||
if (NavigateRight.Name != other.NavigateRight.Name)
|
||||
{
|
||||
jsonWriter.WritePropertyName("NavigationInputActionRight");
|
||||
jsonWriter.WriteValue(NavigationInputActionRight);
|
||||
jsonWriter.WritePropertyName("NavigateRight");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateRight.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
if (NavigateSubmit.Name != other.NavigateSubmit.Name)
|
||||
{
|
||||
jsonWriter.WritePropertyName("NavigateSubmit");
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WritePropertyName("Name");
|
||||
jsonWriter.WriteValue(NavigateSubmit.Name);
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
|
||||
jsonWriter.WriteEndObject();
|
||||
|
||||
@@ -1279,16 +1279,16 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
||||
// Random Float Range
|
||||
case 213:
|
||||
{
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat;
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat;
|
||||
auto a = (float)tryGetValue(node->TryGetBox(1), node->Values[0]);
|
||||
auto b = (float)tryGetValue(node->TryGetBox(2), node->Values[1]);
|
||||
value = Math::Lerp(a, b, RAND);
|
||||
break;
|
||||
}
|
||||
// Random Vector2 Range
|
||||
case 214:
|
||||
{
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat2();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat2();
|
||||
auto a = (Float2)tryGetValue(node->TryGetBox(1), node->Values[0]);
|
||||
auto b = (Float2)tryGetValue(node->TryGetBox(2), node->Values[1]);
|
||||
value = Float2(
|
||||
Math::Lerp(a.X, b.X, RAND),
|
||||
Math::Lerp(a.Y, b.Y, RAND)
|
||||
@@ -1298,8 +1298,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
||||
// Random Vector3 Range
|
||||
case 215:
|
||||
{
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat3();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat3();
|
||||
auto a = (Float3)tryGetValue(node->TryGetBox(1), node->Values[0]);
|
||||
auto b = (Float3)tryGetValue(node->TryGetBox(2), node->Values[1]);
|
||||
value = Float3(
|
||||
Math::Lerp(a.X, b.X, RAND),
|
||||
Math::Lerp(a.Y, b.Y, RAND),
|
||||
@@ -1310,8 +1310,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
||||
// Random Vector4 Range
|
||||
case 216:
|
||||
{
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat4();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat4();
|
||||
auto a = (Float4)tryGetValue(node->TryGetBox(1), node->Values[0]);
|
||||
auto b = (Float4)tryGetValue(node->TryGetBox(2), node->Values[1]);
|
||||
value = Float4(
|
||||
Math::Lerp(a.X, b.X, RAND),
|
||||
Math::Lerp(a.Y, b.Y, RAND),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
apply plugin: 'com.android.application'
|
||||
android {
|
||||
compileSdkVersion 24
|
||||
namespace "${PackageName}"
|
||||
defaultConfig {
|
||||
applicationId "${PackageName}"
|
||||
minSdkVersion 24
|
||||
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath 'com.android.tools.build:gradle:8.1.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
@@ -9,5 +9,4 @@
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
|
||||
@@ -521,7 +521,7 @@ namespace Flax.Build.Bindings
|
||||
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))";
|
||||
else if (returnValueType == "CultureInfo")
|
||||
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.CultureInfoMarshaller))";
|
||||
else if (functionInfo.ReturnType.Type == "Variant")
|
||||
else if (functionInfo.ReturnType.Type == "Variant") // object
|
||||
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))";
|
||||
else if (FindApiTypeInfo(buildData, functionInfo.ReturnType, caller)?.IsInterface ?? false)
|
||||
{
|
||||
@@ -530,6 +530,8 @@ namespace Flax.Build.Bindings
|
||||
}
|
||||
else if (functionInfo.ReturnType.Type == "MonoArray" || functionInfo.ReturnType.Type == "MArray")
|
||||
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))";
|
||||
else if (returnValueType == "object[]")
|
||||
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))";
|
||||
else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BytesContainer" || returnNativeType == "Array")
|
||||
returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))";
|
||||
else if (functionInfo.ReturnType.Type == "Dictionary")
|
||||
@@ -579,6 +581,8 @@ namespace Flax.Build.Bindings
|
||||
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))";
|
||||
else if (parameterInfo.Type.Type == "MonoArray" || parameterInfo.Type.Type == "MArray")
|
||||
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))";
|
||||
else if (nativeType == "object[]")
|
||||
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))";
|
||||
else if (parameterInfo.Type.Type == "Array" && parameterInfo.Type.GenericArgs.Count > 0 && parameterInfo.Type.GenericArgs[0].Type == "bool")
|
||||
parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__{parameterInfo.Name}Count"))})";
|
||||
else if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer" || nativeType == "Array")
|
||||
|
||||
Reference in New Issue
Block a user