This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
using System;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// Provides methods for dealing with common bit operations.
/// </summary>
internal static class BitUtility
{
/// <summary>
/// Evaluates the cardinality of an integer, treating the value as a bit set.
/// Optimization based on http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel.
/// </summary>
/// <param name="integer">The input integer value.</param>
/// <returns>The number of bits set in the provided input integer value.</returns>
internal static int GetCardinality(int integer)
{
unchecked
{
integer = integer - ((integer >> 1) & 0x55555555);
integer = (integer & 0x33333333) + ((integer >> 2) & 0x33333333);
integer = (((integer + (integer >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
}
return integer;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 24a5f331fec74c9aa9e1e5d74b5a9589
timeCreated: 1601747849

View File

@@ -0,0 +1,118 @@
using System;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// A flag enum content provider to be used with the <see cref="SelectionDropDown" /> control.
/// </summary>
/// <typeparam name="T">The flag enum type.</typeparam>
internal class FlagEnumContentProvider<T> : ISelectionDropDownContentProvider where T : Enum
{
private readonly Action<T> m_ValueChangedCallback;
private readonly T[] m_Values;
internal Func<string, string> DisplayNameGenerator = ObjectNames.NicifyVariableName;
private T m_CurrentValue;
/// <summary>
/// Creates a new instance of the <see cref="FlagEnumContentProvider{T}" /> class.
/// </summary>
/// <param name="initialValue">The initial selection value.</param>
/// <param name="valueChangedCallback">The callback to be invoked on selection change.</param>
/// <exception cref="ArgumentException">
/// Thrown if the generic enum parameter type is not integer based
/// or if the initial selection value is empty.
/// </exception>
/// <exception cref="ArgumentNullException">Thrown if the provided change callback is null.</exception>
public FlagEnumContentProvider(T initialValue, Action<T> valueChangedCallback)
{
if (Enum.GetUnderlyingType(typeof(T)) != typeof(int))
{
throw new ArgumentException("Argument underlying type must be integer.");
}
if ((int)(object)initialValue == 0)
{
throw new ArgumentException("The initial value must not be an empty set.", nameof(initialValue));
}
if (valueChangedCallback == null)
{
throw new ArgumentNullException(nameof(valueChangedCallback), "The value change callback must not be null.");
}
m_CurrentValue = initialValue;
m_Values = (T[])Enum.GetValues(typeof(T));
m_ValueChangedCallback = valueChangedCallback;
}
public int Count => m_Values.Length;
public bool IsMultiSelection => true;
public string GetName(int index)
{
return ValidateIndexBounds(index) ? DisplayNameGenerator(m_Values[index].ToString()) : string.Empty;
}
public int[] SeparatorIndices => new int[0];
public bool IsSelected(int index)
{
return ValidateIndexBounds(index) && IsSet(m_Values[index]);
}
public void SelectItem(int index)
{
if (!ValidateIndexBounds(index))
{
return;
}
if (ChangeValue(m_Values[index]))
{
m_ValueChangedCallback(m_CurrentValue);
}
}
private bool ChangeValue(T flag)
{
var value = flag;
var count = GetSetCount();
if (IsSet(value))
{
if (count == 1)
{
return false;
}
m_CurrentValue = FlagEnumUtility.RemoveFlag(m_CurrentValue, flag);
return true;
}
m_CurrentValue = FlagEnumUtility.SetFlag(m_CurrentValue, flag);
return true;
}
private bool IsSet(T flag)
{
return FlagEnumUtility.HasFlag(m_CurrentValue, flag);
}
private int GetSetCount()
{
return BitUtility.GetCardinality((int)(object)m_CurrentValue);
}
private bool ValidateIndexBounds(int index)
{
if (index < 0 || index >= Count)
{
Debug.LogError($"Requesting item index {index} from a collection of size {Count}");
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d31403ec6b334194bdeb4c5ebad64097
timeCreated: 1600072403

View File

@@ -0,0 +1,74 @@
using System;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// Provides methods for dealing with common enumerator operations.
/// </summary>
internal static class FlagEnumUtility
{
/// <summary>
/// Checks for the presence of a flag in a flag enum value.
/// </summary>
/// <param name="value">The value to check for the presence of the flag.</param>
/// <param name="flag">The flag whose presence is to be checked.</param>
/// <typeparam name="T">The flag enum type.</typeparam>
/// <returns></returns>
internal static bool HasFlag<T>(T value, T flag) where T : Enum
{
ValidateUnderlyingType<T>();
var intValue = (int)(object)value;
var intFlag = (int)(object)flag;
return (intValue & intFlag) == intFlag;
}
/// <summary>
/// Sets a flag in a flag enum value.
/// </summary>
/// <param name="value">The value where the flag should be set.</param>
/// <param name="flag">The flag to be set.</param>
/// <typeparam name="T">The flag enum type.</typeparam>
/// <returns>The input value with the flag set.</returns>
internal static T SetFlag<T>(T value, T flag) where T : Enum
{
ValidateUnderlyingType<T>();
var intValue = (int)(object)value;
var intFlag = (int)(object)flag;
var result = intValue | intFlag;
return (T)Enum.ToObject(typeof(T), result);
}
/// <summary>
/// Removes a flag in a flag enum value.
/// </summary>
/// <param name="value">The value where the flag should be removed.</param>
/// <param name="flag">The flag to be removed.</param>
/// <typeparam name="T">The flag enum type.</typeparam>
/// <returns>The input value with the flag removed.</returns>
internal static T RemoveFlag<T>(T value, T flag) where T : Enum
{
ValidateUnderlyingType<T>();
var intValue = (int)(object)value;
var intFlag = (int)(object)flag;
var result = intValue & ~intFlag;
return (T)Enum.ToObject(typeof(T), result);
}
/// <summary>
/// Validates that the underlying type of an enum is integer.
/// </summary>
/// <typeparam name="T">The enum type.</typeparam>
/// <exception cref="ArgumentException">Thrown if the underlying type of the enum type parameter is not integer.</exception>
private static void ValidateUnderlyingType<T>() where T : Enum
{
if (Enum.GetUnderlyingType(typeof(T)) != typeof(int))
{
throw new ArgumentException("Argument underlying type must be integer.");
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ea6cc55375344f10834cf6fa65197525
timeCreated: 1600072466

View File

@@ -0,0 +1,101 @@
using System;
using System.Linq;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// A generic type content provider to be used with the <see cref="SelectionDropDown" /> control.
/// </summary>
/// <typeparam name="T">The type of values represented by content elements.</typeparam>
internal class GenericItemContentProvider<T> : ISelectionDropDownContentProvider where T : IEquatable<T>
{
private readonly ISelectableItem<T>[] m_Items;
private readonly Action<T> m_ValueChangedCallback;
private T m_CurrentValue;
/// <summary>
/// Creates a new instance of the <see cref="GenericItemContentProvider{T}" /> class.
/// </summary>
/// <param name="initialValue">The initial selection value.</param>
/// <param name="items">The set of selectable items.</param>
/// <param name="separatorIndices">The indices of items which should be followed by separator lines.</param>
/// <param name="valueChangedCallback"></param>
/// <exception cref="ArgumentNullException">Thrown if any of the provided arguments is null, except for the separator indices.</exception>
/// <exception cref="ArgumentException">Thrown if the set of items is empty or does not contain the initial selection value.</exception>
public GenericItemContentProvider(T initialValue, ISelectableItem<T>[] items, int[] separatorIndices, Action<T> valueChangedCallback)
{
if (initialValue == null)
{
throw new ArgumentNullException(nameof(initialValue), "The initial selection value must not be null.");
}
if (items == null)
{
throw new ArgumentNullException(nameof(items), "The set of items must not be null.");
}
if (valueChangedCallback == null)
{
throw new ArgumentNullException(nameof(valueChangedCallback), "The value change callback must not be null.");
}
if (items.Length == 0)
{
throw new ArgumentException("The set of items must not be empty.", nameof(items));
}
if (!items.Any(i => i.Value.Equals(initialValue)))
{
throw new ArgumentException("The initial selection value must be in the items set.", nameof(items));
}
m_CurrentValue = initialValue;
m_Items = items;
SeparatorIndices = separatorIndices ?? new int[0];
m_ValueChangedCallback = valueChangedCallback;
}
public int Count => m_Items.Length;
public bool IsMultiSelection => false;
public string GetName(int index)
{
return ValidateIndexBounds(index) ? m_Items[index].DisplayName : string.Empty;
}
public int[] SeparatorIndices { get; }
public void SelectItem(int index)
{
if (!ValidateIndexBounds(index))
{
return;
}
if (IsSelected(index))
{
return;
}
m_CurrentValue = m_Items[index].Value;
m_ValueChangedCallback(m_CurrentValue);
}
public bool IsSelected(int index)
{
return ValidateIndexBounds(index) && m_Items[index].Value.Equals(m_CurrentValue);
}
private bool ValidateIndexBounds(int index)
{
if (index < 0 || index >= Count)
{
Debug.LogError($"Requesting item index {index} from a collection of size {Count}");
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7348ecf02a70497d9a09bd05ad2038ac
timeCreated: 1600072422

View File

@@ -0,0 +1,20 @@
using System;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// Defines a content element which can be used with the <see cref="GenericItemContentProvider{T}" /> content provider.
/// </summary>
internal interface ISelectableItem<out T>
{
/// <summary>
/// The value represented by this item.
/// </summary>
T Value { get; }
/// <summary>
/// The name to be used when displaying this item.
/// </summary>
string DisplayName { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7b076e8f3150431baf926fe9ee030b1e
timeCreated: 1600072411

View File

@@ -0,0 +1,46 @@
using System;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// Defines a content provider that can be used with the <see cref="SelectionDropDown" /> control.
/// </summary>
internal interface ISelectionDropDownContentProvider
{
/// <summary>
/// The total number of items to display.
/// </summary>
int Count { get; }
/// <summary>
/// Multiple selection support.
/// Multiple selection dropdowns don't get closed on selection change.
/// </summary>
bool IsMultiSelection { get; }
/// <summary>
/// The indices of items which should be followed by separator lines.
/// </summary>
int[] SeparatorIndices { get; }
/// <summary>
/// Returns the display name of the item at the specified index.
/// </summary>
/// <param name="index">The index of the item whose display name is to be returned.</param>
/// <returns>The display name of the item at the specified index.</returns>
string GetName(int index);
/// <summary>
/// Signals a request to select the item at the specified index.
/// </summary>
/// <param name="index">The index of the item to be selected.</param>
void SelectItem(int index);
/// <summary>
/// Returns the selection status of the item at the specified index.
/// </summary>
/// <param name="index">The index of the item whose selection status is to be returned.</param>
/// <returns><c>true</c> if the item is currently selected; otherwise, <c>false</c>. </returns>
bool IsSelected(int index);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c2247a4dfeae4607949780b219a7d3c8
timeCreated: 1600072397

View File

@@ -0,0 +1,82 @@
using System;
using System.Linq;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
internal class MultiValueContentProvider<T> : ISelectionDropDownContentProvider where T : IEquatable<T>
{
private T[] m_Values;
private bool[] m_Selected;
private Action<T[]> m_SelectionChangeCallback;
public MultiValueContentProvider(T[] values, T[] selectedValues, Action<T[]> selectionChangeCallback)
{
m_Values = values ?? throw new ArgumentNullException(nameof(values));
if (selectedValues == null)
{
m_Selected = new bool[values.Length];
}
else
{
m_Selected = values.Select(value => selectedValues.Contains(value)).ToArray();
}
m_SelectionChangeCallback = selectionChangeCallback;
}
public int Count
{
get { return m_Values.Length; }
}
public bool IsMultiSelection
{
get { return true; }
}
public int[] SeparatorIndices
{
get { return new int[0]; }
}
public string GetName(int index)
{
if (!ValidateIndexBounds(index))
{
return string.Empty;
}
return m_Values[index].ToString();
}
public void SelectItem(int index)
{
if (!ValidateIndexBounds(index))
{
return;
}
m_Selected[index] = !m_Selected[index];
m_SelectionChangeCallback.Invoke(m_Values.Where((v, i) => m_Selected[i]).ToArray());
}
public bool IsSelected(int index)
{
if (!ValidateIndexBounds(index))
{
return false;
}
return m_Selected[index];
}
private bool ValidateIndexBounds(int index)
{
if (index < 0 || index >= Count)
{
Debug.LogError($"Requesting item index {index} from a collection of size {Count}");
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb736ed47b6da4b499431bf9c6de5890
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// A default implementation of the <see cref="ISelectableItem{T}" /> interface.
/// </summary>
/// <typeparam name="T">The type of the value represented by this content element.</typeparam>
internal class SelectableItemContent<T> : ISelectableItem<T>
{
private readonly string m_DisplayName;
/// <summary>
/// Creates a new instance of the <see cref="SelectableItemContent{T}" /> class
/// </summary>
/// <param name="itemValue">The value represented by this item.</param>
/// <param name="displayName">The display name of this item.</param>
public SelectableItemContent(T itemValue, string displayName)
{
Value = itemValue;
m_DisplayName = displayName;
}
public T Value { get; }
public string DisplayName => m_DisplayName ?? string.Empty;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f7d5e5417ccd4aa0bfa39228c674f142
timeCreated: 1600072417

View File

@@ -0,0 +1,167 @@
using System;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
{
/// <summary>
/// A DropDown editor control accepting <see cref="ISelectionDropDownContentProvider" />-based content providers.
/// </summary>
internal class SelectionDropDown : PopupWindowContent
{
private static readonly int k_ControlId = typeof(SelectionDropDown).GetHashCode();
private readonly ISelectionDropDownContentProvider m_ContentProvider;
private readonly Vector2 m_ContentSize;
private Vector2 m_ScrollPosition = Vector2.zero;
/// <summary>
/// Creates a new instance of the <see cref="SelectionDropDown" /> editor control.
/// </summary>
/// <param name="contentProvider">The content provider to use.</param>
public SelectionDropDown(ISelectionDropDownContentProvider contentProvider)
{
m_ContentProvider = contentProvider;
var width = CalculateContentWidth();
var height = CalculateContentHeight();
m_ContentSize = new Vector2(width, height);
}
public override void OnOpen()
{
base.OnOpen();
editorWindow.wantsMouseMove = true;
editorWindow.wantsMouseEnterLeaveWindow = true;
}
public override void OnClose()
{
GUIUtility.hotControl = 0;
base.OnClose();
}
public override Vector2 GetWindowSize()
{
return m_ContentSize;
}
public override void OnGUI(Rect rect)
{
var evt = Event.current;
var contentRect = new Rect(Styles.TopMargin, 0, 1, m_ContentSize.y);
m_ScrollPosition = UnityEngine.GUI.BeginScrollView(rect, m_ScrollPosition, contentRect);
{
var yPos = Styles.TopMargin;
for (var i = 0; i < m_ContentProvider.Count; ++i)
{
var itemRect = new Rect(0, yPos, rect.width, Styles.LineHeight);
var separatorOffset = 0f;
switch (evt.type)
{
case EventType.Repaint:
var content = new GUIContent(m_ContentProvider.GetName(i));
var hover = itemRect.Contains(evt.mousePosition);
var on = m_ContentProvider.IsSelected(i);
Styles.MenuItem.Draw(itemRect, content, hover, false, on, false);
separatorOffset = DrawSeparator(i, itemRect);
break;
case EventType.MouseDown:
if (evt.button == 0 && itemRect.Contains(evt.mousePosition))
{
m_ContentProvider.SelectItem(i);
if (!m_ContentProvider.IsMultiSelection)
{
editorWindow.Close();
}
evt.Use();
}
break;
case EventType.MouseEnterWindow:
GUIUtility.hotControl = k_ControlId;
evt.Use();
break;
case EventType.MouseLeaveWindow:
GUIUtility.hotControl = 0;
evt.Use();
break;
case EventType.MouseUp:
case EventType.MouseMove:
evt.Use();
break;
}
yPos += Styles.LineHeight + separatorOffset;
}
}
UnityEngine.GUI.EndScrollView();
}
private float CalculateContentWidth()
{
var maxItemWidth = 0f;
for (var i = 0; i < m_ContentProvider.Count; ++i)
{
var itemContent = new GUIContent(m_ContentProvider.GetName(i));
var itemWidth = Styles.MenuItem.CalcSize(itemContent).x;
maxItemWidth = Mathf.Max(itemWidth, maxItemWidth);
}
return maxItemWidth;
}
private float CalculateContentHeight()
{
return m_ContentProvider.Count * Styles.LineHeight
+ m_ContentProvider.SeparatorIndices.Length * Styles.SeparatorHeight
+ Styles.TopMargin + Styles.BottomMargin;
}
private float DrawSeparator(int i, Rect itemRect)
{
if (Array.IndexOf(m_ContentProvider.SeparatorIndices, i) < 0)
{
return 0f;
}
var separatorRect = GetSeparatorRect(itemRect);
DrawRect(separatorRect, Styles.SeparatorColor);
return Styles.SeparatorHeight;
}
private static Rect GetSeparatorRect(Rect itemRect)
{
var x = itemRect.x + Styles.SeparatorMargin;
var y = itemRect.y + itemRect.height + Styles.SeparatorHeight * 0.15f;
var width = itemRect.width - 2 * Styles.SeparatorMargin;
const float height = 1f;
return new Rect(x, y, width, height);
}
private static void DrawRect(Rect rect, Color color)
{
var originalColor = UnityEngine.GUI.color;
UnityEngine.GUI.color *= color;
UnityEngine.GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
UnityEngine.GUI.color = originalColor;
}
private static class Styles
{
public const float LineHeight = EditorGUI.kSingleLineHeight;
public const float TopMargin = 3f;
public const float BottomMargin = 1f;
public const float SeparatorHeight = 4f;
public const float SeparatorMargin = 3f;
public static readonly GUIStyle MenuItem = "MenuItem";
public static readonly Color SeparatorColor = EditorGUIUtility.isProSkin
? new Color(0.32f, 0.32f, 0.32f, 1.333f)
: new Color(0.6f, 0.6f, 0.6f, 1.333f);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f8f9b43d4e034c2d997f4eb6a0d85a96
timeCreated: 1600072477