using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor.EditorTools; using UnityEngine; using Object = UnityEngine.Object; namespace UnityEditor.Tilemaps { /// /// GridPaintingState controls the state of objects for painting with a Tile Palette. /// /// /// Utilize this class to get and set the current painting target and brush for painting /// with the Tile Palette. /// public class GridPaintingState : ScriptableSingleton { [SerializeField] private GameObject m_EditModeScenePaintTarget; // Which GameObject in scene was the last painting target in EditMode [SerializeField] private GameObject m_ScenePaintTarget; // Which GameObject in scene is considered as painting target [SerializeField] private EditorTool[] m_BrushTools; [SerializeField] private GridBrushBase m_Brush; // Which brush will handle painting callbacks [SerializeField] private PaintableGrid m_ActiveGrid; // Grid that has painting focus (can be palette, too) [SerializeField] private PaintableGrid m_LastActiveGrid; // Grid that last had painting focus (can be palette, too) [SerializeField] private HashSet m_InterestedPainters = new HashSet(); // A list of objects that can paint using the GridPaintingState private GameObject[] m_CachedPaintTargets; private bool m_FlushPaintTargetCache; private Editor m_CachedEditor; private bool m_SavingPalette; private float m_BrushToolbarSize; /// /// Callback when the Tile Palette's active target has changed /// public static event Action scenePaintTargetChanged; /// /// Callback when the Tile Palette's active brush has changed. /// public static event Action brushChanged; /// /// Callback when the Tile Palette's active palette GameObject has changed. /// public static event Action paletteChanged; private void OnEnable() { EditorApplication.hierarchyChanged += HierarchyChanged; EditorApplication.playModeStateChanged += PlayModeStateChanged; Selection.selectionChanged += OnSelectionChange; m_FlushPaintTargetCache = true; } private void OnDisable() { m_InterestedPainters.Clear(); EditorApplication.hierarchyChanged -= HierarchyChanged; EditorApplication.playModeStateChanged -= PlayModeStateChanged; Selection.selectionChanged -= OnSelectionChange; FlushCache(); } private void OnSelectionChange() { if (hasInterestedPainters && ValidatePaintTarget(Selection.activeGameObject)) { scenePaintTarget = Selection.activeGameObject; } } private void PlayModeStateChanged(PlayModeStateChange state) { if (state == PlayModeStateChange.ExitingEditMode) { m_EditModeScenePaintTarget = scenePaintTarget; } else if (state == PlayModeStateChange.EnteredEditMode) { if (GridPaintActiveTargetsPreferences.restoreEditModeSelection && m_EditModeScenePaintTarget != null) { scenePaintTarget = m_EditModeScenePaintTarget; } } } private void HierarchyChanged() { if (hasInterestedPainters) { m_FlushPaintTargetCache = true; if (validTargets == null || validTargets.Length == 0 || !validTargets.Contains(scenePaintTarget)) { // case 1102618: Try to use current Selection as scene paint target if possible if (Selection.activeGameObject != null && hasInterestedPainters && ValidatePaintTarget(Selection.activeGameObject)) { scenePaintTarget = Selection.activeGameObject; } else { AutoSelectPaintTarget(); } } } } private GameObject[] GetValidTargets() { if (m_FlushPaintTargetCache) { m_CachedPaintTargets = null; if (activeBrushEditor != null) m_CachedPaintTargets = activeBrushEditor.validTargets; if (m_CachedPaintTargets == null || m_CachedPaintTargets.Length == 0) scenePaintTarget = null; else { var comparer = GridPaintActiveTargetsPreferences.GetTargetComparer(); if (comparer != null) Array.Sort(m_CachedPaintTargets, comparer); } m_FlushPaintTargetCache = false; } return m_CachedPaintTargets; } internal static void AutoSelectPaintTarget() { if (activeBrushEditor != null) { if (validTargets != null && validTargets.Length > 0) { scenePaintTarget = validTargets[0]; } } } /// /// The currently active target for the Tile Palette /// public static GameObject scenePaintTarget { get { return instance.m_ScenePaintTarget; } set { if (value != instance.m_ScenePaintTarget) { instance.m_ScenePaintTarget = value; if (scenePaintTargetChanged != null) scenePaintTargetChanged(instance.m_ScenePaintTarget); RepaintGridPaintPaletteWindow(); } } } /// /// The currently active brush for the Tile Palette /// public static GridBrushBase gridBrush { get { if (instance.m_Brush == null) { instance.m_Brush = GridPaletteBrushes.instance.GetLastUsedBrush(); UpdateBrushToolbar(); } return instance.m_Brush; } set { if (instance.m_Brush != value) { instance.m_Brush = value; instance.m_FlushPaintTargetCache = true; if (value != null) { GridPaletteBrushes.instance.StoreLastUsedBrush(value); UpdateBrushToolbar(); } // Ensure that current scenePaintTarget is still a valid target after a brush change if (scenePaintTarget != null && !ValidatePaintTarget(scenePaintTarget)) scenePaintTarget = null; // Use Selection if previous scenePaintTarget was not valid if (scenePaintTarget == null) scenePaintTarget = ValidatePaintTarget(Selection.activeGameObject) ? Selection.activeGameObject : null; // Auto select a valid target if there is still no scenePaintTarget if (scenePaintTarget == null) AutoSelectPaintTarget(); if (null != brushChanged) brushChanged(value); RepaintGridPaintPaletteWindow(); } } } /// /// Returns all available brushes for the Tile Palette /// public static IList brushes { get { return GridPaletteBrushes.brushes; } } internal static GridBrush defaultBrush { get { return gridBrush as GridBrush; } set { gridBrush = value; } } /// /// The currently active palette GameObject for the Tile Palette /// public static GameObject palette { get { if (GridPaintPaletteWindow.instances.Count > 0) return GridPaintPaletteWindow.instances[0].palette; return null; } set { if (value == null || !GridPalettes.palettes.Contains(value)) throw new ArgumentException(L10n.Tr("Unable to set invalid palette")); if (GridPaintPaletteWindow.instances.Count > 0 && GridPaintPaletteWindow.instances[0].palette != value) { GridPaintPaletteWindow.instances[0].palette = value; } } } /// /// Checks if target GameObject is part of the active Palette. /// /// GameObject to check. /// True if the target GameObject is part of the active palette. False if not. public static bool IsPartOfActivePalette(GameObject target) { if (GridPaintPaletteWindow.instances.Count > 0 && target == GridPaintPaletteWindow.instances[0].paletteInstance) return true; if (target == palette) return true; var parent = target.transform.parent; return parent != null && IsPartOfActivePalette(parent.gameObject); } /// /// Returns all available Palette GameObjects for the Tile Palette /// public static IList palettes { get { return GridPalettes.palettes; } } /// /// The currently active editor for the active brush for the Tile Palette /// public static GridBrushEditorBase activeBrushEditor { get { Editor.CreateCachedEditor(gridBrush, null, ref instance.m_CachedEditor); GridBrushEditorBase baseEditor = instance.m_CachedEditor as GridBrushEditorBase; return baseEditor; } } internal static Editor fallbackEditor { get { Editor.CreateCachedEditor(gridBrush, null, ref instance.m_CachedEditor); return instance.m_CachedEditor; } } internal static PaintableGrid activeGrid { get { return instance.m_ActiveGrid; } set { instance.m_ActiveGrid = value; if (instance.m_ActiveGrid != null) instance.m_LastActiveGrid = value; } } internal static PaintableGrid lastActiveGrid { get { return instance.m_LastActiveGrid; } } internal static EditorTool[] activeBrushTools { get { return instance.m_BrushTools; } set { instance.m_BrushTools = value; } } internal static float activeBrushToolbarSize { get { if (instance.m_BrushToolbarSize == 0.0f) CalculateToolbarSize(); return instance.m_BrushToolbarSize; } set { instance.m_BrushToolbarSize = value; } } private static void CalculateToolbarSize() { GUIStyle toolbarStyle = "Command"; activeBrushToolbarSize = activeBrushTools.Sum(x => toolbarStyle.CalcSize(x.toolbarIcon).x); } internal static void SetBrushTools(EditorTool[] editorTools) { activeBrushTools = editorTools; activeBrushToolbarSize = 0.0f; } private static bool ValidatePaintTarget(GameObject candidate) { if (candidate == null) return false; // Case 1327021: Do not allow disabled GameObjects as a paint target if (!candidate.activeInHierarchy) return false; if (candidate.GetComponentInParent() == null && candidate.GetComponent() == null) return false; if (validTargets != null && validTargets.Length > 0 && !validTargets.Contains(candidate)) return false; if (PrefabUtility.IsPartOfPrefabAsset(candidate)) return false; return true; } internal static void FlushCache() { if (instance.m_CachedEditor != null) { DestroyImmediate(instance.m_CachedEditor); instance.m_CachedEditor = null; } instance.m_FlushPaintTargetCache = true; } /// /// A list of all valid targets that can be set as an active target for the Tile Palette /// public static GameObject[] validTargets { get { return instance.GetValidTargets(); } } internal static bool savingPalette { get { return instance.m_SavingPalette; } set { instance.m_SavingPalette = value; } } internal static void OnPaletteChanged(GameObject palette) { if (null != paletteChanged) paletteChanged(palette); } internal static void UpdateBrushToolbar() { BrushToolsAttribute toolAttribute = null; if (instance.m_Brush != null) toolAttribute = (BrushToolsAttribute)instance.m_Brush.GetType().GetCustomAttribute(typeof(BrushToolsAttribute), false); TilemapEditorTool.UpdateEditorTools(toolAttribute); } internal static void UpdateActiveGridPalette() { if (GridPaintPaletteWindow.instances.Count > 0) GridPaintPaletteWindow.instances[0].DelayedResetPreviewInstance(); } internal static void RepaintGridPaintPaletteWindow() { if (GridPaintPaletteWindow.instances.Count > 0) GridPaintPaletteWindow.instances[0].Repaint(); } internal static void UnlockGridPaintPaletteClipboardForEditing() { if (GridPaintPaletteWindow.instances.Count > 0) GridPaintPaletteWindow.instances[0].clipboardView.UnlockAndEdit(); } internal static void RegisterPainterInterest(Object painter) { instance.m_InterestedPainters.Add(painter); } internal static void UnregisterPainterInterest(Object painter) { instance.m_InterestedPainters.Remove(painter); } private bool hasInterestedPainters { get { return m_InterestedPainters.Count > 0; } } } }