Merge branch 'FlaxEngine:master' into material_import
This commit is contained in:
+68
-2
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user