csharp/jalil49/MoreAccessories/src/Core.MoreAccessories/Extensions/UIUtility.cs

UIUtility.cs
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace MoreAccessoriesKOI.Extensions
{
    public static clast UIUtility
    {
        internal clast ContextMenuUIElement
        {
            public RectTransform rectTransform;
            public Button button;
            public Image icon;
            public Text text;
            public RawImage childIcon;
        }

        public clast ConfirmationDialog : MonoBehaviour
        {
            #region Private Variables
            private Action _currentCallback;
            private Text _text;
            #endregion

            #region Static Methods
            public static Action SpawnUI()
            {
                Component confirmationDialogComponent = null;
                GameObject dialog = null;
                foreach (var c in Resources.FindObjectsOfTypeAll())
                {
                    if (c.gameObject.name.Equals("ConfirmationDialog"))
                    {
                        dialog = c.gameObject;
                        break;
                    }
                }
                if (dialog == null)
                {
                    var c = UIUtility.CreateNewUISystem("ConfirmationDialog");
                    c.sortingOrder = 40;
                    c.transform.localPosition = Vector3.zero;
                    c.transform.localScale = Vector3.one;
                    c.transform.SetRect();
                    c.transform.SetAsLastSibling();

                    var bg = UIUtility.CreateImage("Background", c.transform);
                    bg.rectTransform.SetRect();
                    bg.sprite = null;
                    bg.color = new Color(0f, 0f, 0f, 0.5f);
                    bg.raycastTarget = true;

                    var panel = UIUtility.CreatePanel("Panel", bg.transform);
                    panel.rectTransform.SetRect(new Vector2(0.4f, 0.4f), new Vector2(0.6f, 0.6f));
                    panel.color = Color.gray;

                    var text = UIUtility.CreateText("Text", panel.transform, "");
                    text.rectTransform.SetRect(new Vector2(0f, 0.333333f), Vector2.one, new Vector2(10f, 10f), new Vector2(-10f, -10f));
                    text.color = Color.white;
                    text.resizeTextForBestFit = true;
                    text.resizeTextMaxSize = 100;
                    text.alignByGeometry = true;
                    text.alignment = TextAnchor.MiddleCenter;

                    var yes = UIUtility.CreateButton("YesButton", panel.transform, "Yes");
                    (yes.transform as RectTransform).SetRect(Vector2.zero, new Vector2(0.5f, 0.333333f), new Vector2(10f, 10f), new Vector2(-5f, -10f));
                    text = yes.GetComponentInChildren();
                    text.resizeTextForBestFit = true;
                    text.resizeTextMaxSize = 100;
                    text.alignByGeometry = true;
                    text.alignment = TextAnchor.MiddleCenter;

                    var no = UIUtility.CreateButton("NoButton", panel.transform, "No");
                    (no.transform as RectTransform).SetRect(new Vector2(0.5f, 0f), new Vector2(1f, 0.333333f), new Vector2(5f, 10f), new Vector2(-10f, -10f));
                    text = no.GetComponentInChildren();
                    text.resizeTextForBestFit = true;
                    text.resizeTextMaxSize = 100;
                    text.alignByGeometry = true;
                    text.alignment = TextAnchor.MiddleCenter;

                    confirmationDialogComponent = c.gameObject.AddComponent();
                    c.gameObject.SetActive(false);
                }
                else
                {
                    var components = dialog.GetComponents();
                    foreach (var c in components)
                    {
                        if (c.GetType().Name == nameof(ConfirmationDialog))
                        {
                            confirmationDialogComponent = c;
                            break;
                        }
                    }
                }
                return (Action)Delegate.CreateDelegate(typeof(Action), confirmationDialogComponent, confirmationDialogComponent.GetType().GetMethod(nameof(DisplayDialog), BindingFlags.Instance | BindingFlags.Public));
            }
            #endregion

            #region Unity Methods
            private void Awake()
            {
                transform.Find("Background/Panel/YesButton").GetComponent().onClick.AddListener(YesPressed);
                transform.Find("Background/Panel/NoButton").GetComponent().onClick.AddListener(NoPressed);
                _text = transform.Find("Background/Panel/Text").GetComponent();
            }
            #endregion

            #region Public Methods
            public void DisplayDialog(Action callback, string message = "Are you sure?")
            {
                _currentCallback = callback;
                _text.text = message;
                gameObject.SetActive(true);
            }
            #endregion

            #region Private Methods
            private void NoPressed()
            {
                _currentCallback?.Invoke(false);
                gameObject.SetActive(false);
                _currentCallback = null;
            }

            private void YesPressed()
            {
                _currentCallback?.Invoke(true);
                gameObject.SetActive(false);
                _currentCallback = null;
            }
            #endregion
        }

        public clast MovableWindow : UIBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
        {
            private RectTransform _rectTransform;
            private readonly Vector3[] _draggableZoneCorners = new Vector3[4];
            private readonly Vector3[] _limitCorners = new Vector3[4];
            private Vector2 _cachedDragPosition;
            private Vector2 _cachedMousePosition;
            private bool _pointerDownCalled = false;

            public RectTransform toDrag;
            public RectTransform limit;

            public override void Awake()
            {
                base.Awake();
                _rectTransform = GetComponent();
            }

            public void OnPointerDown(PointerEventData eventData)
            {
                _pointerDownCalled = true;
                _cachedDragPosition = toDrag.position;
                _cachedMousePosition = eventData.position;
            }

            public void OnDrag(PointerEventData eventData)
            {
                if (_pointerDownCalled == false)
                    return;
                toDrag.position = _cachedDragPosition + (eventData.position - _cachedMousePosition);
                Limit();
            }

            public void OnPointerUp(PointerEventData eventData)
            {
                if (_pointerDownCalled == false)
                    return;
                toDrag.position = _cachedDragPosition + (eventData.position - _cachedMousePosition);
                Limit();
                _pointerDownCalled = false;
            }

            private void Limit()
            {
                if (limit != null)
                {
                    _rectTransform.GetWorldCorners(_draggableZoneCorners);
                    limit.GetWorldCorners(_limitCorners);
                    if (_draggableZoneCorners[0].x < _limitCorners[0].x)
                        toDrag.position += new Vector3(_limitCorners[0].x - _draggableZoneCorners[0].x, 0f, 0f);
                    if (_draggableZoneCorners[0].y < _limitCorners[0].y)
                        toDrag.position += new Vector3(0f, _limitCorners[0].y - _draggableZoneCorners[0].y, 0f);
                    if (_draggableZoneCorners[2].x > _limitCorners[2].x)
                        toDrag.position += new Vector3(_limitCorners[2].x - _draggableZoneCorners[2].x, 0f, 0f);
                    if (_draggableZoneCorners[2].y > _limitCorners[2].y)
                        toDrag.position += new Vector3(0f, _limitCorners[2].y - _draggableZoneCorners[2].y, 0f);
                }
            }
        }

        public clast GroupElement : AContextMenuElement
        {
            public List elements;
        }

        public clast LeafElement : AContextMenuElement
        {
            public object parameter;
            public Action onClick;
        }

        public abstract clast AContextMenuElement
        {
            public Sprite icon;
            public Color iconColor = Color.black;
            public string text;
        }

        public const RenderMode canvasRenderMode = RenderMode.ScreenSpaceOverlay;
        public const bool canvasPixelPerfect = false;

        public const Canvastcaler.ScaleMode canvastcalerUiScaleMode = Canvastcaler.ScaleMode.ScaleWithScreenSize;
        public const float canvastcalerReferencePixelsPerUnit = 100f;

        public const bool graphicRaycasterIgnoreReversedGraphics = true;
        public const GraphicRaycaster.BlockingObjects graphicRaycasterBlockingObjects = GraphicRaycaster.BlockingObjects.None;

        public static Sprite checkMark;
        public static Sprite backgroundSprite;
        public static Sprite standardSprite;
        public static Sprite inputFieldBackground;
        public static Sprite knob;
        public static Sprite dropdownArrow;
        public static Sprite mask;
        public static readonly Color whiteColor = new Color(1.000f, 1.000f, 1.000f);
        public static readonly Color grayColor = new Color32(100, 99, 95, 255);
        public static readonly Color lightGrayColor = new Color32(150, 149, 143, 255);
        public static readonly Color greenColor = new Color32(0, 160, 0, 255);
        public static readonly Color lightGreenColor = new Color32(0, 200, 0, 255);
        public static readonly Color purpleColor = new Color(0.000f, 0.007f, 1.000f, 0.545f);
        public static readonly Color transparentGrayColor = new Color32(100, 99, 95, 90);
        public static Font defaultFont;
        public static int defaultFontSize;
        public static DefaultControls.Resources resources;

        private static bool _resourcesLoaded = false;
        private static Action _displayConfirmationDialog;
        private static RectTransform _contextMenuRoot;
        private static readonly List _displayedContextMenuElements = new List();
        private static readonly List _displayedContextMenuGroups = new List();

        public static void Init()
        {
            if (_resourcesLoaded == false)
            {
                astetBundle bundle;

#if HS || PH || EC 
                using (var stream = astembly.GetExecutingastembly().GetManifestResourceStream("UILib.Resources.DefaultResources.unity3d"))
#elif KK || KKS
                using (var stream = astembly.GetExecutingastembly().GetManifestResourceStream("UILib.Resources.DefaultResourcesKOI.unity3d"))
#elif AI
                using (Stream stream = astembly.GetExecutingastembly().GetManifestResourceStream("UILib.Resources.DefaultResourcesAI.unity3d"))
#elif HS2
                using (Stream stream = astembly.GetExecutingastembly().GetManifestResourceStream("UILib.Resources.DefaultResourcesHS2.unity3d"))
#endif
                {
                    var arr = new byte[stream.Length];
                    stream.Read(arr, 0, arr.Length);
                    bundle = astetBundle.LoadFromMemory(arr);
                }
                foreach (var sprite in bundle.LoadAllastets())
                {
                    switch (sprite.name)
                    {
                        case "Background":
                            backgroundSprite = sprite;
                            break;
                        case "UISprite":
                            standardSprite = sprite;
                            break;
                        case "InputFieldBackground":
                            inputFieldBackground = sprite;
                            break;
                        case "Knob":
                            knob = sprite;
                            break;
                        case "Checkmark":
                            checkMark = sprite;
                            break;
                        case "DropdownArrow":
                            dropdownArrow = sprite;
                            break;
                        case "UIMask":
                            mask = sprite;
                            break;
                    }
                }
                defaultFont = Resources.GetBuiltinResource("Arial.ttf");
                resources = new DefaultControls.Resources
                {
                    background = backgroundSprite,
                    checkmark = checkMark,
                    dropdown = dropdownArrow,
                    inputField = inputFieldBackground,
                    knob = knob,
                    mask = mask,
                    standard = standardSprite
                };
                defaultFontSize = 16;
                bundle.Unload(false);
                _resourcesLoaded = true;
            }

#if HS
            SetCustomFont("mplus-1c-medium");
#elif KK || KKS
            SetCustomFont("SourceHanSansJP-Medium");
#elif AI || HS2
            SetCustomFont("Yu Gothic UI Semibold");
#endif
            _displayConfirmationDialog = ConfirmationDialog.SpawnUI();
        }

        public static Canvas CreateNewUISystem(string name = "NewUISystem")
        {
            var go = new GameObject(name, typeof(Canvas), typeof(Canvastcaler), typeof(GraphicRaycaster));
            var c = go.GetComponent();
            c.renderMode = canvasRenderMode;
            //c.pixelPerfect = canvasPixelPerfect;

            var cs = go.GetComponent();
            cs.uiScaleMode = canvastcalerUiScaleMode;
            cs.referencePixelsPerUnit = canvastcalerReferencePixelsPerUnit;
            cs.screenMatchMode = Canvastcaler.ScreenMatchMode.Expand;

            var gr = go.GetComponent();
            gr.ignoreReversedGraphics = graphicRaycasterIgnoreReversedGraphics;
            gr.blockingObjects = graphicRaycasterBlockingObjects;

            return c;
        }

        public static void SetCustomFont(string customFontName)
        {
            foreach (var font in Resources.FindObjectsOfTypeAll())
            {
                if (font.name.Equals(customFontName))
                {
                    defaultFont = font;
                    break;
                }
            }
        }

        public static RectTransform CreateNewUIObject(string objectName = "UIObject", Transform parent = null)
        {
            var t = new GameObject(objectName, typeof(RectTransform)).GetComponent();
            if (parent != null)
            {
                t.SetParent(parent, false);
                t.localPosition = Vector3.zero;
                t.localScale = Vector3.one;
            }

            t.gameObject.layer = 5;
            return t;
        }

        public static InputField CreateInputField(string objectName = "New Input Field", Transform parent = null, string placeholder = "Placeholder...")
        {
            var go = DefaultControls.CreateInputField(resources);
            go.name = objectName;
            foreach (var text in go.GetComponentsInChildren(true))
            {
                text.font = defaultFont;
                text.resizeTextForBestFit = true;
                text.resizeTextMinSize = 2;
                text.resizeTextMaxSize = 100;
                text.alignment = TextAnchor.MiddleLeft;
                text.rectTransform.offsetMin = new Vector2(5f, 2f);
                text.rectTransform.offsetMax = new Vector2(-5f, -2f);
            }
            go.transform.SetParent(parent, false);

            var f = go.GetComponent();
            f.placeholder.GetComponent().text = placeholder;

            return f;
        }

        public static Button CreateButton(string objectName = "New Button", Transform parent = null, string buttonText = "Button")
        {
            var go = DefaultControls.CreateButton(resources);
            go.name = objectName;

            var text = go.GetComponentInChildren(true);
            text.font = defaultFont;
            text.resizeTextForBestFit = true;
            text.resizeTextMinSize = 2;
            text.resizeTextMaxSize = 100;
            text.alignment = TextAnchor.MiddleCenter;
            text.rectTransform.SetRect(Vector2.zero, Vector2.one, new Vector2(2f, 2f), new Vector2(-2f, -2f));
            text.text = buttonText;
            go.transform.SetParent(parent, false);

            return go.GetComponent();
        }

        public static Image CreateImage(string objectName = "New Image", Transform parent = null, Sprite sprite = null)
        {
            var go = DefaultControls.CreateImage(resources);
            go.name = objectName;
            go.transform.SetParent(parent, false);
            var i = go.GetComponent();
            i.sprite = sprite;
            return i;
        }

        public static Text CreateText(string objectName = "New Text", Transform parent = null, string textText = "Text")
        {
            var go = DefaultControls.CreateText(resources);
            go.name = objectName;

            var text = go.GetComponentInChildren(true);
            text.font = defaultFont;
            text.resizeTextForBestFit = true;
            text.resizeTextMinSize = 2;
            text.resizeTextMaxSize = 100;
            text.alignment = TextAnchor.UpperLeft;
            text.text = textText;
            text.color = whiteColor;
            go.transform.SetParent(parent, false);

            return text;
        }

        public static Toggle CreateToggle(string objectName = "New Toggle", Transform parent = null, string label = "Label")
        {
            var go = DefaultControls.CreateToggle(resources);
            go.name = objectName;

            var text = go.GetComponentInChildren(true);
            text.font = defaultFont;
            text.resizeTextForBestFit = true;
            text.resizeTextMinSize = 2;
            text.resizeTextMaxSize = 100;
            text.alignment = TextAnchor.MiddleCenter;
            text.rectTransform.SetRect(Vector2.zero, Vector2.one, new Vector2(23f, 1f), new Vector2(-5f, -2f));
            text.text = label;
            go.transform.SetParent(parent, false);

            return go.GetComponent();
        }

        public static Dropdown CreateDropdown(string objectName = "New Dropdown", Transform parent = null)
        {
            var go = DefaultControls.CreateDropdown(resources);
            go.name = objectName;

            foreach (var text in go.GetComponentsInChildren(true))
            {
                text.font = defaultFont;
                text.resizeTextForBestFit = true;
                text.resizeTextMinSize = 2;
                text.resizeTextMaxSize = 100;
                text.alignment = TextAnchor.MiddleLeft;
                if (text.name.Equals("Label"))
                    text.rectTransform.SetRect(Vector2.zero, Vector2.one, new Vector2(10f, 6f), new Vector2(-25f, -7f));
                else
                    text.rectTransform.SetRect(Vector2.zero, Vector2.one, new Vector2(20f, 1f), new Vector2(-10f, -2f));
            }
            go.transform.SetParent(parent, false);
            return go.GetComponent();
        }

        public static RawImage CreateRawImage(string objectName = "New Raw Image", Transform parent = null, Texture texture = null)
        {
            var go = DefaultControls.CreateRawImage(resources);
            go.name = objectName;
            go.transform.SetParent(parent, false);
            var i = go.GetComponent();
            i.texture = texture;
            return i;
        }

        public static Scrollbar CreateScrollbar(string objectName = "New Scrollbar", Transform parent = null)
        {
            var go = DefaultControls.CreateScrollbar(resources);
            go.name = objectName;
            go.transform.SetParent(parent, false);
            return go.GetComponent();
        }

        public static ScrollRect CreateScrollView(string objectName = "New ScrollView", Transform parent = null)
        {
            var go = DefaultControls.CreateScrollView(resources);
            go.name = objectName;
            go.transform.SetParent(parent, false);
            // I swear this is useful.
            // It prevents the new mask from "combining" with existing masks.
            // Yes, I know, it's weird issue, but I guess that's the price of creating UIs at runtime.
            foreach (var m in go.GetComponentsInChildren(true))
            {
                m.enabled = false;
                m.enabled = true;
            }
            return go.GetComponent();
        }

        public static Slider CreateSlider(string objectName = "New Slider", Transform parent = null)
        {
            var go = DefaultControls.CreateSlider(resources);
            go.name = objectName;
            go.transform.SetParent(parent, false);
            return go.GetComponent();
        }
        public static Image CreatePanel(string objectName = "New Panel", Transform parent = null)
        {
            var go = DefaultControls.CreatePanel(resources);
            go.name = objectName;
            go.transform.SetParent(parent, false);
            return go.GetComponent();
        }

        public static void DisplayConfirmationDialog(Action callback, string message = "Are you sure?")
        {
            _displayConfirmationDialog(callback, message);
        }

        public static Outline AddOutlineToObject(Transform t)
        {
            return AddOutlineToObject(t, Color.black, new Vector2(1f, -1f));
        }

        public static Outline AddOutlineToObject(Transform t, Color c)
        {
            return AddOutlineToObject(t, c, new Vector2(1f, -1f));
        }

        public static Outline AddOutlineToObject(Transform t, Vector2 effectDistance)
        {
            return AddOutlineToObject(t, Color.black, effectDistance);
        }

        public static Outline AddOutlineToObject(Transform t, Color color, Vector2 effectDistance)
        {
            var o = t.gameObject.AddComponent();
            o.effectColor = color;
            o.effectDistance = effectDistance;
            return o;
        }

        public static Toggle AddCheckboxToObject(Transform tr)
        {
            var t = tr.gameObject.AddComponent();

            var bg = CreateNewUIObject("Background", tr.transform);
            t.targetGraphic = AddImageToObject(bg, standardSprite);

            var check = CreateNewUIObject("CheckMark", bg);
            var checkM = AddImageToObject(check, checkMark);
            checkM.color = Color.black;
            t.graphic = checkM;

            bg.SetRect(Vector2.zero, Vector2.one, Vector2.zero, Vector2.zero);
            check.SetRect(Vector2.zero, Vector2.one, Vector2.zero, Vector2.zero);
            return t;
        }

        public static Image AddImageToObject(Transform t, Sprite sprite = null)
        {
            var i = t.gameObject.AddComponent();
            i.type = Image.Type.Sliced;
            i.fillCenter = true;
            i.color = whiteColor;
            i.sprite = sprite == null ? backgroundSprite : sprite;
            return i;
        }

        public static MovableWindow MakeObjectDraggable(RectTransform clickableDragZone, RectTransform draggableObject, RectTransform limit = null)
        {
            var mv = clickableDragZone.gameObject.AddComponent();
            mv.toDrag = draggableObject;
            mv.limit = limit;
            return mv;
        }

        public static void ShowContextMenu(Canvas canvas, Vector2 ancreplaceddPosition, List elements, float width = 120f)
        {
            if (_contextMenuRoot == null)
            {
                _contextMenuRoot = CreateContextMenuGroup("ContextMenu");
                var subCanvas = _contextMenuRoot.gameObject.AddComponent();
                subCanvas.overrideSorting = true;
                subCanvas.sortingOrder = 20;
                subCanvas.gameObject.AddComponent();
            }
            _contextMenuRoot.SetParent(canvas.transform);
            _contextMenuRoot.localPosition = Vector3.zero;
            _contextMenuRoot.localRotation = Quaternion.idensaty;
            _contextMenuRoot.localScale = Vector3.one;
            _contextMenuRoot.SetRect(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f));
            _contextMenuRoot.ancreplaceddPosition = ancreplaceddPosition + new Vector2(2f, -2f);
            _contextMenuRoot.sizeDelta = new Vector2(width, _contextMenuRoot.sizeDelta.y);
            _contextMenuRoot.gameObject.SetActive(true);

            var elementIndex = 0;
            var groupIndex = 0;
            foreach (var element in elements)
            {
                RectTransform rt = null;
                if (element is LeafElement element2)
                    rt = HandleLeafElement(element2, ref elementIndex);
                else if (element is GroupElement element1)
                    rt = HandleGroupElement(element1, ref groupIndex, ref elementIndex, width);
                rt.SetParent(_contextMenuRoot);
                rt.localPosition = Vector3.zero;
                rt.localRotation = Quaternion.idensaty;
                rt.localScale = Vector3.one;
                rt.SetAsLastSibling();
            }

            for (; elementIndex < _displayedContextMenuElements.Count; ++elementIndex)
                _displayedContextMenuElements[elementIndex].rectTransform.gameObject.SetActive(false);
            for (; groupIndex < _displayedContextMenuGroups.Count; ++groupIndex)
                _displayedContextMenuGroups[groupIndex].gameObject.SetActive(false);
        }

        public static bool IsContextMenuDisplayed()
        {
            return _contextMenuRoot != null && _contextMenuRoot.gameObject.activeSelf;
        }

        public static bool WasClickInContextMenu()
        {
            return EventSystem.current.currentSelectedGameObject != null && _contextMenuRoot != null && EventSystem.current.currentSelectedGameObject.transform.IsChildOf(_contextMenuRoot.transform);
        }

        public static void HideContextMenu()
        {
            if (_contextMenuRoot != null)
                _contextMenuRoot.gameObject.SetActive(false);
        }

        private static RectTransform CreateContextMenuGroup(string name = "Group")
        {
            var obj = new GameObject(name, typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
            var rt = (RectTransform)obj.transform;
            rt.pivot = Vector2.zero;
            obj.GetComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
            return rt;
        }

        private static ContextMenuUIElement GetContextMenuUIElement(int index)
        {
            if (index < _displayedContextMenuElements.Count)
                return _displayedContextMenuElements[index];
            var uiElement = new ContextMenuUIElement { rectTransform = ConstuctContextMenuElement() };
            uiElement.button = uiElement.rectTransform.Find("Button").GetComponent();
            uiElement.icon = uiElement.button.transform.Find("Icon").GetComponent();
            uiElement.text = uiElement.button.GetComponentInChildren();
            uiElement.childIcon = uiElement.button.transform.Find("ChildIcon").GetComponent();
            _displayedContextMenuElements.Add(uiElement);
            return uiElement;
        }

        private static RectTransform ConstuctContextMenuElement()
        {
            var root = CreateNewUIObject("ContextMenuElement");
            root.gameObject.AddComponent().preferredHeight = 25;
            var b = CreateButton("Button", root);
            b.transform.SetRect();
            UnityEngine.Object.Destroy(b.GetComponent());
            UnityEngine.Object.Destroy(b.GetComponent());
            b.targetGraphic = CreateImage("Background", b.transform, standardSprite);
            b.targetGraphic.rectTransform.SetRect();
            ((Image)b.targetGraphic).type = Image.Type.Sliced;
            var icon = CreateImage("Icon", b.transform);
            icon.rectTransform.pivot = new Vector2(0f, 0.5f);
            icon.rectTransform.SetRect(new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(2f, 2f), new Vector2(23f, -2f));
            var t = b.GetComponentInChildren();
            t.rectTransform.SetRect(new Vector2(0f, 0f), new Vector2(1f, 1f), new Vector2(25f, 0f), new Vector2(-25f, 0f));
            t.rectTransform.SetAsLastSibling();
            t.alignment = TextAnchor.MiddleLeft;
            t.alignByGeometry = true;
            var childIcon = CreateRawImage("ChildIcon", b.transform, dropdownArrow.texture);
            childIcon.rectTransform.localRotation = Quaternion.AngleAxis(90, Vector3.forward);
            childIcon.rectTransform.SetRect(new Vector2(1f, 0f), new Vector2(1f, 1f), new Vector2(-26.5f, -2f), new Vector2(2.5f, 2f));
            return root;
        }

        private static RectTransform GetContextMenuGroup(int index)
        {
            if (index < _displayedContextMenuGroups.Count)
                return _displayedContextMenuGroups[index];
            var rt = CreateContextMenuGroup();
            _displayedContextMenuGroups.Add(rt);
            return rt;
        }

        private static RectTransform HandleLeafElement(LeafElement element, ref int index)
        {
            var uiElement = GetContextMenuUIElement(index);
            uiElement.rectTransform.SetParent(null);
            uiElement.rectTransform.gameObject.SetActive(true);

            uiElement.icon.gameObject.SetActive(element.icon != null);
            uiElement.icon.sprite = element.icon;
            uiElement.icon.color = element.icon != null ? element.iconColor : Color.clear;
            uiElement.childIcon.gameObject.SetActive(false);
            uiElement.text.rectTransform.offsetMax = Vector2.zero;

            uiElement.text.text = element.text;
            uiElement.rectTransform.gameObject.name = element.text;
            uiElement.button.onClick = new Button.ButtonClickedEvent();
            uiElement.button.onClick.AddListener(() =>
            {
                element.onClick(element.parameter);
                HideContextMenu();
            });
            ++index;
            return uiElement.rectTransform;
        }

        private static RectTransform HandleGroupElement(GroupElement element, ref int groupIndex, ref int elementIndex, float width = 120f)
        {
            var uiElement = GetContextMenuUIElement(elementIndex);
            uiElement.rectTransform.SetParent(null);
            uiElement.rectTransform.gameObject.SetActive(true);

            uiElement.icon.gameObject.SetActive(element.icon != null);
            uiElement.icon.sprite = element.icon;
            uiElement.icon.color = element.icon != null ? element.iconColor : Color.clear;
            uiElement.childIcon.gameObject.SetActive(true);
            uiElement.text.rectTransform.offsetMax = new Vector2(20f, 0f);
            ++elementIndex;


            var group = GetContextMenuGroup(groupIndex);
            group.SetParent(uiElement.rectTransform);
            group.localPosition = Vector3.zero;
            group.localRotation = Quaternion.idensaty;
            group.localScale = Vector3.one;
            group.SetRect(new Vector2(1f, 0f), new Vector2(1f, 0f), Vector2.zero, new Vector2(width, 0f));
            group.gameObject.SetActive(false);

            uiElement.text.text = element.text;
            uiElement.rectTransform.gameObject.name = element.text;
            uiElement.button.onClick = new Button.ButtonClickedEvent();
            uiElement.button.onClick.AddListener(() => group.gameObject.SetActive(!group.gameObject.activeSelf));
            ++groupIndex;

            foreach (var e in element.elements)
            {
                RectTransform rt = null;
                if (e is LeafElement element1)
                    rt = HandleLeafElement(element1, ref elementIndex);
                else if (e is GroupElement element2)
                    rt = HandleGroupElement(element2, ref groupIndex, ref elementIndex, width);
                rt.SetParent(group);
                rt.localPosition = Vector3.zero;
                rt.localRotation = Quaternion.idensaty;
                rt.localScale = Vector3.one;
                rt.SetAsLastSibling();
            }

            group.SetParent(uiElement.rectTransform);
            group.localPosition = Vector3.zero;
            group.localRotation = Quaternion.idensaty;
            group.localScale = Vector3.one;
            group.SetRect(new Vector2(1f, 0f), new Vector2(1f, 0f), Vector2.zero, new Vector2(width, 0f));
            return uiElement.rectTransform;
        }

        //private static string GetPathFrom(this Transform self, string root, bool includeRoot = false)
        //{
        //    if (self.name.Equals(root))
        //        return "";
        //    var self2 = self;
        //    var path = new StringBuilder(self2.name);
        //    self2 = self2.parent;
        //    while (self2 != null && self2.name.Equals(root) == false)
        //    {
        //        path.Insert(0, "/");
        //        path.Insert(0, self2.name);
        //        self2 = self2.parent;
        //    }
        //    if (self2 != null && includeRoot)
        //    {
        //        path.Insert(0, "/");
        //        path.Insert(0, root);
        //    }
        //    return path.ToString();
        //}

    }

    public static clast UIExtensions
    {
        public static void SetRect(this RectTransform self, Vector2 anchorMin)
        {
            SetRect(self, anchorMin, Vector2.one, Vector2.zero, Vector2.zero);
        }
        public static void SetRect(this RectTransform self, Vector2 anchorMin, Vector2 anchorMax)
        {
            SetRect(self, anchorMin, anchorMax, Vector2.zero, Vector2.zero);
        }
        public static void SetRect(this RectTransform self, Vector2 anchorMin, Vector2 anchorMax, Vector2 offsetMin)
        {
            SetRect(self, anchorMin, anchorMax, offsetMin, Vector2.zero);
        }
        public static void SetRect(this RectTransform self, Vector2 anchorMin, Vector2 anchorMax, Vector2 offsetMin, Vector2 offsetMax)
        {
            self.anchorMin = anchorMin;
            self.anchorMax = anchorMax;
            self.offsetMin = offsetMin;
            self.offsetMax = offsetMax;
        }

        public static void SetRect(this RectTransform self, RectTransform other)
        {
            self.anchorMin = other.anchorMin;
            self.anchorMax = other.anchorMax;
            self.offsetMin = other.offsetMin;
            self.offsetMax = other.offsetMax;
        }

        public static void SetRect(this RectTransform self, float anchorLeft = 0f, float anchorBottom = 0f, float anchorRight = 1f, float anchorTop = 1f, float offsetLeft = 0f, float offsetBottom = 0f, float offsetRight = 0f, float offsetTop = 0f)
        {
            self.anchorMin = new Vector2(anchorLeft, anchorBottom);
            self.anchorMax = new Vector2(anchorRight, anchorTop);
            self.offsetMin = new Vector2(offsetLeft, offsetBottom);
            self.offsetMax = new Vector2(offsetRight, offsetTop);
        }

        public static void SetRect(this Transform self, Transform other)
        {
            SetRect((RectTransform)self, other as RectTransform);
        }

        public static void SetRect(this Transform self, Vector2 anchorMin)
        {
            SetRect((RectTransform)self, anchorMin, Vector2.one, Vector2.zero, Vector2.zero);
        }
        public static void SetRect(this Transform self, Vector2 anchorMin, Vector2 anchorMax)
        {
            SetRect((RectTransform)self, anchorMin, anchorMax, Vector2.zero, Vector2.zero);
        }
        public static void SetRect(this Transform self, Vector2 anchorMin, Vector2 anchorMax, Vector2 offsetMin)
        {
            SetRect((RectTransform)self, anchorMin, anchorMax, offsetMin, Vector2.zero);
        }
        public static void SetRect(this Transform self, Vector2 anchorMin, Vector2 anchorMax, Vector2 offsetMin, Vector2 offsetMax)
        {
            var rt = (RectTransform)self;
            rt.anchorMin = anchorMin;
            rt.anchorMax = anchorMax;
            rt.offsetMin = offsetMin;
            rt.offsetMax = offsetMax;
        }

        public static void SetRect(this Transform self, float anchorLeft = 0f, float anchorBottom = 0f, float anchorRight = 1f, float anchorTop = 1f, float offsetLeft = 0f, float offsetBottom = 0f, float offsetRight = 0f, float offsetTop = 0f)
        {
            var rt = (RectTransform)self;
            rt.anchorMin = new Vector2(anchorLeft, anchorBottom);
            rt.anchorMax = new Vector2(anchorRight, anchorTop);
            rt.offsetMin = new Vector2(offsetLeft, offsetBottom);
            rt.offsetMax = new Vector2(offsetRight, offsetTop);
        }

        public static Button LinkButtonTo(this Transform root, string path, UnityAction onClick)
        {
            var b = root.Find(path).GetComponent();
            if (onClick != null)
                b.onClick.AddListener(onClick);
            return b;
        }

        public static Dropdown LinkDropdownTo(this Transform root, string path, UnityAction onValueChanged)
        {
            var b = root.Find(path).GetComponent();
            if (onValueChanged != null)
                b.onValueChanged.AddListener(onValueChanged);
            return b;

        }

        public static InputField LinkInputFieldTo(this Transform root, string path, UnityAction onValueChanged, UnityAction onEndEdit)
        {
            var b = root.Find(path).GetComponent();
            if (onValueChanged != null)
                b.onValueChanged.AddListener(onValueChanged);
            if (onEndEdit != null)
                b.onEndEdit.AddListener(onEndEdit);
            return b;

        }

        public static ScrollRect LinkScrollViewTo(this Transform root, string path, UnityAction onValueChanged)
        {
            var b = root.Find(path).GetComponent();
            if (onValueChanged != null)
                b.onValueChanged.AddListener(onValueChanged);
            return b;

        }

        public static Scrollbar LinkScrollbarTo(this Transform root, string path, UnityAction onValueChanged)
        {
            var b = root.Find(path).GetComponent();
            if (onValueChanged != null)
                b.onValueChanged.AddListener(onValueChanged);
            return b;

        }

        public static Slider LinkSliderTo(this Transform root, string path, UnityAction onValueChanged)
        {
            var b = root.Find(path).GetComponent();
            if (onValueChanged != null)
                b.onValueChanged.AddListener(onValueChanged);
            return b;

        }

        public static Toggle LinkToggleTo(this Transform root, string path, UnityAction onValueChanged)
        {
            var b = root.Find(path).GetComponent();
            if (onValueChanged != null)
                b.onValueChanged.AddListener(onValueChanged);
            return b;

        }

        private static readonly Slider.SliderEvent _emptySliderEvent = new Slider.SliderEvent();
        public static void SetValueNoCallback(this Slider self, float value)
        {
            var cachedEvent = self.onValueChanged;
            self.onValueChanged = _emptySliderEvent;
            self.value = value;
            self.onValueChanged = cachedEvent;
        }

        private static readonly Toggle.ToggleEvent _emptyToggleEvent = new Toggle.ToggleEvent();

        public static void SetIsOnNoCallback(this Toggle self, bool value)
        {
            var cachedEvent = self.onValueChanged;
            self.onValueChanged = _emptyToggleEvent;
            self.Set(value);
            self.onValueChanged = cachedEvent;
        }
    }
}