240 lines
6.7 KiB
C#
240 lines
6.7 KiB
C#
/*
|
|
* 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 System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
namespace PeterHan.PLib.UI.Layouts {
|
|
/// <summary>
|
|
/// The abstract parent of most layout groups.
|
|
/// </summary>
|
|
public abstract class AbstractLayoutGroup : UIBehaviour, ISettableFlexSize, ILayoutElement
|
|
{
|
|
/// <summary>
|
|
/// Sets an object's layout dirty on the next frame.
|
|
/// </summary>
|
|
/// <param name="transform">The transform to set dirty.</param>
|
|
/// <returns>A coroutine to set it dirty.</returns>
|
|
internal static IEnumerator DelayedSetDirty(RectTransform transform) {
|
|
yield return null;
|
|
LayoutRebuilder.MarkLayoutForRebuild(transform);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes and destroys any PLib layouts on the component. They will be replaced with
|
|
/// a static LayoutElement containing the old size of the component.
|
|
/// </summary>
|
|
/// <param name="component">The component to cleanse.</param>
|
|
internal static void DestroyAndReplaceLayout(GameObject component) {
|
|
if (component != null && component.TryGetComponent(out AbstractLayoutGroup
|
|
layoutGroup)) {
|
|
var replacement = component.AddOrGet<LayoutElement>();
|
|
replacement.flexibleHeight = layoutGroup.flexibleHeight;
|
|
replacement.flexibleWidth = layoutGroup.flexibleWidth;
|
|
replacement.layoutPriority = layoutGroup.layoutPriority;
|
|
replacement.minHeight = layoutGroup.minHeight;
|
|
replacement.minWidth = layoutGroup.minWidth;
|
|
replacement.preferredHeight = layoutGroup.preferredHeight;
|
|
replacement.preferredWidth = layoutGroup.preferredWidth;
|
|
DestroyImmediate(layoutGroup);
|
|
}
|
|
}
|
|
|
|
public float minWidth {
|
|
get {
|
|
return mMinWidth;
|
|
}
|
|
set {
|
|
mMinWidth = value;
|
|
}
|
|
}
|
|
|
|
public float preferredWidth {
|
|
get {
|
|
return mPreferredWidth;
|
|
}
|
|
set {
|
|
mPreferredWidth = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The flexible width of the completed layout group can be set.
|
|
/// </summary>
|
|
public float flexibleWidth {
|
|
get {
|
|
return mFlexibleWidth;
|
|
}
|
|
set {
|
|
mFlexibleWidth = value;
|
|
}
|
|
}
|
|
|
|
public float minHeight {
|
|
get {
|
|
return mMinHeight;
|
|
}
|
|
set {
|
|
mMinHeight = value;
|
|
}
|
|
}
|
|
|
|
public float preferredHeight {
|
|
get {
|
|
return mPreferredHeight;
|
|
}
|
|
set {
|
|
mPreferredHeight = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The flexible height of the completed layout group can be set.
|
|
/// </summary>
|
|
public float flexibleHeight {
|
|
get {
|
|
return mFlexibleHeight;
|
|
}
|
|
set {
|
|
mFlexibleHeight = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The priority of this layout group.
|
|
/// </summary>
|
|
public int layoutPriority {
|
|
get {
|
|
return mLayoutPriority;
|
|
}
|
|
set {
|
|
mLayoutPriority = value;
|
|
}
|
|
}
|
|
|
|
protected RectTransform rectTransform {
|
|
get {
|
|
if (cachedTransform == null)
|
|
cachedTransform = gameObject.rectTransform();
|
|
return cachedTransform;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the layout is currently locked.
|
|
/// </summary>
|
|
[SerializeField]
|
|
protected bool locked;
|
|
|
|
// The backing fields must be annotated with the attribute to have prefabbed versions
|
|
// successfully copy the values
|
|
[SerializeField]
|
|
private float mMinWidth, mMinHeight, mPreferredWidth, mPreferredHeight;
|
|
[SerializeField]
|
|
private float mFlexibleWidth, mFlexibleHeight;
|
|
[SerializeField]
|
|
private int mLayoutPriority;
|
|
|
|
/// <summary>
|
|
/// The cached rect transform to speed up layout.
|
|
/// </summary>
|
|
private RectTransform cachedTransform;
|
|
|
|
protected AbstractLayoutGroup() {
|
|
cachedTransform = null;
|
|
locked = false;
|
|
mLayoutPriority = 1;
|
|
}
|
|
|
|
public abstract void CalculateLayoutInputHorizontal();
|
|
|
|
public abstract void CalculateLayoutInputVertical();
|
|
|
|
/// <summary>
|
|
/// Triggers a layout with the current parent, and then locks the layout size. Further
|
|
/// attempts to automatically lay out the component, unless UnlockLayout is called,
|
|
/// will not trigger any action.
|
|
///
|
|
/// The resulting layout has very good performance, but cannot adapt to changes in the
|
|
/// size of its children or its own size.
|
|
/// </summary>
|
|
/// <returns>The computed size of this component when locked.</returns>
|
|
public virtual Vector2 LockLayout() {
|
|
var rt = gameObject.rectTransform();
|
|
if (rt != null) {
|
|
locked = false;
|
|
CalculateLayoutInputHorizontal();
|
|
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, minWidth);
|
|
SetLayoutHorizontal();
|
|
CalculateLayoutInputVertical();
|
|
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, minHeight);
|
|
SetLayoutVertical();
|
|
locked = true;
|
|
}
|
|
return new Vector2(minWidth, minHeight);
|
|
}
|
|
|
|
protected override void OnDidApplyAnimationProperties() {
|
|
base.OnDidApplyAnimationProperties();
|
|
SetDirty();
|
|
}
|
|
|
|
protected override void OnDisable() {
|
|
base.OnDisable();
|
|
SetDirty();
|
|
}
|
|
|
|
protected override void OnEnable() {
|
|
base.OnEnable();
|
|
SetDirty();
|
|
}
|
|
|
|
protected override void OnRectTransformDimensionsChange() {
|
|
base.OnRectTransformDimensionsChange();
|
|
SetDirty();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets this layout as dirty.
|
|
/// </summary>
|
|
protected virtual void SetDirty() {
|
|
if (gameObject != null && IsActive()) {
|
|
if (CanvasUpdateRegistry.IsRebuildingLayout())
|
|
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
|
|
else
|
|
StartCoroutine(DelayedSetDirty(rectTransform));
|
|
}
|
|
}
|
|
|
|
public abstract void SetLayoutHorizontal();
|
|
|
|
public abstract void SetLayoutVertical();
|
|
|
|
/// <summary>
|
|
/// Unlocks the layout, allowing it to again dynamically resize when component sizes
|
|
/// are changed.
|
|
/// </summary>
|
|
public virtual void UnlockLayout() {
|
|
locked = false;
|
|
LayoutRebuilder.MarkLayoutForRebuild(gameObject.rectTransform());
|
|
}
|
|
}
|
|
}
|