diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 7f540b767..54cea319f 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -111,6 +111,11 @@ namespace FlaxEditor.CustomEditors
///
public PropertyNameLabel LinkedLabel;
+ ///
+ /// Gets the layout for this editor. Used to calculate bounds.
+ ///
+ public LayoutElementsContainer Layout => _layout;
+
internal virtual void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
{
_layout = layout;
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index b06e2d55d..7c0670010 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -38,6 +38,9 @@ namespace FlaxEditor.CustomEditors.Editors
///
public readonly int Index;
+ private Rectangle _arrangeButtonRect;
+ private bool _arrangeButtonInUse;
+
///
/// Initializes a new instance of the class.
///
@@ -50,6 +53,12 @@ namespace FlaxEditor.CustomEditors.Editors
Index = index;
SetupContextMenu += OnSetupContextMenu;
+ _arrangeButtonRect = new Rectangle(2, 3, 12, 12);
+
+ // Extend margin of the label to support a dragging handle.
+ Margin m = Margin;
+ m.Left += 16;
+ Margin = m;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
@@ -71,6 +80,107 @@ namespace FlaxEditor.CustomEditors.Editors
b.Enabled = !Editor._readOnly;
}
+ ///
+ public override void OnEndMouseCapture()
+ {
+ base.OnEndMouseCapture();
+
+ _arrangeButtonInUse = false;
+ }
+
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+ var style = FlaxEngine.GUI.Style.Current;
+
+ var mousePosition = PointFromScreen(Input.MouseScreenPosition);
+ var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
+ Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
+ if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
+ {
+ Render2D.FillRectangle(arrangeTargetRect, style.Selection);
+ }
+ }
+
+ private bool ArrangeAreaCheck(out int index, out Rectangle rect)
+ {
+ var child = Editor.ChildrenEditors[0];
+ var container = child.Layout.ContainerControl;
+ var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
+ var barSidesExtend = 20.0f;
+ var barHeight = 5.0f;
+ var barCheckAreaHeight = 40.0f;
+ var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
+
+ for (int i = 0; i < container.Children.Count / 2; i++)
+ {
+ var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
+ if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
+ {
+ index = i;
+ var p1 = containerChild.UpperLeft;
+ rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
+ return true;
+ }
+ }
+
+ var p2 = container.Children[((container.Children.Count / 2) - 1) * 2].BottomLeft;
+ if (pos > p2.Y)
+ {
+ index = (container.Children.Count / 2) - 1;
+ rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
+ return true;
+ }
+
+ index = -1;
+ rect = Rectangle.Empty;
+ return false;
+ }
+
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
+ {
+ _arrangeButtonInUse = true;
+ Focus();
+ StartMouseCapture();
+ return true;
+ }
+
+ return base.OnMouseDown(location, button);
+ }
+
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _arrangeButtonInUse)
+ {
+ _arrangeButtonInUse = false;
+ EndMouseCapture();
+ if (ArrangeAreaCheck(out var index, out _))
+ {
+ Editor.Shift(Index, index);
+ }
+ }
+
+ return base.OnMouseUp(location, button);
+ }
+
+ ///
+ public override void OnLostFocus()
+ {
+ if (_arrangeButtonInUse)
+ {
+ _arrangeButtonInUse = false;
+ EndMouseCapture();
+ }
+
+ base.OnLostFocus();
+ }
+
private void OnMoveUpClicked()
{
Editor.Move(Index, Index - 1);
@@ -106,6 +216,9 @@ namespace FlaxEditor.CustomEditors.Editors
private bool _canReorder = true;
+ private Rectangle _arrangeButtonRect;
+ private bool _arrangeButtonInUse;
+
public void Setup(CollectionEditor editor, int index, bool canReorder = true)
{
HeaderHeight = 18;
@@ -123,10 +236,92 @@ namespace FlaxEditor.CustomEditors.Editors
MouseButtonRightClicked += OnMouseButtonRightClicked;
if (_canReorder)
{
- // TODO: Drag drop
+ HeaderTextMargin = new Margin(18, 0, 0, 0);
+ _arrangeButtonRect = new Rectangle(16, 3, 12, 12);
}
}
+ private bool ArrangeAreaCheck(out int index, out Rectangle rect)
+ {
+ var container = Parent;
+ var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
+ var barSidesExtend = 20.0f;
+ var barHeight = 5.0f;
+ var barCheckAreaHeight = 40.0f;
+ var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
+
+ for (int i = 0; i < (container.Children.Count + 1) / 2; i++) // Add 1 to pretend there is a spacer at the end.
+ {
+ var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
+ if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
+ {
+ index = i;
+ var p1 = containerChild.UpperLeft;
+ rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
+ return true;
+ }
+ }
+
+ var p2 = container.Children[container.Children.Count - 1].BottomLeft;
+ if (pos > p2.Y)
+ {
+ index = ((container.Children.Count + 1) / 2) - 1;
+ rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
+ return true;
+ }
+
+ index = -1;
+ rect = Rectangle.Empty;
+ return false;
+ }
+
+ public override void Draw()
+ {
+ base.Draw();
+ if (_canReorder)
+ {
+ var style = FlaxEngine.GUI.Style.Current;
+
+ var mousePosition = PointFromScreen(Input.MouseScreenPosition);
+ var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
+ Render2D.DrawSprite(FlaxEditor.Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
+ if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
+ {
+ Render2D.FillRectangle(arrangeTargetRect, style.Selection);
+ }
+ }
+ }
+
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
+ {
+ _arrangeButtonInUse = true;
+ Focus();
+ StartMouseCapture();
+ return true;
+ }
+
+ return base.OnMouseDown(location, button);
+ }
+
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _arrangeButtonInUse)
+ {
+ _arrangeButtonInUse = false;
+ EndMouseCapture();
+ if (ArrangeAreaCheck(out var index, out _))
+ {
+ Editor.Shift(Index, index);
+ }
+ }
+
+ return base.OnMouseUp(location, button);
+ }
+
private void OnMouseButtonRightClicked(DropPanel panel, Float2 location)
{
if (LinkedEditor == null)
@@ -324,7 +519,6 @@ namespace FlaxEditor.CustomEditors.Editors
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
elementType.Equals(new ScriptType(typeof(SettingsBase)));
-
for (int i = 0; i < size; i++)
{
// Apply spacing
@@ -440,6 +634,39 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(cloned);
}
+ ///
+ /// Shifts the specified item at the given index and moves it through the list to the other item. It supports undo.
+ ///
+ /// Index of the source item.
+ /// Index of the destination to move to.
+ private void Shift(int srcIndex, int dstIndex)
+ {
+ if (IsSetBlocked)
+ return;
+
+ var cloned = CloneValues();
+ if (dstIndex > srcIndex)
+ {
+ for (int i = srcIndex; i < dstIndex; i++)
+ {
+ var tmp = cloned[i + 1];
+ cloned[i + 1] = cloned[i];
+ cloned[i] = tmp;
+ }
+ }
+ else
+ {
+ for (int i = srcIndex; i > dstIndex; i--)
+ {
+ var tmp = cloned[i - 1];
+ cloned[i - 1] = cloned[i];
+ cloned[i] = tmp;
+ }
+ }
+
+ SetValue(cloned);
+ }
+
///
/// Removes the item at the specified index. It supports undo.
///
diff --git a/Source/Editor/Surface/VisjectSurface.Paramaters.cs b/Source/Editor/Surface/VisjectSurface.Paramaters.cs
index 7eddffbca..d6cf6ca0b 100644
--- a/Source/Editor/Surface/VisjectSurface.Paramaters.cs
+++ b/Source/Editor/Surface/VisjectSurface.Paramaters.cs
@@ -35,6 +35,12 @@ namespace FlaxEditor.Surface
return RootContext.GetParameter(name);
}
+ ///
+ public void OnParamReordered()
+ {
+ MarkAsEdited();
+ }
+
///
public void OnParamCreated(SurfaceParameter param)
{
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index 1f6a180c5..7da0d9707 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -350,6 +350,213 @@ namespace FlaxEditor.Surface
}
}
+ sealed class ReorderParamAction : IUndoAction
+ {
+ ///
+ /// The window reference.
+ ///
+ public IVisjectSurfaceWindow Window;
+
+ ///
+ /// The parameters editor for this action.
+ ///
+ public ParametersEditor Editor;
+
+ ///
+ /// The old index the parameter was at.
+ ///
+ public int OldIndex;
+
+ ///
+ /// The new index the parameter will be at.
+ ///
+ public int NewIndex;
+
+ ///
+ public string ActionString => "Reorder Parameter";
+
+ ///
+ public void Dispose()
+ {
+ Window = null;
+ Editor = null;
+ }
+
+ public void Swap(int oldIdx, int newIdx)
+ {
+ if (oldIdx == newIdx)
+ return; // ?
+
+ var parameters = Window.VisjectSurface.Parameters;
+ if (newIdx > oldIdx)
+ {
+ for (int i = oldIdx; i < newIdx; i++)
+ {
+ SurfaceParameter old = parameters[i + 1];
+ parameters[i + 1] = parameters[i];
+ parameters[i] = old;
+ }
+ }
+ else
+ {
+ for (int i = oldIdx; i > newIdx; i--)
+ {
+ SurfaceParameter old = parameters[i - 1];
+ parameters[i - 1] = parameters[i];
+ parameters[i] = old;
+ }
+ }
+ }
+
+ ///
+ public void Do()
+ {
+ Swap(OldIndex, NewIndex);
+ Window.VisjectSurface.OnParamReordered();
+ }
+
+ ///
+ public void Undo()
+ {
+ Swap(NewIndex, OldIndex);
+ Window.VisjectSurface.OnParamReordered();
+ }
+ }
+
+ ///
+ /// Custom draggable property name label that handles reordering visject parameters.
+ ///
+ ///
+ [HideInEditor]
+ public class ParameterPropertyNameLabel : DraggablePropertyNameLabel
+ {
+ private ParametersEditor _editor;
+ private IVisjectSurfaceWindow _window;
+ private Rectangle _arrangeButtonRect;
+ private bool _arrangeButtonInUse;
+
+ ///
+ public ParameterPropertyNameLabel(string name, ParametersEditor editor)
+ : base(name)
+ {
+ _editor = editor;
+ _window = _editor.Values[0] as IVisjectSurfaceWindow;
+ _arrangeButtonRect = new Rectangle(2, 3, 12, 12);
+
+ // Extend margin of the label to support a dragging handle
+ Margin m = Margin;
+ m.Left += 16;
+ Margin = m;
+ }
+
+ private bool ArrangeAreaCheck(out int index, out Rectangle rect)
+ {
+ var child = _editor.ChildrenEditors[0];
+ var container = child.Layout.ContainerControl;
+ var mousePosition = container.PointFromScreen(Input.MouseScreenPosition);
+ var barSidesExtend = 20.0f;
+ var barHeight = 5.0f;
+ var barCheckAreaHeight = 40.0f;
+ var pos = mousePosition.Y + barCheckAreaHeight * 0.5f;
+
+ for (int i = 0; i < container.Children.Count / 2; i++)
+ {
+ var containerChild = container.Children[i * 2]; // times 2 to skip the value editor
+ if (Mathf.IsInRange(pos, containerChild.Top, containerChild.Top + barCheckAreaHeight) || (i == 0 && pos < containerChild.Top))
+ {
+ index = i;
+ var p1 = containerChild.UpperLeft;
+ rect = new Rectangle(PointFromParent(p1) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
+ return true;
+ }
+ }
+
+ var p2 = container.Children[((container.Children.Count / 2) - 1) * 2].BottomLeft;
+ if (pos > p2.Y)
+ {
+ index = (container.Children.Count / 2) - 1;
+ rect = new Rectangle(PointFromParent(p2) - new Float2(barSidesExtend * 0.5f, barHeight * 0.5f), Width + barSidesExtend, barHeight);
+ return true;
+ }
+
+ index = -1;
+ rect = Rectangle.Empty;
+ return false;
+ }
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+
+ var style = Style.Current;
+ var mousePosition = PointFromScreen(Input.MouseScreenPosition);
+ var dragBarColor = _arrangeButtonRect.Contains(mousePosition) ? style.Foreground : style.ForegroundGrey;
+ Render2D.DrawSprite(Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
+ if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
+ {
+ Render2D.FillRectangle(arrangeTargetRect, style.Selection);
+ }
+ }
+
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _arrangeButtonRect.Contains(ref location))
+ {
+ _arrangeButtonInUse = true;
+ Focus();
+ StartMouseCapture();
+ return true;
+ }
+
+ return base.OnMouseDown(location, button);
+ }
+
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _arrangeButtonInUse)
+ {
+ _arrangeButtonInUse = false;
+ EndMouseCapture();
+ ArrangeAreaCheck(out var index, out _);
+ var action = new ReorderParamAction
+ {
+ OldIndex = (int)Tag,
+ NewIndex = index,
+ Window = _window,
+ Editor = _editor
+ };
+ action.Do();
+ _window.Undo.AddAction(action);
+ }
+
+ return base.OnMouseUp(location, button);
+ }
+
+ ///
+ public override void OnLostFocus()
+ {
+ if (_arrangeButtonInUse)
+ {
+ _arrangeButtonInUse = false;
+ EndMouseCapture();
+ }
+
+ base.OnLostFocus();
+ }
+
+ ///
+ protected override void OnSizeChanged()
+ {
+ base.OnSizeChanged();
+
+ // Center the drag button vertically
+ _arrangeButtonRect = new Rectangle(2, Mathf.Ceil((Height - 12) * 0.5f) + 1, 12, 12);
+ }
+ }
+
///
/// Custom editor for editing Visject Surface parameters collection.
///
@@ -431,10 +638,10 @@ namespace FlaxEditor.Surface
attributes
);
- var propertyLabel = new DraggablePropertyNameLabel(name)
+ var propertyLabel = new ParameterPropertyNameLabel(name, this)
{
Tag = pIndex,
- Drag = OnDragParameter
+ Drag = OnDragParameter,
};
if (!p.IsPublic)
propertyLabel.TextColor = propertyLabel.TextColor.RGBMultiplied(0.7f);