/* Yarn Spinner is licensed to you under the terms found in the file LICENSE.md. */ #nullable enable namespace Yarn.Unity.Editor { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using Yarn.Unity; internal class CommandsCollection : IActionRegistration { public List commandRegistrations = new List(); public List<(string Name, Delegate Function)> functionRegistrations = new List<(string Name, Delegate Function)>(); public IEnumerable GetListItems() { foreach (var registrationMethod in Actions.ActionRegistrationMethods) { registrationMethod.Invoke(this, RegistrationType.Compilation); } yield return new CommandsWindow.HeaderListItem("Commands"); foreach (var command in commandRegistrations) { yield return new CommandsWindow.CommandListItem(command); } // Add a fake 'stop' command to the list, so that it appears in the // window System.Action fakeStop = () => { }; yield return new CommandsWindow.CommandListItem(new Actions.CommandRegistration("stop", fakeStop)); } public void AddCommandHandler(string commandName, Delegate handler) { commandRegistrations.Add(new Actions.CommandRegistration(commandName, handler)); } public void AddCommandHandler(string commandName, MethodInfo methodInfo) { commandRegistrations.Add(new Actions.CommandRegistration(commandName, methodInfo)); } public void AddFunction(string name, Delegate implementation) => functionRegistrations.Add((name, implementation)); public void RemoveCommandHandler(string commandName) { // No-op } public void RemoveFunction(string name) { // No-op } public void RegisterFunctionDeclaration(string name, Type returnType, Type[] parameterTypes) { /* TODO: Implement */ } } public class CommandsWindow : EditorWindow { public interface IListItem { string DisplayName { get; } } public class HeaderListItem : IListItem { public string DisplayName { get; set; } public HeaderListItem(string displayName) { DisplayName = displayName; } } public class CommandListItem : IListItem { internal Actions.CommandRegistration Command; internal CommandListItem(Actions.CommandRegistration command) { Command = command; } public string DisplayName => Command.Name; } [SerializeField] private VisualTreeAsset? uxml; [SerializeField] private VisualTreeAsset? listItemUXML; [SerializeField] private StyleSheet? stylesheet; private List listItems = new(); private List filteredListItems = new(); [MenuItem("Window/Yarn Spinner/Commands...")] static void Summon() { var window = GetWindow("Yarn Commands"); window.Show(); } void CreateGUI() { if (uxml == null) { Debug.LogWarning($"{GetType()}'s {nameof(uxml)} is null"); return; } uxml.CloneTree(rootVisualElement); var listView = rootVisualElement.Q(); var searchField = rootVisualElement.Q(); searchField.RegisterValueChangedCallback(evt => { UpdateFilter(listView, searchField.value); }); var commandsCollection = new CommandsCollection(); listItems = new List(commandsCollection.GetListItems().OrderBy(item => item.DisplayName)); UpdateFilter(listView, searchField.value); // Set ListView.makeItem to initialize each entry in the list. listView.makeItem = () => { if (listItemUXML == null) { throw new InvalidOperationException($"Can't create new list item: {nameof(listItemUXML)} is null"); } var result = listItemUXML.CloneTree(); result.styleSheets.Add(stylesheet); result.AddToClassList("commandListItem"); return result; }; // Set ListView.bindItem to bind an initialized entry to a data item. listView.bindItem = (VisualElement element, int index) => { var listItem = filteredListItems[index]; element.Q