From 9ff584a5342326a0d0f027fc326e1c30fc18a2a5 Mon Sep 17 00:00:00 2001 From: Bruno Mikoski Date: Thu, 9 May 2024 20:01:52 +0100 Subject: [PATCH] add: updates --- CHANGELOG.MD | 14 ++ Scripts/Editor/Core.meta | 2 +- Scripts/Editor/Core/SelectionData.cs | 14 +- Scripts/Editor/Core/SelectionData.cs.meta | 2 +- Scripts/Editor/Core/SelectionHistoryData.cs | 5 + .../Editor/Core/SelectionHistoryData.cs.meta | 2 +- .../Editor/Core/SelectionHistoryToolbar.cs | 61 +++++++-- .../Core/SelectionHistoryToolbar.cs.meta | 2 +- .../Editor/Extensions/ReflectionExtensions.cs | 121 ------------------ .../Extensions/ReflectionExtensions.cs.meta | 11 -- Scripts/Editor/Utility.meta | 2 +- .../Editor/Utility/UnityMainToolbarUtility.cs | 85 +++++------- .../Utility/UnityMainToolbarUtility.cs.meta | 2 +- package.json | 2 +- 14 files changed, 116 insertions(+), 209 deletions(-) delete mode 100644 Scripts/Editor/Extensions/ReflectionExtensions.cs delete mode 100644 Scripts/Editor/Extensions/ReflectionExtensions.cs.meta diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 9b58e46..7781617 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.0] +### Changed +- Changed initialization of the toolbar to improve performance +- Updated the toolbar to cache the items only once +- Toolbar Buttons now should be only visible when history has items +- Updated the system to sse the SessionState instead of Editor Prefs + +### Added +- Forward button now also has right click support + + + ## [0.1.1] ### Changed - Changed implementation @@ -25,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First initial working version +[0.1.1]: https://github.com/brunomikoski/UnityHistoryPanel/releases/tag/v0.2.0 [0.1.1]: https://github.com/brunomikoski/UnityHistoryPanel/releases/tag/v0.1.1 [0.1.0]: https://github.com/brunomikoski/UnityHistoryPanel/releases/tag/v0.1.0 [0.0.2]: https://github.com/brunomikoski/UnityHistoryPanel/releases/tag/v0.0.2 diff --git a/Scripts/Editor/Core.meta b/Scripts/Editor/Core.meta index a4c5e03..5db9e2a 100644 --- a/Scripts/Editor/Core.meta +++ b/Scripts/Editor/Core.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 -guid: 3537d8a55cb243e9a898586d2dc397b7 +guid: c7e2b29fe1abd0641b6f6e4d5aafda32 timeCreated: 1688664814 \ No newline at end of file diff --git a/Scripts/Editor/Core/SelectionData.cs b/Scripts/Editor/Core/SelectionData.cs index 7e3db73..905ae4b 100644 --- a/Scripts/Editor/Core/SelectionData.cs +++ b/Scripts/Editor/Core/SelectionData.cs @@ -16,18 +16,18 @@ internal class SelectionData [SerializeField] private List instanceIDs = new List(); - private string m_displayName; + private string displayName; public string DisplayName { get { - if (string.IsNullOrEmpty(m_displayName)) + if (string.IsNullOrEmpty(displayName)) { - m_displayName = string.Join(", ", GetSelectionObjects().Where(o => o != null).Select(o => o.name)); - if (m_displayName.Length > 50) - m_displayName = m_displayName.Substring(0, 47) + "..."; + displayName = string.Join(", ", GetSelectionObjects().Where(o => o != null).Select(o => o.name)); + if (displayName.Length > 50) + displayName = displayName.Substring(0, 47) + "..."; } - return m_displayName; + return displayName; } } @@ -35,7 +35,7 @@ public string DisplayName public SelectionData(Object[] objects) { - m_displayName = string.Empty; + displayName = string.Empty; for (int i = 0; i < objects.Length; i++) { Object o = objects[i]; diff --git a/Scripts/Editor/Core/SelectionData.cs.meta b/Scripts/Editor/Core/SelectionData.cs.meta index d9cb5ee..c47b6eb 100644 --- a/Scripts/Editor/Core/SelectionData.cs.meta +++ b/Scripts/Editor/Core/SelectionData.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: e15d04bf8204d954cae70385902f0867 +guid: 21a728553280c60489f420c7649487dc MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Scripts/Editor/Core/SelectionHistoryData.cs b/Scripts/Editor/Core/SelectionHistoryData.cs index 977cc3d..d955eca 100644 --- a/Scripts/Editor/Core/SelectionHistoryData.cs +++ b/Scripts/Editor/Core/SelectionHistoryData.cs @@ -31,9 +31,12 @@ public void AddToHistory(Object[] objects) return; if (pointInTime < selectionData.Count - 1) selectionData.RemoveRange(pointInTime, selectionData.Count - 1 - pointInTime); + selectionData.Add(item); + if (selectionData.Count > SelectionHistoryToolbar.MaximumHistoryItems) selectionData.RemoveAt(0); + pointInTime = selectionData.Count - 1; } @@ -41,6 +44,7 @@ public void Back() { if (pointInTime == 0) return; + for (int i = pointInTime - 1; i >= 0; i--) { SelectionData data = selectionData[i]; @@ -59,6 +63,7 @@ public void Forward() return; if (pointInTime + 1 > selectionData.Count - 1) return; + pointInTime++; movingInHistory = true; selectionData[pointInTime].Select(); diff --git a/Scripts/Editor/Core/SelectionHistoryData.cs.meta b/Scripts/Editor/Core/SelectionHistoryData.cs.meta index da59140..848f06d 100644 --- a/Scripts/Editor/Core/SelectionHistoryData.cs.meta +++ b/Scripts/Editor/Core/SelectionHistoryData.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7bc8fa3c943105b47bbba78d530b86e9 +guid: 20e61cb83e5f52d49a7394fb07749d10 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Scripts/Editor/Core/SelectionHistoryToolbar.cs b/Scripts/Editor/Core/SelectionHistoryToolbar.cs index 9ebfcc9..7da7186 100644 --- a/Scripts/Editor/Core/SelectionHistoryToolbar.cs +++ b/Scripts/Editor/Core/SelectionHistoryToolbar.cs @@ -23,7 +23,7 @@ private static SelectionHistoryData History return CACHED_HISTORY; CACHED_HISTORY = new SelectionHistoryData(); - string historyJson = EditorPrefs.GetString(HISTORY_STORAGE_KEY, string.Empty); + string historyJson = SessionState.GetString(HISTORY_STORAGE_KEY, string.Empty); if (!string.IsNullOrEmpty(historyJson)) EditorJsonUtility.FromJsonOverwrite(historyJson, CACHED_HISTORY); return CACHED_HISTORY; @@ -32,6 +32,7 @@ private static SelectionHistoryData History private static int? CACHED_MAXIMUM_HISTORY_ITEMS; + public static int MaximumHistoryItems { get @@ -43,6 +44,9 @@ public static int MaximumHistoryItems } } + private static VisualElement backButton; + private static VisualElement forwardButton; + static SelectionHistoryToolbar() { @@ -78,8 +82,10 @@ private static void Initialize() a => DropdownMenuAction.Status.None); parent.Add(HISTORY_SELECTION_MENU); - parent.Add(AddButton("d_tab_prev@2x", "Go Back in Selection History", GoBack, ShowHistorySelectionMenu)); - parent.Add(AddButton("d_tab_next@2x", "Go Forward in Selection History", GoForward)); + backButton = AddButton("d_tab_prev@2x", "Go Back in Selection History", GoBack, ShowBackwardsHistory); + parent.Add(backButton); + forwardButton = AddButton("d_tab_next@2x", "Go Forward in Selection History", GoForward, ShowForwardHistory); + parent.Add(forwardButton); UnityMainToolbarUtility.AddCustom(UnityMainToolbarUtility.TargetContainer.Left, @@ -104,12 +110,19 @@ private static void SaveHistory() return; string json = EditorJsonUtility.ToJson(CACHED_HISTORY); - EditorPrefs.SetString(HISTORY_STORAGE_KEY, json); + SessionState.SetString(HISTORY_STORAGE_KEY, json); } private static void OnSelectionChanged() { History.AddToHistory(Selection.objects); + UpdateButtonsVisibility(); + } + + private static void UpdateButtonsVisibility() + { + backButton.SetEnabled(History.SelectionData.Count > 1 && History.PointInTime > 0); + forwardButton.SetEnabled(History.SelectionData.Count > 1 && History.PointInTime < History.SelectionData.Count - 1); } [MenuItem("Tools/Selection History/Go Back")] @@ -128,6 +141,7 @@ private static void ClearHistory() { EditorPrefs.DeleteKey(HISTORY_STORAGE_KEY); CACHED_HISTORY = new SelectionHistoryData(); + UpdateButtonsVisibility(); } private static void SetPointInTime(int itemIndex) @@ -135,31 +149,53 @@ private static void SetPointInTime(int itemIndex) History.SetPointInTime(itemIndex); } - private static void ShowHistorySelectionMenu() + private static void ShowBackwardsHistory() { HISTORY_SELECTION_MENU.menu.MenuItems().Clear(); - for (int i = 0; i < History.SelectionData.Count; i++) + for (int i = History.PointInTime-1; i >= 0; i--) { SelectionData selectionData = History.SelectionData[i]; if (!selectionData.IsValid) continue; - bool isOnCurrentItem = i == History.PointInTime; + int targetIndex = i; + HISTORY_SELECTION_MENU.menu.AppendAction(selectionData.DisplayName, a => + { + SetPointInTime(targetIndex); + }); + } + + + HISTORY_SELECTION_MENU.menu.AppendSeparator(); + HISTORY_SELECTION_MENU.menu.AppendAction("Clear History", a => + { + ClearHistory(); + }, a => DropdownMenuAction.Status.Normal); + + HISTORY_SELECTION_MENU.ShowMenu(); + } + + private static void ShowForwardHistory() + { + HISTORY_SELECTION_MENU.menu.MenuItems().Clear(); + + for (int i = History.PointInTime+1; i < History.SelectionData.Count; i++) + { + SelectionData selectionData = History.SelectionData[i]; + + if (!selectionData.IsValid) + continue; - DropdownMenuAction.Status status = DropdownMenuAction.Status.Normal; - if (isOnCurrentItem) - status = DropdownMenuAction.Status.Checked; int targetIndex = i; HISTORY_SELECTION_MENU.menu.AppendAction(selectionData.DisplayName, a => { SetPointInTime(targetIndex); - }, a => status); + }); } - HISTORY_SELECTION_MENU.menu.AppendSeparator(); HISTORY_SELECTION_MENU.menu.AppendAction("Clear History", a => { @@ -169,6 +205,7 @@ private static void ShowHistorySelectionMenu() HISTORY_SELECTION_MENU.ShowMenu(); } + #region UI Elements visuals private static VisualElement AddButton(string iconName, string tooltip, Action leftMouseClickCallback, diff --git a/Scripts/Editor/Core/SelectionHistoryToolbar.cs.meta b/Scripts/Editor/Core/SelectionHistoryToolbar.cs.meta index e3af886..9fb19f7 100644 --- a/Scripts/Editor/Core/SelectionHistoryToolbar.cs.meta +++ b/Scripts/Editor/Core/SelectionHistoryToolbar.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2a06bacb3adb08d498f76e0a72dd732d +guid: 1c9e88040d47c4244afc25afee907648 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Scripts/Editor/Extensions/ReflectionExtensions.cs b/Scripts/Editor/Extensions/ReflectionExtensions.cs deleted file mode 100644 index 9150ef9..0000000 --- a/Scripts/Editor/Extensions/ReflectionExtensions.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Reflection; -using System.Reflection.Emit; -using System.Threading; - -namespace BrunoMikoski.SelectionHistory -{ - public static class ReflectionExtensions - { -#nullable enable - private static int FIELD_ACCESS_ID; - - public static void CreateFieldAccessDelegate(this FieldInfo field, out T @delegate) where T : Delegate => - @delegate = (T) field.CreateFieldAccessDelegate(typeof(T)); - - public static T CreateFieldAccessDelegate(this FieldInfo field) where T : Delegate => - (T) field.CreateFieldAccessDelegate(typeof(T)); - - public static Delegate CreateFieldAccessDelegate(this FieldInfo field, Type delegateType) => - field.CreateFieldAccessMethod(delegateType).CreateDelegate(delegateType); - - public static MethodInfo CreateFieldAccessMethod(this FieldInfo field, Type delegateType) - { - MethodInfo? invokeMethod = delegateType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); - if (invokeMethod is null) - { - throw new ArgumentException($"Expected a delegate type, but got: {delegateType.FullName}.", - nameof(delegateType)); - } - - Type? returnType = invokeMethod.ReturnType; - if (returnType.IsByRef) - { - throw new ArgumentException($"Return type cannot be by-ref, but is: {returnType}.", - nameof(delegateType)); - } - - Type? fieldType = field.FieldType; - if (returnType != fieldType && (fieldType.IsValueType || !returnType.IsAssignableFrom(fieldType))) - { - throw new ArgumentException( - "Delegate return type cannot be assigned from field type. " + - $"Field: {fieldType.FullName}. Delegate: {returnType.FullName}."); - } - - var delegateParams = invokeMethod.GetParameters(); - Type? declaringType = field.DeclaringType; - Type? parameterType; - if (field.IsStatic) - { - if (delegateParams.Length != 0) - { - throw new ArgumentException("Expected a delegate with no parameter, because the field is static.", - nameof(delegateType)); - } - - parameterType = null; - } - else - { - if (delegateParams.Length != 1) - { - throw new ArgumentException( - "Expected a delegate with a single parameter, because the field is not static.", - nameof(delegateType)); - } - - parameterType = delegateParams[0].ParameterType; - bool paramByRef = parameterType.IsByRef; - if (paramByRef) - { - parameterType = parameterType.GetElementType()!; - } - - bool isFieldOnValueType = declaringType!.IsValueType; - bool mustMatchExactly = isFieldOnValueType || paramByRef; - if (!mustMatchExactly && - !declaringType.IsAssignableFrom(parameterType) && - !parameterType.IsAssignableFrom(declaringType)) - { - throw new ArgumentException( - "Delegate parameter type will never be able to be cast to field's instance type. " + - $"Field instance type: {declaringType.FullName}. Parameter type: {parameterType.FullName}.", - nameof(delegateType)); - } - - if (mustMatchExactly && declaringType != parameterType) - { - throw new ArgumentException( - "Delegate parameter type must match exactly, because it is a value-type. " + - $"Field instance type: {declaringType.FullName}. Parameter type: {parameterType.FullName}.", - nameof(delegateType)); - } - } - - DynamicMethod? dynMethod = new DynamicMethod( - $"FieldAccessMethod{Interlocked.Increment(ref FIELD_ACCESS_ID)}", - invokeMethod.ReturnType, - parameterType is null ? Type.EmptyTypes : new[] {delegateParams[0].ParameterType}, true); - ILGenerator? il = dynMethod.GetILGenerator(); - if (parameterType is null) - { - il.Emit(OpCodes.Ldsfld, field); - } - else - { - il.Emit(OpCodes.Ldarg_0); - if (parameterType != declaringType) - { - il.Emit(OpCodes.Castclass, declaringType!); - } - - il.Emit(OpCodes.Ldfld, field); - } - - il.Emit(OpCodes.Ret); - return dynMethod; - } -#nullable restore - } -} diff --git a/Scripts/Editor/Extensions/ReflectionExtensions.cs.meta b/Scripts/Editor/Extensions/ReflectionExtensions.cs.meta deleted file mode 100644 index a91fccf..0000000 --- a/Scripts/Editor/Extensions/ReflectionExtensions.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: abe26b5698af1674ab4ada52fd935509 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Utility.meta b/Scripts/Editor/Utility.meta index 57ec6b7..e60319a 100644 --- a/Scripts/Editor/Utility.meta +++ b/Scripts/Editor/Utility.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 -guid: 2a0ff8a003ce4b28897a9bfb87e42348 +guid: 9bdbb0d6e8ef4784aaf45eefbf3b32d6 timeCreated: 1688664826 \ No newline at end of file diff --git a/Scripts/Editor/Utility/UnityMainToolbarUtility.cs b/Scripts/Editor/Utility/UnityMainToolbarUtility.cs index f4e5341..7fc506a 100644 --- a/Scripts/Editor/Utility/UnityMainToolbarUtility.cs +++ b/Scripts/Editor/Utility/UnityMainToolbarUtility.cs @@ -5,6 +5,7 @@ using UnityEditor.Toolbars; using UnityEngine; using UnityEngine.UIElements; +using Object = UnityEngine.Object; using VisualElement = UnityEngine.UIElements.VisualElement; namespace BrunoMikoski.SelectionHistory @@ -29,40 +30,33 @@ public enum Side private static VisualElement LEFT_ZONE_ROOT; private static VisualElement RIGHT_ZONE_ROOT; - private static VisualElement MainToolbarRoot => FetchMainToolbarRoot(); - private static VisualElement CenterRoot => FetchPlaymodeButtonsRoot(); - private static VisualElement LeftRoot => FetchZoneLeftRoot(); - private static VisualElement RightRoot => FetchZoneRightRoot(); - public static int GetCurrentToolbarVersion() => MainToolbarRoot.GetHashCode(); + private static Type ToolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar"); + private static Func toolbarGetter; private static Func rootGetter; - - public static void RemoveStockButton(string name) - { - MainToolbarRoot.Q(name).RemoveFromHierarchy(); - } - - public static void RemovePlasticSCMButton() - { - LeftRoot.Q(className: "unity-imgui-container").RemoveFromHierarchy(); - } + private static bool initialized; + + private static VisualElement leftZoneRootVisualElement; + private static VisualElement rightZoneRootVisualElement; + private static VisualElement rootVisualElement; public static void AddCustom(TargetContainer container, Side side, VisualElement custom) { + Initialize(); VisualElement containerElement; switch (container) { case TargetContainer.Left: - containerElement = LeftRoot; + containerElement = leftZoneRootVisualElement; break; case TargetContainer.Right: - containerElement = RightRoot; + containerElement = rightZoneRootVisualElement; break; case TargetContainer.Center: - containerElement = CenterRoot; + containerElement = rootVisualElement; break; default: throw new NotSupportedException(); @@ -82,6 +76,8 @@ public static void AddCustom(TargetContainer container, Side side, VisualElement public static EditorToolbarButton AddButton(TargetContainer container, Side side, string name, Texture2D icon, Action callback) { + Initialize(); + EditorToolbarButton button = new EditorToolbarButton() { name = name, @@ -93,13 +89,13 @@ public static void AddCustom(TargetContainer container, Side side, VisualElement switch (container) { case TargetContainer.Left: - containerElement = LeftRoot; + containerElement = leftZoneRootVisualElement; break; case TargetContainer.Right: - containerElement = RightRoot; + containerElement = rightZoneRootVisualElement; break; case TargetContainer.Center: - containerElement = CenterRoot; + containerElement = rootVisualElement; break; default: throw new NotSupportedException(); @@ -120,37 +116,24 @@ public static void AddCustom(TargetContainer container, Side side, VisualElement return button; } - - private static VisualElement FetchZoneLeftRoot() - { - return MainToolbarRoot.Q("ToolbarZoneLeftAlign"); - } - - private static VisualElement FetchZoneRightRoot() - { - return MainToolbarRoot.Q("ToolbarZoneRightAlign"); - } - - - private static VisualElement FetchMainToolbarRoot() - { - if (toolbarGetter == null || rootGetter == null) - { - Type type = TypeCache.GetTypesDerivedFrom().First(x => x.FullName == "UnityEditor.Toolbar"); - type.GetField("get", BindingFlags.Static | BindingFlags.Public) - .CreateFieldAccessDelegate(out toolbarGetter); - FieldInfo rootGetterField = type.GetField("m_Root", BindingFlags.Instance | BindingFlags.NonPublic); - rootGetterField.CreateFieldAccessDelegate(out rootGetter); - } - - object toolbar = toolbarGetter(); - VisualElement root = rootGetter(toolbar); - return root; - } - - private static VisualElement FetchPlaymodeButtonsRoot() + private static void Initialize() { - return MainToolbarRoot.Q("Play").parent; + if (initialized) + return; + + Object[] toolbars = Resources.FindObjectsOfTypeAll(ToolbarType); + + if (toolbars.Length == 0) + return; + + Object toolbar = toolbars[0]; + + FieldInfo rootField = toolbar.GetType().GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance); + rootVisualElement = rootField.GetValue(toolbar) as VisualElement; + leftZoneRootVisualElement = rootVisualElement.Q("ToolbarZoneLeftAlign"); + rightZoneRootVisualElement = rootVisualElement.Q("ToolbarZoneLeftAlign"); + + initialized = true; } } } diff --git a/Scripts/Editor/Utility/UnityMainToolbarUtility.cs.meta b/Scripts/Editor/Utility/UnityMainToolbarUtility.cs.meta index fa252d6..b7b9187 100644 --- a/Scripts/Editor/Utility/UnityMainToolbarUtility.cs.meta +++ b/Scripts/Editor/Utility/UnityMainToolbarUtility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 023a84847c24f9b48b68de5eca88abb1 +guid: cab68dd5eb33b1b4380627af4644f591 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/package.json b/package.json index 7998d08..4291665 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.brunomikoski.editorhistorypanel", "displayName": "Selection History", - "version": "0.1.2", + "version": "0.2.0", "unity": "2022.3", "description": "History navigation panel retained between sessions and with hotkeys", "keywords": [