Singularity/Library/PackageCache/com.unity.2d.tilemap@1.0.0/Editor/PaintableGrid.cs
2024-05-06 11:45:45 -07:00

601 lines
26 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.EditorTools;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace UnityEditor.Tilemaps
{
internal abstract class PaintableGrid : ScriptableObject
{
private const int k_MaxMouseCellDelta = 500;
public enum MarqueeType { None = 0, Pick, Box, Select }
private int m_PermanentControlID;
public abstract void Repaint();
protected abstract void RegisterUndo();
protected abstract void Paint(Vector3Int position);
protected abstract void Erase(Vector3Int position);
protected abstract void BoxFill(BoundsInt position);
protected abstract void BoxErase(BoundsInt position);
protected abstract void FloodFill(Vector3Int position);
protected abstract void PickBrush(BoundsInt position, Vector3Int pickStart);
protected abstract void Select(BoundsInt position);
protected abstract void Move(BoundsInt from, BoundsInt to);
protected abstract void MoveStart(BoundsInt position);
protected abstract void MoveEnd(BoundsInt position);
protected abstract bool CustomTool(bool isHotControl, TilemapEditorTool tool, Vector3Int position);
protected abstract bool ValidateFloodFillPosition(Vector3Int position);
protected abstract Vector2Int ScreenToGrid(Vector2 screenPosition);
protected abstract bool PickingIsDefaultTool();
protected abstract bool CanPickOutsideEditMode();
protected abstract Grid.CellLayout CellLayout();
protected abstract void ClearGridSelection();
public abstract bool isActive { get; }
protected virtual void OnBrushPickStarted() {}
protected virtual void OnBrushPickDragged(BoundsInt position) {}
protected virtual void OnBrushPickCancelled() {}
protected virtual void OnEditStart() {}
protected virtual void OnEditEnd() {}
internal static PaintableGrid s_LastActivePaintableGrid;
private Vector2Int m_PreviousMouseGridPosition;
private Vector2Int m_MouseGridPosition;
private bool m_MouseGridPositionChanged;
private bool m_PositionChangeRepaintDone;
protected Vector2Int? m_PreviousMove;
protected Vector2Int? m_MarqueeStart;
private MarqueeType m_MarqueeType = MarqueeType.None;
private bool m_IsExecuting;
private Type m_TypeBeforeExecution;
private int m_ZPosition;
public Vector2Int mouseGridPosition { get { return m_MouseGridPosition; } }
public bool isPicking { get { return m_MarqueeType == MarqueeType.Pick; } }
public bool isBoxing { get { return m_MarqueeType == MarqueeType.Box; } }
public GridLayout.CellLayout cellLayout { get { return CellLayout(); } }
public int zPosition { get { return m_ZPosition; } set { m_ZPosition = value; } }
protected bool executing
{
get { return m_IsExecuting; }
set
{
var isExecuting = value && isHotControl;
if (isExecuting != m_IsExecuting)
{
if (isExecuting)
OnEditStart();
else
OnEditEnd();
}
m_IsExecuting = isExecuting;
}
}
protected bool isNearestControl { get { return HandleUtility.nearestControl == m_PermanentControlID; } }
protected bool isHotControl { get { return GUIUtility.hotControl == m_PermanentControlID; } }
protected bool mouseGridPositionChanged { get { return m_MouseGridPositionChanged; } }
protected bool inEditMode { get { return PaintableGrid.InGridEditMode(); } }
protected virtual void OnEnable()
{
m_PermanentControlID = GUIUtility.GetPermanentControlID();
}
protected virtual void OnDisable()
{
}
public virtual void OnGUI()
{
var evt = Event.current;
if (CanPickOutsideEditMode() || inEditMode)
{
if (evt.type == EventType.Layout)
HandleUtility.AddDefaultControl(m_PermanentControlID);
HandleBrushPicking();
}
if (inEditMode)
{
HandleBrushPaintAndErase();
HandleSelectTool();
HandleMoveTool();
HandleEditModeChange();
HandleFloodFill();
HandleBoxTool();
HandleCustomTool();
}
else if (isHotControl && !IsPickingEvent(evt))
{
// Release hot control if it still has it while not in picking or grid edit mode
GUIUtility.hotControl = 0;
}
if (mouseGridPositionChanged && !m_PositionChangeRepaintDone)
{
Repaint();
m_PositionChangeRepaintDone = true;
}
}
protected void ResetPreviousMousePositionToCurrentPosition()
{
m_PreviousMouseGridPosition = m_MouseGridPosition;
}
protected void UpdateMouseGridPosition(bool forceUpdate = false)
{
if (Event.current.type == EventType.MouseDrag
|| Event.current.type == EventType.MouseMove
// Case 1075857: Mouse Down when window is not in focus needs to update mouse grid position
|| Event.current.type == EventType.MouseDown
|| Event.current.type == EventType.DragUpdated
|| forceUpdate)
{
Vector2Int newGridPosition = ScreenToGrid(Event.current.mousePosition);
if (newGridPosition != m_MouseGridPosition)
{
var delta = newGridPosition - m_MouseGridPosition;
// Case 1024422: Limit mouse cell delta changes for Grid/Tilemap input handling due to camera changes when switching modes/axis views
if (Mathf.Abs(delta.x) > k_MaxMouseCellDelta)
newGridPosition.x = m_MouseGridPosition.x + Math.Sign(delta.x) * k_MaxMouseCellDelta;
if (Mathf.Abs(delta.y) > k_MaxMouseCellDelta)
newGridPosition.y = m_MouseGridPosition.y + Math.Sign(delta.y) * k_MaxMouseCellDelta;
ResetPreviousMousePositionToCurrentPosition();
m_MouseGridPosition = newGridPosition;
MouseGridPositionChanged();
}
else if (!forceUpdate || Event.current.type == EventType.MouseMove)
{
m_MouseGridPositionChanged = false;
}
}
}
private void MouseGridPositionChanged()
{
m_MouseGridPositionChanged = true;
m_PositionChangeRepaintDone = false;
}
private void HandleEditModeChange()
{
// Handles changes in EditMode while tool is expected to be in the same mode
if (isPicking && !TilemapEditorTool.IsActive(typeof(PickingTool)))
{
m_MarqueeStart = null;
m_MarqueeType = MarqueeType.None;
if (isHotControl)
{
GUI.changed = true;
GUIUtility.hotControl = 0;
}
}
if (isBoxing && !TilemapEditorTool.IsActive(typeof(BoxTool)))
{
m_MarqueeStart = null;
m_MarqueeType = MarqueeType.None;
if (isHotControl)
{
GUI.changed = true;
GUIUtility.hotControl = 0;
}
}
if (!TilemapEditorTool.IsActive(typeof(SelectTool)) && !TilemapEditorTool.IsActive(typeof(MoveTool)))
{
ClearGridSelection();
}
}
private void HandleBrushPicking()
{
Event evt = Event.current;
if (isNearestControl && evt.type == EventType.MouseDown && IsPickingEvent(evt) && !isHotControl)
{
m_TypeBeforeExecution = typeof(PaintTool);
if (inEditMode && !TilemapEditorTool.IsActive(typeof(PickingTool)))
{
m_TypeBeforeExecution = UnityEditor.EditorTools.ToolManager.activeToolType;
TilemapEditorTool.SetActiveEditorTool(typeof(PickingTool));
}
m_MarqueeStart = mouseGridPosition;
m_MarqueeType = MarqueeType.Pick;
s_LastActivePaintableGrid = this;
Event.current.Use();
GUI.changed = true;
GUIUtility.hotControl = m_PermanentControlID;
OnBrushPickStarted();
}
if (evt.type == EventType.MouseDrag && isHotControl && m_MarqueeStart.HasValue && m_MarqueeType == MarqueeType.Pick && IsPickingEvent(evt))
{
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
OnBrushPickDragged(new BoundsInt(new Vector3Int(rect.xMin, rect.yMin, zPosition), new Vector3Int(rect.size.x, rect.size.y, 1)));
Event.current.Use();
GUI.changed = true;
}
if (evt.rawType == EventType.MouseUp && isHotControl && m_MarqueeStart.HasValue && m_MarqueeType == MarqueeType.Pick && IsPickingEvent(evt))
{
// Check if event only occurred in the PaintableGrid window as evt.type will filter for this
if (evt.type == EventType.MouseUp && m_MarqueeType == MarqueeType.Pick)
{
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
Vector2Int pivot = GetMarqueePivot(m_MarqueeStart.Value, mouseGridPosition);
PickBrush(new BoundsInt(new Vector3Int(rect.xMin, rect.yMin, zPosition), new Vector3Int(rect.size.x, rect.size.y, 1)), new Vector3Int(pivot.x, pivot.y, 0));
if (inEditMode && UnityEditor.EditorTools.ToolManager.activeToolType != m_TypeBeforeExecution)
{
if (PickingIsDefaultTool()
&& (m_TypeBeforeExecution == typeof(EraseTool)
|| m_TypeBeforeExecution == typeof(MoveTool)))
{
// If Picking is default, change to a Paint Tool to facilitate editing if previous tool does not allow for painting
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
}
else
{
TilemapEditorTool.SetActiveEditorTool(m_TypeBeforeExecution);
}
}
GridPaletteBrushes.ActiveGridBrushAssetChanged();
s_LastActivePaintableGrid = this;
Event.current.Use();
GUI.changed = true;
}
else
// Event occurred outside of PaintableGrid window, cancel the pick event
{
OnBrushPickCancelled();
}
m_MarqueeType = MarqueeType.None;
m_MarqueeStart = null;
GUIUtility.hotControl = 0;
InspectorWindow.RepaintAllInspectors();
}
}
private bool IsPickingEvent(Event evt)
{
return ((evt.control && !TilemapEditorTool.IsActive(typeof(MoveTool)))
|| TilemapEditorTool.IsActive(typeof(PickingTool))
|| !TilemapEditorTool.IsActive(typeof(SelectTool)) && PickingIsDefaultTool())
&& evt.button == 0 && !evt.alt;
}
private void HandleSelectTool()
{
Event evt = Event.current;
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt && (TilemapEditorTool.IsActive(typeof(SelectTool)) || (TilemapEditorTool.IsActive(typeof(MoveTool)) && evt.control)))
{
if (TilemapEditorTool.IsActive(typeof(MoveTool)) && evt.control)
TilemapEditorTool.SetActiveEditorTool(typeof(SelectTool));
m_PreviousMove = null;
m_MarqueeStart = mouseGridPosition;
m_MarqueeType = MarqueeType.Select;
s_LastActivePaintableGrid = this;
GUIUtility.hotControl = m_PermanentControlID;
Event.current.Use();
}
if (evt.rawType == EventType.MouseUp && evt.button == 0 && !evt.alt && m_MarqueeStart.HasValue && isHotControl && TilemapEditorTool.IsActive(typeof(SelectTool)))
{
// Check if event only occurred in the PaintableGrid window as evt.type will filter for this
if (evt.type == EventType.MouseUp && m_MarqueeType == MarqueeType.Select)
{
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
Select(new BoundsInt(new Vector3Int(rect.xMin, rect.yMin, zPosition), new Vector3Int(rect.size.x, rect.size.y, 1)));
Event.current.Use();
}
if (evt.control)
TilemapEditorTool.SetActiveEditorTool(typeof(MoveTool));
m_MarqueeStart = null;
m_MarqueeType = MarqueeType.None;
InspectorWindow.RepaintAllInspectors();
GUIUtility.hotControl = 0;
}
if (evt.type == EventType.KeyDown && evt.keyCode == KeyCode.Escape && !m_MarqueeStart.HasValue && !m_PreviousMove.HasValue)
{
ClearGridSelection();
Event.current.Use();
}
}
private void HandleMoveTool()
{
Event evt = Event.current;
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt && TilemapEditorTool.IsActive(typeof(MoveTool)))
{
RegisterUndo();
Vector3Int mouse3D = new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, GridSelection.position.zMin);
if (GridSelection.active && GridSelection.position.Contains(mouse3D))
{
GUIUtility.hotControl = m_PermanentControlID;
executing = true;
m_MarqueeStart = null;
m_MarqueeType = MarqueeType.None;
m_PreviousMove = mouseGridPosition;
MoveStart(GridSelection.position);
s_LastActivePaintableGrid = this;
}
Event.current.Use();
}
if (evt.type == EventType.MouseDrag && evt.button == 0 && TilemapEditorTool.IsActive(typeof(MoveTool)) && isHotControl)
{
if (m_MouseGridPositionChanged && m_PreviousMove.HasValue)
{
executing = true;
BoundsInt previousRect = GridSelection.position;
BoundsInt previousBounds = new BoundsInt(new Vector3Int(previousRect.xMin, previousRect.yMin, GridSelection.position.zMin), new Vector3Int(previousRect.size.x, previousRect.size.y, 1));
Vector2Int direction = mouseGridPosition - m_PreviousMove.Value;
BoundsInt pos = GridSelection.position;
pos.position = new Vector3Int(pos.x + direction.x, pos.y + direction.y, pos.z);
GridSelection.position = pos;
Move(previousBounds, pos);
m_PreviousMove = mouseGridPosition;
Event.current.Use();
}
}
if (evt.type == EventType.MouseUp && evt.button == 0 && m_PreviousMove.HasValue && TilemapEditorTool.IsActive(typeof(MoveTool)) && isHotControl)
{
m_PreviousMove = null;
MoveEnd(GridSelection.position);
executing = false;
GUIUtility.hotControl = 0;
Event.current.Use();
}
}
private void HandleBrushPaintAndErase()
{
Event evt = Event.current;
if (!IsPaintingEvent(evt) && !IsErasingEvent(evt))
return;
switch (evt.type)
{
case EventType.MouseDown:
if (isNearestControl)
{
RegisterUndo();
GUIUtility.hotControl = m_PermanentControlID;
executing = true;
m_TypeBeforeExecution = EditorTools.ToolManager.activeToolType;
var position = new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, zPosition);
if (IsErasingEvent(evt))
{
if (!TilemapEditorTool.IsActive(typeof(EraseTool)))
TilemapEditorTool.SetActiveEditorTool(typeof(EraseTool));
Erase(position);
}
else
{
if (!TilemapEditorTool.IsActive(typeof(PaintTool)))
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
Paint(position);
}
ResetPreviousMousePositionToCurrentPosition();
Event.current.Use();
GUI.changed = true;
}
break;
case EventType.MouseDrag:
executing = true;
if (isHotControl && mouseGridPositionChanged)
{
var points = GridEditorUtility.GetPointsOnLine(m_PreviousMouseGridPosition, mouseGridPosition);
if (!evt.shift && !TilemapEditorTool.IsActive(typeof(PaintTool)) && m_TypeBeforeExecution == typeof(PaintTool))
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
else if (evt.shift && TilemapEditorTool.IsActive(typeof(PaintTool)))
TilemapEditorTool.SetActiveEditorTool(typeof(EraseTool));
foreach (var point in points)
{
var position = new Vector3Int(point.x, point.y, zPosition);
if (IsErasingEvent(evt))
Erase(position);
else
Paint(position);
}
ResetPreviousMousePositionToCurrentPosition();
Event.current.Use();
GUI.changed = true;
}
break;
case EventType.MouseUp:
executing = false;
if (isHotControl)
{
if (!TilemapEditorTool.IsActive(typeof(PaintTool)) && m_TypeBeforeExecution == typeof(PaintTool))
{
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
}
Event.current.Use();
GUI.changed = true;
GUIUtility.hotControl = 0;
}
break;
}
}
private bool IsPaintingEvent(Event evt)
{
return (evt.button == 0 && !evt.control && !evt.alt && TilemapEditorTool.IsActive(typeof(PaintTool)));
}
private bool IsErasingEvent(Event evt)
{
return (evt.button == 0 && !evt.control && !evt.alt
&& ((evt.shift && !TilemapEditorTool.IsActive(typeof(BoxTool))
&& !TilemapEditorTool.IsActive(typeof(FillTool))
&& !TilemapEditorTool.IsActive(typeof(SelectTool))
&& !TilemapEditorTool.IsActive(typeof(MoveTool)))
|| TilemapEditorTool.IsActive(typeof(EraseTool))));
}
private void HandleFloodFill()
{
if (TilemapEditorTool.IsActive(typeof(FillTool)) && GridPaintingState.gridBrush != null && ValidateFloodFillPosition(new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, 0)))
{
Event evt = Event.current;
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt)
{
GUIUtility.hotControl = m_PermanentControlID;
GUI.changed = true;
executing = true;
Event.current.Use();
}
if (evt.type == EventType.MouseUp && evt.button == 0 && isHotControl)
{
RegisterUndo();
FloodFill(new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, zPosition));
executing = false;
GUI.changed = true;
Event.current.Use();
GUIUtility.hotControl = 0;
}
}
}
private void HandleBoxTool()
{
Event evt = Event.current;
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt && TilemapEditorTool.IsActive(typeof(BoxTool)))
{
m_MarqueeStart = mouseGridPosition;
m_MarqueeType = MarqueeType.Box;
Event.current.Use();
GUI.changed = true;
executing = true;
GUIUtility.hotControl = m_PermanentControlID;
}
if (evt.type == EventType.MouseDrag && evt.button == 0 && TilemapEditorTool.IsActive(typeof(BoxTool)))
{
if (isHotControl && m_MarqueeStart.HasValue)
{
Event.current.Use();
executing = true;
GUI.changed = true;
}
}
if (evt.type == EventType.MouseUp && evt.button == 0 && TilemapEditorTool.IsActive(typeof(BoxTool)))
{
if (isHotControl && m_MarqueeStart.HasValue)
{
RegisterUndo();
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
if (evt.shift)
BoxErase(new BoundsInt(rect.x, rect.y, zPosition, rect.size.x, rect.size.y, 1));
else
BoxFill(new BoundsInt(rect.x, rect.y, zPosition, rect.size.x, rect.size.y, 1));
Event.current.Use();
executing = false;
GUI.changed = true;
GUIUtility.hotControl = 0;
}
m_MarqueeStart = null;
m_MarqueeType = MarqueeType.None;
}
}
private void HandleCustomTool()
{
Event evt = Event.current;
if (evt.type == EventType.Layout || evt.type == EventType.Repaint)
return;
if (!TilemapEditorTool.IsCustomTilemapEditorToolActive())
return;
TilemapEditorTool activeTool = EditorToolManager.activeTool as TilemapEditorTool;
var executed = CustomTool(isHotControl, activeTool, new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, zPosition));
if (executed != executing)
{
GUIUtility.hotControl = executed ? m_PermanentControlID : 0;
executing = executed;
GUI.changed = true;
Event.current.Use();
}
else if (executing)
{
GUI.changed = true;
Event.current.Use();
}
}
private Vector2Int GetMarqueePivot(Vector2Int start, Vector2Int end)
{
Vector2Int pivot = new Vector2Int(
Math.Max(end.x - start.x, 0),
Math.Max(end.y - start.y, 0)
);
return pivot;
}
public void ChangeZPosition(int change)
{
m_ZPosition += change;
MouseGridPositionChanged();
Repaint();
}
public void ResetZPosition()
{
if (m_ZPosition == 0)
return;
m_ZPosition = 0;
MouseGridPositionChanged();
Repaint();
}
public static bool InGridEditMode()
{
return UnityEditor.EditorTools.ToolManager.activeToolType != null
&& UnityEditor.EditorTools.ToolManager.activeToolType.IsSubclassOf(typeof(TilemapEditorTool));
}
// TODO: Someday EditMode or its future incarnation will be public and we can get rid of this
// TODO: Temporarily use ActiveTool's type to determine brush tool
public static GridBrushBase.Tool EditTypeToBrushTool(Type activeToolType)
{
if (activeToolType == typeof(BoxTool))
return GridBrushBase.Tool.Box;
if (activeToolType == typeof(EraseTool))
return GridBrushBase.Tool.Erase;
if (activeToolType == typeof(FillTool))
return GridBrushBase.Tool.FloodFill;
if (activeToolType == typeof(PaintTool))
return GridBrushBase.Tool.Paint;
if (activeToolType == typeof(PickingTool))
return GridBrushBase.Tool.Pick;
if (activeToolType == typeof(SelectTool))
return GridBrushBase.Tool.Select;
if (activeToolType == typeof(MoveTool))
return GridBrushBase.Tool.Move;
return GridBrushBase.Tool.Paint;
}
}
}