// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
using static Animancer.Editor.AnimancerGUI;
using static Animancer.Editor.TransitionLibraries.TransitionLibrarySelection;
namespace Animancer.Editor.TransitionLibraries
{
/// [Editor-Only]
/// A for editing
/// .
///
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionModifierTableGUI
[Serializable]
public class TransitionModifierTableGUI : TableGUI
{
/************************************************************************************************************************/
[NonSerialized] private TransitionLibraryWindow _Window;
[NonSerialized] private Vector2Int _SelectedCell;
/************************************************************************************************************************/
/// Creates a new .
public TransitionModifierTableGUI()
{
base.DoCellGUI = DoCellGUI;
CalculateWidestLabel = CalculateWidestTransitionLabel;
MinCellSize = new(LineHeight * 2, LineHeight);
MaxCellSize = new(LineHeight * 4, LineHeight);
}
/************************************************************************************************************************/
/// Draws the table GUI.
public void DoGUI(
Rect area,
TransitionLibraryWindow window)
{
_Window = window;
_SelectedCell = RecalculateSelectedCell(window.Selection);
var transitions = window.Data.Transitions;
DoTableGUI(area, transitions.Length, transitions.Length);
}
/************************************************************************************************************************/
/// Calculates the table coordinates of the `selection`.
private Vector2Int RecalculateSelectedCell(TransitionLibrarySelection selection)
{
if (selection.Validate())
{
switch (selection.Type)
{
case SelectionType.FromTransition:
case SelectionType.ToTransition:
case SelectionType.Modifier:
var cell = new Vector2Int(selection.ToIndex, selection.FromIndex);
if (cell.x < 0)
cell.x = int.MinValue;
if (cell.y < 0)
cell.y = int.MinValue;
return cell;
}
}
return new(int.MinValue, int.MinValue);
}
/************************************************************************************************************************/
/// Draws a table cell.
private new void DoCellGUI(Rect area, int column, int row)
{
var invertHover = false;
if (column < 0)
{
if (row < 0)
DoCornerGUI(area);
else
DoLabelGUI(
area,
row,
RightLabelStyle,
SelectionType.FromTransition);
}
else if (row < 0)
{
DoLabelGUI(
area,
column,
EditorStyles.label,
SelectionType.ToTransition);
invertHover = true;
}
else
{
DoFadeDurationGUI(area, _Window, row, column, "");
}
DrawHighlightGUI(area, column, row, invertHover);
}
/************************************************************************************************************************/
/// Draws the header corner.
private void DoCornerGUI(Rect area)
{
area.xMin += StandardSpacing;
var fromArea = area;
fromArea.y += area.height - LineHeight;
fromArea.height = LineHeight;
var toArea = fromArea;
toArea.y -= toArea.height - Padding;
var removeArea = toArea;
removeArea.y -= removeArea.height - Padding;
var createArea = removeArea;
createArea.y -= createArea.height - Padding;
fromArea.width -= VerticalScrollBar.fixedWidth + Padding;
var style = RightLabelStyle;
var fontStyle = style.fontStyle;
style.fontStyle = FontStyle.Bold;
GUI.Label(fromArea, "From", style);
GUI.Label(toArea, "To", style);
style.fontStyle = fontStyle;
DoCreateButtonGUI(createArea);
DoDeleteButtonGUI(removeArea);
}
/************************************************************************************************************************/
/// Draws a button to create a new transition.
private void DoCreateButtonGUI(Rect area)
{
if (GUI.Button(area, "Create Transition"))
TransitionLibraryOperations.CreateTransition(_Window);
}
/************************************************************************************************************************/
/// Draws a button to remove the selected transition.
private void DoDeleteButtonGUI(Rect area)
{
TransitionAssetBase transition = null;
int index = -1;
var selection = _Window.Selection;
switch (selection.Type)
{
case SelectionType.FromTransition:
transition = selection.FromTransition;
index = selection.FromIndex;
break;
case SelectionType.ToTransition:
transition = selection.ToTransition;
index = selection.ToIndex;
break;
}
using (new EditorGUI.DisabledScope(index < 0 || index >= _Window.Data.Transitions.Length))
if (GUI.Button(area, "Remove Transition"))
TransitionLibraryOperations.AskHowToDeleteTransition(transition, index, _Window);
}
/************************************************************************************************************************/
/// Draws a row or column label.
private void DoLabelGUI(
Rect area,
int index,
GUIStyle style,
SelectionType selectionType)
{
if (!_Window.Data.Transitions.TryGet(index, out var transition))
return;
HandleTransitionLabelInput(
ref area,
_Window,
transition,
index,
selectionType,
CalculateTargetTransitionIndex);
GUI.Label(area, GetTransitionName(transition), style);
}
/************************************************************************************************************************/
/// Returns the name of the `transition` with a special message for null.
public static string GetTransitionName(TransitionAssetBase transition)
=> transition != null
? transition.GetCachedName()
: "";
/************************************************************************************************************************/
private static readonly int LabelHint = "Label".GetHashCode();
[NonSerialized] private static bool _IsLabelDrag;
/// Handles input events on transition labels.
public static void HandleTransitionLabelInput(
ref Rect area,
TransitionLibraryWindow window,
TransitionAssetBase transition,
int index,
SelectionType selectionType,
Func calculateTargetTransitionIndex)
{
var control = new GUIControl(area, LabelHint);
switch (control.EventType)
{
case EventType.MouseDown:
if (control.Event.button == 0 &&
control.TryUseMouseDown())
{
if (control.Event.clickCount == 2)
EditorGUIUtility.PingObject(transition);
else
window.Selection.Select(window, transition, index, selectionType);
_IsLabelDrag = false;
}
break;
case EventType.MouseUp:
if (control.TryUseMouseUp() && _IsLabelDrag)
{
var target = calculateTargetTransitionIndex(area, index, control.Event);
TransitionLibrarySort.MoveTransition(window, index, target);
window.Selection.Select(window, transition, index, selectionType);
}
break;
case EventType.MouseDrag:
if (control.TryUseHotControl())
_IsLabelDrag = true;
break;
}
if (GUIUtility.hotControl == control.ID && _IsLabelDrag)
{
RepaintEverything();
area.y = control.Event.mousePosition.y - area.height * 0.5f;
}
}
/************************************************************************************************************************/
/// Calculates the transition index for a drag and drop operation.
private static int CalculateTargetTransitionIndex(
Rect area,
int index,
Event currentEvent)
{
var distance = currentEvent.mousePosition.y - area.y;
var offset = Mathf.FloorToInt(distance / area.height);
return index + offset;
}
/************************************************************************************************************************/
private static GUIStyle _FadeDurationStyle;
/// Draws the fade duration for a particular transition combination.
public static void DoFadeDurationGUI(
Rect area,
TransitionLibraryWindow window,
int from,
int to,
string label)
{
_FadeDurationStyle ??= new(EditorStyles.numberField)
{
alignment = TextAnchor.MiddleLeft,
};
var previousHotControl = GUIUtility.hotControl;
var hasModifier = window.Data.TryGetModifier(from, to, out var modifier);
var labelStyle = EditorStyles.label.fontStyle;
try
{
if (hasModifier)
{
EditorStyles.label.fontStyle = FontStyle.Bold;
_FadeDurationStyle.fontStyle = FontStyle.Bold;
_FadeDurationStyle.fontSize = EditorStyles.numberField.fontSize;
}
else
{
_FadeDurationStyle.fontStyle = FontStyle.Normal;
_FadeDurationStyle.fontSize = EditorStyles.numberField.fontSize * 4 / 5;
}
EditorGUI.BeginChangeCheck();
// This is basically a float field,
// but anything that fails to parse will clear the field instead of setting it to 0.
var text = modifier.FadeDuration.ToStringCached();
text = EditorGUI.TextField(area, label, text, _FadeDurationStyle);
if (EditorGUI.EndChangeCheck())
{
if (!float.TryParse(text, out var fadeDuration))
fadeDuration = float.NaN;
window.RecordUndo()
.SetModifier(modifier.WithFadeDuration(fadeDuration));
hasModifier = true;
RepaintEverything();
}
}
finally
{
EditorStyles.label.fontStyle = labelStyle;
}
if (previousHotControl != GUIUtility.hotControl)
{
window.Selection.Select(
window,
modifier,
modifier.FromIndex,
SelectionType.Modifier);
}
}
/************************************************************************************************************************/
/// Draws the selection and hover highlights for a particular cell.
private void DrawHighlightGUI(Rect area, int column, int row, bool invertHover)
{
if (_Window.Highlighter.EventType != EventType.Repaint)
return;
var selected =
_SelectedCell.x == column ||
_SelectedCell.y == row;
var hover = false;
if (_Window.Highlighter.IsMouseOver)
{
if (invertHover)
(row, column) = (column, row);
var mousePosition = Event.current.mousePosition;
if ((column >= 0 && IsInlineWithX(area, mousePosition.x)) ||
(row >= 0 && IsInlineWithY(area, mousePosition.y)))
{
hover = true;
}
}
_Window.Highlighter.DrawHighlightGUI(area, selected, hover);
}
/************************************************************************************************************************/
/// Is `x` inside the `area`.
private static bool IsInlineWithX(Rect area, float x)
=> area.xMin <= x
&& area.xMax > x;
/// Is `y` inside the `area`.
private static bool IsInlineWithY(Rect area, float y)
=> area.yMin <= y
&& area.yMax > y;
/************************************************************************************************************************/
/// Calculates the largest width of all transition labels.
private float CalculateWidestTransitionLabel()
{
var widest = LineHeight * 2;
var transitions = _Window.Data.Transitions;
for (int i = 0; i < transitions.Length; i++)
{
var transition = transitions[i];
if (transition == null)
continue;
var label = transition.GetCachedName();
var width = CalculateLabelWidth(label);
if (widest < width)
widest = width;
}
return widest;
}
/************************************************************************************************************************/
}
}
#endif