using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using UnityEditorInternal; using UnityEngine; using Object = UnityEngine.Object; namespace UnityEditor.Tilemaps { /// /// A ScriptableObject that stores picks for a particular GridBrushBase type. /// The picks include a list of picks defined by the user and a limited list /// of picks which were last made by the user. The picks can be loaded onto /// the active Brush in the TilePalette. /// public class GridBrushPickStore : ScriptableObject { private static readonly string s_LibraryPickPath = "Library/GridBrush/Pick"; private static readonly string s_LastPickPath = "Library/GridBrush/Last"; private static readonly string s_UserPickPath = "Library/GridBrush/User"; private static readonly string s_LibraryAssetName = "Default"; private static readonly string s_AssetExtension = ".asset"; private static readonly string k_GridBrushPickLastSavedMaxCountPref = "GridBrushPickLastSavedMaxCount"; private static readonly string k_GridBrushPickUserSavedMaxCountPref = "GridBrushPickUserSavedMaxCount"; private static readonly string k_GridBrushPickLastIndexPref = "GridBrushPickLastIndex"; private static readonly string k_GridBrushPickLastSavedCountPref = "GridBrushPickLastSavedCount"; internal static int gridBrushPickLastSavedMaxCount { get => EditorPrefs.GetInt(k_GridBrushPickLastSavedMaxCountPref, 5); set => EditorPrefs.SetInt(k_GridBrushPickLastSavedMaxCountPref, value); } internal static int gridBrushPickUserSavedMaxCount { get => EditorPrefs.GetInt(k_GridBrushPickUserSavedMaxCountPref, 50); set => EditorPrefs.SetInt(k_GridBrushPickUserSavedMaxCountPref, value); } internal static int gridBrushPickLastIndex { get => EditorPrefs.GetInt(k_GridBrushPickLastIndexPref, -1); set => EditorPrefs.SetInt(k_GridBrushPickLastIndexPref, value); } internal static int gridBrushPickLastSavedCount { get => EditorPrefs.GetInt(k_GridBrushPickLastSavedCountPref, 0); set => EditorPrefs.SetInt(k_GridBrushPickLastSavedCountPref, value); } [HideInInspector] [SerializeField] private int m_UserSavedCount; private Type m_FilteredBrushType; [HideInInspector] [SerializeField] private string m_FilteredBrushText; private List m_LastSavedBrushes = new List(); [SerializeField] private List m_UserSavedBrushes = new List(); private List m_FilteredUserSavedBrushes = new List(); /// /// The index of the latest last pick that was made. /// public int lastIndex => gridBrushPickLastIndex; /// /// A list of GridBrushBases which represent the last picks made. /// public List lastSavedBrushes { get => m_LastSavedBrushes; } /// /// A list of GridBrushBases which represent the user picks made. /// public List userSavedBrushes { get => m_UserSavedBrushes; } /// /// A list of GridBrushBases which represent the user picks made /// filtered by the current filter type. /// public List filteredUserSavedBrushes { get => m_FilteredUserSavedBrushes; } private void OnDestroy() { foreach (var brush in m_LastSavedBrushes) { if (!EditorUtility.IsPersistent(brush)) DestroyImmediate(brush); } m_LastSavedBrushes.Clear(); foreach (var brush in m_UserSavedBrushes) { if (!EditorUtility.IsPersistent(brush)) DestroyImmediate(brush); } m_UserSavedBrushes.Clear(); } internal int GetIndexOfLastSavedBrush(GridBrushBase brush) { return m_LastSavedBrushes.IndexOf(brush); } internal int GetIndexOfUserBrush(GridBrushBase brush) { return m_UserSavedBrushes.IndexOf(brush); } internal int GetIndexOfUserBrushFromFilteredIdx(int filteredIdx) { if (filteredIdx < 0 || filteredIdx >= filteredUserSavedBrushes.Count) return -1; return GetIndexOfUserBrush(filteredUserSavedBrushes[filteredIdx]); } internal bool IsValid() { return (lastSavedBrushes.Count == 0 || lastSavedBrushes[0] != null) && (userSavedBrushes.Count == 0 || userSavedBrushes[0] != null) && (filteredUserSavedBrushes.Count == 0 || filteredUserSavedBrushes[0] != null); } /// /// Adds the specified Brush as a new last pick. /// /// /// This will add a copy of the Brush as a new last pick. If this new copy is over /// the limit of the number of last picks, the oldest last pick will be erased. /// The last index of the GridBrushPickStore will be changed to this new addition. /// /// Brush to save as a new last pick. public void AddNewLastSavedBrush(GridBrushBase brush) { if (brush == null) return; var clone = Instantiate(brush); var name = brush.name; if (String.IsNullOrWhiteSpace(name)) name = brush.GetType().Name; clone.name = name; var nextIndex = (lastIndex + 1) % gridBrushPickLastSavedMaxCount; if (nextIndex < m_LastSavedBrushes.Count) m_LastSavedBrushes[nextIndex] = clone; else m_LastSavedBrushes.Add(clone); SaveLibraryGridBrushAsset(clone, nextIndex, false); gridBrushPickLastIndex = nextIndex; gridBrushPickLastSavedCount = m_LastSavedBrushes.Count; GridPaintingState.InvokeBrushPickStoreChanged(); } /// /// Clears the Brush at the index of the last pick list. /// /// /// This will clear the pick at the index, but will not change the size of the /// pick list. /// /// The index of the Brush of the last pick list to clear. public void ClearLastSavedBrush(int index) { if (index < 0 || index >= m_LastSavedBrushes.Count) return; m_LastSavedBrushes[index] = null; FilterBrushes(); SaveGridBrushPickStoreAsset(); var gridBrushPath = GenerateGridBrushInstanceLibraryPath(index, false); FileUtil.DeleteFileOrDirectory(gridBrushPath); } /// /// Adds the specified Brush as a new user pick. /// /// Brush to save as a new user pick. public void AddNewUserSavedBrush(GridBrushBase brush) { if (brush == null) return; var clone = Instantiate(brush); var name = brush.name; if (String.IsNullOrWhiteSpace(name)) name = brush.GetType().Name; clone.name = name; m_UserSavedBrushes.Add(clone); m_UserSavedCount = m_UserSavedBrushes.Count; EditorUtility.SetDirty(this); FilterBrushes(); SaveLibraryGridBrushAsset(clone, m_UserSavedCount - 1, true); SaveGridBrushPickStoreAsset(); } /// /// Swaps the position of the specified Brush /// /// Index of the Brush to swap. /// Index to swap the Brush to. public void SwapUserSavedBrushes(int oldIdx, int newIdx) { if (oldIdx < 0 || oldIdx >= userSavedBrushes.Count) return; if (newIdx < 0 || newIdx >= userSavedBrushes.Count) return; var brush = userSavedBrushes[oldIdx]; userSavedBrushes.RemoveAt(oldIdx); userSavedBrushes.Insert(newIdx, brush); if (AssetDatabase.IsNativeAsset(this)) { SaveGridBrushPickStoreAsset(); } else { SaveUserSavedBrushFromIndex(oldIdx < newIdx ? oldIdx : newIdx); } } private void SaveUserSavedBrushFromIndex(int index) { if (index < 0 || index >= m_UserSavedBrushes.Count) return; for (int i = index; i < m_UserSavedCount; ++i) SaveLibraryGridBrushAsset(m_UserSavedBrushes[i], i, true); FilterBrushes(); } /// /// Saves over a brush in the user pick with the given index with /// the specified Brush. /// /// The index of the Brush of the user pick list to save over. /// Brush to save over as a user pick. public void SaveUserSavedBrush(int index, GridBrushBase brush) { if (brush == null) return; if (index < 0 || index >= m_UserSavedBrushes.Count) return; if (m_UserSavedBrushes[index] != brush) { var clone = Instantiate(brush); clone.name = brush.name; m_UserSavedBrushes[index] = clone; brush = clone; } m_UserSavedCount = m_UserSavedBrushes.Count; EditorUtility.SetDirty(this); FilterBrushes(); SaveLibraryGridBrushAsset(brush, index, true); SaveGridBrushPickStoreAsset(); } /// /// Removes the Brush at the index of the last pick list. /// /// The index of the Brush of the user pick list to remove. /// Whether the Brush was removed. public bool RemoveUserSavedBrush(int index) { if (index < 0 || index >= m_UserSavedBrushes.Count) return false; var brush = m_UserSavedBrushes[index]; m_UserSavedBrushes.RemoveAt(index); m_UserSavedCount = m_UserSavedBrushes.Count; EditorUtility.SetDirty(this); FilterBrushes(); SaveGridBrushPickStoreAsset(); if (brush != null && AssetDatabase.IsNativeAsset(brush)) { AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(brush)); } else { for (int i = index; i < m_UserSavedCount; ++i) SaveLibraryGridBrushAsset(m_UserSavedBrushes[i], i, true); } return true; } /// /// Sets the type to filter all user brushes by. /// /// Type to filter user brushes. /// Text to filter user brush names. public void SetUserBrushFilterType(Type filterType, string filterText) { m_FilteredBrushType = filterType; m_FilteredBrushText = filterText; FilterBrushes(); } private void FilterBrushes() { m_FilteredUserSavedBrushes.Clear(); foreach (var brush in m_UserSavedBrushes) { var validBrush = brush != null; var hasFilteredBrushType = m_FilteredBrushType != null; var hasFilteredBrushText = !String.IsNullOrWhiteSpace(m_FilteredBrushText); if (hasFilteredBrushType && validBrush && brush.GetType() != m_FilteredBrushType) { continue; } if (hasFilteredBrushText && validBrush && !Regex.IsMatch(brush.name, m_FilteredBrushText , RegexOptions.Singleline | RegexOptions.IgnoreCase)) { continue; } // Show brush only if valid or there is no filter for an invalid brush if (validBrush || (!hasFilteredBrushType && !hasFilteredBrushText)) m_FilteredUserSavedBrushes.Add(brush); } } private void SaveGridBrushPickStoreAsset() { if (AssetDatabase.IsNativeAsset(this)) { EditorUtility.SetDirty(this); AssetDatabase.SaveAssetIfDirty(this); } else { var pickPath = GenerateGridBrushPickLibraryPath(s_LibraryAssetName); var folderPath = Path.GetDirectoryName(pickPath); if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } InternalEditorUtility.SaveToSerializedFileAndForget(new Object[] { this }, pickPath, EditorSettings.serializationMode != SerializationMode.ForceBinary); } GridPaintingState.InvokeBrushPickStoreChanged(); } internal static GridBrushPickStore LoadOrCreateLibraryGridBrushPickAsset() { var pickStore = GridBrushPickStoreSettingsProvider.GetUserBrushPickStore(); if (pickStore == null) { // Load library Grid Brush Pick Store var serializedObjects = InternalEditorUtility.LoadSerializedFileAndForget(GenerateGridBrushPickLibraryPath(s_LibraryAssetName)); if (serializedObjects != null && serializedObjects.Length > 0) { pickStore = serializedObjects[0] as GridBrushPickStore; if (pickStore != null) { var count = pickStore.m_UserSavedCount; var userBrushes = new List(); for (int i = 0; i < count; ++i) { var brush = LoadLibraryGridBrushAsset(i, true); if (brush != null) { userBrushes.Add(brush); } else { userBrushes.Add(null); } } pickStore.m_UserSavedBrushes = userBrushes; } } } if (pickStore != null) { var index = gridBrushPickLastIndex; var count = gridBrushPickLastSavedCount; var brushes = new List(); for (int i = 0; i < count; ++i) { var brush = LoadLibraryGridBrushAsset(i, false); if (brush != null) { brushes.Add(brush); } else if (index >= brushes.Count) { index--; } } gridBrushPickLastIndex = index; gridBrushPickLastSavedCount = brushes.Count; pickStore.m_LastSavedBrushes = brushes; pickStore.FilterBrushes(); return pickStore; } return CreateLibraryGridBrushPickAsset(); } private void SaveLibraryGridBrushAsset(GridBrushBase brush, int index, bool user) { if (brush == null) return; if (user && AssetDatabase.IsNativeAsset(brush)) { var assetPath = AssetDatabase.GetAssetPath(brush); var fileName = FileUtil.UnityGetFileNameWithoutExtension(assetPath); if (fileName != brush.name) { var newName = brush.name; brush.name = fileName; AssetDatabase.SaveAssetIfDirty(brush); AssetDatabase.RenameAsset(assetPath, newName); } else { AssetDatabase.SaveAssetIfDirty(brush); } } else if (user && AssetDatabase.IsNativeAsset(this)) { var assetPath = AssetDatabase.GetAssetPath(this); var folderPath = Path.GetDirectoryName(assetPath); var gridBrushPath = FileUtil.CombinePaths(folderPath, $"{brush.name}{s_AssetExtension}"); gridBrushPath = AssetDatabase.GenerateUniqueAssetPath(gridBrushPath); AssetDatabase.CreateAsset(brush, gridBrushPath); } else { var gridBrushPath = GenerateGridBrushInstanceLibraryPath(index, user); var folderPath = Path.GetDirectoryName(gridBrushPath); if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } InternalEditorUtility.SaveToSerializedFileAndForget(new Object[] { brush }, gridBrushPath, EditorSettings.serializationMode != SerializationMode.ForceBinary); } } private static GridBrushBase LoadLibraryGridBrushAsset(int index, bool user) { var gridBrushPath = GenerateGridBrushInstanceLibraryPath(index, user); var serializedObjects = InternalEditorUtility.LoadSerializedFileAndForget(gridBrushPath); if (serializedObjects != null && serializedObjects.Length > 0) { var brush = serializedObjects[0] as GridBrushBase; if (brush != null) return brush; } return null; } private static GridBrushPickStore CreateLibraryGridBrushPickAsset() { var pickStore = CreateInstance(); pickStore.hideFlags = HideFlags.DontSave; pickStore.name = s_LibraryAssetName; pickStore.SaveGridBrushPickStoreAsset(); return pickStore; } private static string GenerateGridBrushPickLibraryPath(string name) { var path = FileUtil.CombinePaths(s_LibraryPickPath, name + s_AssetExtension); path = FileUtil.NiceWinPath(path); return path; } private static string GenerateGridBrushInstanceLibraryPath(int index, bool user) { var path = FileUtil.CombinePaths(user ? s_UserPickPath : s_LastPickPath, index.ToString() + s_AssetExtension); path = FileUtil.NiceWinPath(path); return path; } } }