using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using UnityEngine.Events; using System.Collections.Generic; using System; namespace UIWidgets { /// /// Resizable event. /// [Serializable] public class ResizableEvent : UnityEvent { } /// /// Resizable. /// N - north (top). /// S - south (bottom). /// E - east (right). /// W - west (left). /// [AddComponentMenu("UI/UIWidgets/Resizable")] public class Resizable : MonoBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IPointerEnterHandler, IPointerExitHandler { /// /// Resize directions. /// [Serializable] public struct Directions { /// /// Allow resize from top. /// public bool Top; /// /// Allow resize from bottom. /// public bool Bottom; /// /// Allow resize from left. /// public bool Left; /// /// Allow resize from right. /// public bool Right; /// /// Initializes a new instance of the struct. /// /// If set to true allow resize from top. /// If set to true allow resize from bottom. /// If set to true allow resize from left. /// If set to true allow resize from right. public Directions(bool top, bool bottom, bool left, bool right) { Top = top; Bottom = bottom; Left = left; Right = right; } } /// /// Active resize region. /// public struct Regions { /// /// The top. /// public bool Top; /// /// The bottom. /// public bool Bottom; /// /// The left. /// public bool Left; /// /// The right. /// public bool Right; /// /// NWSE /// /// true if cursor mode is NWSE; otherwise, false. public bool NWSE { get { return (Top && Left) || (Bottom && Right); } } /// /// NESW. /// /// true if cursor mode is NESW; otherwise, false. public bool NESW { get { return (Top && Right) || (Bottom && Left); } } /// /// NS /// /// true if cursor mode is NS; otherwise, false. public bool NS { get { return (Top && !Right) || (Bottom && !Left); } } /// /// EW. /// /// true if cursor mode is EW; otherwise, false. public bool EW { get { return (!Top && Right) || (!Bottom && Left); } } /// /// Is any region active. /// /// true if any region active; otherwise, false. public bool Active { get { return Top || Bottom || Left || Right; } } /// /// Reset this instance. /// public void Reset() { Top = false; Bottom = false; Left = false; Right = false; } /// /// Returns a string that represents the current object. /// /// A string that represents the current object. public override string ToString() { return String.Format("Top: {0}; Bottom: {1}; Left: {2}; Right: {3}", Top, Bottom, Left, Right); } } /// /// Is need to update RectTransform on Resize. /// [SerializeField] public bool UpdateRectTransform = true; /// /// Is need to update LayoutElement on Resize. /// [SerializeField] public bool UpdateLayoutElement = true; /// /// The active region in points from left or right border where resize allowed. /// [SerializeField] [Tooltip("Maximum padding from border where resize active.")] public float ActiveRegion = 5; /// /// The minimum size. /// [SerializeField] public Vector2 MinSize; /// /// The maximum size. /// [SerializeField] [Tooltip("Set 0 to unlimit.")] public Vector2 MaxSize; /// /// The keep aspect ratio. /// Aspect ratio applied after MinSize and MaxSize, so if RectTransform aspect ratio not equal MinSize and MaxSize aspect ratio then real size may be outside limit with one of axis. /// [SerializeField] public bool KeepAspectRatio; /// /// Resize directions. /// [SerializeField] public Directions ResizeDirections = new Directions(true, true, true, true); /// /// The current camera. For Screen Space - Overlay let it empty. /// [SerializeField] public Camera CurrentCamera; /// /// The cursor EW texture. /// [SerializeField] public Texture2D CursorEWTexture; /// /// The cursor EW hot spot. /// [SerializeField] public Vector2 CursorEWHotSpot = new Vector2(16, 16); /// /// The cursor NS texture. /// [SerializeField] public Texture2D CursorNSTexture; /// /// The cursor NS hot spot. /// [SerializeField] public Vector2 CursorNSHotSpot = new Vector2(16, 16); /// /// The cursor NESW texture. /// [SerializeField] public Texture2D CursorNESWTexture; /// /// The cursor NESW hot spot. /// [SerializeField] public Vector2 CursorNESWHotSpot = new Vector2(16, 16); /// /// The cursor NWSE texture. /// [SerializeField] public Texture2D CursorNWSETexture; /// /// The cursor NWSE hot spot. /// [SerializeField] public Vector2 CursorNWSEHotSpot = new Vector2(16, 16); /// /// The default cursor texture. /// [SerializeField] public Texture2D DefaultCursorTexture; /// /// The default cursor hot spot. /// [SerializeField] public Vector2 DefaultCursorHotSpot; /// /// OnStartResize event. /// public ResizableEvent OnStartResize = new ResizableEvent(); /// /// OnEndResize event. /// public ResizableEvent OnEndResize = new ResizableEvent(); RectTransform rectTransform; /// /// Gets the RectTransform. /// /// RectTransform. public RectTransform RectTransform { get { if (rectTransform==null) { rectTransform = transform as RectTransform; } return rectTransform; } } LayoutElement layoutElement; /// /// Gets the LayoutElement. /// /// LayoutElement. public LayoutElement LayoutElement { get { if (layoutElement==null) { layoutElement = GetComponent(); if (layoutElement==null) { layoutElement = gameObject.AddComponent(); } } return layoutElement; } } Regions regions; Regions dragRegions; Canvas canvas; RectTransform canvasRect; bool processDrag; void Start() { var layout = GetComponent(); if (layout) { 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(); } protected static bool globalCursorSetted; protected bool cursorChanged; 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; globalCursorSetted = false; cursorChanged = false; Cursor.SetCursor(DefaultCursorTexture, DefaultCursorHotSpot, Utilites.GetCursorMode()); } void LateUpdate() { if (globalCursorSetted && !cursorChanged) { return ; } globalCursorSetted = false; if (!IsCursorOver) { return ; } if (processDrag) { return ; } if (!Input.mousePresent) { return ; } Vector2 point; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform, Input.mousePosition, CurrentCamera, out point)) { return ; } var r = RectTransform.rect; if (!r.Contains(point)) { regions.Reset(); UpdateCursor(); return ; } UpdateRegions(point); UpdateCursor(); } void UpdateRegions(Vector2 point) { regions.Top = ResizeDirections.Top && CheckTop(point); regions.Bottom = ResizeDirections.Bottom && CheckBottom(point); regions.Left = ResizeDirections.Left && CheckLeft(point); regions.Right = ResizeDirections.Right && CheckRight(point); } void UpdateCursor() { if (regions.NWSE) { globalCursorSetted = true; cursorChanged = true; Cursor.SetCursor(CursorNWSETexture, CursorNWSEHotSpot, Utilites.GetCursorMode()); } else if (regions.NESW) { globalCursorSetted = true; cursorChanged = true; Cursor.SetCursor(CursorNESWTexture, CursorNESWHotSpot, Utilites.GetCursorMode()); } else if (regions.NS) { globalCursorSetted = true; cursorChanged = true; Cursor.SetCursor(CursorNSTexture, CursorNSHotSpot, Utilites.GetCursorMode()); } else if (regions.EW) { globalCursorSetted = true; cursorChanged = true; Cursor.SetCursor(CursorEWTexture, CursorEWHotSpot, Utilites.GetCursorMode()); } else if (cursorChanged) { globalCursorSetted = false; cursorChanged = false; Cursor.SetCursor(DefaultCursorTexture, DefaultCursorHotSpot, Utilites.GetCursorMode()); } } /// /// Checks if point in the top region. /// /// true, if point in the top region, false otherwise. /// Point. bool CheckTop(Vector2 point) { var rect = RectTransform.rect; rect.position = new Vector2(rect.position.x, rect.position.y + rect.height - ActiveRegion); rect.height = ActiveRegion; return rect.Contains(point); } /// /// Checks if point in the right region. /// /// true, if right was checked, false otherwise. /// Point. bool CheckBottom(Vector2 point) { var rect = RectTransform.rect; rect.height = ActiveRegion; return rect.Contains(point); } /// /// Checks if point in the left region. /// /// true, if point in the left region, false otherwise. /// Point. bool CheckLeft(Vector2 point) { var rect = RectTransform.rect; rect.width = ActiveRegion; return rect.Contains(point); } /// /// Checks if point in the right region. /// /// true, if right was checked, false otherwise. /// Point. bool CheckRight(Vector2 point) { var rect = RectTransform.rect; rect.position = new Vector2(rect.position.x + rect.width - ActiveRegion, rect.position.y); rect.width = ActiveRegion; return rect.Contains(point); } /// /// Raises the begin drag event. /// /// Event data. public void OnBeginDrag(PointerEventData eventData) { Vector2 point; processDrag = false; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform, eventData.pressPosition, eventData.pressEventCamera, out point)) { return ; } UpdateRegions(point); processDrag = regions.Active; dragRegions = regions; UpdateCursor(); LayoutElement.preferredHeight = RectTransform.rect.height; LayoutElement.preferredWidth = RectTransform.rect.width; OnStartResize.Invoke(this); } void ResetCursor() { globalCursorSetted = false; cursorChanged = false; Cursor.SetCursor(DefaultCursorTexture, DefaultCursorHotSpot, Utilites.GetCursorMode()); } /// /// Raises the end drag event. /// /// Event data. public void OnEndDrag(PointerEventData eventData) { ResetCursor(); processDrag = false; OnEndResize.Invoke(this); } /// /// 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."); } 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 (UpdateRectTransform) { PerformUpdateRectTransform(delta); } if (UpdateLayoutElement) { PerformUpdateLayoutElement(delta); } } void PerformUpdateRectTransform(Vector2 delta) { var pivot = RectTransform.pivot; var size = RectTransform.rect.size; var prev_size = size; var sign = new Vector2(1, 1); if (dragRegions.Left || dragRegions.Right) { sign.x = dragRegions.Right ? +1 : -1; size.x = Mathf.Max(MinSize.x, size.x + (sign.x * delta.x)); if (MaxSize.x!=0f) { size.x = Mathf.Min(MaxSize.x, size.x); } } if (dragRegions.Top || dragRegions.Bottom) { sign.y = dragRegions.Top ? +1 : -1; size.y = Mathf.Max(MinSize.y, size.y + (sign.y * delta.y)); if (MaxSize.y!=0f) { size.y = Mathf.Min(MaxSize.y, size.y); } } if (KeepAspectRatio) { size = FixAspectRatio(size, prev_size); } var anchorSign = new Vector2(dragRegions.Right ? pivot.x : pivot.x - 1, dragRegions.Top ? pivot.y : pivot.y - 1); var anchorDelta = size - prev_size; anchorDelta = new Vector2(anchorDelta.x * anchorSign.x, anchorDelta.y * anchorSign.y); RectTransform.anchoredPosition += anchorDelta; RectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, size.x); RectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, size.y); } /// /// Fixs the aspect ratio. /// /// The aspect ratio. /// New size. /// Base size. protected Vector2 FixAspectRatio(Vector2 newSize, Vector2 baseSize) { var result = newSize; var aspectRatio = baseSize.x / baseSize.y; var sizeDelta = new Vector2(Mathf.Abs(newSize.x - baseSize.x), Mathf.Abs(newSize.y - baseSize.y)); if (sizeDelta.x >= sizeDelta.y) { result.y = result.x / aspectRatio; } else { result.x = result.y * aspectRatio; } return result; } void PerformUpdateLayoutElement(Vector2 delta) { var size = new Vector2(LayoutElement.preferredWidth, LayoutElement.preferredHeight); var prev_size = size; if (dragRegions.Left || dragRegions.Right) { var sign = (dragRegions.Right) ? +1 : -1; var width = Mathf.Max(MinSize.x, LayoutElement.preferredWidth + (sign * delta.x)); if (MaxSize.x!=0f) { size.x = Mathf.Min(MaxSize.x, width); } } if (dragRegions.Top || dragRegions.Bottom) { var sign = (dragRegions.Top) ? +1 : -1; var height = Mathf.Max(MinSize.y, LayoutElement.preferredHeight + (sign * delta.y)); if (MaxSize.y!=0f) { size.y = Mathf.Min(MaxSize.y, height); } } if (KeepAspectRatio) { size = FixAspectRatio(size, prev_size); } LayoutElement.preferredWidth = size.x; LayoutElement.preferredHeight = size.y; } } }