using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using System.Collections.Generic; using System.Linq; namespace UIWidgets { /// /// IResizableItem. /// public interface IResizableItem { /// /// Gets the objects to resize. /// /// The objects to resize. GameObject[] ObjectsToResize { get; } } /// /// ResizableHeader. /// [AddComponentMenu("UI/UIWidgets/ResizableHeader")] [RequireComponent(typeof(LayoutGroup))] public class ResizableHeader : MonoBehaviour, IDropSupport, IPointerEnterHandler, IPointerExitHandler { /// /// ListView instance. /// [SerializeField] public ListViewBase List; /// /// Allow resize. /// [SerializeField] public bool AllowResize = true; /// /// Allow reorder. /// [SerializeField] public bool AllowReorder = true; /// /// Is now processed cell reorder? /// [System.NonSerialized] [HideInInspector] public bool ProcessCellReorder = false; /// /// Update ListView columns width on drag. /// [SerializeField] public bool OnDragUpdate = true; /// /// The active region in points from left or right border where resize allowed. /// [SerializeField] public float ActiveRegion = 5; /// /// The current camera. For Screen Space - Overlay let it empty. /// [SerializeField] public Camera CurrentCamera; /// /// The cursor texture. /// [SerializeField] public Texture2D CursorTexture; /// /// The cursor hot spot. /// [SerializeField] public Vector2 CursorHotSpot = new Vector2(16, 16); /// /// The cursor texture. /// [SerializeField] public Texture2D AllowDropCursor; /// /// The cursor hot spot. /// [SerializeField] public Vector2 AllowDropCursorHotSpot = new Vector2(4, 2); /// /// The cursor texture. /// [SerializeField] public Texture2D DeniedDropCursor; /// /// The cursor hot spot. /// [SerializeField] public Vector2 DeniedDropCursorHotSpot = new Vector2(4, 2); /// /// The default cursor texture. /// [SerializeField] public Texture2D DefaultCursorTexture; /// /// The default cursor hot spot. /// [SerializeField] public Vector2 DefaultCursorHotSpot; RectTransform rectTransform; /// /// Gets the rect transform. /// /// The rect transform. public RectTransform RectTransform { get { if (rectTransform==null) { rectTransform = transform as RectTransform; } return rectTransform; } } Canvas canvas; RectTransform canvasRect; List childrenLayouts = new List(); List children = new List(); List positions; LayoutElement leftTarget; LayoutElement rightTarget; bool processDrag; List widths; LayoutGroup layout; void Start() { layout = GetComponent(); if (layout!=null) { Utilites.UpdateLayout(layout); } Init(); } /// /// Raises the initialize potential drag event. /// /// Event data. public void OnInitializePotentialDrag(PointerEventData eventData) { //Init(); } /// /// Init this instance. /// public void Init() { canvasRect = Utilites.FindTopmostCanvas(transform) as RectTransform; canvas = canvasRect.GetComponent(); children.Clear(); childrenLayouts.Clear(); int i = 0; foreach (Transform child in transform) { var element = child.GetComponent(); if (element==null) { element = child.gameObject.AddComponent(); } children.Add(child as RectTransform); childrenLayouts.Add(element); var cell = child.gameObject.AddComponent(); cell.Position = i; cell.ResizableHeader = this; cell.AllowDropCursor = AllowDropCursor; cell.AllowDropCursorHotSpot = AllowDropCursorHotSpot; cell.DeniedDropCursor = DeniedDropCursor; cell.DeniedDropCursorHotSpot = DeniedDropCursorHotSpot; var events = child.gameObject.AddComponent(); events.OnInitializePotentialDragEvent.AddListener(OnInitializePotentialDrag); events.OnBeginDragEvent.AddListener(OnBeginDrag); events.OnDragEvent.AddListener(OnDrag); events.OnEndDragEvent.AddListener(OnEndDrag); i++; } positions = Enumerable.Range(0, i).ToList(); CalculateWidths(); //ResetChildren(); //Resize(); } bool inActiveRegion; /// /// Gets a value indicating whether mouse position in active region. /// /// true if in active region; otherwise, false. public bool InActiveRegion { get { return inActiveRegion; } } /// /// Is cursor over gameobject? /// protected bool IsCursorOver; /// /// Called by a BaseInputModule when an OnPointerEnter event occurs. /// /// Event data. public void OnPointerEnter(PointerEventData eventData) { IsCursorOver = true; } /// /// Called by a BaseInputModule when an OnPointerExit event occurs. /// /// Event data. public void OnPointerExit(PointerEventData eventData) { IsCursorOver = false; cursorChanged = false; Cursor.SetCursor(DefaultCursorTexture, DefaultCursorHotSpot, Utilites.GetCursorMode()); } /// /// Is cursor changed? /// protected bool cursorChanged; void LateUpdate() { if (!AllowResize) { return ; } if (!IsCursorOver) { return ; } if (processDrag || ProcessCellReorder) { return ; } if ((CursorTexture==null) || (!Input.mousePresent)) { return ; } inActiveRegion = CheckInActiveRegion(Input.mousePosition, CurrentCamera); if (inActiveRegion) { Cursor.SetCursor(CursorTexture, CursorHotSpot, Utilites.GetCursorMode()); cursorChanged = true; } else if (cursorChanged) { Cursor.SetCursor(DefaultCursorTexture, DefaultCursorHotSpot, Utilites.GetCursorMode()); cursorChanged = false; } } bool CheckInActiveRegion(Vector2 position, Camera currentCamera) { Vector2 point; bool in_active_region = false; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform, position, currentCamera, out point)) { return false; } var rect = RectTransform.rect; if (!rect.Contains(point)) { return false; } point += new Vector2(rect.width * RectTransform.pivot.x, 0); int i = 0; foreach (var child in children) { var is_first = i==0; if (!is_first) { in_active_region = CheckLeft(child, point); if (in_active_region) { break; } } var is_last = i==(children.Count - 1); if (!is_last) { in_active_region = CheckRight(child, point); if (in_active_region) { break; } } i++; } return in_active_region; } float widthLimit; /// /// Raises the begin drag event. /// /// Event data. public virtual void OnBeginDrag(PointerEventData eventData) { if (!AllowResize) { return ; } if (ProcessCellReorder) { return ; } Vector2 point; processDrag = false; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform, eventData.pressPosition, eventData.pressEventCamera, out point)) { return ; } var r = RectTransform.rect; point += new Vector2(r.width * RectTransform.pivot.x, 0); int i = 0; foreach (var child in children) { var is_first = i==0; if (!is_first) { processDrag = CheckLeft(child, point); if (processDrag) { leftTarget = childrenLayouts[i - 1]; rightTarget = childrenLayouts[i]; widthLimit = children[i - 1].rect.width + children[i].rect.width; break; } } var is_last = i==(children.Count - 1); if (!is_last) { processDrag = CheckRight(child, point); if (processDrag) { leftTarget = childrenLayouts[i]; rightTarget = childrenLayouts[i + 1]; widthLimit = children[i].rect.width + children[i + 1].rect.width; break; } } i++; } } /// /// Checks if point in the left region. /// /// true, if point in the left region, false otherwise. /// RectTransform. /// Point. bool CheckLeft(RectTransform childRectTransform, Vector2 point) { var r = childRectTransform.rect; r.position += new Vector2(childRectTransform.anchoredPosition.x, 0); r.width = ActiveRegion; return r.Contains(point); } /// /// Checks if point in the right region. /// /// true, if right was checked, false otherwise. /// Child rect transform. /// Point. bool CheckRight(RectTransform childRectTransform, Vector2 point) { var r = childRectTransform.rect; r.position += new Vector2(childRectTransform.anchoredPosition.x, 0); r.position = new Vector2(r.position.x + r.width - ActiveRegion - 1, r.position.y); r.width = ActiveRegion + 1; return r.Contains(point); } /// /// Raises the end drag event. /// /// Event data. public void OnEndDrag(PointerEventData eventData) { if (!processDrag) { return ; } Cursor.SetCursor(DefaultCursorTexture, DefaultCursorHotSpot, Utilites.GetCursorMode()); CalculateWidths(); ResetChildren(); if (!OnDragUpdate) { Resize(); } processDrag = false; } /// /// Raises the drag event. /// /// Event data. public void OnDrag(PointerEventData eventData) { if (!processDrag) { return ; } if (canvas==null) { throw new MissingComponentException(gameObject.name + " not in Canvas hierarchy."); } Cursor.SetCursor(CursorTexture, CursorHotSpot, Utilites.GetCursorMode()); Vector2 p1; RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform, eventData.position, CurrentCamera, out p1); Vector2 p2; RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform, eventData.position - eventData.delta, CurrentCamera, out p2); var delta = p1 - p2; if (delta.x > 0) { leftTarget.preferredWidth = Mathf.Min(leftTarget.preferredWidth + delta.x, widthLimit - rightTarget.minWidth); rightTarget.preferredWidth = widthLimit - leftTarget.preferredWidth; } else { rightTarget.preferredWidth = Mathf.Min(rightTarget.preferredWidth - delta.x, widthLimit - leftTarget.minWidth); leftTarget.preferredWidth = widthLimit - rightTarget.preferredWidth; } if (layout!=null) { Utilites.UpdateLayout(layout); } if (OnDragUpdate) { CalculateWidths(); Resize(); } } float GetRectWidth(RectTransform rect) { return rect.rect.width; } /// /// Calculates the widths. /// void CalculateWidths() { widths = children.Select(GetRectWidth).ToList(); } /// /// Resets the children widths. /// void ResetChildren() { childrenLayouts.ForEach(ResetChildrenWidth); } void ResetChildrenWidth(LayoutElement element, int index) { element.preferredWidth = widths[index]; } /// /// Resize items in ListView. /// public void Resize() { if (List==null) { return ; } if (widths.Count < 2) { return ; } List.Start(); List.ForEachComponent(ResizeComponent); } void Reorder() { if (List==null) { return ; } if (widths.Count < 2) { return ; } List.Start(); List.ForEachComponent(ReorderComponent); } /// /// Resizes the game object. /// /// Go. /// The index. void ResizeGameObject(GameObject go, int i) { var layoutElement = go.GetComponent(); var position = positions.IndexOf(i); if (layoutElement) { layoutElement.preferredWidth = widths[position]; } else { (go.transform as RectTransform).SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, widths[position]); } } /// /// Resizes the component. /// /// Component. void ResizeComponent(ListViewItem component) { var resizable_item = component as IResizableItem; if (resizable_item!=null) { resizable_item.ObjectsToResize.ForEach(ResizeGameObject); } } void ReorderComponent(ListViewItem component) { var resizable_item = component as IResizableItem; if (resizable_item!=null) { var objects = resizable_item.ObjectsToResize; int i = 0; foreach (var pos in positions) { objects[pos].transform.SetSiblingIndex(i); i++; } } } /// /// Move column from oldColumnPosition to newColumnPosition. /// /// Old column position. /// New column position. public void Reorder(int oldColumnPosition, int newColumnPosition) { var old_pos = positions.IndexOf(oldColumnPosition); var new_pos = positions.IndexOf(newColumnPosition); children[old_pos].SetSiblingIndex(new_pos); ChangePosition(childrenLayouts, old_pos, new_pos); ChangePosition(children, old_pos, new_pos); ChangePosition(positions, old_pos, new_pos); Reorder(); } #region IDropSupport /// /// Determines whether this instance can receive drop with the specified data and eventData. /// /// true if this instance can receive drop with the specified data and eventData; otherwise, false. /// Cell. /// Event data. public bool CanReceiveDrop(ResizableHeaderDragCell cell, PointerEventData eventData) { if (!AllowReorder) { return false; } var target = FindTarget(eventData); return target!=null && target!=cell; } /// /// Process dropped data. /// /// Cell. /// Event data. public void Drop(ResizableHeaderDragCell cell, PointerEventData eventData) { var target = FindTarget(eventData); Reorder(cell.Position, target.Position); } /// /// Process canceled drop. /// /// Cell. /// Event data. public void DropCanceled(ResizableHeaderDragCell cell, PointerEventData eventData) { } protected static void ChangePosition(List list, int oldPosition, int newPosition) { var item = list[oldPosition]; list.RemoveAt(oldPosition); list.Insert(newPosition, item); } List raycastResults = new List(); protected ResizableHeaderDragCell FindTarget(PointerEventData eventData) { raycastResults.Clear(); EventSystem.current.RaycastAll(eventData, raycastResults); foreach (var raycastResult in raycastResults) { if (!raycastResult.isValid) { continue ; } #if UNITY_4_6 || UNITY_4_7 var target = raycastResult.gameObject.GetComponent(typeof(ResizableHeaderDragCell)) as ResizableHeaderDragCell; #else var target = raycastResult.gameObject.GetComponent(); #endif if ((target!=null) && target.transform.IsChildOf(transform)) { return target; } } return null; } #endregion protected virtual void OnDestroy() { children.Clear(); childrenLayouts.Clear(); foreach (Transform child in transform) { var events = child.GetComponent(); if (events==null) { continue ; } events.OnInitializePotentialDragEvent.RemoveListener(OnInitializePotentialDrag); events.OnBeginDragEvent.RemoveListener(OnBeginDrag); events.OnDragEvent.RemoveListener(OnDrag); events.OnEndDragEvent.RemoveListener(OnEndDrag); } } } }