2025-05-09 15:40:34 +08:00

167 lines
6.2 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
#if UNITY_EDITOR
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
using Sequence = Animancer.AnimancerEvent.Sequence;
namespace Animancer.Editor
{
/// <summary>[Editor-Only] Draws the Inspector GUI for a <see cref="Sequence"/>.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/EventSequenceDrawer
public class EventSequenceDrawer
{
/************************************************************************************************************************/
private static readonly ConditionalWeakTable<Sequence, EventSequenceDrawer>
SequenceToDrawer = new();
/// <summary>Returns a cached <see cref="EventSequenceDrawer"/> for the `events`.</summary>
/// <remarks>
/// The cache uses a <see cref="ConditionalWeakTable{TKey, TValue}"/> so it doesn't prevent the `events`
/// from being garbage collected.
/// </remarks>
public static EventSequenceDrawer Get(Sequence events)
{
if (events == null)
return null;
if (!SequenceToDrawer.TryGetValue(events, out var drawer))
SequenceToDrawer.Add(events, drawer = new());
return drawer;
}
/************************************************************************************************************************/
/// <summary>Calculates the number of vertical pixels required to draw the contents of the `events`.</summary>
public float CalculateHeight(Sequence events)
=> AnimancerGUI.CalculateHeight(CalculateLineCount(events));
/// <summary>Calculates the number of lines required to draw the contents of the `events`.</summary>
public int CalculateLineCount(Sequence events)
{
if (events == null)
return 0;
if (!IsExpanded)
return 1;
var count = 1;
for (int i = 0; i < events.Count; i++)
count += DelegateGUI.CalculateLineCount(events[i].callback);
count += DelegateGUI.CalculateLineCount(events.EndEvent.callback);
return count;
}
/************************************************************************************************************************/
/// <summary>Should the target's default be shown?</summary>
public bool IsExpanded { get; set; }
private static ConversionCache<int, string> _EventNumberCache;
private static float _LogButtonWidth = float.NaN;
/************************************************************************************************************************/
/// <summary>Draws the GUI for the `events`.</summary>
public void DoGUI(ref Rect area, Sequence events, GUIContent label)
{
using (var content = PooledGUIContent.Acquire(GetSummary(events)))
DoGUI(ref area, events, label, content);
}
/// <summary>Draws the GUI for the `events`.</summary>
public void DoGUI(ref Rect area, Sequence events, GUIContent label, GUIContent summary)
{
if (events == null)
return;
area.height = AnimancerGUI.LineHeight;
var headerArea = area;
const string LogLabel = "Log";
if (float.IsNaN(_LogButtonWidth))
_LogButtonWidth = EditorStyles.miniButton.CalculateWidth(LogLabel);
var logArea = AnimancerGUI.StealFromRight(ref headerArea, _LogButtonWidth);
if (GUI.Button(logArea, LogLabel, EditorStyles.miniButton))
Debug.Log(events.DeepToString());
IsExpanded = EditorGUI.Foldout(headerArea, IsExpanded, GUIContent.none, true);
EditorGUI.LabelField(headerArea, label, summary);
AnimancerGUI.NextVerticalArea(ref area);
if (!IsExpanded)
return;
var enabled = GUI.enabled;
GUI.enabled = false;
EditorGUI.indentLevel++;
for (int i = 0; i < events.Count; i++)
{
var name = events.GetName(i);
if (string.IsNullOrEmpty(name))
{
_EventNumberCache ??= new(index => $"Event {index}");
name = _EventNumberCache.Convert(i);
}
Draw(ref area, name, events[i]);
}
Draw(ref area, "End Event", events.EndEvent);
EditorGUI.indentLevel--;
GUI.enabled = enabled;
}
/************************************************************************************************************************/
private static readonly ConversionCache<int, string>
SummaryCache = new((count) => $"[{count}]"),
EndSummaryCache = new((count) => $"[{count}] + End");
/// <summary>Returns a summary of the `events`.</summary>
public static string GetSummary(Sequence events)
{
var cache = float.IsNaN(events.NormalizedEndTime) && AnimancerEvent.IsNullOrDummy(events.OnEnd)
? SummaryCache
: EndSummaryCache;
return cache.Convert(events.Count);
}
/************************************************************************************************************************/
private static ConversionCache<float, string> _EventTimeCache;
/// <summary>Draws the GUI for the `animancerEvent`.</summary>
public static void Draw(ref Rect area, string name, AnimancerEvent animancerEvent)
{
_EventTimeCache ??= new((time)
=> float.IsNaN(time) ? "Time = Auto" : $"Time = {time.ToStringCached()}x");
var timeText = _EventTimeCache.Convert(animancerEvent.normalizedTime);
using (var label = PooledGUIContent.Acquire(name))
using (var value = PooledGUIContent.Acquire(timeText))
DelegateGUI.DoGUI(ref area, label, animancerEvent.callback, value);
}
/************************************************************************************************************************/
}
}
#endif