using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace LitMotion.Editor
{
internal sealed class MotionTrackerViewItem : TreeViewItem
{
public MotionTrackerViewItem(int id) : base(id) { }
static readonly Regex removeHref = new("(.+)", RegexOptions.Compiled);
public string MotionType { get; set; }
public string SchedulerType { get; set; }
public string Elapsed { get; set; }
string position;
public string Position
{
get { return position; }
set
{
position = value;
PositionFirstLine = value == null ? string.Empty : GetFirstLine(position);
}
}
public string PositionFirstLine { get; private set; }
static string GetFirstLine(string str)
{
var sb = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
if (str[i] == '\r' || str[i] == '\n')
{
break;
}
sb.Append(str[i]);
}
return removeHref.Replace(sb.ToString(), "$1");
}
}
internal sealed class MotionTrackerTreeView : TreeView
{
const string sortedColumnIndexStateKey = "MotionTrackerTreeView_sortedColumnIndex";
public IReadOnlyList CurrentBindingItems;
public MotionTrackerTreeView()
: this(new TreeViewState(), new MultiColumnHeader(new MultiColumnHeaderState(new[]
{
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Motion Type"), width = 55},
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Scheduler"), width = 25},
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Elapsed"), width = 15},
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Stack Trace")},
})))
{
}
MotionTrackerTreeView(TreeViewState state, MultiColumnHeader header)
: base(state, header)
{
rowHeight = 20;
showAlternatingRowBackgrounds = true;
showBorder = true;
header.sortingChanged += HeaderSortingChanged;
header.ResizeToFit();
Reload();
header.sortedColumnIndex = SessionState.GetInt(sortedColumnIndexStateKey, 1);
}
public void ReloadAndSort()
{
var currentSelected = state.selectedIDs;
Reload();
HeaderSortingChanged(multiColumnHeader);
state.selectedIDs = currentSelected;
}
void HeaderSortingChanged(MultiColumnHeader multiColumnHeader)
{
SessionState.SetInt(sortedColumnIndexStateKey, multiColumnHeader.sortedColumnIndex);
var index = multiColumnHeader.sortedColumnIndex;
var ascending = multiColumnHeader.IsSortedAscending(multiColumnHeader.sortedColumnIndex);
var items = rootItem.children.Cast();
IOrderedEnumerable orderedEnumerable = index switch
{
0 => ascending ? items.OrderBy(item => item.MotionType) : items.OrderByDescending(item => item.MotionType),
1 => ascending ? items.OrderBy(item => item.SchedulerType) : items.OrderByDescending(item => item.SchedulerType),
2 => ascending ? items.OrderBy(item => double.Parse(item.Elapsed)) : items.OrderByDescending(item => double.Parse(item.Elapsed)),
3 => ascending ? items.OrderBy(item => item.Position) : items.OrderByDescending(item => item.PositionFirstLine),
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null),
};
CurrentBindingItems = rootItem.children = orderedEnumerable.Cast().ToList();
BuildRows(rootItem);
}
static string GetSchedulerName(IMotionScheduler scheduler, bool isCreatedOnEditor)
{
static string GetTimeKindName(MotionTimeKind motionTimeKind)
{
return motionTimeKind switch
{
MotionTimeKind.Time => "",
MotionTimeKind.UnscaledTime => "IgnoreTimeScale",
MotionTimeKind.Realtime => "Realtime",
_ => null
};
}
if (scheduler == null)
{
if (isCreatedOnEditor)
{
scheduler = MotionScheduler.DefaultScheduler;
}
else
{
scheduler = EditorMotionScheduler.Update;
}
}
return scheduler switch
{
PlayerLoopMotionScheduler loopMotionScheduler => loopMotionScheduler.playerLoopTiming.ToString() + GetTimeKindName(loopMotionScheduler.timeKind),
ManualMotionScheduler => "Manual",
EditorUpdateMotionScheduler => "EditorUpdate",
_ => scheduler.GetType()?.Name,
};
}
protected override TreeViewItem BuildRoot()
{
var root = new TreeViewItem { depth = -1 };
var children = new List();
var id = 0;
foreach (var tracking in MotionTracker.Items)
{
children.Add(new MotionTrackerViewItem(id)
{
MotionType = $"[{tracking.ValueType.Name}, {tracking.OptionsType.Name}, {tracking.AdapterType.Name}]",
SchedulerType = GetSchedulerName(tracking.Scheduler, tracking.CreatedOnEditor),
Elapsed = (DateTime.UtcNow - tracking.CreationTime).TotalSeconds.ToString("00.00"),
Position = tracking.StackTrace?.AddHyperLink()
});
id++;
}
CurrentBindingItems = children;
root.children = CurrentBindingItems as List;
return root;
}
protected override bool CanMultiSelect(TreeViewItem item)
{
return false;
}
protected override void RowGUI(RowGUIArgs args)
{
var item = args.item as MotionTrackerViewItem;
for (var visibleColumnIndex = 0; visibleColumnIndex < args.GetNumVisibleColumns(); visibleColumnIndex++)
{
var rect = args.GetCellRect(visibleColumnIndex);
var columnIndex = args.GetColumn(visibleColumnIndex);
var labelStyle = args.selected ? EditorStyles.whiteLabel : EditorStyles.label;
labelStyle.alignment = TextAnchor.MiddleLeft;
switch (columnIndex)
{
case 0:
EditorGUI.LabelField(rect, item.MotionType, labelStyle);
break;
case 1:
EditorGUI.LabelField(rect, item.SchedulerType, labelStyle);
break;
case 2:
EditorGUI.LabelField(rect, item.Elapsed, labelStyle);
break;
case 3:
EditorGUI.LabelField(rect, item.PositionFirstLine, labelStyle);
break;
default:
throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, null);
}
}
}
}
}