Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save FleshMobProductions/74c1913a4f66191a9e12d621d2c525f4 to your computer and use it in GitHub Desktop.
Save FleshMobProductions/74c1913a4f66191a9e12d621d2c525f4 to your computer and use it in GitHub Desktop.
Editor scripts to navigate between selections in Unity back (Ctrl + Alt + Z) and forward (Ctrl + Alt + Y). MenuItems can be found under "Edit/Selection - Navigate Back" and "Edit/Selection - Navigate Forward" and an EditorWindow can be found under "Window/Navigate Selection History". Place the scripts inside an "Editor" folder so they are exclud…
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace FMPUtils.Editor
{
[InitializeOnLoad]
public static class SelectionBackwardsForwardsNavigationMenuItem
{
public static event System.Action historyOrSelectionChanged;
private static readonly int historyCount = 50;
private static int currentHistoryIndex = -1;
private static bool ignoreSelectionChangeProcessing;
private static List<Object[]> selectionHistory = new List<Object[]>(historyCount);
public static bool HasNextHistoryEntry => currentHistoryIndex < selectionHistory.Count - 1;
public static bool HasPreviousHistoryEntry => currentHistoryIndex > 0;
public static string NavigateForwardsHotkeyText => "(Ctrl + Alt + Y)";
public static string NavigateBackwardsHotkeyText => "(Ctrl + Alt + Z)";
// Since the static class is constructed twice, make sure that there is only 1 subscription:
// https://answers.unity.com/questions/1339703/initializeonload-for-editor-load-only.html
static SelectionBackwardsForwardsNavigationMenuItem()
{
Selection.selectionChanged -= HandleSelectionChanged;
Selection.selectionChanged += HandleSelectionChanged;
}
private static void HandleSelectionChanged()
{
// Don't record a selection if we apply a selection from the history (a non-direct selection)
if (!ignoreSelectionChangeProcessing)
RecordSelectionHistory();
historyOrSelectionChanged?.Invoke();
}
private static void RecordSelectionHistory()
{
Object[] currentSelection = Selection.objects;
if (currentSelection.Length > 0)
{
// Only record the selection history if there is a selection (that is not identical to the last one)
if (currentHistoryIndex < 0 || !CompareSelections(currentSelection, selectionHistory[currentHistoryIndex]))
{
// Make sure to invalidate the selection history after the current index when the user has made a new selection choice
selectionHistory.RemoveRange(currentHistoryIndex + 1, selectionHistory.Count - currentHistoryIndex - 1);
if (selectionHistory.Count >= historyCount)
selectionHistory.RemoveAt(0);
selectionHistory.Add(currentSelection);
currentHistoryIndex = selectionHistory.Count - 1;
}
}
}
private static bool CompareSelections(Object[] selection1, Object[] selection2)
{
if (selection1.Length != selection2.Length)
{
return false;
}
for (int i = 0; i < selection1.Length; i++)
{
if (selection1[i] != selection2[i])
{
return false;
}
}
return true;
}
// Ctrl + Alt + Z
[MenuItem("Edit/Selection - Navigate Back %&z")]
public static void NavigateSelectionBackwards()
{
if (HasPreviousHistoryEntry)
{
ignoreSelectionChangeProcessing = true;
currentHistoryIndex--;
Selection.objects = selectionHistory[currentHistoryIndex];
historyOrSelectionChanged?.Invoke();
ignoreSelectionChangeProcessing = false;
}
}
// Ctrl + Alt + Y
[MenuItem("Edit/Selection - Navigate Forward %&y")]
public static void NavigateSelectionForwards()
{
if (HasNextHistoryEntry)
{
ignoreSelectionChangeProcessing = true;
currentHistoryIndex++;
Selection.objects = selectionHistory[currentHistoryIndex];
historyOrSelectionChanged?.Invoke();
ignoreSelectionChangeProcessing = false;
}
}
}
}
using UnityEngine;
using UnityEditor;
namespace FMPUtils.Editor
{
// For newer versions of Unity (2021.1+) this could be implemented as ToolbarOverlay instead
public class SelectionBackwardsForwardsNavigationWindow : EditorWindow
{
[MenuItem("Window/Navigate Selection History")]
public static void ShowWindow()
{
var window = GetWindow<SelectionBackwardsForwardsNavigationWindow>(false, "Navigate Selection History", true);
// https://forum.unity.com/threads/constraining-editorwindow-size.86619/
window.minSize = new Vector2(100f, 30f);
window.maxSize = new Vector2(250f, 30f);
window.Show();
}
private void OnEnable()
{
// EditorWindows are not usually repainted when inactive, so force a redraw of the selection window on selection change
// or when the history changes
// This isn't done through Selection.selectionChanged subscription directly because the repaint has to happen after
// the selection history in SelectionBackwardsForwardsNavigationMenuItem has been updated.
SelectionBackwardsForwardsNavigationMenuItem.historyOrSelectionChanged -= Repaint;
SelectionBackwardsForwardsNavigationMenuItem.historyOrSelectionChanged += Repaint;
}
private void OnDestroy()
{
SelectionBackwardsForwardsNavigationMenuItem.historyOrSelectionChanged -= Repaint;
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal();
GUI.enabled = SelectionBackwardsForwardsNavigationMenuItem.HasPreviousHistoryEntry;
if (GUILayout.Button("Back"))
{
SelectionBackwardsForwardsNavigationMenuItem.NavigateSelectionBackwards();
}
GUI.enabled = SelectionBackwardsForwardsNavigationMenuItem.HasNextHistoryEntry;
if (GUILayout.Button("Forward"))
{
SelectionBackwardsForwardsNavigationMenuItem.NavigateSelectionForwards();
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
}
}
}
#if UNITY_2021_1_OR_NEWER
using UnityEngine;
using UnityEditor;
using UnityEditor.Overlays;
using UnityEditor.Toolbars;
using UnityEngine.UIElements;
namespace FMPUtils.Editor
{
// https://docs.unity3d.com/Manual/overlays-custom.html
// Overlay EditorWindow to be available in all editor windows
// Editor icon reference: https://github.com/halak/unity-editor-icons
[Overlay(typeof(EditorWindow), "Selection History")]
public class SelectionBackwardsForwardsToolbar : ToolbarOverlay
{
public const string Id = "navigate-selection-history";
SelectionBackwardsForwardsToolbar() : base(NavigateBackwardButton.Id, NavigateForwardButton.Id) { }
[EditorToolbarElement(Id, typeof(EditorWindow))]
class NavigateBackwardButton : EditorToolbarButton
{
public const string Id = "navigate-selection-history/Backward";
private bool isInteractive;
public NavigateBackwardButton()
{
this.text = "Back";
this.icon = EditorGUIUtility.IconContent("Animation.PrevKey").image as Texture2D;
this.tooltip = $"Navigate the selection history backward {SelectionBackwardsForwardsNavigationMenuItem.NavigateBackwardsHotkeyText}";
this.clicked += OnClick;
SelectionBackwardsForwardsNavigationMenuItem.historyOrSelectionChanged += UpdateVisibility;
UpdateVisibility();
}
private void OnClick()
{
if (isInteractive)
SelectionBackwardsForwardsNavigationMenuItem.NavigateSelectionBackwards();
}
private void UpdateVisibility()
{
isInteractive = SelectionBackwardsForwardsNavigationMenuItem.HasPreviousHistoryEntry;
style.visibility = new StyleEnum<Visibility>(isInteractive ? Visibility.Visible : Visibility.Hidden);
}
}
[EditorToolbarElement(Id, typeof(EditorWindow))]
class NavigateForwardButton : EditorToolbarButton
{
public const string Id = "navigate-selection-history/Forward";
private bool isInteractive;
public NavigateForwardButton()
{
this.text = "Forw.";
this.icon = EditorGUIUtility.IconContent("Animation.NextKey").image as Texture2D;
this.tooltip = $"Navigate the selection history forward {SelectionBackwardsForwardsNavigationMenuItem.NavigateBackwardsHotkeyText}";
this.clicked += OnClick;
SelectionBackwardsForwardsNavigationMenuItem.historyOrSelectionChanged += UpdateVisibility;
UpdateVisibility();
}
private void OnClick()
{
if (isInteractive)
SelectionBackwardsForwardsNavigationMenuItem.NavigateSelectionForwards();
}
private void UpdateVisibility()
{
isInteractive = SelectionBackwardsForwardsNavigationMenuItem.HasNextHistoryEntry;
style.visibility = new StyleEnum<Visibility>(isInteractive ? Visibility.Visible : Visibility.Hidden);
}
}
}
}
#endif
@FleshMobProductions
Copy link
Author

Thanks to WarpedImagination - The Toolbar overlay was mainly inspired by the video "How To LEVEL UP With Overlay Tools In Unity" on his Youtube channel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment