// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using UnityEngine; namespace Microsoft.MixedReality.Toolkit.Experimental.UI { /// /// This component represents and ordered collection of UI elements. You /// can add to the UICollection by either dropping UI elements in the UI /// element this component is attached to in the Editor or by calling the /// AddItem(RectTransform) method. UI elements that are added to this /// collection via the Editor will be automatically arranged when this /// component executes. /// To use this component attach it to a UI element (a GameObject with a /// RectTransform component) such as an Image or Panel. /// [AddComponentMenu("Scripts/MRTK/Experimental/UICollection")] [RequireComponent(typeof(RectTransform))] [ExecuteInEditMode] public class UICollection : MonoBehaviour { /// /// The maximum width that the collection should expand to. If the value is -1.0 then it will use /// the width specified by the RectTransform of this component's GameObject. /// [Experimental] [SerializeField] private float maxWidth = -1.0f; public float MaxWidth { get => maxWidth; set => maxWidth = value; } /// /// The maximum height that the collection should expand to. If the value is -1.0 then it will use /// the height specified by the RectTransform of this component's GameObject. /// [SerializeField] private float maxHeight = -1.0f; public float MaxHeight { get => maxHeight; set => maxHeight = value; } /// /// The amount of horizontal spacing (in pixels) to use between items in this collection. /// [SerializeField] private float horizontalSpacing = 0.0f; public float HorizontalSpacing { get => horizontalSpacing; set => horizontalSpacing = value; } /// /// The amount of vertical spacing (in pixels) to use between items in this collection. /// [SerializeField] private float verticalSpacing = 0.0f; public float VerticalSpacing { get => verticalSpacing; set => verticalSpacing = value; } /// /// A list of items in this collection. This list should not be modified directly. Instead /// use AddItem(RectTransform) and RemoveItem(RectTransform). /// public List Items { get; private set; } /// /// Cached rect transform to use for collection /// private RectTransform rectTransform; private void Awake() { Items = new List(); } // Use this for initialization private void Start() { // Verify this is attached to a GameObject with a rect transform rectTransform = GetComponent(); if (!Application.isEditor) { return; } // Collect children items already added (likely added in the Editor) CollectItems(); UpdateLayout(); } private void Update() { if (!Application.isEditor) { return; } CollectItems(); UpdateLayout(); } /// /// Adds a UI element to the collection. This will cause the collection /// layout to update immediately. /// NOTE: The added item's RectTransform will get modified in order to layout properly in this collection. /// /// The UI element to add to the collection. public void AddItem(RectTransform item) { Items.Add(item); item.SetParent(transform); item.transform.localScale = Vector3.one; item.position = Vector3.zero; item.anchoredPosition3D = Vector3.zero; UpdateLayout(); } /// /// Removes a UI element from the collection. This will cause the collection /// layout to update immediately. /// NOTE: This method does not call Destroy removed items. /// /// The UI element to remove from the collection. public void RemoveItem(RectTransform item) { Items.Remove(item); UpdateLayout(); } /// /// Removes all UI elements added to the collection. This will cause the collection /// layout to update immediately. /// NOTE: This method does not call Destroy removed items. /// public void RemoveAllItems() { Items.Clear(); UpdateLayout(); } private void CollectItems() { Items.Clear(); foreach (Transform childTransform in transform) { RectTransform childRect = childTransform.GetComponent(); if (childRect != null) { AddItem(childRect); } } } protected virtual void UpdateLayout() { Rect rect = rectTransform.rect; Vector2 updatedSize = Vector2.zero; if (maxWidth < 0.0f) { // Set to the width of the panel updatedSize.x = rect.width; } else { // Set to the max width updatedSize.x = maxWidth; } if (maxHeight < 0.0f) { // Set to the height of the panel updatedSize.y = rect.height; } else { // Set to the max height updatedSize.y = maxHeight; } Vector2 currentOffset = Vector2.zero; Vector2 anchorVec = Vector2.up; float columnHeight = 0.0f; float maxPanelWidth = 0.0f; for (int i = 0; i < Items.Count; i++) { // Ensure the anchors and pivot are set properly for positioning in the UICollection Items[i].anchorMin = anchorVec; Items[i].anchorMax = anchorVec; Items[i].pivot = anchorVec; columnHeight = Mathf.Max(Items[i].rect.height, columnHeight); if (Items[i].rect.width + currentOffset.x > updatedSize.x) { // Move to next column currentOffset.y += columnHeight + verticalSpacing; currentOffset.x = 0.0f; columnHeight = Items[i].rect.height; // Check to see if it can fit in the next column if (Items[i].rect.height + currentOffset.y > updatedSize.y) { // Bail out... can't fit any more items!!! break; } } // Position item Items[i].anchoredPosition = new Vector2(currentOffset.x, -currentOffset.y); // Update current offset currentOffset.x += Items[i].rect.width + horizontalSpacing; maxPanelWidth = Mathf.Max(currentOffset.x - horizontalSpacing, maxPanelWidth); } // Update the panel size float finalWidth = maxWidth < 0.0f ? rect.width : maxPanelWidth; float finalHeight = maxHeight < 0.0f ? rect.height : columnHeight + currentOffset.y; rectTransform.sizeDelta = new Vector2(finalWidth, finalHeight); } } }