using System;
using System.Collections.Generic;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
using UnityEngine;

namespace UnityEditor.U2D.Sprites
{
    internal abstract partial class SpriteFrameModuleBase : SpriteEditorModuleModeSupportBase
    {
        protected enum GizmoMode
        {
            BorderEditing,
            RectEditing
        }

        protected class Styles
        {
            public readonly GUIStyle dragdot = "U2D.dragDot";
            public readonly GUIStyle dragdotactive = "U2D.dragDotActive";
            public readonly GUIStyle createRect = "U2D.createRect";
            public readonly GUIStyle pivotdotactive = "U2D.pivotDotActive";
            public readonly GUIStyle pivotdot = "U2D.pivotDot";

            public readonly GUIStyle dragBorderdot = new GUIStyle();
            public readonly GUIStyle dragBorderDotActive = new GUIStyle();

            public readonly GUIStyle toolbar;

            public readonly string lockToolTip = "Editing is disabled. Go to the Lock dropdown at the toolbar above to change this.";
            public Styles()
            {
                toolbar = new GUIStyle(EditorStyles.inspectorBig);
                toolbar.margin.top = 0;
                toolbar.margin.bottom = 0;
                createRect.border = new RectOffset(3, 3, 3, 3);

                dragBorderdot.fixedHeight = 5f;
                dragBorderdot.fixedWidth = 5f;
                dragBorderdot.normal.background = EditorGUIUtility.whiteTexture;

                dragBorderDotActive.fixedHeight = dragBorderdot.fixedHeight;
                dragBorderDotActive.fixedWidth = dragBorderdot.fixedWidth;
                dragBorderDotActive.normal.background = EditorGUIUtility.whiteTexture;
            }
        }

        private static Styles s_Styles;

        protected static Styles styles
        {
            get
            {
                if (s_Styles == null)
                    s_Styles = new Styles();
                return s_Styles;
            }
        }

        private const float kInspectorWidth = 330f;
        private const float kInspectorHeight = 170;
        private float m_Zoom = 1.0f;
        private GizmoMode m_GizmoMode;

        private VisualElement m_NameElement;
        private TextField m_NameField;
        private VisualElement m_PositionElement;
        private IntegerField m_PositionFieldX;
        private IntegerField m_PositionFieldY;
        private IntegerField m_PositionFieldW;
        private IntegerField m_PositionFieldH;
        private IntegerField m_BorderFieldL;
        private IntegerField m_BorderFieldT;
        private IntegerField m_BorderFieldR;
        private IntegerField m_BorderFieldB;
        private EnumField m_PivotField;
        private EnumField m_PivotUnitModeField;
        private VisualElement m_CustomPivotElement;
        private FloatField m_CustomPivotFieldX;
        private FloatField m_CustomPivotFieldY;
        private VisualElement m_SelectedFrameInspector;
        VisualElement m_PositionFieldContainer;
        VisualElement m_BorderFieldContainer;
        VisualElement m_PivotFieldContainer;
        bool m_EnableInspector = true;

        private bool ShouldShowRectScaling()
        {
            return hasSelected && m_GizmoMode == GizmoMode.RectEditing;
        }

        private static Rect inspectorRect
        {
            get
            {
                return new Rect(
                    0, 0,
                    kInspectorWidth,
                    kInspectorHeight);
            }
        }

        private void RemoveMainUI(VisualElement mainView)
        {
            if (mainView.Contains(m_SelectedFrameInspector))
                mainView.Remove(m_SelectedFrameInspector);
            mainView.UnregisterCallback<SpriteSelectionChangeEvent>(SelectionChange);
        }

        protected void UpdatePositionField(FocusOutEvent evt)
        {
            if (hasSelected)
            {
                m_PositionFieldX.SetValueWithoutNotify((int)selectedSpriteRect_Rect.x);
                m_PositionFieldY.SetValueWithoutNotify((int)selectedSpriteRect_Rect.y);
                m_PositionFieldW.SetValueWithoutNotify((int)selectedSpriteRect_Rect.width);
                m_PositionFieldH.SetValueWithoutNotify((int)selectedSpriteRect_Rect.height);
            }
        }

        private void UpdateBorderField(FocusOutEvent evt)
        {
            if (hasSelected)
            {
                m_BorderFieldL.SetValueWithoutNotify((int)selectedSpriteBorder.x);
                m_BorderFieldB.SetValueWithoutNotify((int)selectedSpriteBorder.y);
                m_BorderFieldR.SetValueWithoutNotify((int)selectedSpriteBorder.z);
                m_BorderFieldT.SetValueWithoutNotify((int)selectedSpriteBorder.w);
            }
        }

        void SetupIntegerField(IntegerField field, EventCallback<FocusOutEvent> onFocusOutEvent, EventCallback<ChangeEvent<int>> onChangeEvent)
        {
            field.RegisterCallback(onFocusOutEvent);
            field.RegisterValueChangedCallback(onChangeEvent);
        }

        void SetDragFieldLimit(IntegerField field, int value)
        {
            // The only way to know if value change is due to dragger or text input
            var t = field.Q("unity-text-input");
            if (!t.focusController.IsFocused(t))
            {
                // Value changed due to drag. We set back the field so to show the drag limit
                field.SetValueWithoutNotify(value);
            }
        }

        void OnPositionIntXChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var rect = selectedSpriteRect_Rect;
                rect.x = evt.newValue;
                selectedSpriteRect_Rect = rect;
                SetDragFieldLimit(m_PositionFieldX, (int)selectedSpriteRect_Rect.x);
                m_PositionFieldW.SetValueWithoutNotify((int)selectedSpriteRect_Rect.width);
            }
        }

        void OnPositionIntYChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var rect = selectedSpriteRect_Rect;
                rect.y = evt.newValue;
                selectedSpriteRect_Rect = rect;
                SetDragFieldLimit(m_PositionFieldY, (int)selectedSpriteRect_Rect.y);
                m_PositionFieldH.SetValueWithoutNotify((int)selectedSpriteRect_Rect.height);
            }
        }

        void OnPositionIntWChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var rect = selectedSpriteRect_Rect;
                rect.width = evt.newValue;
                selectedSpriteRect_Rect = rect;
                SetDragFieldLimit(m_PositionFieldW, (int)selectedSpriteRect_Rect.width);
                m_PositionFieldX.SetValueWithoutNotify((int)selectedSpriteRect_Rect.x);
            }
        }

        void OnPositionIntHChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var rect = selectedSpriteRect_Rect;
                rect.height = evt.newValue;
                selectedSpriteRect_Rect = rect;
                SetDragFieldLimit(m_PositionFieldH, (int)selectedSpriteRect_Rect.height);
                m_PositionFieldY.SetValueWithoutNotify((int)selectedSpriteRect_Rect.y);
            }
        }

        void OnBorderIntLChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var border = selectedSpriteBorder;
                border.x = evt.newValue;
                selectedSpriteBorder = border;
                SetDragFieldLimit(m_BorderFieldL, (int)selectedSpriteBorder.x);
            }
        }

        void OnBorderIntBChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var border = selectedSpriteBorder;
                border.y = evt.newValue;
                selectedSpriteBorder = border;
                SetDragFieldLimit(m_BorderFieldB, (int)selectedSpriteBorder.y);
            }
        }

        void OnBorderIntRChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var border = selectedSpriteBorder;
                border.z = (evt.newValue + border.x) <= selectedSpriteRect_Rect.width ? evt.newValue : selectedSpriteRect_Rect.width - border.x;
                selectedSpriteBorder = border;
                SetDragFieldLimit(m_BorderFieldR, (int)selectedSpriteBorder.z);
            }
        }

        void OnBorderIntTChange(ChangeEvent<int> evt)
        {
            if (hasSelected)
            {
                var border = selectedSpriteBorder;
                border.w = (evt.newValue + border.y) <= selectedSpriteRect_Rect.height ? evt.newValue : selectedSpriteRect_Rect.height - border.y;
                selectedSpriteBorder = border;
                SetDragFieldLimit(m_BorderFieldT, (int)selectedSpriteBorder.w);
            }
        }

        private void AddMainUI(VisualElement mainView)
        {
            var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Packages/com.unity.2d.sprite/Editor/UI/SpriteEditor/SpriteFrameModuleInspector.uxml") as VisualTreeAsset;
            m_SelectedFrameInspector = visualTree.CloneTree().Q("spriteFrameModuleInspector");

            if (EditorGUIUtility.isProSkin)
            {
                foreach (var lockToggle in m_SelectedFrameInspector.Query("lockToggle").ToList())
                {
                    lockToggle.AddToClassList("Dark");
                }
            }

            m_NameElement = m_SelectedFrameInspector.Q("name");
            m_NameField = m_SelectedFrameInspector.Q<TextField>("spriteName");
            m_NameField.RegisterValueChangedCallback((evt) =>
            {
                if (hasSelected)
                {
                    selectedSpriteName = evt.newValue;
                }
            });

            m_NameField.RegisterCallback<FocusOutEvent>((focus) =>
            {
                if (hasSelected)
                {
                    m_NameField.SetValueWithoutNotify(selectedSpriteName);
                }
            });


            m_PositionElement = m_SelectedFrameInspector.Q("position");
            m_PositionFieldX = m_PositionElement.Q<IntegerField>("positionX");
            SetupIntegerField(m_PositionFieldX, UpdatePositionField, OnPositionIntXChange);

            m_PositionFieldY = m_PositionElement.Q<IntegerField>("positionY");
            SetupIntegerField(m_PositionFieldY, UpdatePositionField, OnPositionIntYChange);

            m_PositionFieldW = m_PositionElement.Q<IntegerField>("positionW");
            SetupIntegerField(m_PositionFieldW,  UpdatePositionField, OnPositionIntWChange);

            m_PositionFieldH = m_PositionElement.Q<IntegerField>("positionH");
            SetupIntegerField(m_PositionFieldH, UpdatePositionField, OnPositionIntHChange);

            var borderElement = m_SelectedFrameInspector.Q("border");
            m_BorderFieldL = borderElement.Q<IntegerField>("borderL");
            SetupIntegerField(m_BorderFieldL, UpdateBorderField, OnBorderIntLChange);

            m_BorderFieldT = borderElement.Q<IntegerField>("borderT");
            SetupIntegerField(m_BorderFieldT, UpdateBorderField, OnBorderIntTChange);

            m_BorderFieldR = borderElement.Q<IntegerField>("borderR");
            SetupIntegerField(m_BorderFieldR, UpdateBorderField, OnBorderIntRChange);

            m_BorderFieldB = borderElement.Q<IntegerField>("borderB");
            SetupIntegerField(m_BorderFieldB, UpdateBorderField, OnBorderIntBChange);

            m_PivotField = m_SelectedFrameInspector.Q<EnumField>("pivotField");
            m_PivotField.Init(SpriteAlignment.Center);
            m_PivotField.label = L10n.Tr("Pivot");
            m_PivotField.RegisterValueChangedCallback((evt) =>
            {
                if (hasSelected)
                {
                    SpriteAlignment alignment = (SpriteAlignment)evt.newValue;
                    SetSpritePivotAndAlignment(selectedSpritePivot, alignment);
                    m_CustomPivotElement.SetEnabled(selectedSpriteAlignment == SpriteAlignment.Custom);
                    Vector2 pivot = selectedSpritePivotInCurUnitMode;
                    m_CustomPivotFieldX.SetValueWithoutNotify(pivot.x);
                    m_CustomPivotFieldY.SetValueWithoutNotify(pivot.y);
                }
            });


            m_PivotUnitModeField = m_SelectedFrameInspector.Q<EnumField>("pivotUnitModeField");
            m_PivotUnitModeField.Init(PivotUnitMode.Normalized);
            m_PivotUnitModeField.label = L10n.Tr("Pivot Unit Mode");
            m_PivotUnitModeField.RegisterValueChangedCallback((evt) =>
            {
                if (hasSelected)
                {
                    pivotUnitMode = (PivotUnitMode)evt.newValue;

                    Vector2 pivot = selectedSpritePivotInCurUnitMode;
                    m_CustomPivotFieldX.SetValueWithoutNotify(pivot.x);
                    m_CustomPivotFieldY.SetValueWithoutNotify(pivot.y);
                }
            });

            m_CustomPivotElement = m_SelectedFrameInspector.Q("customPivot");
            m_CustomPivotElement.AddManipulator(new ContextualMenuManipulator((evt) =>
            {
                evt.menu.AppendAction("Copy"
                    , (x) => Clipboard.vector2Value = selectedSpritePivotInCurUnitMode
                    , DropdownMenuAction.AlwaysEnabled);
                evt.menu.AppendAction("Paste"
                    , (x) =>
                    {
                        Vector2 newPivot = Vector2.zero;
                        if (Clipboard.hasVector2)
                            newPivot = Clipboard.vector2Value;
                        if (Clipboard.hasVector3)
                            newPivot = Clipboard.vector3Value;
                        newPivot = pivotUnitMode == PivotUnitMode.Pixels
                            ? SpritePivotUtility.ConvertFromRectToNormalizedSpace(newPivot, selectedSpriteRect_Rect)
                            : newPivot;
                        SetSpritePivotAndAlignment(newPivot, selectedSpriteAlignment);
                        m_CustomPivotFieldX.SetValueWithoutNotify(selectedSpritePivotInCurUnitMode.x);
                        m_CustomPivotFieldY.SetValueWithoutNotify(selectedSpritePivotInCurUnitMode.y);
                    }, (y) =>
                    {
                        if (hasSelected && selectedSpriteAlignment == SpriteAlignment.Custom)
                        {
                            if (Clipboard.hasVector3 || Clipboard.hasVector2)
                                return DropdownMenuAction.Status.Normal;
                        }
                        return DropdownMenuAction.Status.Disabled;
                    });
            }));
            m_CustomPivotFieldX = m_CustomPivotElement.Q<FloatField>("customPivotX");
            m_CustomPivotFieldX.RegisterValueChangedCallback((evt) =>
            {
                if (hasSelected)
                {
                    float newValue = (float)evt.newValue;
                    float pivotX = pivotUnitMode == PivotUnitMode.Pixels
                        ? SpritePivotUtility.ConvertFromRectToNormalizedSpace(new Vector2(newValue, 0.0f), selectedSpriteRect_Rect).x
                        : newValue;

                    var pivot = selectedSpritePivot;
                    pivot.x = pivotX;
                    SetSpritePivotAndAlignment(pivot, selectedSpriteAlignment);
                }
            });

            m_CustomPivotFieldY = m_CustomPivotElement.Q<FloatField>("customPivotY");
            m_CustomPivotFieldY.RegisterValueChangedCallback((evt) =>
            {
                if (hasSelected)
                {
                    float newValue = (float)evt.newValue;
                    float pivotY = pivotUnitMode == PivotUnitMode.Pixels
                        ? SpritePivotUtility.ConvertFromRectToNormalizedSpace(new Vector2(0.0f, newValue), selectedSpriteRect_Rect).y
                        : newValue;

                    var pivot = selectedSpritePivot;
                    pivot.y = pivotY;
                    SetSpritePivotAndAlignment(pivot, selectedSpriteAlignment);
                }
            });

            m_PositionFieldContainer = m_SelectedFrameInspector.Q("positionField");
            m_BorderFieldContainer = m_SelectedFrameInspector.Q("borderField");
            m_PivotFieldContainer = m_SelectedFrameInspector.Q("pivot");
            //// Force an update of all the fields.
            PopulateSpriteFrameInspectorField();

            mainView.RegisterCallback<SpriteSelectionChangeEvent>(SelectionChange);

            // Stop mouse events from reaching the main view.
            m_SelectedFrameInspector.pickingMode = PickingMode.Ignore;
            m_SelectedFrameInspector.RegisterCallback<MouseDownEvent>((e) => { e.StopPropagation(); });
            m_SelectedFrameInspector.RegisterCallback<MouseUpEvent>((e) => { e.StopPropagation(); });
            m_SelectedFrameInspector.AddToClassList("moduleWindow");
            m_SelectedFrameInspector.AddToClassList("bottomRightFloating");
            mainView.Add(m_SelectedFrameInspector);
        }

        public void EnableSpriteFrameInspector(bool value)
        {
            m_EnableInspector = value;
            SelectionChange(new SpriteSelectionChangeEvent());
        }

        private void SelectionChange(SpriteSelectionChangeEvent evt)
        {
            PopulateSpriteFrameInspectorField();
        }

        private void UIUndoCallback()
        {
            PopulateSpriteFrameInspectorField();
        }

        protected void PopulateSpriteFrameInspectorField()
        {
            m_SelectedFrameInspector.style.display = hasSelected && m_EnableInspector ? DisplayStyle.Flex : DisplayStyle.None;
            if (!hasSelected)
                return;

            m_NameField.SetValueWithoutNotify(selectedSpriteName);
            m_PositionElement.SetEnabled(containsMultipleSprites);

            var spriteRect = selectedSpriteRect_Rect;
            m_PositionFieldX.SetValueWithoutNotify(Mathf.RoundToInt(spriteRect.x));
            m_PositionFieldY.SetValueWithoutNotify(Mathf.RoundToInt(spriteRect.y));
            m_PositionFieldW.SetValueWithoutNotify(Mathf.RoundToInt(spriteRect.width));
            m_PositionFieldH.SetValueWithoutNotify(Mathf.RoundToInt(spriteRect.height));
            var spriteBorder = selectedSpriteBorder;
            m_BorderFieldL.SetValueWithoutNotify(Mathf.RoundToInt(spriteBorder.x));
            m_BorderFieldT.SetValueWithoutNotify(Mathf.RoundToInt(spriteBorder.w));
            m_BorderFieldR.SetValueWithoutNotify(Mathf.RoundToInt(spriteBorder.z));
            m_BorderFieldB.SetValueWithoutNotify(Mathf.RoundToInt(spriteBorder.y));
            m_PivotField.SetValueWithoutNotify(selectedSpriteAlignment);
            m_PivotUnitModeField.SetValueWithoutNotify(pivotUnitMode);
            Vector2 pivot = selectedSpritePivotInCurUnitMode;
            m_CustomPivotFieldX.SetValueWithoutNotify(pivot.x);
            m_CustomPivotFieldY.SetValueWithoutNotify(pivot.y);

            m_CustomPivotElement.SetEnabled(hasSelected && selectedSpriteAlignment == SpriteAlignment.Custom);

            var enabled = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditSpriteName);
            m_NameElement.SetEnabled(enabled && containsMultipleSprites);
            m_NameElement.tooltip = enabled ? "" : styles.lockToolTip;

            enabled = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditSpriteRect);
            m_PositionFieldContainer.SetEnabled(enabled);
            m_PositionFieldContainer.tooltip = enabled ? "" : styles.lockToolTip;

            enabled = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditBorder);
            m_BorderFieldContainer.SetEnabled(enabled);
            m_BorderFieldContainer.tooltip = enabled ? "" : styles.lockToolTip;

            enabled = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditPivot);
            m_PivotFieldContainer.SetEnabled(enabled);
            m_PivotFieldContainer.tooltip = enabled ? "" : styles.lockToolTip;
        }

        private static Vector2 ApplySpriteAlignmentToPivot(Vector2 pivot, Rect rect, SpriteAlignment alignment)
        {
            if (alignment != SpriteAlignment.Custom)
            {
                Vector2[] snapPoints = GetSnapPointsArray(rect);
                Vector2 texturePos = snapPoints[(int)alignment];
                return ConvertFromTextureToNormalizedSpace(texturePos, rect);
            }
            return pivot;
        }

        private static Vector2 ConvertFromTextureToNormalizedSpace(Vector2 texturePos, Rect rect)
        {
            return new Vector2((texturePos.x - rect.xMin) / rect.width, (texturePos.y - rect.yMin) / rect.height);
        }

        private static Vector2[] GetSnapPointsArray(Rect rect)
        {
            Vector2[] snapPoints = new Vector2[9];
            snapPoints[(int)SpriteAlignment.TopLeft] = new Vector2(rect.xMin, rect.yMax);
            snapPoints[(int)SpriteAlignment.TopCenter] = new Vector2(rect.center.x, rect.yMax);
            snapPoints[(int)SpriteAlignment.TopRight] = new Vector2(rect.xMax, rect.yMax);
            snapPoints[(int)SpriteAlignment.LeftCenter] = new Vector2(rect.xMin, rect.center.y);
            snapPoints[(int)SpriteAlignment.Center] = new Vector2(rect.center.x, rect.center.y);
            snapPoints[(int)SpriteAlignment.RightCenter] = new Vector2(rect.xMax, rect.center.y);
            snapPoints[(int)SpriteAlignment.BottomLeft] = new Vector2(rect.xMin, rect.yMin);
            snapPoints[(int)SpriteAlignment.BottomCenter] = new Vector2(rect.center.x, rect.yMin);
            snapPoints[(int)SpriteAlignment.BottomRight] = new Vector2(rect.xMax, rect.yMin);
            return snapPoints;
        }

        protected void Repaint()
        {
            spriteEditor.RequestRepaint();
        }

        protected void HandleGizmoMode()
        {
            GizmoMode oldGizmoMode = m_GizmoMode;
            var evt = eventSystem.current;
            if (evt.control)
                m_GizmoMode = GizmoMode.BorderEditing;
            else
                m_GizmoMode = GizmoMode.RectEditing;

            if (oldGizmoMode != m_GizmoMode && (evt.type == EventType.KeyDown || evt.type == EventType.KeyUp) && (evt.keyCode == KeyCode.LeftControl || evt.keyCode == KeyCode.RightControl || evt.keyCode == KeyCode.LeftAlt || evt.keyCode == KeyCode.RightAlt))
                Repaint();
        }

        protected bool MouseOnTopOfInspector()
        {
            if (hasSelected == false)
                return false;

            var point = GUIClip.Unclip(eventSystem.current.mousePosition);
            point = m_SelectedFrameInspector.parent.LocalToWorld(point);

            var selectedElement = m_SelectedFrameInspector.panel.Pick(point);
            if (selectedElement != null
                && selectedElement.pickingMode != PickingMode.Ignore
                && selectedElement.FindCommonAncestor(m_SelectedFrameInspector) == m_SelectedFrameInspector)
                return true;

            return false;
        }

        protected void HandlePivotHandle()
        {
            if (!hasSelected)
                return;

            EditorGUI.BeginChangeCheck();

            SpriteAlignment alignment = selectedSpriteAlignment;
            Vector2 pivot = selectedSpritePivot;
            Rect rect = selectedSpriteRect_Rect;
            pivot = ApplySpriteAlignmentToPivot(pivot, rect, alignment);
            bool canEdit = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditPivot);
            Vector2 pivotHandlePosition = SpriteEditorHandles.PivotSlider(rect, pivot, styles.pivotdot, styles.pivotdotactive, canEdit);

            if (EditorGUI.EndChangeCheck() && canEdit)
            {
                // Pivot snapping only happen when ctrl is press. Same as scene view snapping move
                if (eventSystem.current.control)
                    SnapPivotToSnapPoints(pivotHandlePosition, out pivot, out alignment);
                else if (pivotUnitMode == PivotUnitMode.Pixels)
                    SnapPivotToPixels(pivotHandlePosition, out pivot, out alignment);
                else
                {
                    pivot = pivotHandlePosition;
                    alignment = SpriteAlignment.Custom;
                }
                SetSpritePivotAndAlignment(pivot, alignment);
                PopulateSpriteFrameInspectorField();
            }
        }

        protected void HandleBorderSidePointScalingSliders()
        {
            if (!hasSelected)
                return;

            GUIStyle dragDot = styles.dragBorderdot;
            GUIStyle dragDotActive = styles.dragBorderDotActive;
            var color = new Color(0f, 1f, 0f);

            Rect rect = selectedSpriteRect_Rect;
            Vector4 border = selectedSpriteBorder;

            float left = rect.xMin + border.x;
            float right = rect.xMax - border.z;
            float top = rect.yMax - border.w;
            float bottom = rect.yMin + border.y;

            EditorGUI.BeginChangeCheck();

            float horizontal = bottom - (bottom - top) / 2;
            float vertical = left - (left - right) / 2;
            float center = horizontal;

            bool canEdit = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditBorder);
            HandleBorderPointSlider(ref left, ref center, canEdit ? MouseCursor.ResizeHorizontal : MouseCursor.NotAllowed, false, dragDot, dragDotActive, color);

            center = horizontal;
            HandleBorderPointSlider(ref right, ref center, canEdit ? MouseCursor.ResizeHorizontal : MouseCursor.NotAllowed, false, dragDot, dragDotActive, color);

            center = vertical;
            HandleBorderPointSlider(ref center, ref top, canEdit ? MouseCursor.ResizeVertical : MouseCursor.NotAllowed, false, dragDot, dragDotActive, color);

            center = vertical;
            HandleBorderPointSlider(ref center, ref bottom, canEdit ? MouseCursor.ResizeVertical : MouseCursor.NotAllowed, false, dragDot, dragDotActive, color);

            if (EditorGUI.EndChangeCheck() && canEdit)
            {
                border.x = left - rect.xMin;
                border.z = rect.xMax - right;
                border.w = rect.yMax - top;
                border.y = bottom - rect.yMin;
                selectedSpriteBorder = border;
                PopulateSpriteFrameInspectorField();
            }
        }

        protected void HandleBorderCornerScalingHandles()
        {
            if (!hasSelected)
                return;

            GUIStyle dragDot = styles.dragBorderdot;
            GUIStyle dragDotActive = styles.dragBorderDotActive;
            var color = new Color(0f, 1f, 0f);

            Rect rect = selectedSpriteRect_Rect;
            Vector4 border = selectedSpriteBorder;

            float left = rect.xMin + border.x;
            float right = rect.xMax - border.z;
            float top = rect.yMax - border.w;
            float bottom = rect.yMin + border.y;

            EditorGUI.BeginChangeCheck();

            bool canEdit = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditBorder);
            // Handle corner points, but hide them if border values are below 1
            HandleBorderPointSlider(ref left, ref top, canEdit ? MouseCursor.ResizeUpLeft : MouseCursor.NotAllowed, border.x < 1 && border.w < 1, dragDot, dragDotActive, color);
            HandleBorderPointSlider(ref right, ref top, canEdit ? MouseCursor.ResizeUpRight : MouseCursor.NotAllowed, border.z < 1 && border.w < 1, dragDot, dragDotActive, color);
            HandleBorderPointSlider(ref left, ref bottom, canEdit ? MouseCursor.ResizeUpRight : MouseCursor.NotAllowed, border.x < 1 && border.y < 1, dragDot, dragDotActive, color);
            HandleBorderPointSlider(ref right, ref bottom, canEdit ? MouseCursor.ResizeUpLeft : MouseCursor.NotAllowed, border.z < 1 && border.y < 1, dragDot, dragDotActive, color);

            if (EditorGUI.EndChangeCheck() && canEdit)
            {
                border.x = left - rect.xMin;
                border.z = rect.xMax - right;
                border.w = rect.yMax - top;
                border.y = bottom - rect.yMin;
                selectedSpriteBorder = border;
                PopulateSpriteFrameInspectorField();
            }
        }

        protected void HandleBorderSideScalingHandles()
        {
            if (!hasSelected)
                return;

            Rect rect = new Rect(selectedSpriteRect_Rect);
            Vector4 border = selectedSpriteBorder;

            float left = rect.xMin + border.x;
            float right = rect.xMax - border.z;
            float top = rect.yMax - border.w;
            float bottom = rect.yMin + border.y;

            Vector2 screenRectTopLeft = Handles.matrix.MultiplyPoint(new Vector3(rect.xMin, rect.yMin));
            Vector2 screenRectBottomRight = Handles.matrix.MultiplyPoint(new Vector3(rect.xMax, rect.yMax));

            float screenRectWidth = Mathf.Abs(screenRectBottomRight.x - screenRectTopLeft.x);
            float screenRectHeight = Mathf.Abs(screenRectBottomRight.y - screenRectTopLeft.y);

            EditorGUI.BeginChangeCheck();

            bool canEdit = m_CurrentEditEditCapability.data.HasCapability(EEditCapability.EditBorder);
            left = HandleBorderScaleSlider(left, rect.yMax, screenRectWidth, screenRectHeight, true, canEdit);
            right = HandleBorderScaleSlider(right, rect.yMax, screenRectWidth, screenRectHeight, true, canEdit);

            top = HandleBorderScaleSlider(rect.xMin, top, screenRectWidth, screenRectHeight, false, canEdit);
            bottom = HandleBorderScaleSlider(rect.xMin, bottom, screenRectWidth, screenRectHeight, false, canEdit);

            if (EditorGUI.EndChangeCheck() && canEdit)
            {
                border.x = left - rect.xMin;
                border.z = rect.xMax - right;
                border.w = rect.yMax - top;
                border.y = bottom - rect.yMin;

                selectedSpriteBorder = border;
                PopulateSpriteFrameInspectorField();
            }
        }

        protected void HandleBorderPointSlider(ref float x, ref float y, MouseCursor mouseCursor, bool isHidden, GUIStyle dragDot, GUIStyle dragDotActive, Color color)
        {
            var originalColor = GUI.color;

            if (isHidden)
                GUI.color = new Color(0, 0, 0, 0);
            else
                GUI.color = color;

            Vector2 point = SpriteEditorHandles.PointSlider(new Vector2(x, y), mouseCursor, dragDot, dragDotActive);
            x = point.x;
            y = point.y;

            GUI.color = originalColor;
        }

        protected float HandleBorderScaleSlider(float x, float y, float width, float height, bool isHorizontal, bool canEdit)
        {
            float handleSize = styles.dragBorderdot.fixedWidth;
            Vector2 point = Handles.matrix.MultiplyPoint(new Vector2(x, y));
            float result;

            EditorGUI.BeginChangeCheck();

            if (isHorizontal)
            {
                Rect newRect = new Rect(point.x - handleSize * .5f, point.y, handleSize, height);
                result = SpriteEditorHandles.ScaleSlider(point, canEdit ? MouseCursor.ResizeHorizontal : MouseCursor.NotAllowed, newRect).x;
            }
            else
            {
                Rect newRect = new Rect(point.x, point.y - handleSize * .5f, width, handleSize);
                result = SpriteEditorHandles.ScaleSlider(point, canEdit ? MouseCursor.ResizeVertical : MouseCursor.NotAllowed, newRect).y;
            }

            if (EditorGUI.EndChangeCheck())
                return result;

            return isHorizontal ? x : y;
        }

        protected void DrawSpriteRectGizmos()
        {
            if (eventSystem.current.type != EventType.Repaint)
                return;

            SpriteEditorUtility.BeginLines(new Color(0f, 1f, 0f, 0.7f));
            var selectedGUID = selected != null ? selected.spriteID : new GUID();
            for (int i = 0; i < spriteCount; i++)
            {
                Vector4 border = GetSpriteBorderAt(i);
                if (m_GizmoMode != GizmoMode.BorderEditing && (m_RectsCache != null && m_RectsCache.spriteRects[i].spriteID != selectedGUID))
                {
                    // border does not contain negative values
                    if (border.sqrMagnitude < Mathf.Epsilon * 8)
                        continue;
                }

                var rect = GetSpriteRectAt(i);
                SpriteEditorUtility.DrawLine(new Vector3(rect.xMin + border.x, rect.yMin), new Vector3(rect.xMin + border.x, rect.yMax));
                SpriteEditorUtility.DrawLine(new Vector3(rect.xMax - border.z, rect.yMin), new Vector3(rect.xMax - border.z, rect.yMax));

                SpriteEditorUtility.DrawLine(new Vector3(rect.xMin, rect.yMin + border.y), new Vector3(rect.xMax, rect.yMin + border.y));
                SpriteEditorUtility.DrawLine(new Vector3(rect.xMin, rect.yMax - border.w), new Vector3(rect.xMax, rect.yMax - border.w));
            }
            SpriteEditorUtility.EndLines();

            if (ShouldShowRectScaling())
            {
                Rect r = selectedSpriteRect_Rect;
                SpriteEditorUtility.BeginLines(new Color(0f, 0.1f, 0.3f, 0.25f));
                SpriteEditorUtility.DrawBox(new Rect(r.xMin + 1f / m_Zoom, r.yMin + 1f / m_Zoom, r.width, r.height));
                SpriteEditorUtility.EndLines();
                SpriteEditorUtility.BeginLines(new Color(0.25f, 0.5f, 1f, 0.75f));
                SpriteEditorUtility.DrawBox(r);
                SpriteEditorUtility.EndLines();
            }
        }

        protected void DrawRectGizmos(IEnumerable<Rect> rects, Color color)
        {
            if (eventSystem.current.type != EventType.Repaint)
                return;

            SpriteEditorUtility.BeginLines(color);
            foreach (var rect in rects)
            {
                SpriteEditorUtility.DrawLine(new Vector3(rect.xMin, rect.yMin), new Vector3(rect.xMin, rect.yMax));
                SpriteEditorUtility.DrawLine(new Vector3(rect.xMax, rect.yMin), new Vector3(rect.xMax, rect.yMax));

                SpriteEditorUtility.DrawLine(new Vector3(rect.xMin, rect.yMin), new Vector3(rect.xMax, rect.yMin));
                SpriteEditorUtility.DrawLine(new Vector3(rect.xMin, rect.yMax), new Vector3(rect.xMax, rect.yMax));
            }
            SpriteEditorUtility.EndLines();
        }

        // implements ISpriteEditorModule

        public override void DoMainGUI()
        {
            m_Zoom = Handles.matrix.GetColumn(0).magnitude;
        }

        public override void DoPostGUI()
        {
        }
    }
}
