using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace Unity.Tutorials.Core.Editor
{
///
/// Runs IET project initialization logic.
///
[InitializeOnLoad]
public static class UserStartupCode
{
const string k_DefaultsFolder = "Tutorial Defaults";
const string k_EditorLanguageInitializedState = "EditorLanguageInitialized";
const string k_EditorLanguagePreference = "EditorLanguage";
static bool DisplayWelcomeDialogOnStartup
{
get => TutorialFrameworkModel.s_DisplayWelcomeDialogOnStartup;
set => TutorialFrameworkModel.s_DisplayWelcomeDialogOnStartup.SetValue(value, true);
}
static bool IsLanguageInitialized() => SessionState.GetBool(k_EditorLanguageInitializedState, false);
static void SetLanguageInitialized() => SessionState.SetBool(k_EditorLanguageInitializedState, true);
static SystemLanguage LoadPreviousEditorLanguage() => (SystemLanguage)EditorPrefs.GetInt(k_EditorLanguagePreference, (int)SystemLanguage.English);
static void SaveCurrentEditorLanguage() => EditorPrefs.SetInt(k_EditorLanguagePreference, (int)LocalizationDatabaseProxy.currentEditorLanguage);
static UserStartupCode()
{
if (EditorApplication.isPlayingOrWillChangePlaymode
|| TutorialWindow.s_IsLoadingLayout)
{
return;
}
// Language change triggers an assembly reload.
if (LoadPreviousEditorLanguage() != LocalizationDatabaseProxy.currentEditorLanguage)
{
SaveCurrentEditorLanguage();
// There are several smaller and bigger localization issues with if we don't restart
// the Editor so let's query the user to do so.
string title = Localization.Tr(LocalizationKeys.k_TOCLabelTitle);
string message = Localization.Tr(LocalizationKeys.k_LanguageDialogMessage);
string ok = Localization.Tr(LocalizationKeys.k_LanguageDialogButtonOk);
string cancel = Localization.Tr(LocalizationKeys.k_LanguageDialogButtonCancel);
if (EditorUtility.DisplayDialog(title, message, ok, cancel))
{
RestartEditor();
}
}
EditorApplication.update += InitRunStartupCode;
}
internal static void RunStartupCode(TutorialProjectSettings projectSettings)
{
if (projectSettings.InitialScene != null)
{
EditorSceneManager.OpenScene(AssetDatabase.GetAssetPath(projectSettings.InitialScene));
}
BackupProjectAssets();
// Ensure Editor is in predictable state
EditorPrefs.SetString("ComponentSearchString", string.Empty);
Tools.current = Tool.Move;
if (TutorialEditorUtils.FindAssets().Any())
{
var existingWindow = EditorWindowUtils.FindOpenInstance();
if (existingWindow)
{
existingWindow.Close();
}
TutorialWindow.ShowWindow();
}
// NOTE camera settings can be applied successfully only after potential layout changes
if (projectSettings.InitialCameraSettings != null && projectSettings.InitialCameraSettings.Enabled)
{
projectSettings.InitialCameraSettings.Apply();
}
if (projectSettings.WelcomePage)
{
TutorialModalWindow.Show(projectSettings.WelcomePage);
}
}
static void InitRunStartupCode()
{
if (LocalizationDatabaseProxy.enableEditorLocalization && !IsLanguageInitialized())
{
/* Need to Request a script reload in order overcome Editor Localization issues
with static initialization when opening the project for the first time. */
SetLanguageInitialized();
EditorUtility.RequestScriptReload();
return;
}
/* Prepare the layout always. For example, the user might have moved the project around,
so we need to ensure the file paths in the layouts are correct. */
TutorialController.PrepareWindowLayouts();
EditorApplication.update -= InitRunStartupCode;
if (!DisplayWelcomeDialogOnStartup)
{
return;
}
DisplayWelcomeDialogOnStartup = false;
RunStartupCode(TutorialProjectSettings.Instance);
}
///
/// Restart the Editor.
///
internal static void RestartEditor()
{
// In older versions, calling EditorApplication.OpenProject() while having unsaved modifications
// can cause us to get stuck in a dialog loop. This seems to be fixed in 2020.1 (and newer?).
// As a workaround, ask for saving before starting to restart the Editor for real. However,
// we get the dialog twice and it can cause issues if user chooses first "Don't save" and then tries
// to "Cancel" in the second dialog.
#if !UNITY_2020_1_OR_NEWER
if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
#endif
{
EditorApplication.OpenProject(".");
}
}
internal static void BackupProjectAssets()
{
if (!TutorialProjectSettings.Instance.RestoreAssetsBackupOnTutorialReload)
{
return;
}
if (EditorApplication.isPlaying)
{
Debug.LogError("Defaults cannot be written during play mode");
return;
}
string defaultsPath = Path.Combine(Directory.GetParent(Application.dataPath).FullName, k_DefaultsFolder);
DirectoryInfo defaultsDirectory = new DirectoryInfo(defaultsPath);
if (defaultsDirectory.Exists)
{
foreach (var file in defaultsDirectory.GetFiles())
{
file.Delete();
}
foreach (var directory in defaultsDirectory.GetDirectories())
{
directory.Delete(true);
}
}
DirectoryCopy(Application.dataPath, defaultsPath);
}
internal static void DirectoryCopy(string sourceDirectory, string destinationDirectory, HashSet dirtyMetaFiles = default)
{
var sourceDir = new DirectoryInfo(sourceDirectory);
if (!sourceDir.Exists)
{
return;
}
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
foreach (var file in sourceDir.GetFiles())
{
string tempPath = Path.Combine(destinationDirectory, file.Name);
if (dirtyMetaFiles != null && string.Equals(Path.GetExtension(tempPath), ".meta", StringComparison.OrdinalIgnoreCase))
{
if (!File.Exists(tempPath)
|| !File.ReadAllBytes(tempPath).SequenceEqual(File.ReadAllBytes(file.FullName)))
{
dirtyMetaFiles.Add(tempPath);
}
}
file.CopyTo(tempPath, true);
}
foreach (var subdir in sourceDir.GetDirectories())
{
string tempPath = Path.Combine(destinationDirectory, subdir.Name);
DirectoryCopy(subdir.FullName, tempPath, dirtyMetaFiles);
}
}
///
/// Shows Tutorials window using the currently specified behaviour.
///
/// The displayed TutorialWindow
[Obsolete("Will be removed in v4. Use TutorialWindow.ShowWindow() instead")] //todo: remove in v4
public static TutorialWindow ShowTutorialWindow()
{
return TutorialWindow.ShowWindow();
}
}
}