288 lines
8.8 KiB
C#
288 lines
8.8 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|