// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Animancer.Editor
{
/// [Editor-Only] Various GUI utilities used throughout Animancer.
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerGUI
///
public static class AnimancerGUI
{
/************************************************************************************************************************/
#region Standard Values
/************************************************************************************************************************/
/// The highlight color used for fields showing a warning.
public static readonly Color
WarningFieldColor = new Color(1, 0.9f, 0.6f);
/// The highlight color used for fields showing an error.
public static readonly Color
ErrorFieldColor = new Color(1, 0.6f, 0.6f);
/************************************************************************************************************************/
/// set to false.
public static readonly GUILayoutOption[]
DontExpandWidth = { GUILayout.ExpandWidth(false) };
/************************************************************************************************************************/
/// Returns .
public static float LineHeight => EditorGUIUtility.singleLineHeight;
/************************************************************************************************************************/
/// Returns .
public static float StandardSpacing => EditorGUIUtility.standardVerticalSpacing;
/************************************************************************************************************************/
private static float _IndentSize = -1;
///
/// The number of pixels of indentation for each increment.
///
public static float IndentSize
{
get
{
if (_IndentSize < 0)
{
var indentLevel = EditorGUI.indentLevel;
EditorGUI.indentLevel = 1;
_IndentSize = EditorGUI.IndentedRect(new Rect()).x;
EditorGUI.indentLevel = indentLevel;
}
return _IndentSize;
}
}
/************************************************************************************************************************/
private static float _ToggleWidth = -1;
/// The width of a standard with no label.
public static float ToggleWidth
{
get
{
if (_ToggleWidth == -1)
_ToggleWidth = GUI.skin.toggle.CalculateWidth(GUIContent.none);
return _ToggleWidth;
}
}
/************************************************************************************************************************/
/// The color of the standard label text.
public static Color TextColor => GUI.skin.label.normal.textColor;
/************************************************************************************************************************/
private static GUIStyle _MiniButton;
/// A more compact with a fixed size as a tiny box.
public static GUIStyle MiniButton
{
get
{
if (_MiniButton == null)
{
_MiniButton = new GUIStyle(EditorStyles.miniButton)
{
margin = new RectOffset(0, 0, 2, 0),
padding = new RectOffset(2, 3, 2, 2),
alignment = TextAnchor.MiddleCenter,
fixedHeight = LineHeight,
fixedWidth = LineHeight - 1
};
}
return _MiniButton;
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Layout
/************************************************************************************************************************/
/// Calls .
public static void RepaintEverything()
=> UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
/************************************************************************************************************************/
/// Indicates where should add the .
public enum SpacingMode
{
/// No extra space.
None,
/// Add extra space before the new area.
Before,
/// Add extra space after the new area.
After,
/// Add extra space before and after the new area.
BeforeAndAfter
}
///
/// Uses to get a occupying a single
/// standard line with the added according to the specified `spacing`.
///
public static Rect LayoutSingleLineRect(SpacingMode spacing = SpacingMode.None)
{
Rect rect;
switch (spacing)
{
case SpacingMode.None:
return GUILayoutUtility.GetRect(0, LineHeight);
case SpacingMode.Before:
rect = GUILayoutUtility.GetRect(0, LineHeight + StandardSpacing);
rect.yMin += StandardSpacing;
return rect;
case SpacingMode.After:
rect = GUILayoutUtility.GetRect(0, LineHeight + StandardSpacing);
rect.height -= StandardSpacing;
return rect;
case SpacingMode.BeforeAndAfter:
rect = GUILayoutUtility.GetRect(0, LineHeight + StandardSpacing * 2);
rect.yMin += StandardSpacing;
rect.height -= StandardSpacing;
return rect;
default:
throw new ArgumentException($"Unsupported {nameof(StandardSpacing)}: " + spacing, nameof(spacing));
}
}
/************************************************************************************************************************/
///
/// If the is positive, this method moves the by that amount and
/// adds the .
///
public static void NextVerticalArea(ref Rect area)
{
if (area.height > 0)
area.y += area.height + StandardSpacing;
}
/************************************************************************************************************************/
///
/// Subtracts the `width` from the left side of the `area` and returns a new occupying the
/// removed section.
///
public static Rect StealFromLeft(ref Rect area, float width, float padding = 0)
{
var newRect = new Rect(area.x, area.y, width, area.height);
area.xMin += width + padding;
return newRect;
}
///
/// Subtracts the `width` from the right side of the `area` and returns a new occupying the
/// removed section.
///
public static Rect StealFromRight(ref Rect area, float width, float padding = 0)
{
area.width -= width + padding;
return new Rect(area.xMax + padding, area.y, width, area.height);
}
/************************************************************************************************************************/
///
/// Divides the given `area` such that the fields associated with both labels will have equal space
/// remaining after the labels themselves.
///
public static void SplitHorizontally(Rect area, string label0, string label1,
out float width0, out float width1, out Rect rect0, out Rect rect1)
{
width0 = CalculateLabelWidth(label0);
width1 = CalculateLabelWidth(label1);
const float Padding = 1;
rect0 = rect1 = area;
var remainingWidth = area.width - width0 - width1 - Padding;
rect0.width = width0 + remainingWidth * 0.5f;
rect1.xMin = rect0.xMax + Padding;
}
/************************************************************************************************************************/
/// [Animancer Extension] Calls and returns the max width.
public static float CalculateWidth(this GUIStyle style, GUIContent content)
{
style.CalcMinMaxWidth(content, out _, out var width);
return Mathf.Ceil(width);
}
/// [Animancer Extension] Calls and returns the max width.
public static float CalculateWidth(this GUIStyle style, string text)
{
using (ObjectPool.Disposable.AcquireContent(out var content, text, null, false))
return style.CalculateWidth(content);
}
/************************************************************************************************************************/
///
/// Creates a for calculating the GUI width occupied by text using the
/// specified `style`.
///
public static ConversionCache CreateWidthCache(GUIStyle style)
=> new ConversionCache((text) => style.CalculateWidth(text));
/************************************************************************************************************************/
private static ConversionCache _LabelWidthCache;
///
/// Calls using and returns the max
/// width. The result is cached for efficient reuse.
///
public static float CalculateLabelWidth(string text)
{
if (_LabelWidthCache == null)
_LabelWidthCache = CreateWidthCache(GUI.skin.label);
return _LabelWidthCache.Convert(text);
}
/************************************************************************************************************************/
///
/// Begins a vertical layout group using the given style and decreases the
/// to compensate for the indentation.
///
public static void BeginVerticalBox(GUIStyle style)
{
if (style == null)
{
GUILayout.BeginVertical();
return;
}
GUILayout.BeginVertical(style);
EditorGUIUtility.labelWidth -= style.padding.left;
}
///
/// Ends a layout group started by and restores the
/// .
///
public static void EndVerticalBox(GUIStyle style)
{
if (style != null)
EditorGUIUtility.labelWidth += style.padding.left;
GUILayout.EndVertical();
}
/************************************************************************************************************************/
/// Clears the then returns it to its current state.
///
/// This forces the drawer to adjust to height changes which
/// it unfortunately doesn't do on its own..
///
public static void ReSelectCurrentObjects()
{
var selection = Selection.objects;
Selection.objects = Array.Empty