LMQT/Assets/TouchScript/Editor/Debugging/PointerDebuggerWindow.cs
2024-12-10 09:03:45 +08:00

709 lines
21 KiB
C#

/*
* @author Valentin Simonov / http://va.lent.in/
*/
#if TOUCHSCRIPT_DEBUG
using System;
using System.Collections.Generic;
using System.IO;
using TouchScript.Debugging;
using TouchScript.Debugging.Filters;
using TouchScript.Debugging.GL;
using TouchScript.Debugging.Loggers;
using TouchScript.Editor.EditorUI;
using TouchScript.Utils;
using UnityEditor;
using UnityEngine;
namespace TouchScript.Editor.Debugging
{
public class PointerDebuggerWindow : EditorWindow
{
private class Styles : IDisposable
{
public Texture2D BG;
public int Padding = 5;
public int GlobalPadding = 10;
public int TabHeight = 20;
public int TabWidth = 80;
public int TabPadding = 10;
public int TopWindowHeight = 240;
public int RefreshHeight = 30;
public int PointerItemHeight = 22;
public Color PointerItemSelected = new Color(.86f, .86f, .86f, 1f);
public Color PointerItemEmpty = new Color(.7f, .7f, .7f, .2f);
public GUIStyle PointerItemStyle;
public GUIStyle EnterPlayModeText;
public GUIStyle SmallText;
public GUIStyle SmallButton;
public GUIStyle FilterToggle;
public Styles()
{
BG = CreateColorTexture(new Color(0, 0, 0, 0.05f));
PointerItemStyle = new GUIStyle("ShurikenModuleTitle")
{
font = (new GUIStyle("Label")).font,
border = new RectOffset(15, 7, 4, 4),
fixedHeight = 22,
contentOffset = new Vector2(20f, -2f),
};
EnterPlayModeText = new GUIStyle("miniLabel")
{
alignment = TextAnchor.MiddleCenter,
};
SmallText = new GUIStyle("miniLabel")
{
alignment = TextAnchor.UpperLeft,
};
SmallButton = new GUIStyle("Button")
{
fontSize = SmallText.fontSize,
fontStyle = SmallText.fontStyle,
font = SmallText.font,
};
FilterToggle = new GUIStyle("ShurikenToggle")
{
fontSize = SmallText.fontSize,
fontStyle = SmallText.fontStyle,
font = SmallText.font,
};
FilterToggle.normal.textColor = SmallText.normal.textColor;
FilterToggle.onNormal.textColor = SmallText.normal.textColor;
}
public void Dispose()
{
DestroyImmediate(BG);
}
public static Texture2D CreateColorTexture(Color color)
{
var texture = new Texture2D(1, 1);
texture.hideFlags = HideFlags.HideAndDontSave;
texture.name = "Color " + color;
texture.SetPixel(0, 0, color);
texture.Apply();
return texture;
}
}
public enum LogType
{
Editor,
File
}
// sec
private const float UPDATE_INTERVAL = 1f;
private enum Tab
{
Pointers,
Event,
Filters
}
[MenuItem("Window/TouchScript/Debug", false, 0)]
static void createWindow()
{
EditorWindow window = GetWindow<PointerDebuggerWindow>(false, "TSDebugger", true);
window.minSize = new Vector2(300, 600);
window.Show();
}
private Styles styles;
private LogType logType;
private IPointerLogger pLogger;
private PointerVisualizer pointerVisualizer;
private PagedList pointerList;
private PagedList eventList;
[NonSerialized]
private Tab activeTab;
[NonSerialized]
private int pointerDataCount = 0;
[NonSerialized]
private List<PointerData> pointerData = new List<PointerData>();
[NonSerialized]
private List<string> pointerStrings = new List<string>();
[NonSerialized]
private List<PointerLog> pointerEvents = new List<PointerLog>();
[NonSerialized]
private PointerLog selectedEvent;
[NonSerialized]
private int selectedEventId = -1;
[NonSerialized]
private Dictionary<int, string> pointerEventStrings = new Dictionary<int, string>();
[NonSerialized]
private PointerLogFilter logFilter;
private FilterState filterState;
//private Vector2 filterScroll;
private bool autoRefresh = true;
[NonSerialized]
private float refreshTime;
private void OnEnable()
{
setupLogging();
if (EditorApplication.isPlayingOrWillChangePlaymode)
setupPlaymodeLogging();
if (filterState == null)
{
filterState = new FilterState();
filterState.Load();
}
EditorApplication.update += updateHandler;
}
private void OnDisable()
{
if (styles != null) styles.Dispose();
EditorApplication.update -= updateHandler;
}
private void updateHandler()
{
if (pLogger == null) return;
if (pLogger.PointerCount != pointerDataCount)
{
updatePointers();
}
if (autoRefresh)
{
var time = Time.unscaledTime;
if (time > refreshTime)
{
refreshTime = time + UPDATE_INTERVAL;
updateEventList();
}
}
}
#region Init
private void setupPlaymodeLogging()
{
TouchScriptDebugger.Instance.PointerLogger = pLogger = new PointerLogger();
}
private void setupLogging()
{
pointerVisualizer = new PointerVisualizer();
pointerList = new PagedList(22, drawPointerItem, pointerSelectionChangeHandler);
eventList = new PagedList(22, drawEventItem, eventSelectionChangeHandler);
logFilter = new PointerLogFilter();
}
private void loadLogFile()
{
var path = EditorUtility.OpenFilePanel("Load log data", Application.dataPath, "bin");
if (string.IsNullOrEmpty(path)) return;
pLogger = new FileReaderLogger(path);
updatePointers();
}
private void updateLogType(LogType type)
{
logType = type;
if (type == LogType.Editor)
{
if (pLogger != null) pLogger.Dispose();
if (EditorApplication.isPlayingOrWillChangePlaymode) setupPlaymodeLogging();
}
else
{
TouchScriptDebugger.Instance.ClearPointerLogger();
}
}
#endregion
#region Update
private void updatePointers()
{
pointerData = pLogger.GetFilteredPointerData();
pointerList.Count = pointerData.Count;
pointerDataCount = pointerData.Count;
pointerStrings.Clear();
foreach (var data in pointerData)
{
pointerStrings.Add(string.Format("{0} (id: {1})", data.Type, data.Id));
}
Repaint();
}
private void updateEventList()
{
if (pointerList.SelectedId == -1)
{
pointerEvents.Clear();
eventList.Count = 0;
}
else
{
var id = pointerData[pointerList.SelectedId].Id;
syncFilter();
pointerEvents = pLogger.GetFilteredLogsForPointer(id, logFilter);
eventList.Count = pointerEvents.Count;
}
Repaint();
}
private void selectPointer()
{
updateEventList();
pointerVisualizer.Hide();
}
private void selectEvent()
{
if (eventList.SelectedId == -1)
{
pointerVisualizer.Hide();
selectedEventId = -1;
return;
}
selectedEventId = eventList.SelectedId;
selectedEvent = pointerEvents[selectedEventId];
pointerVisualizer.Show(selectedEvent.State.Position);
switchTab(Tab.Event);
}
private void syncFilter()
{
logFilter.EventMask = filterState.PointerEventMask;
}
private string getEventString(int id)
{
var evt = pointerEvents[id];
string str = null;
if (!pointerEventStrings.TryGetValue(evt.Id, out str))
{
DateTime time = new DateTime(evt.Tick);
str = string.Format("{0} > {1}", time.ToString("HH:mm:ss.ffffff"), evt.Event);
pointerEventStrings.Add(evt.Id, str);
}
return str;
}
#endregion
#region Misc
private void switchTab(Tab newTab)
{
activeTab = newTab;
//if (activeTab == Tab.Filters)
//{
// filterScroll = Vector2.zero;
//}
Repaint();
}
#endregion
#region Drawing
private void OnGUI()
{
if (styles == null) styles = new Styles();
int height = styles.TopWindowHeight;
//int height = pointerList.FitHeight(10);
var rect = GUIUtils.GetPaddedRect(height + styles.GlobalPadding * 2, styles.Padding);
GUI.DrawTexture(rect, styles.BG);
GUIUtils.ContractRect(ref rect, styles.GlobalPadding);
switch (activeTab)
{
case Tab.Pointers:
if (pointerData.Count == 0)
drawNoData(rect);
else
pointerList.Draw(rect);
break;
case Tab.Event:
if (selectedEventId == -1)
drawNoData(rect);
else
drawSelectedEvent(rect);
break;
case Tab.Filters:
drawFilters(rect);
break;
}
drawTabs();
drawRefresh();
//eventList.Count = 100;
rect = GUIUtils.GetPaddedRect(0, styles.Padding, true);
GUI.DrawTexture(rect, styles.BG);
GUIUtils.ContractRect(ref rect, styles.GlobalPadding);
if (pointerEvents.Count == 0)
drawNoData(rect);
else
eventList.Draw(rect);
}
private void drawFilters(Rect rect)
{
//GUI.Toggle(rect, true, " Test", styles.FilterToggle);
GUI.Label(rect, "Show pointer events:");
rect.y += 20;
rect.height -= 20;
var scrollRect = new Rect(rect);
scrollRect.height *= 2;
scrollRect.width -= 40;
//scrollRect.x = 0;
//scrollRect.y = 0;
//using (var scope = new GUI.ScrollViewScope(rect, filterScroll, scrollRect))
//{
scrollRect.height = 14;
var names = Enum.GetNames(typeof (PointerEvent));
using (var changeScope = new EditorGUI.ChangeCheckScope())
{
for (var i = 1; i < names.Length; i++)
{
var evt = (PointerEvent) i;
filterState.SetEventValue(evt,
GUI.Toggle(scrollRect, filterState.IsEventEnabled(evt), " " + names[i], styles.FilterToggle));
scrollRect.y += scrollRect.height;
}
if (changeScope.changed) filterState.Save();
}
// filterScroll = scope.scrollPosition;
//}
scrollRect.y += 4;
scrollRect.height = 20;
using (var scope = new EditorGUI.DisabledScope(pointerList.SelectedId == -1))
{
if (GUI.Button(scrollRect, "Apply filter"))
{
updateEventList();
}
}
}
private void drawTabs()
{
var rect = GUILayoutUtility.GetRect(0, styles.TabHeight, GUILayout.ExpandWidth(true));
rect.x += styles.Padding;
rect.y -= styles.Padding;
rect.width = styles.TabWidth;
if (drawTab(rect, "Pointers", activeTab == Tab.Pointers))
activeTab = Tab.Pointers;
rect.x += rect.width;
if (drawTab(rect, "Event", activeTab == Tab.Event))
activeTab = Tab.Event;
rect.x += rect.width;
if (drawTab(rect, "Filters", activeTab == Tab.Filters))
activeTab = Tab.Filters;
}
private void drawRefresh()
{
var rect = GUILayoutUtility.GetRect(0, styles.RefreshHeight, GUILayout.ExpandWidth(true));
GUIUtils.ContractRect(ref rect, styles.Padding);
var sourceRect = new Rect(rect);
sourceRect.width = 50;
GUI.Label(sourceRect, " Source", styles.SmallText);
sourceRect.x += sourceRect.width;
using (var scope = new EditorGUI.ChangeCheckScope())
{
logType = (LogType) EditorGUI.EnumPopup(sourceRect, "", logType);
if (scope.changed) updateLogType(logType);
}
if (logType == LogType.File)
{
sourceRect.x += sourceRect.width + 2;
sourceRect.width = 40;
sourceRect.height = 15;
if (GUI.Button(sourceRect, "Load", styles.SmallButton))
{
loadLogFile();
}
}
var refreshRect = new Rect(rect);
refreshRect.x = refreshRect.width - 50 - 60;
refreshRect.width = 50;
autoRefresh = GUI.Toggle(refreshRect, autoRefresh, " Auto", styles.FilterToggle);
using (var scope = new EditorGUI.DisabledScope(autoRefresh))
{
rect.x = rect.width - 60;
rect.width = 60;
rect.height = 15;
rect.y -= 1;
if (GUI.Button(rect, "Refresh", styles.SmallButton))
{
updateEventList();
}
}
}
private void drawSelectedEvent(Rect rect)
{
if (selectedEvent.Id == -1)
{
GUI.Label(rect, "No event selected.", styles.EnterPlayModeText);
return;
}
var transform = selectedEvent.State.Target;
var path = selectedEvent.State.TargetPath;
GUI.Label(rect, string.Format("{0}\nPosition: {1}\nPrevious: {2}\nFlags: {3}, Buttons: {4}",
getEventString(selectedEventId), selectedEvent.State.Position,
selectedEvent.State.PreviousPosition, selectedEvent.State.Flags,
PointerUtils.ButtonsToString(selectedEvent.State.Buttons)));
rect.y += 64;
rect.height = 20;
GUI.Label(rect, "Target: ");
using (var scope = new EditorGUI.DisabledScope(true))
{
var fieldRect = new Rect(rect);
fieldRect.x += 50;
fieldRect.width -= 50;
EditorGUI.ObjectField(fieldRect, transform, typeof (Transform), true);
}
if (path != null)
{
rect.y += 20;
rect.height = 16;
GUI.Label(rect, path, styles.SmallText);
}
}
private bool drawTab(Rect rect, string content, bool selected)
{
switch (Event.current.type)
{
case EventType.MouseDown:
if (rect.Contains(Event.current.mousePosition))
{
Event.current.Use();
return true;
}
break;
case EventType.Layout:
case EventType.Repaint:
if (selected) GUI.DrawTexture(rect, styles.BG);
rect.x += styles.TabPadding;
GUI.Label(rect, content);
break;
}
return false;
}
private void drawNoData(Rect rect)
{
GUI.Label(rect, "No data available.", styles.EnterPlayModeText);
}
private void drawPlaymodeText(Rect rect)
{
GUI.Label(rect, "Data is only available in Play Mode.", styles.EnterPlayModeText);
}
private void drawPointerItem(int id, Rect rect, bool selected)
{
var bg = GUI.backgroundColor;
if (id == -1)
{
GUI.backgroundColor = styles.PointerItemEmpty;
GUI.Box(rect, "", styles.PointerItemStyle);
GUI.backgroundColor = bg;
return;
}
if (selected)
{
GUI.backgroundColor = styles.PointerItemSelected;
}
GUI.Box(rect, pointerStrings[id], styles.PointerItemStyle);
GUI.backgroundColor = bg;
}
private void drawEventItem(int id, Rect rect, bool selected)
{
var bg = GUI.backgroundColor;
if (id == -1)
{
GUI.backgroundColor = styles.PointerItemEmpty;
GUI.Box(rect, "", styles.PointerItemStyle);
GUI.backgroundColor = bg;
return;
}
if (selected)
{
GUI.backgroundColor = styles.PointerItemSelected;
}
GUI.Box(rect, getEventString(id), styles.PointerItemStyle);
GUI.backgroundColor = bg;
}
#endregion
#region List handlers
private void pointerSelectionChangeHandler(int id)
{
selectPointer();
}
private void eventSelectionChangeHandler(int id)
{
selectEvent();
}
#endregion
private class PointerVisualizer
{
private int currentDebugId = -1;
public PointerVisualizer() {}
public void Show(Vector2 position)
{
if (!Application.isPlaying) return;
if (currentDebugId != -1) Hide();
currentDebugId = GLDebug.DrawSquareScreenSpace(position, 0, Vector2.one * 20, GLDebug.MULTIPLY, float.MaxValue);
}
public void Hide()
{
if (!Application.isPlaying) return;
GLDebug.RemoveFigure(currentDebugId);
currentDebugId = -1;
}
}
[Serializable]
private class FilterState : ISerializationCallbackReceiver
{
private const string KEY = "TouchScript:Debugger:FilterState";
[SerializeField]
private List<bool> pointerEvents;
public uint PointerEventMask
{
get { return BinaryUtils.ToBinaryMask(pointerEvents); }
}
public FilterState()
{
var eventsCount = Enum.GetValues(typeof (PointerEvent)).Length;
pointerEvents = new List<bool>(eventsCount);
syncPointerEvents(eventsCount);
}
public bool IsEventEnabled(PointerEvent evt)
{
var id = (int) evt;
if (id >= pointerEvents.Count) return false;
return pointerEvents[id];
}
public void SetEventValue(PointerEvent evt, bool value)
{
pointerEvents[(int) evt] = value;
}
public void Save()
{
var json = JsonUtility.ToJson(this);
EditorPrefs.SetString(KEY, json);
}
public void Load()
{
if (!EditorPrefs.HasKey(KEY)) return;
var json = EditorPrefs.GetString(KEY);
JsonUtility.FromJsonOverwrite(json, this);
}
private void syncPointerEvents(int count)
{
for (var i = pointerEvents.Count; i < count; i++) pointerEvents.Add(true);
}
public void OnBeforeSerialize() {}
public void OnAfterDeserialize()
{
var eventsCount = Enum.GetValues(typeof (PointerEvent)).Length;
if (pointerEvents.Count != eventsCount)
{
Debug.Log("FilterState serialization error!");
if (pointerEvents.Count > eventsCount)
{
pointerEvents = new List<bool>(eventsCount);
}
syncPointerEvents(eventsCount);
}
}
}
}
}
#endif