cvr-props/Assets/ABI.CCK/Scripts/Editor/GUIExtensions/EditorGUIExtensions.cs

426 lines
No EOL
18 KiB
C#
Executable file

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ABI.CCK.Scripts.Editor.Tools;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEditorInternal;
using UnityEngine;
namespace ABI.CCK.Scripts.Editor
{
public static partial class EditorGUIExtensions
{
private const float DEFAULT_DROPDOWN_BUTTON_WIDTH = 35;
private static readonly Dictionary<string, GUIContent> s_iconContentCache = new();
private static int s_lastSelectedValue = -1;
#region Specialized EditorGUI
public delegate void AppendAdditionalMenuItemsDelegate(GenericMenuBuilder genericMenuBuilder, ReorderableList list);
public static void UtilityMenu(Rect rect, ReorderableList list, SerializedProperty property = null, AppendAdditionalMenuItemsDelegate appendAdditionalMenuItems = null)
{
Rect dropdownButtonRect = new(rect.x + rect.width - 21, rect.y, 20, EditorGUIUtility.singleLineHeight);
if (!EditorGUI.DropdownButton(dropdownButtonRect, GUIContent.none, FocusType.Passive))
return;
GenericMenuBuilder genericMenuBuilder = new();
ClipboardUtils.AppendToMenu(genericMenuBuilder, list, property); // default for all uses atm
if (appendAdditionalMenuItems != null)
{
genericMenuBuilder.AddSeparator();
appendAdditionalMenuItems.Invoke(genericMenuBuilder, list);
}
genericMenuBuilder.DropDown(dropdownButtonRect);
}
public static string AdvancedDropdownInput(
Rect rect,
string currentValue,
List<string> dropdownItems,
string label,
string defaultTitle,
GUIContent buttonContent = null)
{
if (buttonContent == null)
{
buttonContent = dropdownItems.Contains(currentValue)
? GetCachedIconContent("d_Search Icon")
: GetCachedIconContent("console.erroricon.sml");
}
string title = (dropdownItems.Count == 0 ? defaultTitle : currentValue);
return CustomDropdown(rect, label, currentValue, dropdownItems.ToArray(), buttonContent, DEFAULT_DROPDOWN_BUTTON_WIDTH, title);
}
public static string AdvancedDropdownInput(
Rect rect,
string currentValue,
List<string> dropdownItems,
string title,
GUIContent buttonContent = null)
{
if (buttonContent == null)
{
buttonContent = dropdownItems.Contains(currentValue)
? GetCachedIconContent("d_Search Icon")
: GetCachedIconContent("console.erroricon.sml");
}
return CustomDropdown(rect, currentValue, dropdownItems.ToArray(), buttonContent, DEFAULT_DROPDOWN_BUTTON_WIDTH, title);
}
public static string AdvancedDropdownInput(
Rect rect,
string currentValue,
string[] dropdownItems,
string title,
GUIContent buttonContent = null)
{
if (buttonContent == null)
{
buttonContent = dropdownItems.Contains(currentValue)
? GetCachedIconContent("d_Search Icon")
: GetCachedIconContent("console.erroricon.sml");
}
return CustomDropdown(rect, currentValue, dropdownItems, buttonContent, DEFAULT_DROPDOWN_BUTTON_WIDTH, title);
}
// bit lazy ig
public static void LimitSliderSided(string label, ref Vector2 limits, float hardLimitMin, float hardLimitMax)
{
var originalLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 150;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.Width(originalLabelWidth - 16));
limits.x = EditorGUILayout.FloatField(limits.x, GUILayout.Width(50));
GUILayout.Space(-13);
EditorGUILayout.MinMaxSlider(ref limits.x, ref limits.y, hardLimitMin, hardLimitMax);
GUILayout.Space(-13);
limits.y = EditorGUILayout.FloatField(limits.y, GUILayout.Width(50));
EditorGUILayout.EndHorizontal();
EditorGUIUtility.labelWidth = originalLabelWidth;
}
public static void LimitSliderSided(ref Rect rect, float spacing, string label, ref Vector2 limits, float hardLimitMin, float hardLimitMax)
{
// Save the original label width and adjust it for your custom drawing
var originalLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 150;
// Calculate positions and sizes for custom drawing elements within the rect
float labelWidth = originalLabelWidth - 16;
float fieldWidth = 50;
float sliderWidth = rect.width - labelWidth - fieldWidth * 2 - spacing * 4;
float currentY = rect.y;
// Label
Rect labelRect = new Rect(rect.x, currentY, labelWidth, EditorGUIUtility.singleLineHeight);
GUI.Label(labelRect, label);
// Field for limits.x
Rect fieldXRect = new Rect(labelRect.xMax + spacing, currentY, fieldWidth, EditorGUIUtility.singleLineHeight);
limits.x = EditorGUI.FloatField(fieldXRect, limits.x);
// MinMaxSlider
Rect sliderRect = new Rect(fieldXRect.xMax + spacing, currentY, sliderWidth, EditorGUIUtility.singleLineHeight);
EditorGUI.MinMaxSlider(sliderRect, ref limits.x, ref limits.y, hardLimitMin, hardLimitMax);
// Field for limits.y
Rect fieldYRect = new Rect(sliderRect.xMax + spacing, currentY, fieldWidth, EditorGUIUtility.singleLineHeight);
limits.y = EditorGUI.FloatField(fieldYRect, limits.y);
// Reset the original label width
EditorGUIUtility.labelWidth = originalLabelWidth;
// Adjust rect.y for the next control, if you're drawing multiple controls in sequence
rect.y += spacing;
}
public static void LimitSliderSidedProp(string label, SerializedProperty limitsProp, float hardLimitMin, float hardLimitMax) {
Vector2 limits = limitsProp.vector2Value;
LimitSliderSided(label, ref limits, hardLimitMin, hardLimitMax);
limitsProp.vector2Value = limits;
}
public static void LimitSliderSidedFloats(string label, SerializedProperty floatMin, SerializedProperty floatMax, float hardLimitMin, float hardLimitMax) {
Vector2 limits = new(floatMin.floatValue, floatMax.floatValue);
LimitSliderSided(label, ref limits, hardLimitMin, hardLimitMax);
floatMin.floatValue = limits.x;
floatMax.floatValue = limits.y;
}
public static void LimitSliderSidedFloats(ref Rect rect, float spacing, string label, SerializedProperty floatMin, SerializedProperty floatMax, float hardLimitMin, float hardLimitMax) {
Vector2 limits = new(floatMin.floatValue, floatMax.floatValue);
LimitSliderSided(ref rect, spacing, label, ref limits, hardLimitMin, hardLimitMax);
floatMin.floatValue = limits.x;
floatMax.floatValue = limits.y;
}
#endregion
#region CustomPopup
public static string CustomPopup(Rect rect, string label, string currentItem, string[] items,
string title = null)
{
// TODO: temp fix
int currentIndex = Array.IndexOf(items, currentItem);
currentIndex = CustomPopup(rect, label, currentIndex, items, title);
return currentIndex >= 0 && currentIndex < items.Length ? items[currentIndex] : CVRCommon.NONE_OR_EMPTY;
}
public static string CustomPopup(Rect rect, string currentItem, string[] items,
string title = null)
{
// TODO: temp fix
int currentIndex = Array.IndexOf(items, currentItem);
currentIndex = CustomPopup(rect, currentIndex, items, title);
return currentIndex >= 0 && currentIndex < items.Length ? items[currentIndex] : CVRCommon.NONE_OR_EMPTY;
}
public static int CustomPopup(Rect rect, string label, int currentItem, string[] items, string title = null)
{
int controlId = GUIUtility.GetControlID(FocusType.Passive);
// Use label width to position the dropdown appropriately
float labelWidth = EditorGUIUtility.labelWidth;
Rect labelRect = new Rect(rect.x, rect.y, labelWidth, rect.height);
Rect buttonRect = new Rect(rect.x + labelWidth, rect.y, rect.width - labelWidth, rect.height);
EditorGUI.LabelField(labelRect, label);
string current = currentItem >= 0 && currentItem < items.Length ? items[currentItem] : CVRCommon.NONE_OR_EMPTY;
if (EditorGUI.DropdownButton(buttonRect, new GUIContent(current), FocusType.Passive))
{
GUIUtility.keyboardControl = controlId;
new CustomAdvancedDropdown(new AdvancedDropdownState(), items, title, selected =>
{
s_lastSelectedValue = selected;
}).Show(buttonRect); // We'll show the dropdown right where our buttonRect is
}
if (controlId == GUIUtility.keyboardControl && s_lastSelectedValue != -1)
{
currentItem = s_lastSelectedValue;
s_lastSelectedValue = -1;
}
return currentItem;
}
// CustomPopup but no label
public static int CustomPopup(Rect rect, int currentItem, string[] items, string title = null)
{
int controlId = GUIUtility.GetControlID(FocusType.Passive);
Rect buttonRect = new Rect(rect.x, rect.y, rect.width, rect.height);
string current = currentItem >= 0 && currentItem < items.Length ? items[currentItem] : CVRCommon.NONE_OR_EMPTY;
if (EditorGUI.DropdownButton(buttonRect, new GUIContent(current), FocusType.Passive))
{
GUIUtility.keyboardControl = controlId;
new CustomAdvancedDropdown(new AdvancedDropdownState(), items, title, selected =>
{
s_lastSelectedValue = selected;
}).Show(buttonRect); // We'll show the dropdown right where our buttonRect is
}
if (controlId == GUIUtility.keyboardControl && s_lastSelectedValue != -1)
{
currentItem = s_lastSelectedValue;
s_lastSelectedValue = -1;
}
return currentItem;
}
#endregion
#region CustomDropdown
public static string CustomDropdown(
Rect rect,
string label,
string currentItem,
string[] items,
string title = null)
{
GUIContent defaultButtonContent = items.Contains(currentItem)
? GetCachedIconContent("d_Search Icon")
: GetCachedIconContent("console.erroricon.sml");
return CustomDropdown(rect, label, currentItem, items, defaultButtonContent, DEFAULT_DROPDOWN_BUTTON_WIDTH, title);
}
public static string CustomDropdown(
Rect rect,
string label,
string currentItem,
string[] items,
GUIContent buttonContent,
string title = null)
{
return CustomDropdown(rect, label, currentItem, items, buttonContent, DEFAULT_DROPDOWN_BUTTON_WIDTH, title);
}
private static string CustomDropdown(
Rect rect,
string label,
string currentItem,
string[] items,
GUIContent buttonContent,
float dropdownButtonWidth,
string title)
{
float labelWidth = 2f + EditorGUIUtility.labelWidth;
float indentOffset = 15 * EditorGUI.indentLevel;
Rect textFieldRect = new Rect(rect.x + labelWidth - indentOffset, rect.y, rect.width - labelWidth + indentOffset - dropdownButtonWidth, rect.height);
Rect buttonRect = new Rect(rect.x + rect.width - dropdownButtonWidth, rect.y, dropdownButtonWidth, rect.height);
Rect dropdownRect = new Rect(rect.x + labelWidth, rect.y, rect.width - labelWidth, rect.height);
EditorGUI.LabelField(new Rect(rect.x, rect.y, labelWidth, rect.height), label);
currentItem = EditorGUI.TextField(textFieldRect, currentItem);
return CustomDropdown(currentItem, items, title, buttonRect, dropdownRect, buttonContent);
}
private static string CustomDropdown(
Rect rect,
string currentItem,
string[] items,
GUIContent buttonContent,
float dropdownButtonWidth,
string title)
{
float indentOffset = 15 * EditorGUI.indentLevel;
Rect textFieldRect = new Rect(rect.x - indentOffset, rect.y, rect.width + indentOffset - dropdownButtonWidth, rect.height);
Rect buttonRect = new Rect(rect.x + rect.width - dropdownButtonWidth, rect.y, dropdownButtonWidth, rect.height);
Rect dropdownRect = new Rect(rect.x, rect.y, rect.width, rect.height);
currentItem = EditorGUI.TextField(textFieldRect, currentItem);
return CustomDropdown(currentItem, items, title, buttonRect, dropdownRect, buttonContent);
}
private static string CustomDropdown(
string currentItem,
string[] items,
string title,
Rect buttonRect,
Rect dropdownRect,
GUIContent buttonContent)
{
int controlId = GUIUtility.GetControlID(FocusType.Passive);
if (EditorGUI.DropdownButton(buttonRect, buttonContent, FocusType.Passive))
{
GUIUtility.keyboardControl = controlId;
new CustomAdvancedDropdown(new AdvancedDropdownState(), items, title, selected =>
{
s_lastSelectedValue = selected;
}).Show(dropdownRect);
}
if (controlId == GUIUtility.keyboardControl && s_lastSelectedValue != -1)
{
currentItem = items[s_lastSelectedValue];
s_lastSelectedValue = -1;
}
return currentItem;
}
#endregion
#region Private Methods
public static GUIContent GetCachedIconContent(string iconName)
{
if (s_iconContentCache.TryGetValue(iconName, out GUIContent iconContent))
return iconContent;
iconContent = EditorGUIUtility.IconContent(iconName);
s_iconContentCache[iconName] = iconContent;
return iconContent;
}
#endregion
#region Custom GUI
private class CustomAdvancedDropdown : AdvancedDropdown
{
private readonly Action<int> _onItemSelected;
private readonly string[] _items;
private readonly string _title;
internal CustomAdvancedDropdown(AdvancedDropdownState state, string[] items, string title, Action<int> onItemSelected) : base(state)
{
_items = items ?? Array.Empty<string>();
_title = title ?? "Items";
_onItemSelected = onItemSelected;
}
internal new void Show(Rect rect)
{
minimumSize = new Vector2(rect.width, 200f);
// We have to set the maximumSize through reflection, cause unity is cool
typeof(AdvancedDropdown).GetProperty("maximumSize", BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(this, new Vector2(rect.width, 400f));
base.Show(rect);
}
protected override AdvancedDropdownItem BuildRoot()
{
AdvancedDropdownItem root = new AdvancedDropdownItem(_title);
for (var itemIdx = 0; itemIdx < _items.Length; itemIdx++)
{
var item = _items[itemIdx];
AdvancedDropdownItem current = root;
string[] parts = item.Split('/');
for (int i = 0; i < parts.Length; i++)
{
string name = parts[i];
AdvancedDropdownItem child = current.children.FirstOrDefault(c => c.name == name);
if (child == null)
{
child = new AdvancedDropdownItem(name)
{
id = itemIdx // id is based on itteration order
};
if (name == item && name == _title)
child.icon = GetCachedIconContent("d_FilterSelectedOnly").image as Texture2D;
current.AddChild(child);
}
current = child;
}
}
return root;
}
protected override void ItemSelected(AdvancedDropdownItem item)
{
base.ItemSelected(item);
_onItemSelected?.Invoke(item.id);
}
}
#endregion
}
}
#endif