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;
}
}
}