namespace UIWidgets { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.UI; /// /// Groupable component. /// [RequireComponent(typeof(Resizable))] [RequireComponent(typeof(Rotatable))] [RequireComponent(typeof(Graphic))] [AddComponentMenu("UI/New UI Widgets/Interactions/Groupable")] [DisallowMultipleComponent] public class Groupable : UIBehaviourConditional { /// /// Selection mode. /// public enum Mode { /// /// Selects gameobjects fully inside the selection area. /// Contains = 0, /// /// Selects gameobjects inside the selection area or partially overlaps the selection area. /// Overlaps = 1, } #region Interactable [SerializeField] bool interactable = true; /// /// Is widget interactable. /// /// true if interactable; otherwise, false. public bool Interactable { get { return interactable; } set { if (interactable != value) { interactable = value; InteractableChanged(); } } } /// /// If the canvas groups allow interaction. /// protected bool GroupsAllowInteraction = true; /// /// The CanvasGroup cache. /// protected List CanvasGroupCache = new List(); /// /// Process the CanvasGroupChanged event. /// protected override void OnCanvasGroupChanged() { var groupAllowInteraction = true; var t = transform; while (t != null) { t.GetComponents(CanvasGroupCache); var shouldBreak = false; foreach (var canvas_group in CanvasGroupCache) { if (!canvas_group.interactable) { groupAllowInteraction = false; shouldBreak = true; } shouldBreak |= canvas_group.ignoreParentGroups; } if (shouldBreak) { break; } t = t.parent; } if (groupAllowInteraction != GroupsAllowInteraction) { GroupsAllowInteraction = groupAllowInteraction; InteractableChanged(); } } /// /// Returns true if the GameObject and the Component are active. /// /// true if the GameObject and the Component are active; otherwise false. public override bool IsActive() { return base.IsActive() && GroupsAllowInteraction && Interactable; } /// /// Is instance interactable? /// /// true if instance interactable; otherwise false public bool IsInteractable() { return GroupsAllowInteraction && Interactable; } /// /// Process interactable change. /// protected virtual void InteractableChanged() { if (!base.IsActive()) { return; } OnInteractableChange(GroupsAllowInteraction && Interactable); } /// /// Process interactable change. /// /// Current interactable state. protected virtual void OnInteractableChange(bool interactableState) { } #endregion /// /// Selected elements. /// [NonSerialized] protected List selected = new List(); /// /// Resizable components of the selected elements. /// [NonSerialized] protected List SelectedResizable = new List(); /// /// Selected elements. /// public ReadOnlyCollection Selected { get { return selected.AsReadOnly(); } } /// /// RectTransform. /// [NonSerialized] protected RectTransform RectTransform; /// /// Parent RectTransform. /// [NonSerialized] protected RectTransform ParentRectTransform; /// /// Parent DragListener. /// [NonSerialized] protected DragListener ParentDragListener; /// /// Parent ClickListener. /// [NonSerialized] protected ClickListener ParentClickListener; /// /// Resizable. /// [NonSerialized] protected Resizable Resizable; /// /// ResizableHandles. /// [NonSerialized] protected ResizableHandles ResizableHandles; /// /// Rotatable. /// [NonSerialized] protected Rotatable Rotatable; /// /// RotatableHandle. /// [NonSerialized] protected RotatableHandle RotatableHandle; /// /// Graphic. /// [NonSerialized] protected Graphic Graphic; /// /// Highlight template. /// [SerializeField] protected RectTransform highlightTemplate; /// /// Highlight template. /// public RectTransform HighlightTemplate { get { return highlightTemplate; } set { if (highlightTemplate != value) { highlightTemplate = value; HighlightsPool.Template = highlightTemplate; } } } /// /// Used instances of the DefaultItem. /// [SerializeField] [HideInInspector] protected List Highlights = new List(); /// /// Unused instances of the DefaultItem. /// [SerializeField] [HideInInspector] protected List HighlightsCache = new List(); ListComponentPool highlightsPool; /// /// Components pool. /// protected ListComponentPool HighlightsPool { get { if (highlightsPool == null) { highlightsPool = new ListComponentPool(HighlightTemplate, Highlights, HighlightsCache, transform.parent as RectTransform); } return highlightsPool; } } /// /// Selection mode. /// [SerializeField] public Mode SelectionMode = Mode.Contains; /// /// Rotation mode. /// [SerializeField] public bool GroupRotation = true; /// /// Start selection event. /// [SerializeField] public UnityEvent OnStartSelection = new UnityEvent(); /// /// Selection event. /// [SerializeField] public UnityEvent OnSelection = new UnityEvent(); /// /// End selection event. /// [SerializeField] public UnityEvent OnEndSelection = new UnityEvent(); /// /// Starts this instance. /// protected override void Start() { base.Start(); Init(); } bool isInited; /// /// Init this instance. /// public virtual void Init() { if (isInited) { return; } isInited = true; RectTransform = transform as RectTransform; ParentRectTransform = transform.parent as RectTransform; ParentDragListener = Utilities.GetOrAddComponent(ParentRectTransform); ParentDragListener.OnDragStartEvent.AddListener(StartDrag); ParentDragListener.OnDragEvent.AddListener(Drag); ParentDragListener.OnDragEndEvent.AddListener(EndDrag); ParentClickListener = Utilities.GetOrAddComponent(ParentRectTransform); ParentClickListener.ClickEvent.AddListener(OnClick); Resizable = GetComponent(); Resizable.OnStartResize.AddListener(OnStartResize); Resizable.OnResizeDelta.AddListener(OnResize); ResizableHandles = GetComponent(); if (ResizableHandles != null) { ResizableHandles.OnStartResize.AddListener(OnStartResize); ResizableHandles.OnResize.AddListener(OnResize); } Rotatable = GetComponent(); Rotatable.OnStartRotate.AddListener(OnStartRotate); Rotatable.OnRotate.AddListener(OnRotate); Rotatable.OnEndRotate.AddListener(OnEndRotate); RotatableHandle = GetComponent(); if (RotatableHandle != null) { RotatableHandle.OnStartRotate.AddListener(OnStartRotate); RotatableHandle.OnRotate.AddListener(OnRotate); RotatableHandle.OnEndRotate.AddListener(OnEndRotate); } Graphic = GetComponent(); #if UNITY_5_3 || UNITY_5_3_OR_NEWER Graphic.raycastTarget = false; #endif if (HighlightTemplate != null) { HighlightTemplate.gameObject.SetActive(false); } DisableSelection(); } /// /// Process destroy event. /// protected override void OnDestroy() { if (ParentDragListener != null) { ParentDragListener.OnDragStartEvent.RemoveListener(StartDrag); ParentDragListener.OnDragEvent.RemoveListener(Drag); ParentDragListener.OnDragEndEvent.RemoveListener(EndDrag); } if (ParentClickListener != null) { ParentClickListener.ClickEvent.AddListener(OnClick); } if (Resizable != null) { Resizable.OnStartResize.RemoveListener(OnStartResize); Resizable.OnResizeDelta.RemoveListener(OnResize); } if (ResizableHandles != null) { ResizableHandles.OnStartResize.RemoveListener(OnStartResize); ResizableHandles.OnResize.RemoveListener(OnResize); } if (Rotatable != null) { Rotatable.OnStartRotate.AddListener(OnStartRotate); Rotatable.OnRotate.AddListener(OnRotate); Rotatable.OnEndRotate.AddListener(OnEndRotate); } if (RotatableHandle != null) { RotatableHandle.OnStartRotate.AddListener(OnStartRotate); RotatableHandle.OnRotate.AddListener(OnRotate); RotatableHandle.OnEndRotate.AddListener(OnEndRotate); } base.OnDestroy(); } /// /// Process click event. /// /// Event data. protected virtual void OnClick(PointerEventData eventData) { if (IsDrag) { return; } DisableSelection(); } /// /// Process start rotation event. /// /// Rotatable. protected virtual void OnStartRotate(Rotatable rotatable) { if (GroupRotation) { var highlight = HighlightTemplate != null; for (int i = 0; i < selected.Count; i++) { selected[i].SetParent(RectTransform, true); if (highlight) { HighlightsPool[i].SetParent(RectTransform, true); } } } } /// /// Process rotation event. /// /// Rotatable. protected virtual void OnRotate(Rotatable rotatable) { if (!GroupRotation) { var rotation = RectTransform.localRotation; for (int i = 0; i < selected.Count; i++) { selected[i].localRotation = rotation; } UpdateHighlights(); } } /// /// Process end rotation event. /// /// Rotatable. protected virtual void OnEndRotate(Rotatable rotatable) { if (GroupRotation) { var highlight = HighlightTemplate != null; for (int i = 0; i < selected.Count; i++) { selected[i].SetParent(ParentRectTransform, true); if (highlight) { HighlightsPool[i].SetParent(ParentRectTransform, true); } } } } /// /// Process start resize event. /// /// Resizable. protected virtual void OnStartResize(Resizable resizable) { for (int i = 0; i < SelectedResizable.Count; i++) { SelectedResizable[i].InitResize(); SelectedResizable[i].KeepAspectRatio = resizable.KeepAspectRatio; } } /// /// Process resize event. /// /// Resizable. /// Regions. /// Delta. protected virtual void OnResize(Resizable resizable, Resizable.Regions regions, Vector2 delta) { for (int i = 0; i < SelectedResizable.Count; i++) { SelectedResizable[i].Resize(regions, delta); } UpdateHighlights(); } /// /// Is currently dragged? /// protected bool IsDrag; /// /// Original value of Resizable.AspectRatio. /// protected bool KeepAspectRatio; /// /// Process begin drag event. /// /// Event data. protected virtual void StartDrag(PointerEventData eventData) { if (!IsInteractable()) { return; } Vector2 point; RectTransformUtility.ScreenPointToLocalPointInRectangle(ParentRectTransform, eventData.position, eventData.pressEventCamera, out point); var size = ParentRectTransform.rect.size; var pivot = ParentRectTransform.pivot; var position = new Vector2(point.x + (size.x * pivot.x), (size.y * pivot.y) + point.y); IsDrag = true; RectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, position.x, 0f); RectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Bottom, position.y, 0f); gameObject.SetActive(true); RectTransform.localRotation = Quaternion.Euler(Vector3.zero); Graphic.enabled = true; KeepAspectRatio = Resizable.KeepAspectRatio; Resizable.KeepAspectRatio = false; Resizable.Init(); Resizable.InitResize(); RectTransform.SetAsLastSibling(); DisableHandles(); FindSelected(); OnStartSelection.Invoke(); } /// /// Process drag event. /// /// Event data. protected virtual void Drag(PointerEventData eventData) { if (!IsDrag) { return; } Vector2 current_point; Vector2 original_point; RectTransformUtility.ScreenPointToLocalPointInRectangle(ParentRectTransform, eventData.position, eventData.pressEventCamera, out current_point); RectTransformUtility.ScreenPointToLocalPointInRectangle(ParentRectTransform, eventData.pressPosition, eventData.pressEventCamera, out original_point); var delta = current_point - original_point; var regions = new Resizable.Regions() { Top = delta.y >= 0f, Bottom = delta.y < 0f, Left = delta.x < 0f, Right = delta.x >= 0f, }; Resizable.Resize(regions, delta); FindSelected(); OnSelection.Invoke(); } /// /// Process end drag event. /// /// Event data. protected virtual void EndDrag(PointerEventData eventData) { if (!IsDrag) { return; } IsDrag = false; FindSelected(); UpdateSelectedResizable(); SelectionFinished(); Resizable.KeepAspectRatio = KeepAspectRatio; OnEndSelection.Invoke(); } /// /// Toggle state to selection finished. /// protected virtual void SelectionFinished() { if (selected.Count == 0) { DisableSelection(); } else { EnableHandles(); Graphic.enabled = false; } } /// /// Enable handles. /// public void EnableHandles() { if (ResizableHandles != null) { ResizableHandles.EnableHandles(); } if (RotatableHandle != null) { RotatableHandle.EnableHandle(); } } /// /// Disable handles. /// public void DisableHandles() { if (ResizableHandles != null) { ResizableHandles.DisableHandles(); } if (RotatableHandle != null) { RotatableHandle.DisableHandle(); } } /// /// Disable selection. /// public virtual void DisableSelection() { selected.Clear(); gameObject.SetActive(false); DisableHandles(); HighlightsPool.Require(0); } /// /// Set selected elements. /// /// Selected elements. public virtual void SetSelected(IList newSelected) { Init(); selected.Clear(); selected.AddRange(newSelected); var selection = default(Rect); for (int i = 0; i < selected.Count; i++) { var rect = GetRect(selected[i]); selection.xMin = Mathf.Min(selection.xMin, rect.xMin); selection.xMax = Mathf.Max(selection.xMax, rect.xMax); selection.yMin = Mathf.Min(selection.yMin, rect.yMin); selection.yMax = Mathf.Max(selection.yMax, rect.yMax); } selection.xMin = Mathf.FloorToInt(selection.xMin); selection.xMax = Mathf.RoundToInt(selection.xMax); selection.yMin = Mathf.FloorToInt(selection.yMin); selection.yMax = Mathf.RoundToInt(selection.yMax); var parent = RectTransform.parent as RectTransform; var parent_size = parent.rect.size; var parent_pivot = parent.pivot; selection.x += parent_size.x * parent_pivot.x; selection.y += parent_size.y * parent_pivot.y; RectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, selection.x, selection.width); RectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, selection.y, selection.height); gameObject.SetActive(true); SelectionFinished(); UpdateHighlights(); } /// /// Get Rect with values relative to the parent. /// /// RectTransform /// Rect. protected virtual Rect GetRect(RectTransform rectTransform) { var rect = rectTransform.rect; var pivot = rectTransform.pivot; var pos = rectTransform.localPosition; rect.x = pos.x - (pivot.x * rect.width); rect.y = pos.y - (pivot.y * rect.height); return rect; } /// /// Find selected elements. /// protected virtual void FindSelected() { selected.Clear(); SelectedResizable.Clear(); var main = GetRect(RectTransform); for (int i = 0; i < ParentRectTransform.childCount; i++) { var rt = ParentRectTransform.GetChild(i) as RectTransform; if (!CanBeSelected(rt)) { continue; } var sub = GetRect(rt); if (IsSelected(main, sub)) { selected.Add(rt); } } UpdateHighlights(); } /// /// Check is element can be selected. /// /// Element. /// true if element can selected; otherwise false. protected virtual bool CanBeSelected(RectTransform element) { if (!element.gameObject.activeSelf) { return false; } if (element.GetInstanceID() == RectTransform.GetInstanceID()) { return false; } if (HighlightTemplate != null) { if (element.GetInstanceID() == HighlightTemplate.GetInstanceID()) { return false; } } if (Highlights.Contains(element)) { return false; } return true; } /// /// Update list of the resizable components. /// protected virtual void UpdateSelectedResizable() { SelectedResizable.Clear(); for (int i = 0; i < selected.Count; i++) { var resizable = selected[i].GetComponent(); if (resizable == null) { resizable = selected[i].gameObject.AddComponent(); resizable.Interactable = false; } SelectedResizable.Add(resizable); } } /// /// Check is element inside selection. /// /// Selection. /// Element. /// true if element inside selection. protected bool IsSelected(Rect selection, Rect element) { if (SelectionMode == Mode.Contains) { var top_left = new Vector2(element.xMin, element.yMin); var top_right = new Vector2(element.xMax, element.yMin); var bottom_left = new Vector2(element.xMin, element.yMax); var bottom_right = new Vector2(element.xMax, element.yMax); return selection.Contains(top_left) && selection.Contains(top_right) && selection.Contains(bottom_left) && selection.Contains(bottom_right); } if (SelectionMode == Mode.Overlaps) { return selection.Overlaps(element); } throw new NotSupportedException("Specified SelectionMode is not supported."); } /// /// Update highlights. /// public virtual void UpdateHighlights() { if (HighlightTemplate == null) { return; } HighlightsPool.Require(selected.Count); for (int i = 0; i < HighlightsPool.Count; i++) { SetHighlighted(selected[i], HighlightsPool[i]); } } /// /// Set highlight for the specified element. /// /// Element. /// Highlight. protected virtual void SetHighlighted(RectTransform element, RectTransform highlight) { UtilitiesRectTransform.CopyValues(element, highlight); highlight.SetAsLastSibling(); } #region Align /// /// Left align. /// public void AlignLeft() { foreach (var s in selected) { UtilitiesAlign.Left(s); } UpdateHighlights(); } /// /// Center align. /// public void AlignCenter() { foreach (var s in selected) { UtilitiesAlign.Center(s); } UpdateHighlights(); } /// /// Right align. /// public void AlignRight() { foreach (var s in selected) { UtilitiesAlign.Right(s); } UpdateHighlights(); } /// /// Top align. /// public void AlignTop() { foreach (var s in selected) { UtilitiesAlign.Top(s); } UpdateHighlights(); } /// /// Middle align. /// public void AlignMiddle() { foreach (var s in selected) { UtilitiesAlign.Middle(s); } UpdateHighlights(); } /// /// Bottom align. /// public void AlignBottom() { foreach (var s in selected) { UtilitiesAlign.Bottom(s); } UpdateHighlights(); } #endregion } }