using System; using UnityEditor; using UnityEngine; using Ubisoft.Hotel.Package.Editor; namespace Ubisoft.Hotel.PackageManager.Editor { [CustomEditor(typeof(PackageManager))] internal sealed class PackageManagerEditor : UnityEditor.Editor { private static PackageManagerEditor Instance { get; set; } private const string SECTION_GENERAL_SETTING_TITLE = "1) General settings"; private const string SECTION_COMMON_PROPERTIES_TITLE = "2) Permanent inspector properties"; private const string SECTION_SELECTED_PACKAGE_SUITE_TITLE = "3) Selected package suite"; private const string SECTION_RELEASE_TITLE = "4) Release"; private const string SECTION_BUILD_TITLE = "5) Build"; private const string DROPDOWN_PACKAGE_SUITE = "Package suite"; private const string DROPDOWN_BUILD_TARGET = "Editor build target"; private const string BUTTON_EXPORT_SETTINGS = "Export settings"; private const string BUTTON_IMPORT_SETTINGS = "Import settings"; public const string BUTTON_GENERATE_PACKAGE_SUITES = "Generate package suites"; private const string BUTTON_RELEASE_VERSION = "Release version"; private const string BUTTON_BUILD_PLAYER = "Build player"; private const string BUTTON_APPLY_CHANGES = "Apply changes"; private const string BUTTON_RESET = "Reset"; private const string TOOLTIP_EXPORT_SETTINGS = "Export your project's hotel settings so you can import them into another project"; private const string TOOLTIP_IMPORT_SETTINGS = "Import another project's hotel settings into this project. Be aware that this project's current hotel settings will be overridden"; private static readonly string TOOLTIP_GENERATE_PACKAGE_SUITES = $"Generate a set of ScriptableObjects, which represents the package suites defined in {PackageManager.CONSUMER_BUILD_SUITE_PROVIDER_FILENAME}, so that you can easily interact with them"; private const string TOOLTIP_RELEASE_VERSION = "Release version (bump version & tag)"; private const string TOOLTIP_BUILD_PLAYER = "Build the player with the selected package suite as a build configuration. Use this to test the build command called by your CI/CD pipeline"; private const string TOOLTIP_APPLY_CHANGES = "Apply the properties of the package suite that is selected along with all changes that you may have made to the package suite"; private const string RELEASE_BUMP_VERSION_TITLE = "Bump version"; private const string RELEASE_TAG_TITLE = "Tag"; private const string RELEASE_TAG_NAME_TITLE = "Tag name"; public static void Clear() { if (Instance != null) { Instance.PackageManager = null; Instance.AppSpaceBuildSuiteProvider = null; Instance.CurrentSuiteEditor = null; Instance.AppSpaceBuildSuiteEditor = null; Instance.PackageSpaceBuildSuiteEditor = null; Instance.CombinedSpaceBuildSuiteEditor = null; } } private enum ETab { Basic, AppSpace, PackageSpace, CombinedSpace } private ETab m_selectedTab = ETab.Basic; private readonly string[] m_tabNames = Enum.GetNames(typeof(ETab)); private PackageManager PackageManager { get; set; } private AppSpaceBuildSuiteProvider AppSpaceBuildSuiteProvider { get; set; } private PackageSuiteEditor CurrentSuiteEditor { get; set; } private UnityEditor.Editor AppSpaceBuildSuiteEditor { get; set; } private UnityEditor.Editor PackageSpaceBuildSuiteEditor { get; set; } private UnityEditor.Editor CombinedSpaceBuildSuiteEditor { get; set; } private UnityEditor.Editor GameVersionPropertyEditor { get; set; } private UnityEditor.Editor BuildSettingsEditor { get; set; } private SerializedProperty ApplyChangesAutomaticallyProperty { get; set; } private SerializedProperty AskConfirmationBeforeSyncProperty { get; set; } private bool NeedsToApply { get; set; } = false; private string BuildArguments { get; set; } = null; private bool ReleaseBumpVersionIsEnabled { get; set; } = true; private bool ReleaseTagIsEnabled { get; set; } = true; private string ReleaseTagName { get; set; } = null; private void Init() { Instance = this; PackageManager = (PackageManager)target; AppSpaceBuildSuiteProvider = PackageManager.AppSpaceBuildSuiteProvider; AppSpaceBuildSuiteEditor = CreateEditor(PackageManager.AppSpaceBuildSuite); PackageSpaceBuildSuiteEditor = CreateEditor(PackageManager.PackageSpaceBuildSuite); CombinedSpaceBuildSuiteEditor = CreateEditor(PackageManager.CombinedSpaceBuildSuite); GameVersionPropertyEditor = CreateEditor(AppSpaceBuildSuiteProvider.GameVersionProperty); BuildSettingsEditor = CreateEditor(AppSpaceBuildSuiteProvider.BuildSettings); ApplyChangesAutomaticallyProperty = serializedObject.FindProperty(PackageManager.ATT_APPLY_CHANGES_AUTOMATICALLY); AskConfirmationBeforeSyncProperty = serializedObject.FindProperty(PackageManager.ATT_ASK_CONFIRMATION_BEFORE_SYNC); NeedsToApply = false; UpdateCurrentSuiteEditor(); } private void UpdateCurrentSuiteEditor() { CurrentSuiteEditor = null; if (AppSpaceBuildSuiteProvider != null) { PackageSuite currentPackageSuite = AppSpaceBuildSuiteProvider.GetCurrentPackageSuite(); if (currentPackageSuite != null) { CurrentSuiteEditor = (PackageSuiteEditor)CreateEditor(currentPackageSuite); } } } private bool NeedsToInit() { return Instance == null || PackageManager == null; } public void Apply(bool force = false) { if (NeedsToApply || force) { NeedsToApply = false; if (PackageManager != null) { PackageManager.Apply(true); } } } public override void OnInspectorGUI() { if (NeedsToInit()) { Init(); } serializedObject.Update(); m_selectedTab = (ETab)GUILayout.Toolbar((int)m_selectedTab, m_tabNames); switch (m_selectedTab) { case ETab.Basic: DrawAppSpace(false); break; case ETab.AppSpace: DrawAppSpace(true); break; case ETab.PackageSpace: DrawPackageSpace(); break; case ETab.CombinedSpace: DrawCombinedSpace(); break; } } #region AppSpace private void DrawAppSpace(bool fullMode) { GUIStyle titleStyle = EditorStyles.whiteLabel; EditorGUI.BeginChangeCheck(); bool uiIsEnabled = AppSpaceBuildSuiteProvider.IsEnabled(); bool appSpaceIsEmpty = AppSpaceBuildSuiteProvider.IsEmpty(); DrawSectionSeparator(); bool continueDrawing = DrawGeneralSettingsSection(titleStyle, uiIsEnabled, appSpaceIsEmpty, fullMode); if (continueDrawing && uiIsEnabled) { DrawSectionSeparator(); DrawCommonProperties(titleStyle); DrawSectionSeparator(); DrawPackageSuiteSection(titleStyle, fullMode); DrawSectionSeparator(); DrawReleaseSection(titleStyle); DrawSectionSeparator(); DrawBuildSuiteSection(titleStyle, fullMode); } else if (!continueDrawing) { GUIUtility.ExitGUI(); return; } if (EditorGUI.EndChangeCheck()) { _ = serializedObject.ApplyModifiedProperties(); if (PackageManager != null && PackageManager.ApplyChangesAutomatically) { NeedsToApply = true; } } } private bool DrawGeneralSettingsSection(GUIStyle titleStyle, bool appSpaceProviderIsEnabled, bool appSpaceProviderIsEmpty, bool fullMode) { string message = SECTION_GENERAL_SETTING_TITLE; EditorGUILayout.LabelField(message, titleStyle); bool continueDrawing = DrawGeneralSettingsButtons(appSpaceProviderIsEnabled, appSpaceProviderIsEmpty, fullMode); if (!continueDrawing) { return false; } else if (!appSpaceProviderIsEnabled) { return true; } // Choose current PackageSuite message = $"'{DROPDOWN_PACKAGE_SUITE}' lets you select the package suite to apply"; EditorGUILayout.HelpBox(message, MessageType.None); string[] options = AppSpaceBuildSuiteProvider.PackageSuiteNames; if (options != null) { int latestIndex = AppSpaceBuildSuiteProvider.GetCurrentPackageSuiteIndex(); int newIndex = EditorGUILayout.Popup(DROPDOWN_PACKAGE_SUITE, latestIndex, options); if (newIndex != latestIndex) { AppSpaceBuildSuiteProvider.CurrentPackageSuiteName = options[newIndex]; UpdateCurrentSuiteEditor(); } } // Build target message = $"'{DROPDOWN_BUILD_TARGET}' lets you select the platform you want to simulate in Editor"; EditorGUILayout.HelpBox(message, MessageType.None); options = PackageManager.BuildTargetOptions; if (options != null) { int latestIndex = PackageManager.BuildTargetIndex; int newIndex = EditorGUILayout.Popup(DROPDOWN_BUILD_TARGET, latestIndex, options); if (newIndex != latestIndex) { PackageManager.BuildTargetIndex = newIndex; // Suites are generated since they might depend on the build target PackageManager.Generate(); } } return true; } private bool DrawGeneralSettingsButtons(bool appSpaceProviderIsEnabled, bool appSpaceProviderIsEmpty, bool fullMode) { bool returnValue = true; GUILayoutOption buttonOption = GUILayout.Width(300); if (!appSpaceProviderIsEnabled) { string message = $"Hotel suites are explicitly disabled for {EditorUserBuildSettings.activeBuildTarget}. "; message += $" You can enable them by changing {PackageManager.CONSUMER_BUILD_SUITE_PROVIDER_FILENAME_NO_EXT}.IsEnabled()."; EditorGUILayout.HelpBox(message, MessageType.Info); return true; } if (appSpaceProviderIsEmpty) { string message = $"Define at least one package suite in {PackageManager.CONSUMER_BUILD_SUITE_PROVIDER_FILENAME} or "; message += $"import settings from another project to start using Hotel settings"; EditorGUILayout.HelpBox(message, MessageType.Info); } GUILayout.BeginVertical(); if (fullMode) { if (appSpaceProviderIsEnabled) { if (GUILayout.Button(new GUIContent(BUTTON_EXPORT_SETTINGS, TOOLTIP_EXPORT_SETTINGS), buttonOption)) { var path = EditorUtility.SaveFilePanel("Export Hotel settings", "", "", ""); if (path.Length != 0) { PackageManager.ExportSettings(path); returnValue = false; } } } if (GUILayout.Button(new GUIContent(BUTTON_IMPORT_SETTINGS, TOOLTIP_IMPORT_SETTINGS), buttonOption)) { string path = EditorUtility.OpenFilePanel("Import Hotel settings", "", ""); if (path.Length != 0) { PackageManager.ImportSettings(path); // Importing settings will destroy current settings so we need to clear them and exit Clear(); returnValue = false; } } } if (returnValue) { if (GUILayout.Toggle(PackageManager.IsOptimisedMode, new GUIContent("Optimised mode", "It is recommended for consumers that are not modifying any Hotel packages")) != PackageManager.IsOptimisedMode) { PackageManager.SwitchIsOptimiseMode(); } if (GUILayout.Button(new GUIContent(BUTTON_GENERATE_PACKAGE_SUITES, TOOLTIP_GENERATE_PACKAGE_SUITES), buttonOption)) { PackageManager.Generate(); returnValue = false; } if (appSpaceProviderIsEnabled) { if (GUILayout.Button(new GUIContent(BUTTON_APPLY_CHANGES, TOOLTIP_APPLY_CHANGES), buttonOption)) { Apply(true); returnValue = false; } } DrawSectionSeparator(); if (appSpaceProviderIsEnabled && fullMode) { if (!returnValue) { return returnValue; } if (ApplyChangesAutomaticallyProperty != null) { string msg = $"'Apply Changes Automatically': When enabled all changes that you may have made to current package suite properties will be applied automatically "; msg += $"when focus leaves {PackageManager.SETTINGS_FILENAME}. When disabled you need to hit '{BUTTON_APPLY_CHANGES}' button to apply changes."; EditorGUILayout.HelpBox(msg, MessageType.None); _ = EditorGUILayout.PropertyField(ApplyChangesAutomaticallyProperty); } if (AskConfirmationBeforeSyncProperty != null) { string msg = "'Ask Confirmation Before Sync': When enabled a confirmation popup will be prompted to you after making some changes to the code that produces package suites"; msg += " so you can decide whether or not to apply changes. When disabled these changes will be applied automatically."; EditorGUILayout.HelpBox(msg, MessageType.None); _ = EditorGUILayout.PropertyField(AskConfirmationBeforeSyncProperty); } } } GUILayout.EndVertical(); return returnValue; } private void DrawCommonProperties(GUIStyle titleStyle) { EditorGUILayout.LabelField(SECTION_COMMON_PROPERTIES_TITLE, titleStyle); if (GameVersionPropertyEditor != null) { string message = $"This section contains inspector properties that are shared across all package suites."; message += " These are the only inspector properties that are permanent."; EditorGUILayout.HelpBox(message, MessageType.None); GameVersionPropertyEditor.OnInspectorGUI(); } } private void DrawPackageSuiteSection(GUIStyle titleStyle, bool fullMode) { // Draw the content of the current suite string message = SECTION_SELECTED_PACKAGE_SUITE_TITLE; EditorGUILayout.LabelField(message, titleStyle); message = $"This section shows the package properties defined in the selected package suite as they are defined in {PackageManager.CONSUMER_BUILD_SUITE_PROVIDER_FILENAME}"; EditorGUILayout.HelpBox(message, MessageType.None); message = $"Change these properties ONLY for testing purposes.\nAny changes that are meant to be persistent MUST be made in {PackageManager.CONSUMER_BUILD_SUITE_PROVIDER_FILENAME}"; EditorGUILayout.HelpBox(message, MessageType.Info); DrawSectionSeparator(); if (CurrentSuiteEditor != null) { CurrentSuiteEditor.Draw(fullMode); } } private void DrawReleaseSection(GUIStyle titleStyle) { if (PackageManager == null) { return; } // Draw build properties string message = SECTION_RELEASE_TITLE; EditorGUILayout.LabelField(message, titleStyle); message = $"Hitting {BUTTON_RELEASE_VERSION} button may commit and push to your repository. If {RELEASE_TAG_TITLE} is enabled then CICD will trigger a job to build a collection of full builds."; EditorGUILayout.HelpBox(message, MessageType.Info); DrawSectionSeparator(); GUILayout.BeginVertical(); // Bump version ReleaseBumpVersionIsEnabled = GUILayout.Toggle(ReleaseBumpVersionIsEnabled, RELEASE_BUMP_VERSION_TITLE); // Tag ReleaseTagIsEnabled = GUILayout.Toggle(ReleaseTagIsEnabled, RELEASE_TAG_TITLE); if (ReleaseTagIsEnabled) { GUILayout.BeginHorizontal(); GUILayout.Label(RELEASE_TAG_NAME_TITLE, GUILayout.Width(100)); string calculatedArguments = PackageManager.Release_GetDefaultTag(ReleaseBumpVersionIsEnabled); string arguments = ReleaseTagName; if (string.IsNullOrEmpty(ReleaseTagName)) { arguments = calculatedArguments; } ReleaseTagName = GUILayout.TextArea(arguments); if (ReleaseTagName == calculatedArguments) { ReleaseTagName = null; } if (GUILayout.Button(new GUIContent(BUTTON_RESET, "Use release/ as tag name"), GUILayout.Width(50))) { ReleaseTagName = null; } GUILayout.EndHorizontal(); } // Release button GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); float width = 300f; GUILayoutOption widthOption = GUILayout.Width(width); if (GUILayout.Button(new GUIContent(BUTTON_RELEASE_VERSION, TOOLTIP_RELEASE_VERSION), widthOption)) { Debug.EditorLog(EditorApplication.applicationContentsPath); PackageManager.Release_Perform(ReleaseBumpVersionIsEnabled, ReleaseTagIsEnabled, ReleaseTagName); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void DrawBuildSuiteSection(GUIStyle titleStyle, bool fullMode) { // Draw build properties string message = SECTION_BUILD_TITLE; EditorGUILayout.LabelField(message, titleStyle); if (fullMode) { message = "4.1) Player"; EditorGUILayout.LabelField(message, titleStyle); } // Build Settings if (BuildSettingsEditor != null) { // Build Settings GUILayout.BeginVertical(); if (fullMode) { GUILayout.Space(10); } BuildSettingsEditor.OnInspectorGUI(); GUILayout.Space(20); GUILayout.EndVertical(); } GUILayout.BeginHorizontal(); GUILayout.Label("CLI arguments:", GUILayout.Width(100)); string calculatedArguments = (CurrentSuiteEditor == null) ? "" : CurrentSuiteEditor.GetSetupSelectedValuesAsString(); string arguments = BuildArguments; if (string.IsNullOrEmpty(BuildArguments)) { arguments = calculatedArguments; } BuildArguments = GUILayout.TextArea(arguments); if (BuildArguments.Equals(calculatedArguments)) { BuildArguments = null; } if (GUILayout.Button(new GUIContent(BUTTON_RESET, "Set the setup of the selected package suite"), GUILayout.Width(50))) { BuildArguments = null; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); float width = 300f; GUILayoutOption widthOption = GUILayout.Width(width); if (GUILayout.Button(new GUIContent(BUTTON_BUILD_PLAYER, TOOLTIP_BUILD_PLAYER), widthOption)) { arguments = BuildArguments == null ? calculatedArguments : BuildArguments; string[] argumentsAsArray = null; if (!string.IsNullOrEmpty(arguments)) { argumentsAsArray = arguments.Split(' '); } // Consumer is prompted with a popup to enter location right away if (AppSpaceBuildSuiteProvider.BuildSettings.AskForLocation) { // We need to save consumer's location in order to make sure it won't get lost since building player involves some steps // that might trigger recompilation BuildPlayerOptions buildPlayerOptions = BuildStep_BuildPlayer.GetBuildPlayerOptionsViaReflection(true); BuildStep.BuildSettings.location = HtSystemIO.NormalizePath(buildPlayerOptions.locationPathName); BuildStep.BuildSettings.Save(); } EPlatform platform = PackageManager.GetPlatform(); PackageManager.Build_PerformFullBatch(platform, argumentsAsArray, true, false); GUIUtility.ExitGUI(); return; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); if (fullMode) { DrawSectionSeparator(); message = "4.2) Properties"; EditorGUILayout.LabelField(message, titleStyle); message = $"This section shows the build properties that will be applied upon hitting '{BUTTON_APPLY_CHANGES}' or '{BUTTON_BUILD_PLAYER}' buttons in '{SECTION_GENERAL_SETTING_TITLE}' section.\n"; message += $"These properties are built out of the package properties of the selected package suite, which you can change in '{SECTION_SELECTED_PACKAGE_SUITE_TITLE}' section."; EditorGUILayout.HelpBox(message, MessageType.None); if (AppSpaceBuildSuiteEditor != null) { AppSpaceBuildSuiteEditor.OnInspectorGUI(); } } } private void DrawSectionSeparator() { EditorGUILayout.Space(); EditorGUILayout.Space(); } #endregion private void DrawPackageSpace() { if (PackageSpaceBuildSuiteEditor != null) { PackageSpaceBuildSuiteEditor.OnInspectorGUI(); } } private void DrawCombinedSpace() { if (CombinedSpaceBuildSuiteEditor != null) { CombinedSpaceBuildSuiteEditor.OnInspectorGUI(); } } } }