Add reordering and reparenting decorators (with undo)

This commit is contained in:
Wojtek Figat
2023-08-25 14:54:40 +02:00
parent 99547a1ff4
commit 1287731816
3 changed files with 233 additions and 67 deletions

View File

@@ -254,27 +254,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// Small image control added per script group that allows to drag and drop a reference to it. Also used to reorder the scripts.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.Image" />
internal class ScriptDragIcon : Image
internal class DragImage : Image
{
private ScriptsEditor _editor;
private bool _isMouseDown;
private Float2 _mouseDownPos;
/// <summary>
/// Gets the target script.
/// Action called when drag event should start.
/// </summary>
public Script Script => (Script)Tag;
/// <summary>
/// Initializes a new instance of the <see cref="ScriptDragIcon"/> class.
/// </summary>
/// <param name="editor">The script editor.</param>
/// <param name="script">The target script.</param>
public ScriptDragIcon(ScriptsEditor editor, Script script)
{
Tag = script;
_editor = editor;
}
public Action<DragImage> Drag;
/// <inheritdoc />
public override void OnMouseEnter(Float2 location)
@@ -287,11 +275,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc />
public override void OnMouseLeave()
{
// Check if start drag drop
if (_isMouseDown)
{
DoDrag();
_isMouseDown = false;
Drag(this);
}
base.OnMouseLeave();
@@ -300,11 +287,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
// Check if start drag drop
if (_isMouseDown && Float2.Distance(location, _mouseDownPos) > 10.0f)
{
DoDrag();
_isMouseDown = false;
Drag(this);
}
base.OnMouseMove(location);
@@ -315,8 +301,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (button == MouseButton.Left)
{
// Clear flag
_isMouseDown = false;
return true;
}
return base.OnMouseUp(location, button);
@@ -327,21 +313,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (button == MouseButton.Left)
{
// Set flag
_isMouseDown = true;
_mouseDownPos = location;
return true;
}
return base.OnMouseDown(location, button);
}
private void DoDrag()
{
var script = Script;
_editor.OnScriptDragChange(true, script);
DoDragDrop(DragScripts.GetDragData(script));
_editor.OnScriptDragChange(false, script);
}
}
internal class ScriptArrangeBar : Control
@@ -639,7 +617,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
_scriptToggles[i] = scriptToggle;
// Add drag button to the group
var scriptDrag = new ScriptDragIcon(this, script)
var scriptDrag = new DragImage
{
TooltipText = "Script reference",
AutoFocus = true,
@@ -650,6 +628,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
Margin = new Margin(1),
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
Tag = script,
Drag = img =>
{
var s = (Script)img.Tag;
OnScriptDragChange(true, s);
img.DoDragDrop(DragScripts.GetDragData(s));
OnScriptDragChange(false, s);
}
};
// Add settings button to the group

View File

@@ -58,7 +58,6 @@ namespace FlaxEditor.GUI.Drag
{
if (item == null)
throw new ArgumentNullException();
return new DragDataText(DragPrefix + item.ID.ToString("N"));
}
@@ -71,11 +70,9 @@ namespace FlaxEditor.GUI.Drag
{
if (items == null)
throw new ArgumentNullException();
string text = DragPrefix;
foreach (var item in items)
text += item.ID.ToString("N") + '\n';
return new DragDataText(text);
}
@@ -83,9 +80,7 @@ namespace FlaxEditor.GUI.Drag
/// Tries to parse the drag data.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>
/// Gathered objects or empty IEnumerable if cannot get any valid.
/// </returns>
/// <returns>Gathered objects or empty IEnumerable if cannot get any valid.</returns>
public override IEnumerable<Script> FromDragData(DragData data)
{
if (data is DragDataText dataText)
@@ -97,12 +92,9 @@ namespace FlaxEditor.GUI.Drag
var results = new List<Script>(ids.Length);
for (int i = 0; i < ids.Length; i++)
{
// Find element
if (Guid.TryParse(ids[i], out Guid id))
{
var obj = FlaxEngine.Object.Find<Script>(ref id);
// Check it
if (obj != null)
results.Add(obj);
}
@@ -111,11 +103,11 @@ namespace FlaxEditor.GUI.Drag
return results.ToArray();
}
}
return new Script[0];
return Utils.GetEmptyArray<Script>();
}
/// <summary>
/// Tries to parse the drag data to validate if it has valid scripts darg.
/// Tries to parse the drag data to validate if it has valid scripts drag.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>True if drag data has valid scripts, otherwise false.</returns>
@@ -138,7 +130,6 @@ namespace FlaxEditor.GUI.Drag
}
}
}
return false;
}
}

View File

@@ -2,13 +2,14 @@
using System;
using System.Collections.Generic;
using FlaxEditor.CustomEditors.Dedicated;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Surface.Archetypes
{
@@ -23,6 +24,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
internal class NodeBase : SurfaceNode
{
protected const float ConnectionAreaMargin = 12.0f;
protected const float ConnectionAreaHeight = 12.0f;
protected const float DecoratorsMarginX = 5.0f;
protected const float DecoratorsMarginY = 2.0f;
protected ScriptType _type;
internal bool _isValueEditing;
@@ -138,7 +144,7 @@ namespace FlaxEditor.Surface.Archetypes
if (IsDisposing)
return;
_type = ScriptType.Null;
Object.Destroy(ref Instance);
FlaxEngine.Object.Destroy(ref Instance);
base.OnDestroy();
}
@@ -149,11 +155,6 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
internal class Node : NodeBase
{
private const float ConnectionAreaMargin = 12.0f;
private const float ConnectionAreaHeight = 12.0f;
private const float DecoratorsMarginX = 5.0f;
private const float DecoratorsMarginY = 2.0f;
private InputBox _input;
private OutputBox _output;
internal List<Decorator> _decorators;
@@ -186,20 +187,7 @@ namespace FlaxEditor.Surface.Archetypes
}
return result;
}
set
{
var ids = new byte[sizeof(uint) * value.Count];
if (value != null)
{
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
for (var i = 0; i < value.Count; i++)
ptr[i] = value[i];
}
}
SetValue(2, ids);
}
set => SetDecoratorIds(value, true);
}
public unsafe List<Decorator> Decorators
@@ -244,6 +232,28 @@ namespace FlaxEditor.Surface.Archetypes
}
}
public unsafe void SetDecoratorIds(List<uint> value, bool withUndo)
{
var ids = new byte[sizeof(uint) * value.Count];
if (value != null)
{
fixed (byte* data = ids)
{
uint* ptr = (uint*)data;
for (var i = 0; i < value.Count; i++)
ptr[i] = value[i];
}
}
if (withUndo)
SetValue(2, ids);
else
{
Values[2] = ids;
OnValuesChanged();
Surface?.MarkAsEdited();
}
}
public override unsafe SurfaceNode[] SealedNodes
{
get
@@ -422,9 +432,9 @@ namespace FlaxEditor.Surface.Archetypes
{
decorator.ResizeAuto();
height += decorator.Height + DecoratorsMarginY;
width = Mathf.Max(width, decorator.Width + 2 * DecoratorsMarginX);
width = Mathf.Max(width, decorator.Width - FlaxEditor.Surface.Constants.NodeCloseButtonSize - 2 * DecoratorsMarginX);
}
Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize);
Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2 + FlaxEditor.Surface.Constants.NodeCloseButtonSize, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize);
UpdateRectangles();
}
@@ -470,14 +480,94 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary>
internal class Decorator : NodeBase
{
private sealed class DragDecorator : DragHelper<uint, DragEventArgs>
{
public const string DragPrefix = "DECORATOR!?";
public DragDecorator(Func<uint, bool> validateFunction)
: base(validateFunction)
{
}
public override DragData ToDragData(uint item) => new DragDataText(DragPrefix + item);
public override DragData ToDragData(IEnumerable<uint> items)
{
throw new NotImplementedException();
}
public override IEnumerable<uint> FromDragData(DragData data)
{
if (data is DragDataText dataText)
{
if (dataText.Text.StartsWith(DragPrefix))
{
var id = dataText.Text.Remove(0, DragPrefix.Length).Split('\n');
return new[] { uint.Parse(id[0]) };
}
}
return Utils.GetEmptyArray<uint>();
}
}
private sealed class ReorderDecoratorAction : IUndoAction
{
public VisjectSurface Surface;
public uint DecoratorId, PrevNodeId, NewNodeId;
public int PrevIndex, NewIndex;
public string ActionString => "Reorder decorator";
private void Do(uint nodeId, int index)
{
var decorator = Surface.FindNode(DecoratorId) as Decorator;
if (decorator == null)
throw new Exception("Missing decorator");
var node = decorator.Node;
var decorators = node.DecoratorIds;
decorators.Remove(DecoratorId);
if (node.ID != nodeId)
{
node.SetDecoratorIds(decorators, false);
node = Surface.FindNode(nodeId) as Node;
decorators = node.DecoratorIds;
}
if (index < 0 || index >= decorators.Count)
decorators.Add(DecoratorId);
else
decorators.Insert(index, DecoratorId);
node.SetDecoratorIds(decorators, false);
}
public void Do()
{
Do(NewNodeId, NewIndex);
}
public void Undo()
{
Do(PrevNodeId, PrevIndex);
}
public void Dispose()
{
Surface = null;
}
}
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
{
return new Decorator(id, context, nodeArch, groupArch);
}
private DragImage _dragIcon;
private DragDecorator _dragDecorator;
private float _dragLocation = -1;
internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_dragDecorator = new DragDecorator(ValidateDrag);
}
public Node Node
@@ -497,7 +587,7 @@ namespace FlaxEditor.Surface.Archetypes
protected override Float2 CalculateNodeSize(float width, float height)
{
return new Float2(width, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize + 2 * DecoratorsMarginX, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
}
protected override void UpdateRectangles()
@@ -505,6 +595,8 @@ namespace FlaxEditor.Surface.Archetypes
base.UpdateRectangles();
_footerRect = Rectangle.Empty;
if (_dragIcon != null)
_dragIcon.Bounds = new Rectangle(_closeButtonRect.X - _closeButtonRect.Width, _closeButtonRect.Y, _closeButtonRect.Size);
}
protected override void UpdateTitle()
@@ -518,17 +610,46 @@ namespace FlaxEditor.Surface.Archetypes
Node?.ResizeAuto();
}
public override void OnLoaded(SurfaceNodeActions action)
{
// Add drag button to reorder decorator
_dragIcon = new DragImage
{
Color = Style.Current.ForegroundGrey,
Parent = this,
Margin = new Margin(1),
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
Tag = this,
Drag = img => { img.DoDragDrop(_dragDecorator.ToDragData(ID)); }
};
base.OnLoaded(action);
}
private bool ValidateDrag(uint id)
{
return Surface.FindNode(id) != null;
}
public override void Draw()
{
base.Draw();
var style = Style.Current;
// Outline
if (!_isSelected)
{
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
Render2D.DrawRectangle(rect, style.BorderHighlighted);
}
// Drag hint
if (IsDragOver && _dragDecorator.HasValidDrag)
{
var rect = new Rectangle(0, _dragLocation < Height * 0.5f ? 0 : Height - 6, Width, 6);
Render2D.FillRectangle(rect, style.BackgroundSelected);
}
}
public override void OnSurfaceLoaded(SurfaceNodeActions action)
@@ -546,6 +667,75 @@ namespace FlaxEditor.Surface.Archetypes
}
}
}
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
if (_dragDecorator.OnDragEnter(data))
{
_dragLocation = location.Y;
result = DragDropEffect.Move;
}
return result;
}
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
if (_dragDecorator.HasValidDrag)
{
_dragLocation = location.Y;
result = DragDropEffect.Move;
}
return result;
}
public override void OnDragLeave()
{
_dragLocation = -1;
_dragDecorator.OnDragLeave();
base.OnDragLeave();
}
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
var result = base.OnDragDrop(ref location, data);
if (result != DragDropEffect.None)
return result;
if (_dragDecorator.HasValidDrag)
{
// Reorder or reparent decorator
var decorator = (Decorator)Surface.FindNode(_dragDecorator.Objects[0]);
var prevNode = decorator.Node;
var prevIndex = prevNode.Decorators.IndexOf(decorator);
var newNode = Node;
var newIndex = newNode.Decorators.IndexOf(this);
if (_dragLocation >= Height * 0.5f)
newIndex++;
if (prevIndex != newIndex || prevNode != newNode)
{
var action = new ReorderDecoratorAction
{
Surface = Surface,
DecoratorId = decorator.ID,
PrevNodeId = prevNode.ID,
PrevIndex = prevIndex,
NewNodeId = newNode.ID,
NewIndex = newIndex,
};
action.Do();
Surface.Undo?.AddAction(action);
}
_dragLocation = -1;
_dragDecorator.OnDragDrop();
result = DragDropEffect.Move;
}
return result;
}
}
/// <summary>