Skip to content

Instantly share code, notes, and snippets.

@babon
Created May 20, 2023 08:58
Show Gist options
  • Save babon/1e0307283b645974904a3fb8d9142b7a to your computer and use it in GitHub Desktop.
Save babon/1e0307283b645974904a3fb8d9142b7a to your computer and use it in GitHub Desktop.
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System;
// TODO / WARNING: This script has some jenky behavior that needs ironing out.
// You probably want to use somebody else's back-button-in-unity implementation.
// TRY HERE: https://forum.unity.com/threads/feature-request-back-button-in-the-editor.653332/#post-4457944
namespace Mel.MLSelectionHistory
{
public class MLHistory
{
private MLHistory()
{
Selection.selectionChanged += this.handleSelectionChange;
}
private static MLHistory _Instance;
public static MLHistory Instance
{
get
{
if (MLHistory._Instance == null)
{
MLHistory._Instance = new MLHistory();
MLHistory._Instance.clearAndSave();
Debug.LogWarning("a new MLHistory is born");
}
return MLHistory._Instance;
}
}
public Action onStateChanged;
void notifyStateChange()
{
if (onStateChanged != null)
{
onStateChanged.Invoke();
}
}
private int[] selectionIDs;
List<int[]> history = new List<int[]>();
private static int maxHistory = 50;
int cursor
{
get { return EditorPrefs.GetInt(MEL_SelectionHistoryCursor); }
set { EditorPrefs.SetInt(MEL_SelectionHistoryCursor, value); }
}
int numEntries
{
get { return EditorPrefs.GetInt(MEL_SelectionHistoryCount); }
set { EditorPrefs.SetInt(MEL_SelectionHistoryCount, value); }
}
void pruneAfterCursor()
{
if (this.cursor >= this.history.Count - 1)
{
return;
}
this.history.RemoveRange(this.cursor + 1, this.history.Count - (this.cursor + 1));
}
void pushSelection()
{
this.history.Add(Selection.instanceIDs);
this.numEntries = this.history.Count;
this.cursor = this.history.Count - 1;
}
bool isCurrentAlreadyRecorded()
{
if (this.history.Count == 0) { return false; }
var last = this.history[this.cursor];
if (last.Length != Selection.instanceIDs.Length)
{
return false;
}
for (int i = 0; i < last.Length; ++i)
{
if (last[i] != Selection.instanceIDs[i])
{
return false;
}
}
return true;
}
void shiftIfLimit()
{
if (this.history.Count < MLHistory.maxHistory + 1) { return; }
this.history.RemoveAt(0);
}
public void handleSelectionChange()
{
this.pruneAndPushCurrent();
this.SaveHistory();
this.notifyStateChange();
}
void pruneAndPushCurrent()
{
this.LoadHistory();
if (this.isCurrentAlreadyRecorded())
{
return;
}
this.pruneAfterCursor();
this.pushSelection();
this.shiftIfLimit();
this.SaveHistory();
}
public void Dbug(string msg)
{
// var output = EditorPrefs.GetString(MEL_SelectionHistory);
// output = " Cursor: " + this.cursor + output;
// Debug.Log(msg + output);
}
void selectCurrent()
{
if (this.history.Count == 0)
{
return;
}
if (this.cursor >= this.history.Count)
{
return;
}
Selection.instanceIDs = this.history[this.cursor];
}
public bool isForwardAvailable()
{
return this.cursor < this.history.Count - 1;
}
public bool isBackAvailable()
{
return this.cursor > 0;
}
public void clearAndSave()
{
this.history.Clear();
this.cursor = 0;
this.SaveHistory();
this.notifyStateChange();
}
public void moveBack()
{
this.LoadHistory();
if (!this.isBackAvailable())
{
return;
}
this.cursor--;
this.selectCurrent();
this.notifyStateChange();
}
public void moveForward()
{
this.LoadHistory();
if (!this.isForwardAvailable())
{
return;
}
this.cursor++;
this.selectCurrent();
this.notifyStateChange();
}
static string MEL_SelectionHistory = "__MEL_SelectionHistory__";
static string MEL_SelectionHistoryCursor = "__MEL_SelectionHistoryCursor__";
static string MEL_SelectionHistoryCount = "__MEL_SelectionHistoryCount__";
StringBuilder sb = new StringBuilder();
void SaveHistory()
{
sb.Clear();
foreach (var ids in this.history)
{
for (int i = 0; i < ids.Length; ++i)
{
sb.Append(ids[i].ToString());
if (i < ids.Length - 1)
{
sb.Append(";");
}
}
sb.Append("|");
}
var outString = sb.ToString().TrimEnd(char.Parse("|"));
EditorPrefs.SetString(MEL_SelectionHistory, outString);
this.numEntries = this.history.Count;
}
void LoadHistory()
{
var strIDrays = EditorPrefs.GetString(MEL_SelectionHistory).Split(char.Parse("|"));
this.history.Clear();
sb.Clear();
foreach (var idstrs in strIDrays)
{
if (string.IsNullOrEmpty(idstrs)) { continue; }
var ids = idstrs.Split(char.Parse(";")).Select(strID => int.Parse(strID));
this.history.Add(ids.ToArray<int>());
}
}
}
public class MLHistoryBackForward : EditorWindow
{
[MenuItem("Tools/MLHistory/Show back-forward window")]
private static void Init()
{
MLHistoryBackForward window = (MLHistoryBackForward)GetWindow(typeof(MLHistoryBackForward));
window.Show();
}
void Awake()
{
MLHistory.Instance.onStateChanged = () =>
{
Debug.LogFormat("got state changed");
this.Repaint();
};
}
// Ctrl + minus (%-) and Ctrl + shift + minus (%#-) to move forward and back.
// Obviously feel free to customize them.
// By default Ctrl+minus conflicts with a hierarchy command. (Click resolve
// and delete that binding if you want to keep this one.)
[MenuItem("Tools/MLHistory/Move Selection Back %-")]
private static void MoveBack()
{
MLHistory.Instance.moveBack();
}
[MenuItem("Tools/MLHistory/Move Selection Forward %#-")]
private static void MoveForward()
{
MLHistory.Instance.moveForward();
}
[MenuItem("Tools/MLHistory/Start tracking if not already")]
private static void StartIfNotAlready()
{
MLHistory.Instance.Dbug("that should do it");
}
void OnGUI()
{
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginDisabledGroup(!MLHistory.Instance.isBackAvailable());
if (GUILayout.Button("Back"))
{
MLHistory.Instance.moveBack();
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(!MLHistory.Instance.isForwardAvailable());
if (GUILayout.Button("Forward"))
{
MLHistory.Instance.moveForward();
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
}
}
}
// EXTRA CREDIT: make back / forward buttons on the toolbar
// depends on: https://github.com/marijnz/unity-toolbar-extender/
//
// static class ToolbarStyles
// {
// public static readonly GUIStyle commandButtonStyle;
// static ToolbarStyles()
// {
// commandButtonStyle = new GUIStyle("Command")
// {
// fontSize = 16,
// alignment = TextAnchor.MiddleCenter,
// imagePosition = ImagePosition.ImageAbove,
// fontStyle = FontStyle.Bold
// };
// }
// }
// [InitializeOnLoad]
// public class MLInitBrowserStyleButtons
// {
// static MLInitBrowserStyleButtons() {
// UnityToolbarExtender.ToolbarExtender.RightToolbarGUI.Add(OnGUIRightToolbar);
// }
// static void OnGUIRightToolbar() {
// GUILayout.FlexibleSpace();
// EditorGUI.BeginDisabledGroup(!MLHistory.Instance.isBackAvailable());
// if (GUILayout.Button("Back")) {
// MLHistory.Instance.moveBack();
// }
// EditorGUI.EndDisabledGroup();
// EditorGUI.BeginDisabledGroup(!MLHistory.Instance.isForwardAvailable());
// if (GUILayout.Button("Forward")) {
// MLHistory.Instance.moveForward();
// }
// EditorGUI.EndDisabledGroup();
// }
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment