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);