// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Animancer.Editor
{
/// [Editor-Only] Various GUI utilities used throughout Animancer.
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerGUI
public static partial class AnimancerGUI
{
/************************************************************************************************************************/
#region Standard Values
/************************************************************************************************************************/
/// The highlight color used for fields showing a warning.
public static readonly Color
WarningFieldColor = new(1, 0.9f, 0.6f);
/// The highlight color used for fields showing an error.
public static readonly Color
ErrorFieldColor = new(1, 0.6f, 0.6f);
/// Returns a color with uniform Red, Green, and Blue values.
public static Color Grey(float rgb, float alpha = 1)
=> new(rgb, rgb, rgb, alpha);
/************************************************************************************************************************/
/// set to false.
public static readonly GUILayoutOption[]
DontExpandWidth = { GUILayout.ExpandWidth(false) };
/************************************************************************************************************************/
/// Returns .
public static float LineHeight => EditorGUIUtility.singleLineHeight;
///
/// Calculates the number of vertical pixels required to draw the specified `lineCount` using the
/// and .
///
public static float CalculateHeight(int lineCount)
=> lineCount <= 0
? 0
: LineHeight * lineCount + StandardSpacing * (lineCount - 1);
/************************************************************************************************************************/
/// Returns .
public static float StandardSpacing => EditorGUIUtility.standardVerticalSpacing;
/************************************************************************************************************************/
private static float _IndentSize = float.NaN;
///
/// The number of pixels of indentation for each increment.
///
public static float IndentSize
{
get
{
if (float.IsNaN(_IndentSize))
{
var indentLevel = EditorGUI.indentLevel;
EditorGUI.indentLevel = 1;
_IndentSize = EditorGUI.IndentedRect(default).x;
EditorGUI.indentLevel = indentLevel;
}
return _IndentSize;
}
}
/************************************************************************************************************************/
private static float _ToggleWidth = -1;
/// The width of a standard with no label.
public static float ToggleWidth
{
get
{
if (_ToggleWidth == -1)
_ToggleWidth = GUI.skin.toggle.CalculateWidth(GUIContent.none);
return _ToggleWidth;
}
}
/************************************************************************************************************************/
/// The color of the standard label text.
public static Color TextColor
=> GUI.skin.label.normal.textColor;
/************************************************************************************************************************/
private static GUIStyle _MiniButtonStyle;
/// A more compact with a fixed size as a tiny box.
public static GUIStyle MiniButtonStyle
=> _MiniButtonStyle ??= new(EditorStyles.miniButton)
{
margin = new(0, 0, 2, 0),
padding = new(2, 3, 2, 2),
alignment = TextAnchor.MiddleCenter,
fixedHeight = LineHeight,
fixedWidth = LineHeight - 1,
};
private static GUIStyle _NoPaddingButtonStyle;
/// with no .
public static GUIStyle NoPaddingButtonStyle
=> _NoPaddingButtonStyle ??= new(MiniButtonStyle)
{
padding = new(),
fixedWidth = LineHeight,
};
/************************************************************************************************************************/
private static GUIStyle _RightLabelStyle;
/// using .
public static GUIStyle RightLabelStyle
=> _RightLabelStyle ??= new(EditorStyles.label)
{
alignment = TextAnchor.MiddleRight,
};
/************************************************************************************************************************/
private static GUIStyle _MiniButtonNoPadding;
/// A more compact with no padding for its content.
public static GUIStyle MiniButtonNoPadding
{
get
{
_MiniButtonNoPadding ??= new(EditorStyles.miniButton)
{
padding = new(),
overflow = new(),
};
return _MiniButtonNoPadding;
}
}
/************************************************************************************************************************/
/// Constants used by .
/// Key combinations are listed for Windows. Other platforms may differ.
public static class Commands
{
/************************************************************************************************************************/
///
public const string SoftDelete = "SoftDelete";
/// +
public const string Delete = "Delete";
/// +
public const string Copy = "Copy";
/// +
public const string Cut = "Cut";
/// +
public const string Paste = "Paste";
/// +
public const string Duplicate = "Duplicate";
/// +
public const string SelectAll = "SelectAll";
///
public const string FrameSelected = "FrameSelected";
/// +
public const string FrameSelectedWithLock = "FrameSelectedWithLock";
/// +
public const string Find = "Find";
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Layout
/************************************************************************************************************************/
/// The offset currently applied to the GUI by .
public static Vector2 GuiOffset { get; set; }
/************************************************************************************************************************/
/// Calls .
public static void RepaintEverything()
=> UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
/************************************************************************************************************************/
///
public static Rect LayoutRect(float height)
=> GUILayoutUtility.GetRect(0, height);
///
public static Rect LayoutRect(float height, GUIStyle style)
=> GUILayoutUtility.GetRect(0, height, style);
/************************************************************************************************************************/
/// Indicates where should add the .
public enum SpacingMode
{
/// No extra space.
None,
/// Add extra space before the new area.
Before,
/// Add extra space after the new area.
After,
/// Add extra space before and after the new area.
BeforeAndAfter
}
///
/// Uses to get a with the specified
/// `height` and the added according to the specified `spacing`.
///
public static Rect LayoutRect(float height, SpacingMode spacing)
{
Rect rect;
switch (spacing)
{
case SpacingMode.None:
return LayoutRect(height);
case SpacingMode.Before:
rect = LayoutRect(height + StandardSpacing);
rect.yMin += StandardSpacing;
return rect;
case SpacingMode.After:
rect = LayoutRect(height + StandardSpacing);
rect.height -= StandardSpacing;
return rect;
case SpacingMode.BeforeAndAfter:
rect = LayoutRect(height + StandardSpacing * 2);
rect.yMin += StandardSpacing;
rect.height -= StandardSpacing;
return rect;
default:
throw new ArgumentException($"Unsupported {nameof(StandardSpacing)}: " + spacing, nameof(spacing));
}
}
///
/// Uses to get a occupying a single
/// standard line with the added according to the specified `spacing`.
///
public static Rect LayoutSingleLineRect(SpacingMode spacing = SpacingMode.None)
=> LayoutRect(LineHeight, spacing);
/************************************************************************************************************************/
///
/// If the is positive, this method moves the by that amount and
/// adds the .
///
public static void NextVerticalArea(ref Rect area)
{
if (area.height > 0)
area.y += area.height + StandardSpacing;
}
/************************************************************************************************************************/
///
/// Subtracts the `width` from the left side of the `area`
/// and returns a new occupying the removed section.
///
public static Rect StealFromLeft(ref Rect area, float width, float padding = 0)
{
var newRect = new Rect(area.x, area.y, width, area.height);
area.xMin += width + padding;
return newRect;
}
///
/// Subtracts the `width` from the right side of the `area`
/// and returns a new occupying the removed section.
///
public static Rect StealFromRight(ref Rect area, float width, float padding = 0)
{
area.width -= width + padding;
return new(area.xMax + padding, area.y, width, area.height);
}
///
/// Subtracts the `height` from the top side of the `area`
/// and returns a new occupying the removed section.
///
public static Rect StealFromTop(ref Rect area, float height, float padding = 0)
{
var newRect = new Rect(area.x, area.y, area.width, height);
area.yMin += height + padding;
return newRect;
}
/************************************************************************************************************************/
///
/// Subtracts the from the top side of the `area`
/// and returns a new occupying the removed section.
///
public static Rect StealLineFromTop(ref Rect area)
=> StealFromTop(ref area, LineHeight, StandardSpacing);
/************************************************************************************************************************/
///
/// Returns a copy of the `rect` expanded by the specified `amount`
/// (or contracted if negative).
///
public static Rect Expand(this Rect rect, float amount)
=> new(
rect.x - amount,
rect.y - amount,
rect.width + amount * 2,
rect.height + amount * 2);
///
/// Returns a copy of the `rect` expanded by the specified amounts
/// on each axis (or contracted if negative).
///
public static Rect Expand(this Rect rect, float x, float y)
=> new(
rect.x - x,
rect.y - y,
rect.width + x * 2,
rect.height + y * 2);
/************************************************************************************************************************/
/// Returns a copy of the `rect` expanded to include the `other`.
public static Rect Encapsulate(this Rect rect, Rect other)
=> Rect.MinMaxRect(
Math.Min(rect.xMin, other.xMin),
Math.Min(rect.yMin, other.yMin),
Math.Max(rect.xMax, other.xMax),
Math.Max(rect.yMax, other.yMax));
/************************************************************************************************************************/
///
/// Divides the given `area` such that the fields associated with both labels will have equal space
/// remaining after the labels themselves.
///
public static void SplitHorizontally(
Rect area,
string label0,
string label1,
out float width0,
out float width1,
out Rect rect0,
out Rect rect1)
{
width0 = CalculateLabelWidth(label0);
width1 = CalculateLabelWidth(label1);
const float Padding = 1;
rect0 = rect1 = area;
var remainingWidth = area.width - width0 - width1 - Padding;
rect0.width = width0 + remainingWidth * 0.5f;
rect1.xMin = rect0.xMax + Padding;
}
/************************************************************************************************************************/
/// [Animancer Extension] Calls and returns the max width.
public static float CalculateWidth(this GUIStyle style, GUIContent content)
{
style.CalcMinMaxWidth(content, out _, out var width);
return Mathf.Ceil(width);
}
/// [Animancer Extension] Calls and returns the max width.
public static float CalculateWidth(this GUIStyle style, string text)
{
using (var content = PooledGUIContent.Acquire(text))
return style.CalculateWidth(content);
}
/************************************************************************************************************************/
private static ConversionCache _LabelWidthCache;
///
/// Calls using and returns the max
/// width. The result is cached for efficient reuse.
///
public static float CalculateLabelWidth(string text)
{
_LabelWidthCache ??= ConversionCache.CreateWidthCache(GUI.skin.label);
return _LabelWidthCache.Convert(text);
}
/************************************************************************************************************************/
private static string[] _IntToStringCache;
/// Caches and returns if 0 <= value < 100.
public static string ToStringCached(this int value)
{
const int CacheSize = 100;
if (value < 0 || value >= CacheSize)
return value.ToString();
if (_IntToStringCache == null)
{
_IntToStringCache = new string[CacheSize];
for (int i = 0; i < _IntToStringCache.Length; i++)
_IntToStringCache[i] = i.ToString();
}
return _IntToStringCache[value];
}
/************************************************************************************************************************/
///
/// Begins a vertical layout group using the given style and decreases the
/// to compensate for the indentation.
///
public static void BeginVerticalBox(GUIStyle style)
{
if (style == null)
{
GUILayout.BeginVertical();
return;
}
GUILayout.BeginVertical(style);
EditorGUIUtility.labelWidth -= style.padding.left;
}
///
/// Ends a layout group started by and restores the
/// .
///
public static void EndVerticalBox(GUIStyle style)
{
if (style != null)
EditorGUIUtility.labelWidth += style.padding.left;
GUILayout.EndVertical();
}
/************************************************************************************************************************/
private static Func _GetGUIClipRect;
/// Returns the of the current .
public static Rect GetGUIClipRect()
{
if (_GetGUIClipRect != null)
return _GetGUIClipRect();
var type = typeof(GUI).Assembly.GetType("UnityEngine.GUIClip");
var method = type?.GetMethod("GetTopRect", AnimancerReflection.AnyAccessBindings);
if (method != null &&
method.ReturnType != null &&
method.GetParameters().Length == 0)
{
_GetGUIClipRect = (Func)Delegate.CreateDelegate(typeof(Func), method);
}
else
{
_GetGUIClipRect = () => new(0, 0, Screen.width, Screen.height);
}
return _GetGUIClipRect();
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Labels
/************************************************************************************************************************/
private static GUIStyle _WeightLabelStyle;
private static float _WeightLabelWidth = -1;
///
/// Draws a label showing the `weight` aligned to the right side of the `area` and reduces its
/// to remove that label from its area.
///
public static void DoWeightLabel(ref Rect area, float weight, float effectiveWeight)
{
var label = WeightToShortString(weight, out var isExact);
_WeightLabelStyle ??= new(GUI.skin.label)
{
alignment = TextAnchor.MiddleRight,
};
if (_WeightLabelWidth < 0)
{
_WeightLabelStyle.fontStyle = FontStyle.Italic;
_WeightLabelWidth = _WeightLabelStyle.CalculateWidth("0.0");
}
_WeightLabelStyle.normal.textColor = Color.Lerp(Color.grey, TextColor, 0.2f + effectiveWeight * 0.8f);
_WeightLabelStyle.fontStyle = isExact ? FontStyle.Normal : FontStyle.Italic;
var weightArea = StealFromRight(ref area, _WeightLabelWidth);
GUI.Label(weightArea, label, _WeightLabelStyle);
}
/************************************************************************************************************************/
private static ConversionCache _ShortWeightCache;
/// Returns a string which approximates the `weight` into no more than 3 digits.
private static string WeightToShortString(float weight, out bool isExact)
{
isExact = true;
if (weight == 0)
return "0.0";
if (weight == 1)
return "1.0";
isExact = false;
if (weight >= -0.5f && weight < 0.05f)
return "~0.";
if (weight >= 0.95f && weight < 1.05f)
return "~1.";
if (weight <= -99.5f)
return "-??";
if (weight >= 999.5f)
return "???";
_ShortWeightCache ??= new(value =>
{
if (value < -9.5f) return $"{value:F0}";
if (value < -0.5f) return $"{value:F0}.";
if (value < 9.5f) return $"{value:F1}";
if (value < 99.5f) return $"{value:F0}.";
return $"{value:F0}";
});
var rounded = weight > 0 ? Mathf.Floor(weight * 10) : Mathf.Ceil(weight * 10);
isExact = Mathf.Approximately(weight * 10, rounded);
return _ShortWeightCache.Convert(weight);
}
/************************************************************************************************************************/
/// The from before .
private static float _TightLabelWidth;
///
/// Stores the and changes it to the exact width of the `label`.
///
public static string BeginTightLabel(string label)
{
_TightLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = CalculateLabelWidth(label) + EditorGUI.indentLevel * IndentSize;
return label;
}
/// Reverts to its previous value.
public static void EndTightLabel()
{
EditorGUIUtility.labelWidth = _TightLabelWidth;
}
/************************************************************************************************************************/
/// Draws a button using and .
public static bool CompactMiniButton(GUIContent content)
=> GUILayout.Button(content, EditorStyles.miniButton, DontExpandWidth);
/// Draws a button using .
public static bool CompactMiniButton(Rect area, GUIContent content)
=> GUI.Button(area, content, EditorStyles.miniButton);
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Fields
/************************************************************************************************************************/
/// Draws a label field with a foldout.
public static bool DoLabelFoldoutFieldGUI(string label, string value, bool isExpanded)
{
using (var labelContent = PooledGUIContent.Acquire(label))
using (var valueContent = PooledGUIContent.Acquire(value))
return DoLabelFoldoutFieldGUI(labelContent, valueContent, isExpanded);
}
/// Draws a label field with a foldout.
public static bool DoLabelFoldoutFieldGUI(GUIContent label, GUIContent value, bool isExpanded)
{
var area = LayoutSingleLineRect();
EditorGUI.LabelField(area, label, value);
return EditorGUI.Foldout(area, isExpanded, "", true);
}
/// Draws a foldout which stores its state in a hash set.
public static bool DoHashedFoldoutGUI(Rect area, HashSet expandedItems, T item)
{
var wasExpanded = expandedItems.Contains(item);
var isExpanded = EditorGUI.Foldout(area, wasExpanded, "", true);
if (isExpanded != wasExpanded)
if (isExpanded)
expandedItems.Add(item);
else
expandedItems.Remove(item);
return isExpanded;
}
/************************************************************************************************************************/
/// Draws an object reference field.
public static T DoObjectFieldGUI(
Rect area,
GUIContent label,
T value,
bool allowSceneObjects)
where T : Object
=> EditorGUI.ObjectField(area, label, value, typeof(T), allowSceneObjects) as T;
/// Draws an object reference field.
public static T DoObjectFieldGUI(
Rect area,
string label,
T value,
bool allowSceneObjects)
where T : Object
{
using var content = PooledGUIContent.Acquire(label);
return DoObjectFieldGUI(area, content, value, allowSceneObjects);
}
/************************************************************************************************************************/
/// Draws an object reference field.
public static T DoObjectFieldGUI(
GUIContent label,
T value,
bool allowSceneObjects)
where T : Object
{
var height = EditorGUIUtility.HasObjectThumbnail(typeof(T)) ? 64f : LineHeight;
var area = EditorGUILayout.GetControlRect(label != null, height);
return DoObjectFieldGUI(area, label, value, allowSceneObjects);
}
/// Draws an object reference field.
public static T DoObjectFieldGUI(
string label,
T value,
bool allowSceneObjects)
where T : Object
{
using var content = PooledGUIContent.Acquire(label);
return DoObjectFieldGUI(content, value, allowSceneObjects);
}
/************************************************************************************************************************/
///
/// Draws an object reference field with a dropdown button as its label
/// and returns true if clicked.
///
public static bool DoDropdownObjectFieldGUI(
Rect area,
GUIContent label,
bool showDropdown,
ref T value)
where T : Object
{
var labelWidth = EditorGUIUtility.labelWidth;
labelWidth += 2;
area.xMin -= 1;
var spacing = StandardSpacing;
var labelArea = StealFromLeft(ref area, labelWidth - spacing, spacing);
value = DoObjectFieldGUI(area, "", value, true);
if (showDropdown)
{
return EditorGUI.DropdownButton(labelArea, label, FocusType.Passive);
}
else
{
GUI.Label(labelArea, label);
return false;
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Events
/************************************************************************************************************************/
/// Sets if `guiChanged` is true.
public static void SetGuiChanged(bool guiChanged)
{
if (guiChanged)
GUI.changed = true;
}
/************************************************************************************************************************/
///
/// Calls and sets the
/// and .
///
public static void Use(this Event guiEvent, int controlId, bool guiChanged = false)
{
SetGuiChanged(guiChanged);
GUIUtility.hotControl = controlId;
guiEvent.Use();
}
/************************************************************************************************************************/
///
/// Sets the and uses the `currentEvent`
/// if the mouse position is inside the `area`.
///
/// This method is useful for handling .
public static bool TryUseMouseDown(Rect area, Event currentEvent, int controlID)
{
if (!area.Contains(currentEvent.mousePosition))
return false;
GUIUtility.keyboardControl = 0;
GUIUtility.hotControl = controlID;
currentEvent.Use();
return true;
}
/************************************************************************************************************************/
///
/// Releases the and uses the `currentEvent` if it was the active control.
///
/// This method is useful for handling .
public static bool TryUseMouseUp(Event currentEvent, int controlID, bool guiChanged = false)
{
if (GUIUtility.hotControl != controlID)
return false;
GUIUtility.hotControl = 0;
currentEvent.Use();
SetGuiChanged(guiChanged);
return true;
}
/************************************************************************************************************************/
///
/// Uses the `currentEvent` and sets
/// if the `controlID` matches the .
///
/// This method is useful for handling .
public static bool TryUseHotControl(Event currentEvent, int controlID, bool guiChanged = true)
{
if (GUIUtility.hotControl != controlID)
return false;
SetGuiChanged(guiChanged);
currentEvent.Use();
return true;
}
/************************************************************************************************************************/
///
/// Uses the `currentEvent` if the `controlID` has .
/// If a `key` is specified, other keys will be ignored.
///
///
/// This method is useful for handling
/// and .
///
public static bool TryUseKey(Event currentEvent, int controlID, KeyCode key = KeyCode.None)
{
if (GUIUtility.keyboardControl != controlID)
return false;
if (key != KeyCode.None && currentEvent.keyCode != key)
return false;
currentEvent.Use();
GUI.changed = true;
return true;
}
/************************************************************************************************************************/
///
/// Returns true and uses the current event if it is
/// inside the specified `area`.
///
/// Uses and events.
public static bool TryUseClickEvent(Rect area, int button = -1, int controlID = 0)
{
if (controlID == 0)
controlID = GUIUtility.GetControlID(FocusType.Passive);
var currentEvent = Event.current;
if (button >= 0 && currentEvent.button != button)
return false;
switch (currentEvent.type)
{
case EventType.MouseDown:
TryUseMouseDown(area, currentEvent, controlID);
break;
case EventType.MouseUp:
return TryUseMouseUp(currentEvent, controlID, true) && area.Contains(currentEvent.mousePosition);
}
return false;
}
///
/// Returns true and uses the current event if it is inside the last GUI Layout
/// that was drawn.
///
public static bool TryUseClickEventInLastRect(int button = -1)
=> TryUseClickEvent(GUILayoutUtility.GetLastRect(), button);
/************************************************************************************************************************/
/// Is the `currentEvent` a Middle Click or Alt + Left Click?
public static bool IsMiddleClick(this Event currentEvent)
=> currentEvent.button == 2
|| (currentEvent.button == 0 && currentEvent.modifiers == EventModifiers.Alt);
/************************************************************************************************************************/
/// Deselects any selected IMGUI control.
public static void Deselect()
{
GUIUtility.hotControl = 0;
GUIUtility.keyboardControl = 0;
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Other
/************************************************************************************************************************/
/// Draws a line.
///
/// Use , , and
/// if you want to draw multiple lines more efficiently.
///
public static void DrawLine(
Vector2 a,
Vector2 b,
float width,
Color color)
{
BeginTriangles(color);
DrawLineBatched(a, b, width);
EndTriangles();
}
/************************************************************************************************************************/
/// Sets up the rendering details for .
///
/// If the color doesn't work correctly, you may need to call
/// before this.
///
public static void BeginTriangles(Color color)
{
GL.Begin(GL.TRIANGLES);
GL.Color(color);
}
/// Cleans up the rendering details for .
public static void EndTriangles()
{
GL.End();
}
/************************************************************************************************************************/
/// Draws a line.
/// Must be called after and before .
public static void DrawLineBatched(
Vector2 a,
Vector2 b,
float width)
{
var perpendicular = 0.5f * width * (a - b).GetPerpendicular().normalized;
var a0 = a - perpendicular;
var a1 = a + perpendicular;
var b0 = b - perpendicular;
var b1 = b + perpendicular;
GL.Vertex(a0);
GL.Vertex(a1);
GL.Vertex(b0);
GL.Vertex(a1);
GL.Vertex(b0);
GL.Vertex(b1);
}
/************************************************************************************************************************/
/// Draws triangular arrow.
/// Must be called after and before .
public static void DrawArrowTriangleBatched(
Vector2 point,
Vector2 direction,
float width,
float length)
{
direction.Normalize();
var perpendicular = 0.5f * width * direction.GetPerpendicular();
// These commented out bits would use the point as the center of the triangle instead.
direction *= length;// * 0.5f;
var back = point - direction;
GL.Vertex(point);// + direction);
GL.Vertex(back + perpendicular);
GL.Vertex(back - perpendicular);
}
/************************************************************************************************************************/
/// Returns a vector perpendicular to the given value with the same magnitude.
public static Vector2 GetPerpendicular(this Vector2 vector)
=> new(vector.y, -vector.x);
/************************************************************************************************************************/
/// Draws a `sprite` in the given `area`.
public static void DrawSprite(Rect area, Sprite sprite)
{
var texture = sprite.texture;
var textureWidth = texture.width;
var textureHeight = texture.height;
var spriteRect = sprite.rect;
spriteRect.x /= textureWidth;
spriteRect.y /= textureHeight;
spriteRect.width /= textureWidth;
spriteRect.height /= textureHeight;
GUI.DrawTextureWithTexCoords(
area,
texture,
spriteRect);
}
/************************************************************************************************************************/
/// Returns a colour with its hue based on the `hash`.
public static Color GetHashColor(int hash, float s = 1, float v = 1, float a = 1)
{
uint uHash = (uint)hash;
double dHash = (double)uHash / uint.MaxValue;
float h = (float)dHash;
var color = Color.HSVToRGB(h, s, v);
color.a = a;
return color;
}
/************************************************************************************************************************/
/// Clears the then returns it to its current state.
///
/// This forces the drawer to adjust to height changes which
/// it unfortunately doesn't do on its own..
///
public static void ReSelectCurrentObjects()
{
var selection = Selection.objects;
Selection.objects = Array.Empty