/* Text Animator for Yarn Spinner is licensed to you under the terms found in the file LICENSE.md. */ #if YS_USE_TEXT_ANIMATOR_2 || YS_USE_TEXT_ANIMATOR_3 #define USE_TEXT_ANIMATOR #endif #nullable enable namespace Yarn.Unity.Addons.TextAnimatorIntegration.Editor { using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.Build; using System.Collections.Generic; using System.Reflection; using System.Linq; #if YS_USE_TEXT_ANIMATOR_2 using Febucci.UI; using ActionScriptableBase = Febucci.UI.Actions.ActionScriptableBase; #elif YS_USE_TEXT_ANIMATOR_3 using Febucci.TextAnimatorForUnity; using ActionScriptableBase = Febucci.TextAnimatorForUnity.Actions.Core.ActionScriptableBase; #else using ActionScriptableBase = UnityEngine.ScriptableObject; #endif public class TextAnimatorConfigurationWindow : EditorWindow { private const string YSTA2Define = "YS_USE_TEXT_ANIMATOR_2"; private const string YSTA3Define = "YS_USE_TEXT_ANIMATOR_3"; private const string TextAnimatorV2SettingsType = "Febucci.UI.TextAnimatorSettings"; private const string TextAnimatorV3SettingsType = "Febucci.TextAnimatorForUnity.TextAnimatorSettings"; private const string TextAnimatorSettingsName = "TextAnimatorSettings"; private const string TextAnimatorPageURL = "https://www.febucci.com/tools/text-animator-unity"; private const string installedAndNotDefinedUXMLGuid = "b2fe556531a6644c0b405d6482d9de4f"; private const string notInstalledUXMLGuid = "c39c92b7ce46b46329bda933bb30f5fc"; private const string InstalledAndDefinedUXMLGuid = "02d739c3d6bb545fdb6d4bf14bf07822"; private const string TextAnimatorRunnerPrefabGuidV2 = "eeb327a65c4bc4376ae432ce641f04af"; private const string TextAnimatorRunnerPrefabGuidV3 = "cd09801223b5f4504a921d33ef1ff98c"; private const string TextAnimatorRunnerPackageGuidV2 = "5a3687d8c4744405692bd44867d6ac58"; private const string TextAnimatorRunnerPackageGuidV3 = "014e0ab0fc9ca4cd69ce3203bfe646f9"; // this is a terrible hack but seems to work // it will get all the possible build targets for whatever version of unity the package is installed in // this works because unity has kindly defined a whole bunch of public static build targets already // would be nicer to be able to go like `NamedBuildTarget.All` and have it return a list of them, but oh well private static List AllBuildTargets { get { var targets = new List(); // we have this because there are a few targets (like CloudRendering) that are redirected to a different target // and these change on a unity version basis so we can't just manually ignore them like we do with Unknown // so instead if we have run into a build target with a resolved name that we've already seen we ignore it var names = new HashSet(); var fields = typeof(NamedBuildTarget).GetFields(BindingFlags.Public | BindingFlags.Static); foreach (var field in fields) { // so turns out weird things can happen when trying to set or get defines on unknown // plus it doesn't really mean anything so why bother, let's just skip it if (field.Name == "Unknown") { continue; } // if it isn't a NamedBuildTarget we ignore it if (field.FieldType == typeof(NamedBuildTarget)) { var t = (NamedBuildTarget)field.GetValue(null); // Stadia is deprecated and will throw an error if we // attempt to use it; ignore it if (t.TargetName == "Stadia") { continue; } if (names.Add(t.TargetName)) { targets.Add(t); } } } return targets; } } // Because we don't know if TextAnimator is installed we can't really safely add the scripting define // So this property does a check looking for the TextAnimator global settings in the resources folder // if we find something with it's name we then check if the name of the type is what we'd expect // this isn't perfect but seems to work pretty well and is mostly robust to change private bool IsTextAnimatorInstalled { get { var resources = Resources.LoadAll(TextAnimatorSettingsName); foreach (var resource in resources) { if (resource.GetType().ToString().EndsWith(TextAnimatorV2SettingsType)) { return true; } else if (resource.GetType().ToString().EndsWith(TextAnimatorV3SettingsType)) { return true; } } return false; } } private bool IsTextAnimatorV2Installed { get { var resources = Resources.LoadAll(TextAnimatorSettingsName); foreach (var resource in resources) { if (resource.GetType().ToString().EndsWith(TextAnimatorV2SettingsType)) { return true; } } return false; } } private bool IsTextAnimatorV3Installed { get { var resources = Resources.LoadAll(TextAnimatorSettingsName); foreach (var resource in resources) { if (resource.GetType().ToString().EndsWith(TextAnimatorV3SettingsType)) { return true; } } return false; } } private string? ScriptingDefine { get { if (IsTextAnimatorV2Installed) { return YSTA2Define; } else if (IsTextAnimatorV3Installed) { return YSTA3Define; } else { return null; } } } private void AddDefines(Dictionary filters) { var definitionSymbol = ScriptingDefine; if (string.IsNullOrEmpty(definitionSymbol)) { Debug.LogWarning("Was unable to determine the valid scripting define for this version of Text Animator. Cannot add the necessary defines."); return; } foreach (var target in AllBuildTargets) { try { if (filters.TryGetValue(target.TargetName, out var value)) { if (value == false) { Debug.Log($"skipping {target.TargetName}"); continue; } } PlayerSettings.GetScriptingDefineSymbols(target, out var defines); if (defines.Contains(definitionSymbol)) { continue; } else { var allSymbols = defines.ToList(); allSymbols.Add(definitionSymbol); PlayerSettings.SetScriptingDefineSymbols(target, allSymbols.ToArray()); } } catch (System.ArgumentException) { Debug.LogWarning($"{target.TargetName} is not supported"); continue; } } this.Close(); } private enum InstallationState { InstalledAndDefined, InstalledAndNotDefined, NotInstalledAndNotDefined, NotInstalledAndDefined, } private InstallationState installationState { get { #if USE_TEXT_ANIMATOR if (IsTextAnimatorInstalled) { return InstallationState.InstalledAndDefined; } return InstallationState.NotInstalledAndDefined; #else if (IsTextAnimatorInstalled) { return InstallationState.InstalledAndNotDefined; } return InstallationState.NotInstalledAndNotDefined; #endif } } [SerializeField] private VisualTreeAsset? m_VisualTreeAsset = default; [MenuItem("Window/Yarn Spinner/Text Animator Integration Configuration")] public static void OpenWindow() { TextAnimatorConfigurationWindow window = GetWindow(true, "Yarn Spinner Text Animator Configuration", true); var height = 438; switch (window.installationState) { case InstallationState.NotInstalledAndDefined: goto case InstallationState.NotInstalledAndNotDefined; case InstallationState.NotInstalledAndNotDefined: height += 54; break; case InstallationState.InstalledAndDefined: if (TextAnimationActionState.DefaultActionIsRegistered() == TextAnimationActionState.RegistrationResult.NotRegistered) { height += 99; } else { height += 19; } break; case InstallationState.InstalledAndNotDefined: height += 63 + AllBuildTargets.Count * 19; break; } window.minSize = new Vector2(512, height); window.maxSize = window.minSize; window.Show(); } public void CreateGUI() { VisualElement root = rootVisualElement; if (m_VisualTreeAsset == null) { return; } var installation = installationState; string activeContainerGUID = installation switch { InstallationState.InstalledAndDefined => InstalledAndDefinedUXMLGuid, InstallationState.InstalledAndNotDefined => installedAndNotDefinedUXMLGuid, _ => notInstalledUXMLGuid, }; // Instantiate UXML VisualElement labelFromUXML = m_VisualTreeAsset.Instantiate(); root.Add(labelFromUXML); root.Q().style.flexGrow = 1; var tree = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(activeContainerGUID)); if (tree == null) { return; } var container = tree.CloneTree(); root.Add(container); switch (installation) { case InstallationState.InstalledAndDefined: CreateInstalledAndDefinedGUI(container); break; case InstallationState.InstalledAndNotDefined: CreateInstalledAndNotDefinedGUI(container); break; case InstallationState.NotInstalledAndDefined: goto case InstallationState.NotInstalledAndNotDefined; case InstallationState.NotInstalledAndNotDefined: CreateNotInstalledGUI(container); break; } } public void CreateInstalledAndNotDefinedGUI(VisualElement root) { var container = root.Q("scriptingToggleContainer"); if (container == null) { return; } var button = root.Q