Initial commit

This commit is contained in:
2026-06-02 18:57:47 -04:00
commit 59d26a915d
268 changed files with 41240 additions and 0 deletions
@@ -0,0 +1,92 @@
using System;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Editor.Utility
{
/// <summary>
/// Referenced via https://github.com/marijnz/unity-autocomplete-search-field
/// </summary>
[Serializable]
[Obsolete("Use Odin or some attribute-based item instead.")]
[ExcludeFromCoverage]
public class EditorSearchField
{
private static class Styles
{
public const float RESULT_HEIGHT = 20f;
public const float RESULT_BORDER_WIDTH = 2f;
public const float RESULT_MARGIN = 15f;
public const float RESULT_LABEL_OFFSET = 2f;
public static readonly GUIStyle ENTRY_EVEN;
public static readonly GUIStyle ENTRY_ODD;
public static readonly GUIStyle LABEL_STYLE;
public static readonly GUIStyle RESULTS_BORDER_STYLE;
static Styles()
{
ENTRY_EVEN = new GUIStyle("CN EntryBackEven");
ENTRY_ODD = new GUIStyle("CN EntryBackOdd");
RESULTS_BORDER_STYLE = new GUIStyle("hostview");
LABEL_STYLE = new GUIStyle
{
alignment = TextAnchor.MiddleLeft,
richText = true
};
}
}
private static void RepaintFocusedWindow()
{
if (EditorWindow.focusedWindow != null)
EditorWindow.focusedWindow.Repaint();
}
private SearchField searchField;
private string searchString;
public event Action<string> OnInputChanged;
public bool HasSearchString => !string.IsNullOrEmpty(SearchString);
public string SearchString
{
get => searchString;
set
{
searchString = value;
OnGUI();
}
}
public void OnGUI()
{
var rect = GUILayoutUtility.GetRect(1, 1, 18, 18, GUILayout.ExpandWidth(true));
GUILayout.BeginHorizontal();
DoSearchField(rect);
GUILayout.EndHorizontal();
}
private void DoSearchField(Rect rect)
{
if (searchField == null)
searchField = new SearchField();
var result = searchField.OnGUI(rect, searchString);
if (result != searchString)
OnInputChanged?.Invoke(result);
searchString = result;
if (HasSearchbarFocused())
RepaintFocusedWindow();
}
private bool HasSearchbarFocused() => GUIUtility.keyboardControl == searchField.searchFieldControlID;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0320c64c0c142a999d375385c65efee
timeCreated: 1727491490
@@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Editor.Utility
{
/// <summary>
/// A set-up for a component that can be a searchable list. Deprecated but fun to create.
/// Doesn't work for entry adds.
/// </summary>
/// <typeparam name="T"></typeparam>
[ExcludeFromCoverage]
[Obsolete]
public class SearchableScrollView<T>
{
public delegate void DataDelegate(int index, string originalValue, string newValue);
public delegate bool ValidationDelegate(string entry);
private static class Styles
{
public static readonly GUIStyle SELECTED;
static Styles()
{
var t = new Texture2D(1, 1);
t.SetPixel(0, 0, new Color(0.4f, 0.4f, 0.4f));
t.Apply();
SELECTED = new GUIStyle(EditorStyles.label);
SELECTED.normal.textColor = Color.white;
SELECTED.normal.background = t;
}
}
private static readonly string EDIT_FIELD = "selectedTextField";
private readonly EditorSearchField searchField;
private IList<T> data;
private IList<string> dataLabels = new List<string>();
private IList<string> searchResults;
private readonly IDictionary<int, int> filteredLabelsToData = new Dictionary<int, int>();
private EventType prevEventType;
private int selectedIndex = -1;
private bool isEditing;
private bool isNewEntry;
private bool cancelEdit;
private bool editingFinished;
private string originalEntryText;
private string editedEntryText;
private Vector2 scrollPosition;
public float MaxWidth { get; set; }
public IList<T> Data
{
set
{
if (value == null)
return;
data = value;
searchField.SearchString = string.Empty;
dataLabels.Clear();
dataLabels = data.Select(d => d.ToString()).ToList();
filteredLabelsToData.Clear();
}
}
/// <summary>
/// The modified index of the selection in the list with the search results taken into account.
/// </summary>
public int SelectedIndex => filteredLabelsToData.ContainsKey(selectedIndex) ? filteredLabelsToData[selectedIndex] : selectedIndex;
public ValidationDelegate ValidateEntry { get; set; }
public DataDelegate DataEntryAdded { get; set; }
public DataDelegate DataEntryEdited { get; set; }
public DataDelegate DataEntryRemoved { get; set; }
private IList<string> TargetData => (searchResults?.Count ?? 0) > 0 ? searchResults : dataLabels;
public SearchableScrollView()
{
searchField = new EditorSearchField();
searchField.OnInputChanged += SearchFieldInputChanged;
}
public void OnGUI()
{
HandleEditing();
GUILayout.BeginVertical();
GUILayout.BeginVertical(EditorStyles.helpBox);
searchField.OnGUI();
scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, GUILayout.MaxWidth(MaxWidth));
GUILayout.BeginVertical();
// Output all keys in the list, with some being modified in style or interact-ability by way of flags
var newEntryIndex = -1;
var targetData = TargetData;
for (var i = 0; i < (targetData?.Count ?? 0); i++)
{
var target = targetData![i];
var selected = selectedIndex == i;
if (isEditing && selected)
{
if (originalEntryText == null)
{
originalEntryText = target;
editedEntryText = target;
}
GUI.SetNextControlName(EDIT_FIELD);
editedEntryText = GUILayout.TextField(editedEntryText, Styles.SELECTED);
GUI.FocusControl(EDIT_FIELD);
}
else if (cancelEdit && selected)
{
cancelEdit = false;
if (isNewEntry)
newEntryIndex = i;
else
targetData[i] = originalEntryText;
originalEntryText = null;
editedEntryText = null;
}
else if (editingFinished && selected)
{
editingFinished = false;
UpdateListValue(i, originalEntryText, editedEntryText);
originalEntryText = null;
editedEntryText = null;
}
else if (GUILayout.Button(target, selected ? Styles.SELECTED : EditorStyles.label) && !isEditing)
selectedIndex = selected ? -1 : i;
}
GUILayout.FlexibleSpace();
GUILayout.EndVertical();
GUILayout.EndScrollView();
GUILayout.EndVertical();
GUILayout.Label("F2 = Edit entry\nEnter = Accept and apply changes\nEscape = Cancel entry edit", EditorStyles.helpBox);
GUILayout.EndVertical();
}
public void AddNewEntry()
{
searchField.SearchString = string.Empty;
dataLabels.Add(string.Empty);
selectedIndex = dataLabels.Count - 1;
isEditing = true;
isNewEntry = true;
}
public void DeleteEntry()
{
if (selectedIndex < 0)
return;
dataLabels.RemoveAt(SelectedIndex);
DataEntryRemoved?.Invoke(SelectedIndex, null, null);
searchField.SearchString = string.Empty;
}
private void FinalizeNewEntry()
{
if (!isEditing)
return;
isEditing = false;
editingFinished = true;
}
private void HandleEditing()
{
var eventType = Event.current.type;
var isKeyDown = eventType == EventType.KeyDown;
if (selectedIndex < 0 || !isKeyDown || eventType == prevEventType)
{
if (!isKeyDown)
prevEventType = eventType;
return;
}
prevEventType = eventType;
var keyCode = Event.current.keyCode;
switch (keyCode)
{
case KeyCode.Escape:
if (!isEditing)
return;
isEditing = false;
cancelEdit = true;
break;
case KeyCode.Return:
case KeyCode.KeypadEnter:
FinalizeNewEntry();
break;
case KeyCode.F2:
isEditing = true;
break;
}
}
private void UpdateListValue(int index, string origText, string editedText)
{
if (editedText == origText || (ValidateEntry != null && !ValidateEntry(editedText)))
{
if (isNewEntry)
{
isNewEntry = false;
dataLabels.RemoveAt(dataLabels.Count - 1);
}
return;
}
if (filteredLabelsToData.TryGetValue(index, out var origIndex))
{
searchResults[index] = editedText;
dataLabels[origIndex] = editedText;
index = origIndex;
}
else
dataLabels[index] = editedText;
if (isNewEntry)
{
isNewEntry = false;
DataEntryAdded?.Invoke(index, null, editedText);
}
else
DataEntryEdited?.Invoke(index, origText, editedText);
}
private void SearchFieldInputChanged(string searchString)
{
searchResults ??= new List<string>();
searchResults.Clear();
filteredLabelsToData.Clear();
if (searchString != null)
{
var newIndex = 0;
searchResults = dataLabels.Where((label, index) =>
{
var result = label.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0;
if (result)
filteredLabelsToData.Add(newIndex++, index);
return result;
}).ToList();
}
else
searchResults = dataLabels;
if (selectedIndex >= searchResults.Count)
selectedIndex = -1;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7685e09f2a5b45dc82e8010310780446
timeCreated: 1727611465
@@ -0,0 +1,21 @@
using UnityEngine;
using UnityEngine.TestTools;
namespace BracerLib.Editor.Utility
{
[ExcludeFromCoverage]
public static class TextureUtility
{
public static Texture2D MakeTexture(int width, int height, Color color)
{
var pixels = new Color[width * height];
for (var i = 0; i < pixels.Length; i++)
pixels[i] = color;
var tex = new Texture2D(width, height);
tex.SetPixels(pixels);
tex.Apply();
return tex;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 73762c73e5e84089b53c84bbf6e66665
timeCreated: 1716170448