/*
* Copyright 2022 Peter Han
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using PeterHan.PLib.UI.Layouts;
using System;
using UnityEngine;
using UnityEngine.UI;
namespace PeterHan.PLib.UI {
///
/// A freezable layout manager that displays one of its contained objects at a time.
/// Unlike other layout groups, even inactive children are considered for sizing.
///
public sealed class CardLayoutGroup : AbstractLayoutGroup {
///
/// Calculates the size of the card layout container.
///
/// The container to lay out.
/// The parameters to use for layout.
/// The direction which is being calculated.
/// The minimum and preferred box layout size.
private static CardLayoutResults Calc(GameObject obj, PanelDirection direction) {
var transform = obj.AddOrGet();
int n = transform.childCount;
var result = new CardLayoutResults(direction, n);
var components = ListPool.Allocate();
for (int i = 0; i < n; i++) {
var child = transform.GetChild(i)?.gameObject;
if (child != null) {
bool active = child.activeInHierarchy;
// Not only on active game objects
components.Clear();
child.GetComponents(components);
child.SetActive(true);
var hc = PUIUtils.CalcSizes(child, direction, components);
if (!hc.ignore) {
result.Expand(hc);
result.children.Add(hc);
}
child.SetActive(active);
}
}
components.Recycle();
return result;
}
///
/// Lays out components in the card layout container.
///
/// The margin to allow around the components.
/// The calculated minimum and preferred sizes.
/// The total available size in this dimension.
private static void DoLayout(RectOffset margin, CardLayoutResults required, float size)
{
if (required == null)
throw new ArgumentNullException(nameof(required));
var direction = required.direction;
var components = ListPool.Allocate();
// Compensate for margins
if (direction == PanelDirection.Horizontal)
size -= margin.left + margin.right;
else
size -= margin.top + margin.bottom;
foreach (var child in required.children) {
var obj = child.source;
if (obj != null) {
float compSize = PUIUtils.GetProperSize(child, size);
// Place and size component
var transform = obj.AddOrGet();
if (direction == PanelDirection.Horizontal)
transform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left,
margin.left, compSize);
else
transform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top,
margin.top, compSize);
// Invoke SetLayout on dependents
components.Clear();
obj.GetComponents(components);
foreach (var component in components)
if (direction == PanelDirection.Horizontal)
component.SetLayoutHorizontal();
else // if (direction == PanelDirection.Vertical)
component.SetLayoutVertical();
}
}
components.Recycle();
}
///
/// The margin around the components as a whole.
///
public RectOffset Margin {
get {
return margin;
}
set {
margin = value;
}
}
///
/// Results from the horizontal calculation pass.
///
private CardLayoutResults horizontal;
///
/// The margin around the components as a whole.
///
[SerializeField]
private RectOffset margin;
///
/// Results from the vertical calculation pass.
///
private CardLayoutResults vertical;
internal CardLayoutGroup() {
horizontal = null;
layoutPriority = 1;
vertical = null;
}
public override void CalculateLayoutInputHorizontal() {
if (!locked) {
var margin = Margin;
float gap = (margin == null) ? 0.0f : margin.left + margin.right;
horizontal = Calc(gameObject, PanelDirection.Horizontal);
var hTotal = horizontal.total;
minWidth = hTotal.min + gap;
preferredWidth = hTotal.preferred + gap;
}
}
public override void CalculateLayoutInputVertical() {
if (!locked) {
var margin = Margin;
float gap = (margin == null) ? 0.0f : margin.top + margin.bottom;
vertical = Calc(gameObject, PanelDirection.Vertical);
var vTotal = vertical.total;
minHeight = vTotal.min + gap;
preferredHeight = vTotal.preferred + gap;
}
}
protected override void OnDisable() {
base.OnDisable();
horizontal = null;
vertical = null;
}
protected override void OnEnable() {
base.OnEnable();
horizontal = null;
vertical = null;
}
///
/// Switches the active card.
///
/// The child to make active, or null to inactivate all children.
public void SetActiveCard(GameObject card) {
int n = transform.childCount;
for (int i = 0; i < n; i++) {
var child = transform.GetChild(i)?.gameObject;
if (child != null)
child.SetActive(child == card);
}
}
///
/// Switches the active card.
///
/// The child index to make active, or -1 to inactivate all children.
public void SetActiveCard(int index) {
int n = transform.childCount;
for (int i = 0; i < n; i++) {
var child = transform.GetChild(i)?.gameObject;
if (child != null)
child.SetActive(i == index);
}
}
public override void SetLayoutHorizontal() {
if (horizontal != null && !locked)
DoLayout(Margin ?? new RectOffset(), horizontal, rectTransform.rect.width);
}
public override void SetLayoutVertical() {
if (vertical != null && !locked)
DoLayout(Margin ?? new RectOffset(), vertical, rectTransform.rect.height);
}
}
}