init
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e1eb37f0147d7d4a8ddf11a7d7b04ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
using static Animancer.Editor.TransitionLibraries.TransitionLibrarySelection;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A <see cref="TransitionLibraryWindowPage"/> for editing transition aliases.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryAliasesPage
|
||||
[Serializable]
|
||||
public class TransitionLibraryAliasesPage : TransitionLibraryWindowPage
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private Vector2 _ScrollPosition;
|
||||
|
||||
[NonSerialized]
|
||||
private bool _HasSorted;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DisplayName
|
||||
=> "Transition Aliases";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string HelpTooltip
|
||||
=> "Aliases are custom names which can be used to refer to transitions instead of direct references.";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Index
|
||||
=> 1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly List<Rect>
|
||||
TransitionAreas = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnGUI(Rect area)
|
||||
{
|
||||
var definition = Window.Data;
|
||||
|
||||
if (!_HasSorted)
|
||||
{
|
||||
_HasSorted = true;
|
||||
definition.SortAliases();
|
||||
}
|
||||
|
||||
var currentEvent = Event.current;
|
||||
var isRepaint = currentEvent.type == EventType.Repaint;
|
||||
if (isRepaint)
|
||||
TransitionAreas.Clear();
|
||||
|
||||
area.yMin += StandardSpacing;
|
||||
area.xMin += StandardSpacing;
|
||||
area.xMax -= StandardSpacing;
|
||||
|
||||
var transitions = definition.Transitions;
|
||||
var aliases = definition.Aliases;
|
||||
|
||||
var viewArea = new Rect(
|
||||
0,
|
||||
0,
|
||||
area.width,
|
||||
CalculateHeight(1 + transitions.Length + aliases.Length) + StandardSpacing);
|
||||
|
||||
if (viewArea.height > area.height)
|
||||
viewArea.width -= GUI.skin.verticalScrollbar.fixedWidth;
|
||||
|
||||
_ScrollPosition = GUI.BeginScrollView(area, _ScrollPosition, viewArea);
|
||||
|
||||
viewArea.height = LineHeight;
|
||||
|
||||
DoAliasAllGUI(viewArea);
|
||||
|
||||
NextVerticalArea(ref viewArea);
|
||||
|
||||
var aliasIndex = 0;
|
||||
|
||||
for (int i = 0; i < transitions.Length; i++)
|
||||
{
|
||||
var totalTransitionArea = viewArea;
|
||||
|
||||
if (isRepaint)
|
||||
TransitionAreas.Add(viewArea);
|
||||
|
||||
DoTransitionGUI(viewArea, transitions[i], i);
|
||||
|
||||
NextVerticalArea(ref viewArea);
|
||||
|
||||
while (aliasIndex < aliases.Length)
|
||||
{
|
||||
var alias = aliases[aliasIndex];
|
||||
|
||||
if (alias.Index != i)
|
||||
{
|
||||
if (alias.Index < i && currentEvent.type != EventType.Used)
|
||||
{
|
||||
Debug.LogError("Aliases aren't properly sorted.");
|
||||
definition.SortAliases();
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
DoAliasGUI(viewArea, alias, aliasIndex++);
|
||||
|
||||
NextVerticalArea(ref viewArea);
|
||||
}
|
||||
|
||||
// Highlights.
|
||||
|
||||
totalTransitionArea.yMax = viewArea.yMin - StandardSpacing;
|
||||
|
||||
var selected = Window.Selection.FromIndex == i || Window.Selection.ToIndex == i;
|
||||
var hover = totalTransitionArea.Contains(currentEvent.mousePosition);
|
||||
|
||||
Window.Highlighter.DrawHighlightGUI(totalTransitionArea, selected, hover);
|
||||
}
|
||||
|
||||
GUI.EndScrollView();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws <see cref="TransitionLibraryDefinition.AliasAllTransitions"/>.</summary>
|
||||
private void DoAliasAllGUI(Rect area)
|
||||
{
|
||||
var definition = Window.Data;
|
||||
|
||||
using (var label = PooledGUIContent.Acquire(
|
||||
"Alias All Transitions",
|
||||
TransitionLibraryDefinition.AliasAllTransitionsTooltip))
|
||||
definition.AliasAllTransitions = EditorGUI.Toggle(area, label, definition.AliasAllTransitions);
|
||||
|
||||
if (TryUseClickEvent(area, 0))
|
||||
definition.AliasAllTransitions = !definition.AliasAllTransitions;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a `transition`.</summary>
|
||||
private void DoTransitionGUI(Rect area, TransitionAssetBase transition, int index)
|
||||
{
|
||||
var addArea = StealFromLeft(ref area, LineHeight * 5, StandardSpacing);
|
||||
|
||||
TransitionModifierTableGUI.HandleTransitionLabelInput(
|
||||
ref area,
|
||||
Window,
|
||||
transition,
|
||||
index,
|
||||
SelectionType.ToTransition,
|
||||
CalculateTargetTransitionIndex);
|
||||
|
||||
var typeArea = StealFromRight(ref area, area.width * 0.5f, StandardSpacing);
|
||||
|
||||
var label = transition.GetCachedName();
|
||||
GUI.Label(area, label);
|
||||
|
||||
var wrappedTransition = transition.GetTransition();
|
||||
var type = wrappedTransition != null
|
||||
? wrappedTransition.GetType().GetNameCS(false)
|
||||
: "Null";
|
||||
GUI.Label(typeArea, type);
|
||||
|
||||
if (GUI.Button(addArea, "Add"))
|
||||
{
|
||||
var alias = new NamedIndex(null, index);
|
||||
Window.RecordUndo().AddAlias(alias);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the transition index for a drag and drop operation.</summary>
|
||||
private static int CalculateTargetTransitionIndex(
|
||||
Rect area,
|
||||
int index,
|
||||
Event currentEvent)
|
||||
{
|
||||
var y = currentEvent.mousePosition.y;
|
||||
|
||||
for (int i = 0; i < TransitionAreas.Count; i++)
|
||||
if (y <= TransitionAreas[i].yMax)
|
||||
return i;
|
||||
|
||||
return TransitionAreas.Count;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws an `alias`.</summary>
|
||||
private void DoAliasGUI(Rect area, NamedIndex alias, int index)
|
||||
{
|
||||
var removeArea = StealFromLeft(ref area, LineHeight * 5, StandardSpacing);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var name = StringAssetDrawer.DrawGUI(area, GUIContent.none, alias.Name, Window.SourceObject, out _);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Window.RecordUndo().Aliases[index] = alias.With(name as StringAsset);
|
||||
}
|
||||
|
||||
if (GUI.Button(removeArea, "Remove"))
|
||||
{
|
||||
Window.RecordUndo().RemoveAlias(index);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d5be687cb9b8da4e9c61bfb49ab861d
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A <see cref="TransitionLibraryWindowPage"/> for editing transition modifiers.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryModifiersPage
|
||||
[Serializable]
|
||||
public class TransitionLibraryModifiersPage : TransitionLibraryWindowPage
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TransitionModifierTableGUI _TableGUI;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DisplayName
|
||||
=> "Transition Modifiers";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string HelpTooltip
|
||||
=> "Modifiers allow you to replace the usual fade duration for specific combinations of transitions.";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Index
|
||||
=> 0;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnGUI(Rect area)
|
||||
{
|
||||
_TableGUI ??= new();
|
||||
|
||||
if (Window.Data.Transitions.Length == 0)
|
||||
{
|
||||
area = new Rect(
|
||||
area.x + AnimancerGUI.StandardSpacing,
|
||||
area.y + AnimancerGUI.StandardSpacing,
|
||||
area.width - AnimancerGUI.StandardSpacing * 2,
|
||||
AnimancerGUI.LineHeight);
|
||||
|
||||
GUI.Label(area, "Library contains no Transitions");
|
||||
|
||||
AnimancerGUI.NextVerticalArea(ref area);
|
||||
|
||||
if (GUI.Button(area, "Create Transition"))
|
||||
TransitionLibraryOperations.CreateTransition(Window);
|
||||
}
|
||||
else
|
||||
{
|
||||
_TableGUI.DoGUI(area, Window);
|
||||
}
|
||||
|
||||
TransitionLibraryOperations.HandleBackgroundInput(area, Window);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de81cf7262eb1514894f722e6050eead
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// Manages the selection of pages in the <see cref="TransitionLibraryWindow"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryWindowPage
|
||||
[Serializable]
|
||||
public abstract class TransitionLibraryWindowPage : IComparable<TransitionLibraryWindowPage>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The window containing this page.</summary>
|
||||
public TransitionLibraryWindow Window { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The name of this page.</summary>
|
||||
public abstract string DisplayName { get; }
|
||||
|
||||
/// <summary>The text to use for the tooltip on the help button while this page is visible.</summary>
|
||||
public abstract string HelpTooltip { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The sorting index of this page.</summary>
|
||||
public abstract int Index { get; }
|
||||
|
||||
/// <summary>Compares the <see cref="Index"/>.</summary>
|
||||
public int CompareTo(TransitionLibraryWindowPage other)
|
||||
=> Index.CompareTo(other.Index);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI of this page.</summary>
|
||||
public abstract void OnGUI(Rect area);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61210733873388e4e815c6628988b306
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A <see cref="TableGUI"/> for editing
|
||||
/// <see cref="Animancer.TransitionLibraries.TransitionLibraryDefinition.Modifiers"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionModifierTableGUI
|
||||
[Serializable]
|
||||
public class TransitionModifierTableGUI : TableGUI
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private TransitionLibraryWindow _Window;
|
||||
[NonSerialized] private Vector2Int _SelectedCell;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionModifierTableGUI"/>.</summary>
|
||||
public TransitionModifierTableGUI()
|
||||
{
|
||||
base.DoCellGUI = DoCellGUI;
|
||||
CalculateWidestLabel = CalculateWidestTransitionLabel;
|
||||
MinCellSize = new(LineHeight * 2, LineHeight);
|
||||
MaxCellSize = new(LineHeight * 4, LineHeight);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the table GUI.</summary>
|
||||
public void DoGUI(
|
||||
Rect area,
|
||||
TransitionLibraryWindow window)
|
||||
{
|
||||
_Window = window;
|
||||
_SelectedCell = RecalculateSelectedCell(window.Selection);
|
||||
|
||||
var transitions = window.Data.Transitions;
|
||||
DoTableGUI(area, transitions.Length, transitions.Length);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the table coordinates of the `selection`.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a table cell.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the header corner.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a button to create a new transition.</summary>
|
||||
private void DoCreateButtonGUI(Rect area)
|
||||
{
|
||||
if (GUI.Button(area, "Create Transition"))
|
||||
TransitionLibraryOperations.CreateTransition(_Window);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a button to remove the selected transition.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a row or column label.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the name of the `transition` with a special message for <c>null</c>.</summary>
|
||||
public static string GetTransitionName(TransitionAssetBase transition)
|
||||
=> transition != null
|
||||
? transition.GetCachedName()
|
||||
: "<Missing Transition>";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly int LabelHint = "Label".GetHashCode();
|
||||
|
||||
[NonSerialized] private static bool _IsLabelDrag;
|
||||
|
||||
/// <summary>Handles input events on transition labels.</summary>
|
||||
public static void HandleTransitionLabelInput(
|
||||
ref Rect area,
|
||||
TransitionLibraryWindow window,
|
||||
TransitionAssetBase transition,
|
||||
int index,
|
||||
SelectionType selectionType,
|
||||
Func<Rect, int, Event, int> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the transition index for a drag and drop operation.</summary>
|
||||
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;
|
||||
|
||||
/// <summary>Draws the fade duration for a particular transition combination.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the selection and hover highlights for a particular cell.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is `x` inside the `area`.</summary>
|
||||
private static bool IsInlineWithX(Rect area, float x)
|
||||
=> area.xMin <= x
|
||||
&& area.xMax > x;
|
||||
|
||||
/// <summary>Is `y` inside the `area`.</summary>
|
||||
private static bool IsInlineWithY(Rect area, float y)
|
||||
=> area.yMin <= y
|
||||
&& area.yMax > y;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the largest width of all transition labels.</summary>
|
||||
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
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78f4b212f3ad9cd4285498110975c389
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61919166e1e7d634f95c77e80360ab63
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A dummy object for tracking the selection within the <see cref="TransitionLibraryWindow"/>
|
||||
/// and showing its details in the Inspector.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelection
|
||||
[AnimancerHelpUrl(typeof(TransitionLibrarySelection))]
|
||||
public class TransitionLibrarySelection : ScriptableObject
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Types of objects can be selected.</summary>
|
||||
public enum SelectionType
|
||||
{
|
||||
/// <summary>Nothing selected.</summary>
|
||||
None,
|
||||
|
||||
/// <summary>The main library.</summary>
|
||||
Library,
|
||||
|
||||
/// <summary>A from-transition.</summary>
|
||||
FromTransition,
|
||||
|
||||
/// <summary>A to-transition.</summary>
|
||||
ToTransition,
|
||||
|
||||
/// <summary>A fade duration modifier.</summary>
|
||||
Modifier,
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField] private TransitionLibraryWindow _Window;
|
||||
[SerializeField] private SelectionType _Type;
|
||||
[SerializeField] private int _FromIndex = -1;
|
||||
[SerializeField] private int _ToIndex = -1;
|
||||
[SerializeField] private int _Version;
|
||||
|
||||
/// <summary>The window this selection is associated with.</summary>
|
||||
public TransitionLibraryWindow Window
|
||||
=> _Window;
|
||||
|
||||
/// <summary>The type of selected object.</summary>
|
||||
public SelectionType Type
|
||||
=> _Type;
|
||||
|
||||
/// <summary>The index of the <see cref="FromTransition"/>.</summary>
|
||||
public int FromIndex
|
||||
=> _FromIndex;
|
||||
|
||||
/// <summary>The index of the <see cref="ToTransition"/>.</summary>
|
||||
public int ToIndex
|
||||
=> _ToIndex;
|
||||
|
||||
/// <summary>The number of times this selection has been changed.</summary>
|
||||
public int Version
|
||||
=> _Version;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The transition the current selection is coming from.</summary>
|
||||
public TransitionAssetBase FromTransition { get; private set; }
|
||||
|
||||
/// <summary>The transition the current selection is going to.</summary>
|
||||
public TransitionAssetBase ToTransition { get; private set; }
|
||||
|
||||
/// <summary>The <see cref="ITransition.FadeDuration"/> of the current selection.</summary>
|
||||
public float FadeDuration { get; private set; }
|
||||
|
||||
/// <summary>Does the current selection have a modified <see cref="FadeDuration"/>?</summary>
|
||||
public bool HasModifier { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private object _Selected;
|
||||
|
||||
/// <summary>The currently selected object.</summary>
|
||||
public object Selected
|
||||
{
|
||||
get
|
||||
{
|
||||
Validate();
|
||||
return _Selected;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Deselects the current object if it isn't valid.</summary>
|
||||
public bool Validate()
|
||||
{
|
||||
if (IsValid())
|
||||
return true;
|
||||
|
||||
Deselect();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Is the current selection valid?</summary>
|
||||
public bool IsValid()
|
||||
{
|
||||
if (this == null ||
|
||||
_Window == null ||
|
||||
Selection.activeObject != this)
|
||||
return false;
|
||||
|
||||
var library = _Window.SourceObject;
|
||||
if (library == null)
|
||||
return false;
|
||||
|
||||
FromTransition = null;
|
||||
ToTransition = null;
|
||||
FadeDuration = float.NaN;
|
||||
HasModifier = false;
|
||||
|
||||
switch (_Type)
|
||||
{
|
||||
case SelectionType.Library:
|
||||
name = "Transition Library";
|
||||
_Selected = library;
|
||||
return library != null;
|
||||
|
||||
case SelectionType.FromTransition:
|
||||
name = "From Transition";
|
||||
if (!_Window.Data.Transitions.TryGet(_FromIndex, out var transition))
|
||||
return false;
|
||||
|
||||
FromTransition = transition;
|
||||
FadeDuration = transition.TryGetFadeDuration();
|
||||
_Selected = transition;
|
||||
return true;
|
||||
|
||||
case SelectionType.ToTransition:
|
||||
name = "To Transition";
|
||||
if (!_Window.Data.Transitions.TryGet(_ToIndex, out transition))
|
||||
return false;
|
||||
|
||||
ToTransition = transition;
|
||||
FadeDuration = transition.TryGetFadeDuration();
|
||||
_Selected = transition;
|
||||
return true;
|
||||
|
||||
case SelectionType.Modifier:
|
||||
name = "Transition Modifier";
|
||||
|
||||
var hasTransitions = _Window.Data.TryGetTransition(_FromIndex, out transition);
|
||||
FromTransition = transition;
|
||||
|
||||
hasTransitions |= _Window.Data.TryGetTransition(_ToIndex, out transition);
|
||||
ToTransition = transition;
|
||||
|
||||
if (_Window.Data.TryGetModifier(_FromIndex, _ToIndex, out var modifier))
|
||||
{
|
||||
HasModifier = true;
|
||||
}
|
||||
else if (hasTransitions)
|
||||
{
|
||||
modifier = modifier.WithFadeDuration(transition.TryGetFadeDuration());
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FadeDuration = modifier.FadeDuration;
|
||||
_Selected = modifier;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sets the <see cref="Selected"/> object.</summary>
|
||||
/// <remarks>
|
||||
/// We can't simply set the <see cref="Selection.activeObject"/>
|
||||
/// because it might not be a <see cref="UnityEngine.Object"/>
|
||||
/// and if it is then we don't want the Project window to move to it.
|
||||
/// <para></para>
|
||||
/// So instead, we select this dummy object and <see cref="TransitionLibrarySelectionEditor"/>
|
||||
/// draws a custom Inspector for the target object.
|
||||
/// </remarks>
|
||||
public void Select(
|
||||
TransitionLibraryWindow window,
|
||||
object select,
|
||||
int index,
|
||||
SelectionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SelectionType.Library:
|
||||
_FromIndex = -1;
|
||||
_ToIndex = -1;
|
||||
break;
|
||||
|
||||
case SelectionType.FromTransition:
|
||||
_FromIndex = index;
|
||||
_ToIndex = -1;
|
||||
break;
|
||||
|
||||
case SelectionType.ToTransition:
|
||||
_FromIndex = -1;
|
||||
_ToIndex = index;
|
||||
break;
|
||||
|
||||
case SelectionType.Modifier:
|
||||
if (select is TransitionModifierDefinition modifier)
|
||||
{
|
||||
_FromIndex = modifier.FromIndex;
|
||||
_ToIndex = modifier.ToIndex;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
Deselect();
|
||||
throw new ArgumentException($"Unhandled {nameof(SelectionType)}", nameof(type));
|
||||
}
|
||||
|
||||
_Window = window;
|
||||
_Type = type;
|
||||
_Selected = select;
|
||||
_Version++;
|
||||
|
||||
Selection.activeObject = this;
|
||||
|
||||
Validate();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clears the <see cref="Selected"/> object.</summary>
|
||||
public void Deselect()
|
||||
{
|
||||
_Window = null;
|
||||
_Type = default;
|
||||
_FromIndex = -1;
|
||||
_ToIndex = -1;
|
||||
_Selected = null;
|
||||
_Version++;
|
||||
|
||||
if (Selection.activeObject == this)
|
||||
Selection.activeObject = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles selection changes.</summary>
|
||||
public void OnSelectionChange()
|
||||
{
|
||||
if (Selection.activeObject == this)
|
||||
return;
|
||||
|
||||
Deselect();
|
||||
|
||||
if (_Window != null)
|
||||
_Window.Repaint();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Selects this object if it contains a valid selection.</summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (Selected != null)
|
||||
Selection.activeObject = this;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d5cccea07064a74a91890855fd2aaa4
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A custom Inspector for <see cref="TransitionLibrarySelection"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelectionEditor
|
||||
[CustomEditor(typeof(TransitionLibrarySelection), true)]
|
||||
public class TransitionLibrarySelectionEditor : UnityEditor.Editor
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Casts the <see cref="UnityEditor.Editor.target"/>.</summary>
|
||||
public TransitionLibrarySelection Target
|
||||
=> target as TransitionLibrarySelection;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var target = Target;
|
||||
if (target == null || !target.Validate())
|
||||
return;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
switch (target.Type)
|
||||
{
|
||||
case TransitionLibrarySelection.SelectionType.Library:
|
||||
DoNestedEditorGUI(target.Selected as TransitionLibraryAsset, "Transition Library");
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.FromTransition:
|
||||
case TransitionLibrarySelection.SelectionType.ToTransition:
|
||||
DoTransitionGUI(target.Selected as TransitionAssetBase);
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.Modifier:
|
||||
DoModifierGUI(target, (TransitionModifierDefinition)target.Selected);
|
||||
break;
|
||||
|
||||
default:
|
||||
target.Deselect();
|
||||
break;
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
target.Window.Repaint();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Nested Editor
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private readonly CachedEditor NestedEditor = new();
|
||||
[NonSerialized] private readonly CachedEditor NestedEditor2 = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the <see cref="UnityEditor.Editor"/> for the `target`.</summary>
|
||||
private void DoNestedEditorGUI<T>(T target, string referenceLabel)
|
||||
where T : Object
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
AnimancerGUI.DoObjectFieldGUI(referenceLabel, target, false);
|
||||
|
||||
var editor = NestedEditor.GetEditor(target);
|
||||
if (editor != null)
|
||||
editor.OnInspectorGUI();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Cleans up any nested editors.</summary>
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
NestedEditor.Dispose();
|
||||
NestedEditor2.Dispose();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Transitions
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI for the `transition`.</summary>
|
||||
private void DoTransitionGUI(
|
||||
TransitionAssetBase transition)
|
||||
{
|
||||
DoTransitionNameGUI(transition);
|
||||
DoNestedEditorGUI(transition, "Transition Asset");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a field for editing the name of the `transition`.</summary>
|
||||
private void DoTransitionNameGUI(
|
||||
TransitionAssetBase transition)
|
||||
{
|
||||
var isSubAsset = AssetDatabase.IsSubAsset(transition);
|
||||
var isMainAsset = !isSubAsset && AssetDatabase.IsMainAsset(transition);
|
||||
var label = isSubAsset
|
||||
? "Sub-Asset Name"
|
||||
: isMainAsset
|
||||
? "File Name"
|
||||
: "Name";
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var name = TransitionModifierTableGUI.GetTransitionName(transition);
|
||||
name = EditorGUILayout.DelayedTextField(label, name);
|
||||
|
||||
if (EditorGUI.EndChangeCheck() && transition != null)
|
||||
{
|
||||
transition.SetName(name);
|
||||
|
||||
if (isSubAsset)
|
||||
{
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
else if (isMainAsset)
|
||||
{
|
||||
AssetDatabase.RenameAsset(
|
||||
AssetDatabase.GetAssetPath(transition),
|
||||
name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Modifiers
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly BoolPref
|
||||
IsFromExpanded = new($"{nameof(TransitionLibrarySelectionEditor)}.{nameof(IsFromExpanded)}"),
|
||||
IsToExpanded = new($"{nameof(TransitionLibrarySelectionEditor)}.{nameof(IsToExpanded)}");
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI for the `modifier`.</summary>
|
||||
private void DoModifierGUI(
|
||||
TransitionLibrarySelection selection,
|
||||
TransitionModifierDefinition modifier)
|
||||
{
|
||||
var library = selection.Window.Data;
|
||||
DoTransitionField(library, NestedEditor, IsFromExpanded, modifier.FromIndex, "From");
|
||||
DoTransitionField(library, NestedEditor2, IsToExpanded, modifier.ToIndex, "To");
|
||||
|
||||
var area = AnimancerGUI.LayoutSingleLineRect();
|
||||
TransitionModifierTableGUI.DoFadeDurationGUI(
|
||||
area,
|
||||
selection.Window,
|
||||
modifier.FromIndex,
|
||||
modifier.ToIndex,
|
||||
"Fade Duration");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI for a transition.</summary>
|
||||
private TransitionAssetBase DoTransitionField(
|
||||
TransitionLibraryDefinition library,
|
||||
CachedEditor cachedEditor,
|
||||
BoolPref isExpanded,
|
||||
int transitionIndex,
|
||||
string label)
|
||||
{
|
||||
library.TryGetTransition(transitionIndex, out var transition);
|
||||
|
||||
var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.After);
|
||||
var labelArea = area;
|
||||
labelArea.width = EditorGUIUtility.labelWidth;
|
||||
|
||||
isExpanded.Value = EditorGUI.Foldout(labelArea, isExpanded, GUIContent.none, true);
|
||||
|
||||
var enabled = GUI.enabled;
|
||||
GUI.enabled = false;
|
||||
|
||||
AnimancerGUI.DoObjectFieldGUI(area, label, transition, false);
|
||||
|
||||
GUI.enabled = enabled;
|
||||
|
||||
if (isExpanded)
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
|
||||
var editor = cachedEditor.GetEditor(transition);
|
||||
editor.OnInspectorGUI();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
return transition;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cfcfbc7bdddf4d4fabcf7aa15635df6
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using Animancer.Editor.Previews;
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only] Custom preview for <see cref="TransitionLibrarySelection"/>.</summary>
|
||||
/// <remarks>Parts of this class are based on Unity's <see cref="MeshPreview"/>.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelectionPreview
|
||||
[CustomPreview(typeof(TransitionLibrarySelection))]
|
||||
public class TransitionLibrarySelectionPreview : ObjectPreview
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField] private AnimancerPreviewRenderer _PreviewRenderer;
|
||||
[SerializeField] private TransitionPreviewPlayer _PreviewPlayer;
|
||||
|
||||
[NonSerialized] private TransitionLibrarySelection _Target;
|
||||
[NonSerialized] private int _TargetVersion = -1;
|
||||
|
||||
[NonSerialized] private readonly TransitionLibrarySelectionPreviewSpeed Speed = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize(Object[] targets)
|
||||
{
|
||||
_PreviewRenderer ??= new();
|
||||
_PreviewPlayer ??= new();
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
_Target = targets[0] as TransitionLibrarySelection;
|
||||
if (_Target != null)
|
||||
{
|
||||
_TargetVersion = _Target.Version - 1;
|
||||
if (_Target.Window != null)
|
||||
_PreviewRenderer.PreviewObject.TrySelectBestModel(_Target.Window.Data);
|
||||
CheckTarget();
|
||||
}
|
||||
}
|
||||
|
||||
base.Initialize(targets);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Cleanup()
|
||||
{
|
||||
base.Cleanup();
|
||||
_PreviewPlayer?.Dispose();
|
||||
_PreviewPlayer = null;
|
||||
_PreviewRenderer?.Dispose();
|
||||
_PreviewRenderer = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles changes to the target object.</summary>
|
||||
private void CheckTarget()
|
||||
{
|
||||
if (_TargetVersion == _Target.Version)
|
||||
return;
|
||||
|
||||
_TargetVersion = _Target.Version;
|
||||
_PreviewPlayer.IsPlaying = false;
|
||||
|
||||
switch (_Target.Type)
|
||||
{
|
||||
case TransitionLibrarySelection.SelectionType.FromTransition:
|
||||
_PreviewPlayer.FromTransition = _Target.FromTransition;
|
||||
_PreviewPlayer.ToTransition = null;
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.ToTransition:
|
||||
_PreviewPlayer.FromTransition = null;
|
||||
_PreviewPlayer.ToTransition = _Target.ToTransition;
|
||||
break;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.Modifier:
|
||||
_PreviewPlayer.FromTransition = _Target.FromTransition;
|
||||
_PreviewPlayer.ToTransition = _Target.ToTransition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Updates the settings of the <see cref="TransitionPreviewPlayer"/>.</summary>
|
||||
private void UpdatePlayerSettings()
|
||||
{
|
||||
_PreviewPlayer.Graph = _PreviewRenderer.PreviewObject.Graph;
|
||||
|
||||
_PreviewPlayer.FadeDuration = _Target.FadeDuration;
|
||||
_PreviewPlayer.Speed = Speed.Speed;
|
||||
_PreviewPlayer.RecalculateTimeBounds();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly GUIContent
|
||||
Title = new("Preview");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override GUIContent GetPreviewTitle()
|
||||
=> Title;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasPreviewGUI()
|
||||
=> _Target != null
|
||||
&& _Target.Type switch
|
||||
{
|
||||
TransitionLibrarySelection.SelectionType.FromTransition or
|
||||
TransitionLibrarySelection.SelectionType.ToTransition or
|
||||
TransitionLibrarySelection.SelectionType.Modifier
|
||||
=> true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Header Settings
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIStyle _ToolbarButtonStyle;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnPreviewSettings()
|
||||
{
|
||||
CheckTarget();
|
||||
|
||||
_ToolbarButtonStyle ??= new(EditorStyles.toolbarButton)
|
||||
{
|
||||
padding = new(),
|
||||
};
|
||||
|
||||
var area = GUILayoutUtility.GetRect(LineHeight * 1.5f, LineHeight);
|
||||
DoPlayPauseToggle(area, _ToolbarButtonStyle);
|
||||
|
||||
area = GUILayoutUtility.GetRect(LineHeight * 2f, LineHeight);
|
||||
Speed.DoToggleGUI(area, _ToolbarButtonStyle);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a toggle to play and pause the preview.</summary>
|
||||
private void DoPlayPauseToggle(Rect area, GUIStyle style)
|
||||
{
|
||||
if (TryUseClickEvent(area, 1) || TryUseClickEvent(area, 2))
|
||||
_PreviewPlayer.CurrentTime = _PreviewPlayer.MinTime;
|
||||
|
||||
_PreviewPlayer.IsPlaying = AnimancerGUI.DoPlayPauseToggle(
|
||||
area,
|
||||
_PreviewPlayer.IsPlaying,
|
||||
style,
|
||||
"Left Click = Play/Pause\nRight Click = Reset Time");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInteractivePreviewGUI(Rect area, GUIStyle background)
|
||||
{
|
||||
if (_Target == null)
|
||||
return;
|
||||
|
||||
CheckTarget();
|
||||
UpdatePlayerSettings();
|
||||
|
||||
DoSettingsGUI(ref area);
|
||||
|
||||
DoTimelineGUI(ref area);
|
||||
|
||||
_PreviewRenderer.DoGUI(area, background);
|
||||
|
||||
AnimancerPreviewObjectGUI.HandleDragAndDrop(area, _PreviewRenderer.PreviewObject);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws settings for modifying the preview.</summary>
|
||||
private void DoSettingsGUI(ref Rect area)
|
||||
{
|
||||
if (!Speed.IsOn)
|
||||
return;
|
||||
|
||||
area.yMin += StandardSpacing;
|
||||
|
||||
Speed.DoSpeedSlider(ref area, EditorStyles.toolbar);
|
||||
|
||||
var preview = _PreviewRenderer.PreviewObject;
|
||||
var height = AnimancerPreviewObjectGUI.CalculateHeight(preview);
|
||||
var settingsArea = StealFromTop(ref area, height, StandardSpacing);
|
||||
settingsArea = settingsArea.Expand(-StandardSpacing, 0);
|
||||
|
||||
GUI.Label(settingsArea, GUIContent.none, EditorStyles.toolbar);
|
||||
|
||||
AnimancerPreviewObjectGUI.DoModelGUI(settingsArea, preview);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Timeline
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the preview timeline.</summary>
|
||||
private void DoTimelineGUI(ref Rect area)
|
||||
{
|
||||
var timelineArea = StealFromTop(ref area, EditorStyles.toolbar.fixedHeight, StandardSpacing);
|
||||
|
||||
EditorGUI.DrawRect(timelineArea, Grey(0.25f, 0.3f));
|
||||
EditorGUI.DrawRect(new(timelineArea.x, timelineArea.yMax - 1, timelineArea.width, 1), Grey(0, 0.5f));
|
||||
|
||||
DoFadeDurationSliderGUI(timelineArea);
|
||||
DoTimeSliderGUI(timelineArea);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly int SliderHash = "Slider".GetHashCode();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the fade duration slider.</summary>
|
||||
private void DoFadeDurationSliderGUI(Rect area)
|
||||
{
|
||||
if (!CalculateFadeBounds(area, out var startFadeX, out var endFadeX))
|
||||
return;
|
||||
|
||||
switch (_Target.Type)
|
||||
{
|
||||
default:
|
||||
return;
|
||||
|
||||
case TransitionLibrarySelection.SelectionType.FromTransition:
|
||||
case TransitionLibrarySelection.SelectionType.ToTransition:
|
||||
case TransitionLibrarySelection.SelectionType.Modifier:
|
||||
break;
|
||||
}
|
||||
|
||||
var sliderArea = area;
|
||||
sliderArea.width = LineHeight * 0.5f;
|
||||
sliderArea.x = endFadeX - sliderArea.width * 0.5f;
|
||||
|
||||
var control = new GUIControl(sliderArea, SliderHash);
|
||||
|
||||
switch (control.EventType)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (control.TryUseMouseDown())
|
||||
_PreviewPlayer.IsPlaying = false;
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
control.TryUseMouseUp();
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (control.TryUseHotControl())
|
||||
{
|
||||
var x = Math.Max(startFadeX, control.Event.mousePosition.x);
|
||||
var normalizedTime = area.InverseLerpUnclampedX(x);
|
||||
var normalizedStartFade = area.InverseLerpUnclampedX(startFadeX);
|
||||
|
||||
_PreviewPlayer.NormalizedTime = normalizedTime;
|
||||
var fadeDuration =
|
||||
_PreviewPlayer.LerpTimeUnclamped(normalizedTime) -
|
||||
_PreviewPlayer.LerpTimeUnclamped(normalizedStartFade);
|
||||
|
||||
var selected = _Target.Selected;
|
||||
if (selected is TransitionModifierDefinition modifier)
|
||||
{
|
||||
_Target.Window.RecordUndo()
|
||||
.SetModifier(modifier.WithFadeDuration(fadeDuration));
|
||||
}
|
||||
else if (selected is TransitionAssetBase transitionAsset)
|
||||
{
|
||||
if (fadeDuration < 0)
|
||||
fadeDuration = 0;
|
||||
|
||||
using var serializedObject = new SerializedObject(transitionAsset);
|
||||
var property = serializedObject.FindProperty(TransitionAssetBase.TransitionField);
|
||||
property = property.FindPropertyRelative("_" + nameof(ITransition.FadeDuration));
|
||||
property.floatValue = fadeDuration;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
_Target.Window.Repaint();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
|
||||
var color = AnimancerStateDrawerColors.FadeLineColor;
|
||||
|
||||
var showCursor = GUIUtility.hotControl == 0 || GUIUtility.hotControl == control.ID;
|
||||
if (showCursor)
|
||||
EditorGUIUtility.AddCursorRect(sliderArea, MouseCursor.ResizeHorizontal);
|
||||
|
||||
if (!showCursor || !sliderArea.Contains(control.Event.mousePosition))
|
||||
color.a *= 0.5f;
|
||||
|
||||
EditorGUI.DrawRect(
|
||||
new(endFadeX, sliderArea.y, 1, sliderArea.height - 1),
|
||||
color);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the preview time slider.</summary>
|
||||
private void DoTimeSliderGUI(Rect area)
|
||||
{
|
||||
var control = new GUIControl(area, SliderHash);
|
||||
|
||||
switch (control.EventType)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (control.TryUseMouseDown())
|
||||
{
|
||||
_ForceClampTime = true;
|
||||
_DidWrapTime = false;
|
||||
HandleDragTime(area, control.Event);
|
||||
|
||||
_ForceClampTime = control.Event.control;
|
||||
if (!_ForceClampTime)
|
||||
EditorGUIUtility.SetWantsMouseJumping(1);
|
||||
|
||||
_PreviewPlayer.IsPlaying = control.Event.clickCount > 1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (control.TryUseMouseUp())
|
||||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (control.TryUseHotControl())
|
||||
HandleDragTime(area, control.Event);
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
|
||||
BeginTriangles(AnimancerStateDrawerColors.FadeLineColor);
|
||||
|
||||
if (CalculateFadeBounds(area, out var startFadeX, out var endFadeX))
|
||||
{
|
||||
// Fade.
|
||||
DrawLineBatched(
|
||||
new(startFadeX, area.yMin + 1),
|
||||
new(endFadeX, area.yMax - 1),
|
||||
1);
|
||||
|
||||
// To.
|
||||
if (endFadeX < area.xMax)
|
||||
DrawLineBatched(
|
||||
new(endFadeX, area.yMax - 1),
|
||||
new(area.xMax, area.yMax - 1),
|
||||
1);
|
||||
}
|
||||
|
||||
// From.
|
||||
if (area.xMin < startFadeX)
|
||||
DrawLineBatched(
|
||||
new(area.xMin, area.yMin + 1),
|
||||
new(startFadeX, area.yMin + 1),
|
||||
1);
|
||||
|
||||
var color = _PreviewPlayer.IsPlaying
|
||||
? AnimancerStateDrawerColors.PlayingBarColor
|
||||
: AnimancerStateDrawerColors.PausedBarColor;
|
||||
color.a = 1;
|
||||
|
||||
var timeX = area.LerpUnclampedX(_PreviewPlayer.NormalizedTime);
|
||||
|
||||
GL.Color(color);
|
||||
DrawLineBatched(new(timeX, area.yMin), new(timeX, area.yMax), 2);
|
||||
|
||||
EndTriangles();
|
||||
|
||||
DoTransitionLabels(area);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool _ForceClampTime;
|
||||
private bool _DidWrapTime;
|
||||
|
||||
/// <summary>Draws handles drag events to control the preview time.</summary>
|
||||
private void HandleDragTime(Rect area, Event currentEvent)
|
||||
{
|
||||
if (_ForceClampTime)
|
||||
{
|
||||
_PreviewPlayer.NormalizedTime = area.InverseLerpUnclampedX(currentEvent.mousePosition.x);
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = currentEvent.delta.x;
|
||||
|
||||
var normalizedTime = _PreviewPlayer.NormalizedTime;
|
||||
if (normalizedTime == 0 && !_DidWrapTime && delta > 0)
|
||||
{
|
||||
var x = currentEvent.mousePosition.x;
|
||||
if (area.xMin > x || area.xMax < x)
|
||||
return;
|
||||
}
|
||||
|
||||
normalizedTime += delta / area.width;
|
||||
if (normalizedTime >= 0 || _DidWrapTime)
|
||||
{
|
||||
|
||||
if (normalizedTime > 1)
|
||||
_DidWrapTime = true;
|
||||
|
||||
normalizedTime = AnimancerUtilities.Wrap01(normalizedTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedTime = 0;
|
||||
}
|
||||
|
||||
_PreviewPlayer.NormalizedTime = normalizedTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the start and end pixels of the fade.</summary>
|
||||
private bool CalculateFadeBounds(
|
||||
Rect area,
|
||||
out float startFadeX,
|
||||
out float endFadeX)
|
||||
{
|
||||
var fadeDuration = _Target.FadeDuration;
|
||||
if (!float.IsNaN(fadeDuration))
|
||||
{
|
||||
startFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(0));
|
||||
|
||||
endFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(fadeDuration));
|
||||
|
||||
if (_Target.FromTransition.IsValid())
|
||||
{
|
||||
if (!_Target.ToTransition.IsValid())
|
||||
{
|
||||
endFadeX -= startFadeX;
|
||||
startFadeX = area.xMin;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_Target.ToTransition.IsValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(0));
|
||||
endFadeX = startFadeX;
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws labels for the selected transitions.</summary>
|
||||
private void DoTransitionLabels(Rect area)
|
||||
{
|
||||
area.xMin += 1;
|
||||
area.xMax -= 2;
|
||||
|
||||
var mid = area.width * 0.5f;
|
||||
var leftArea = area;
|
||||
var rightArea = area;
|
||||
|
||||
var fromTransition = _Target.FromTransition;
|
||||
var toTransition = _Target.ToTransition;
|
||||
|
||||
var hasFrom = fromTransition.IsValid();
|
||||
var hasTo = toTransition.IsValid();
|
||||
|
||||
if (hasFrom && hasTo)
|
||||
{
|
||||
leftArea.width = mid - StandardSpacing * 0.5f;
|
||||
|
||||
rightArea.x = area.xMax - leftArea.width;
|
||||
rightArea.width = leftArea.width;
|
||||
}
|
||||
|
||||
if (hasFrom)
|
||||
GUI.Label(leftArea, _Target.FromTransition.GetCachedName());
|
||||
|
||||
if (hasTo)
|
||||
GUI.Label(rightArea, _Target.ToTransition.GetCachedName(), RightLabelStyle);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7de29fe26fe25b540987aef2f28c1ee0
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// <see cref="ToggledSpeedSlider"/> for <see cref="TransitionLibrarySelectionPreview"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelectionPreviewSpeed
|
||||
public class TransitionLibrarySelectionPreviewSpeed : ToggledSpeedSlider
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private const string
|
||||
SpeedPrefKey = nameof(TransitionLibrarySelectionPreviewSpeed) + "." + nameof(Speed);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionLibrarySelectionPreviewSpeed"/>.</summary>
|
||||
public TransitionLibrarySelectionPreviewSpeed()
|
||||
: base(nameof(TransitionLibrarySelectionPreviewSpeed) + ".Show")
|
||||
{
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSetSpeed(float speed)
|
||||
{
|
||||
EditorPrefs.SetFloat(SpeedPrefKey, speed);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool DoToggleGUI(Rect area, GUIStyle style)
|
||||
{
|
||||
if (float.IsNaN(Speed))
|
||||
Speed = EditorPrefs.GetFloat(SpeedPrefKey, 1);
|
||||
|
||||
return base.DoToggleGUI(area, style);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29d5d1e026917374788c9f8258fc1fca
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,179 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A custom Inspector for <see cref="TransitionLibraryAsset"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryAssetEditor
|
||||
[CustomEditor(typeof(TransitionLibraryAsset), true)]
|
||||
public class TransitionLibraryAssetEditor : UnityEditor.Editor
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIStyle _HeaderStyle;
|
||||
|
||||
/// <summary>Style for section headers.</summary>
|
||||
public static GUIStyle HeaderStyle
|
||||
=> _HeaderStyle ??= new(EditorStyles.label)
|
||||
{
|
||||
fontSize = EditorStyles.label.fontSize * 2,
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized]
|
||||
private SerializedProperty _AliasAllTransitions;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called when a <see cref="TransitionLibraryAsset"/> is selected.</summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_AliasAllTransitions = serializedObject.FindProperty(
|
||||
TransitionLibraryAsset.DefinitionField + "." + TransitionLibraryDefinition.AliasAllTransitionsField);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called when a <see cref="TransitionLibraryAsset"/> is deselected.</summary>
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
NestedEditor.Dispose();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var library = target as TransitionLibraryAsset;
|
||||
if (library == null)
|
||||
return;
|
||||
|
||||
DoMainButtonsGUI(library);
|
||||
DoSettingsGUI(library);
|
||||
DoEditorDataGUI(library);
|
||||
DoSubAssetWarningGUI(library);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws several buttons with utility functions.</summary>
|
||||
private void DoMainButtonsGUI(TransitionLibraryAsset library)
|
||||
{
|
||||
var editLabel = TransitionLibraryWindow.IsShowing(library)
|
||||
? "Currently Editing"
|
||||
: "Edit";
|
||||
if (GUILayout.Button(editLabel))
|
||||
TransitionLibraryWindow.Open(library);
|
||||
|
||||
using (var label = PooledGUIContent.Acquire("Documentation", Strings.DocsURLs.TransitionLibraries))
|
||||
if (GUILayout.Button(label))
|
||||
Application.OpenURL(Strings.DocsURLs.TransitionLibraries);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the `library`'s main settings.</summary>
|
||||
private void DoSettingsGUI(TransitionLibraryAsset library)
|
||||
{
|
||||
GUILayout.Space(AnimancerGUI.LineHeight);
|
||||
GUILayout.Label("Settings", HeaderStyle);
|
||||
|
||||
EditorGUILayout.PropertyField(_AliasAllTransitions);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private readonly CachedEditor NestedEditor = new();
|
||||
|
||||
/// <summary>Draws the `library`'s <see cref="TransitionLibraryEditorData"/>.</summary>
|
||||
private void DoEditorDataGUI(TransitionLibraryAsset library)
|
||||
{
|
||||
GUILayout.Space(AnimancerGUI.LineHeight);
|
||||
GUILayout.Label("Editor-Only Settings", HeaderStyle);
|
||||
|
||||
var data = library.GetOrCreateEditorData();
|
||||
var editor = NestedEditor.GetEditor(data);
|
||||
editor.OnInspectorGUI();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws warnings about any sub-assets which aren't actually referenced by the `library`.</summary>
|
||||
private void DoSubAssetWarningGUI(TransitionLibraryAsset library)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(library);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
return;
|
||||
|
||||
var subAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
for (int i = 0; i < subAssets.Length; i++)
|
||||
DoSubAssetWarningGUI(library, assetPath, subAssets[i]);
|
||||
}
|
||||
|
||||
/// <summary>Draws a warning about the `subAsset` if it isn't actually referenced by the `library`.</summary>
|
||||
private void DoSubAssetWarningGUI(
|
||||
TransitionLibraryAsset library,
|
||||
string assetPath,
|
||||
Object subAsset)
|
||||
{
|
||||
switch (subAsset)
|
||||
{
|
||||
case TransitionAssetBase transition:
|
||||
if (Array.IndexOf(library.Definition.Transitions, transition) < 0)
|
||||
break;
|
||||
|
||||
return;
|
||||
|
||||
case StringAsset alias:
|
||||
var aliases = library.Definition.Aliases;
|
||||
for (int i = 0; i < aliases.Length; i++)
|
||||
if (aliases[i].Name == alias)
|
||||
return;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
$"Sub-Asset '{subAsset.name}' isn't referenced by this Transition Library." +
|
||||
$" Click to ping. Shift + Click to delete.",
|
||||
MessageType.Warning);
|
||||
|
||||
if (AnimancerGUI.TryUseClickEventInLastRect(0))
|
||||
{
|
||||
if (Event.current.shift)
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Delete Sub-Asset",
|
||||
$"Are you sure you want to delete '{subAsset.name}'" +
|
||||
$" inside {assetPath}?" +
|
||||
$"\n\nThis operation cannot be undone.",
|
||||
"Delete",
|
||||
"Cancel"))
|
||||
AnimancerEditorUtilities.DeleteSubAsset(subAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUIUtility.PingObject(subAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e85015084cb6e947947199df796b532
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,170 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// Additional data for a <see cref="TransitionLibraryAsset"/> which is excluded from Runtime Builds.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryEditorData
|
||||
[AnimancerHelpUrl(typeof(TransitionLibraryEditorData))]
|
||||
public partial class TransitionLibraryEditorData : ScriptableObject
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Dictionary<TransitionLibraryAsset, TransitionLibraryEditorData>
|
||||
LibraryToEditorData = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
private TransitionLibraryAsset _Library;
|
||||
|
||||
/// <summary>The library this data is associated with.</summary>
|
||||
public TransitionLibraryAsset Library
|
||||
{
|
||||
get => _Library;
|
||||
private set
|
||||
{
|
||||
if (_Library == value)
|
||||
return;
|
||||
|
||||
if (_Library != null)
|
||||
LibraryToEditorData.Remove(_Library);
|
||||
|
||||
_Library = value;
|
||||
EditorUtility.SetDirty(this);
|
||||
|
||||
if (_Library != null)
|
||||
LibraryToEditorData.Add(_Library, this);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Registers this data for the <see cref="Library"/>.</summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_Library != null)
|
||||
LibraryToEditorData[_Library] = this;
|
||||
}
|
||||
|
||||
/// <summary>Un-registers this data for the <see cref="Library"/>.</summary>
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_Library != null)
|
||||
LibraryToEditorData.Remove(_Library);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to get the `data` associated with the `library`.</summary>
|
||||
private static bool TryGet(
|
||||
TransitionLibraryAsset library,
|
||||
out TransitionLibraryEditorData data)
|
||||
{
|
||||
if (!LibraryToEditorData.TryGetValue(library, out data))
|
||||
return false;
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
data.Library = library;
|
||||
return true;
|
||||
}
|
||||
|
||||
LibraryToEditorData.Remove(library);
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="TransitionLibraryEditorData"/> sub-asset of the `library` if one exists.
|
||||
/// </summary>
|
||||
public static TransitionLibraryEditorData GetEditorData(TransitionLibraryAsset library)
|
||||
{
|
||||
if (TryGet(library, out var data))
|
||||
return data;
|
||||
|
||||
var assetPath = AssetDatabase.GetAssetPath(library);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
return null;
|
||||
|
||||
var subAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
|
||||
for (int i = 0; i < subAssets.Length; i++)
|
||||
{
|
||||
if (subAssets[i] is TransitionLibraryEditorData editorData)
|
||||
{
|
||||
editorData.Library = library;
|
||||
return editorData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="TransitionLibraryEditorData"/> sub-asset of the `library` if one exists.
|
||||
/// Otherwise, creates and saves a new one.
|
||||
/// </summary>
|
||||
public static TransitionLibraryEditorData GetOrCreateEditorData(TransitionLibraryAsset library)
|
||||
{
|
||||
var data = library.GetEditorData();
|
||||
if (data != null)
|
||||
return data;
|
||||
|
||||
data = CreateInstance<TransitionLibraryEditorData>();
|
||||
data.name = "Editor Data";
|
||||
data.hideFlags = HideFlags.DontSaveInBuild | HideFlags.HideInHierarchy;
|
||||
data.Library = library;
|
||||
|
||||
EditorApplication.CallbackFunction addSubAsset = null;
|
||||
|
||||
addSubAsset = () =>
|
||||
{
|
||||
if (AssetDatabase.Contains(library))
|
||||
{
|
||||
EditorApplication.update -= addSubAsset;
|
||||
|
||||
AssetDatabase.AddObjectToAsset(data, library);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
};
|
||||
|
||||
EditorApplication.update += addSubAsset;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] Extension methods for <see cref="TransitionLibraryEditorData"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryEditorDataExtensions
|
||||
public static class TransitionLibraryEditorDataExtensions
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary><see cref="TransitionLibraryEditorData.GetEditorData"/></summary>
|
||||
public static TransitionLibraryEditorData GetEditorData(this TransitionLibraryAsset library)
|
||||
=> TransitionLibraryEditorData.GetEditorData(library);
|
||||
|
||||
/// <summary><see cref="TransitionLibraryEditorData.GetOrCreateEditorData"/></summary>
|
||||
public static TransitionLibraryEditorData GetOrCreateEditorData(this TransitionLibraryAsset library)
|
||||
=> TransitionLibraryEditorData.GetOrCreateEditorData(library);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f04d4a67566152e40aaa01b8b745ff6b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only] Custom Inspector for <see cref="TransitionLibraryEditorData"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryEditorDataEditor
|
||||
[CustomEditor(typeof(TransitionLibraryEditorData), true)]
|
||||
public class TransitionLibraryEditorDataEditor : UnityEditor.Editor
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var target = this.target as TransitionLibraryEditorData;
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
var transitionSortMode = target.TransitionSortMode;
|
||||
|
||||
base.OnInspectorGUI();
|
||||
|
||||
if (transitionSortMode != target.TransitionSortMode &&
|
||||
target.Library != null)
|
||||
TransitionLibrarySort.Sort(target.Library);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e70b9fb99ed77049978e82246db2d3d
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,257 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
using static Animancer.Editor.TransitionLibraries.TransitionLibrarySelection;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// Operations for modifying a <see cref="TransitionLibraryAsset"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryOperations
|
||||
public static class TransitionLibraryOperations
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles input events for the background of the `window`.</summary>
|
||||
public static void HandleBackgroundInput(
|
||||
Rect area,
|
||||
TransitionLibraryWindow window)
|
||||
{
|
||||
// Click to select the library.
|
||||
if (TryUseClickEvent(area, 0))
|
||||
window.Selection.Select(window, window.SourceObject, -1, SelectionType.Library);
|
||||
|
||||
var currentEvent = Event.current;
|
||||
switch (currentEvent.type)
|
||||
{
|
||||
// Drag and drop to add transition.
|
||||
case EventType.DragUpdated:
|
||||
case EventType.DragPerform:
|
||||
HandleDragAndDrop(currentEvent, window);
|
||||
break;
|
||||
|
||||
case EventType.ValidateCommand:
|
||||
case EventType.ExecuteCommand:
|
||||
switch (currentEvent.commandName)
|
||||
{
|
||||
// Delete to remove the selection.
|
||||
case Commands.Delete:
|
||||
case Commands.SoftDelete:
|
||||
HandleDelete(currentEvent, window);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles drag and drop events to add transitions to the `window`.</summary>
|
||||
private static void HandleDragAndDrop(
|
||||
Event currentEvent,
|
||||
TransitionLibraryWindow window)
|
||||
{
|
||||
var dragging = DragAndDrop.objectReferences;
|
||||
|
||||
TransitionAssetBase dropped = null;
|
||||
int index = -1;
|
||||
|
||||
for (int i = dragging.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var transition = TryCreateTransitionAttribute.TryCreateTransitionAsset(dragging[i]);
|
||||
if (transition != null &&
|
||||
Array.IndexOf(window.Data.Transitions, transition) >= 0)
|
||||
continue;
|
||||
|
||||
switch (currentEvent.type)
|
||||
{
|
||||
case EventType.DragUpdated:
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
||||
currentEvent.Use();
|
||||
return;
|
||||
|
||||
case EventType.DragPerform:
|
||||
dropped = transition;
|
||||
index = window.Data.Transitions.Length;
|
||||
window.RecordUndo().AddTransition(transition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dropped != null)
|
||||
{
|
||||
window.Selection.Select(
|
||||
window,
|
||||
dropped,
|
||||
index,
|
||||
SelectionType.ToTransition);
|
||||
DragAndDrop.AcceptDrag();
|
||||
currentEvent.Use();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new transition as a sub-asset of the `window`'s library.</summary>
|
||||
public static TransitionAssetBase CreateTransition(
|
||||
TransitionLibraryWindow window)
|
||||
{
|
||||
var createInstance = TransitionAssetBase.CreateInstance;
|
||||
if (createInstance == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"{nameof(CreateTransition)} failed because " +
|
||||
$"{nameof(TransitionAssetBase)}.{nameof(TransitionAssetBase.CreateInstance)}" +
|
||||
$" hasn't been assigned." +
|
||||
$" It should be automatically initialized by TransitionAsset.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var definition = window.RecordUndo();
|
||||
|
||||
var transition = createInstance(null);
|
||||
transition.name = "Transition " + (definition.Transitions.Length + 1);
|
||||
AnimancerReflection.TryInvoke(transition, "Reset");
|
||||
|
||||
definition.AddTransition(transition);
|
||||
|
||||
var index = definition.Transitions.Length;
|
||||
window.Selection.Select(window, transition, index, SelectionType.ToTransition);
|
||||
|
||||
return transition;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles a delete event.</summary>
|
||||
private static void HandleDelete(
|
||||
Event currentEvent,
|
||||
TransitionLibraryWindow window)
|
||||
{
|
||||
if (!window.Selection.Validate())
|
||||
return;
|
||||
|
||||
switch (window.Selection.Type)
|
||||
{
|
||||
case SelectionType.FromTransition:
|
||||
if (currentEvent.type == EventType.ExecuteCommand &&
|
||||
window.Selection.Selected is TransitionAssetBase fromTransition)
|
||||
AskHowToDeleteTransition(
|
||||
fromTransition,
|
||||
window.Selection.FromIndex,
|
||||
window);
|
||||
break;
|
||||
|
||||
case SelectionType.ToTransition:
|
||||
if (currentEvent.type == EventType.ExecuteCommand &&
|
||||
window.Selection.Selected is TransitionAssetBase toTransition)
|
||||
AskHowToDeleteTransition(
|
||||
toTransition,
|
||||
window.Selection.ToIndex,
|
||||
window);
|
||||
break;
|
||||
|
||||
case SelectionType.Modifier:
|
||||
if (currentEvent.type == EventType.ExecuteCommand &&
|
||||
window.Selection.Selected is TransitionModifierDefinition modifier)
|
||||
window.RecordUndo().RemoveModifier(modifier);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
currentEvent.Use();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Asks if the user wants to delete a transition asset or just remove it from the library.</summary>
|
||||
public static void AskHowToDeleteTransition(
|
||||
TransitionAssetBase transition,
|
||||
int index,
|
||||
TransitionLibraryWindow window)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(transition);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
if (transition != null)
|
||||
Undo.DestroyObjectImmediate(transition);
|
||||
|
||||
window.RecordUndo().RemoveTransition(index);
|
||||
return;
|
||||
}
|
||||
|
||||
var isMainAsset = AssetDatabase.IsMainAsset(transition);
|
||||
var assetType = isMainAsset ? "Asset" : "Sub-Asset";
|
||||
var isSubAssetOfLibrary =
|
||||
!isMainAsset &&
|
||||
assetPath == AssetDatabase.GetAssetPath(window.SourceObject);
|
||||
|
||||
var message = assetPath;
|
||||
if (!isSubAssetOfLibrary)
|
||||
message += "\n\nRemove Transition: removes it from this Transition Library.";
|
||||
|
||||
message += $"\n\nDelete {assetType}: deletes the Transition {assetType} from your project (cannot be undone).";
|
||||
|
||||
int choice;
|
||||
if (isSubAssetOfLibrary)
|
||||
{
|
||||
if (EditorUtility.DisplayDialog(
|
||||
"Delete transition?",
|
||||
message,
|
||||
"Delete " + assetType,
|
||||
"Cancel"))
|
||||
choice = 2;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
choice = EditorUtility.DisplayDialogComplex(
|
||||
"Remove or Delete transition?",
|
||||
message,
|
||||
"Remove Transition",
|
||||
"Cancel",
|
||||
"Delete " + assetType);
|
||||
}
|
||||
|
||||
switch (choice)
|
||||
{
|
||||
case 0:// Remove.
|
||||
window.RecordUndo().RemoveTransition(index);
|
||||
break;
|
||||
|
||||
case 2:// Delete.
|
||||
if (isMainAsset)
|
||||
{
|
||||
AssetDatabase.DeleteAsset(assetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimancerEditorUtilities.DeleteSubAsset(transition);
|
||||
}
|
||||
|
||||
window.Data.RemoveTransition(index);
|
||||
Undo.ClearUndo(window);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 981e396a9df2f7741b95b02098bb3838
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,408 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only] Utility for sorting a <see cref="TransitionLibraryAsset"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySort
|
||||
public class TransitionLibrarySort : AssetModificationProcessor
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Automation
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that a <see cref="TransitionLibraryAsset"/> is sorted before being saved.</summary>
|
||||
private static string[] OnWillSaveAssets(string[] paths)
|
||||
{
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (!path.EndsWith(".asset", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var library = AssetDatabase.LoadAssetAtPath<TransitionLibraryAsset>(path);
|
||||
if (library == null)
|
||||
continue;
|
||||
|
||||
Sort(library);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Sort Modes
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Applies the <see cref="TransitionLibraryEditorData.TransitionSortMode"/>.</summary>
|
||||
public static void Sort(TransitionLibraryAsset library)
|
||||
{
|
||||
// Can't have editor data if not an asset, so the sort mode will be custom anyway.
|
||||
if (!AssetDatabase.Contains(library))
|
||||
return;
|
||||
|
||||
var data = library.GetOrCreateEditorData();
|
||||
if (data.TransitionSortMode == TransitionSortMode.Custom)
|
||||
return;
|
||||
|
||||
NameCache.Clear();
|
||||
|
||||
switch (data.TransitionSortMode)
|
||||
{
|
||||
case TransitionSortMode.Name:
|
||||
Sort(library.Definition, Static<CompareName>.Instance);
|
||||
break;
|
||||
|
||||
case TransitionSortMode.Path:
|
||||
Sort(library.Definition, Static<ComparePath>.Instance);
|
||||
break;
|
||||
|
||||
case TransitionSortMode.TypeThenName:
|
||||
Sort(library.Definition, Static<CompareTypeThenName>.Instance);
|
||||
break;
|
||||
|
||||
case TransitionSortMode.TypeThenPath:
|
||||
Sort(library.Definition, Static<CompareTypeThenPath>.Instance);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Compares the asset names then GUIDs.</summary>
|
||||
private class CompareName : IComparer<TransitionAssetBase>
|
||||
{
|
||||
public int Compare(TransitionAssetBase a, TransitionAssetBase b)
|
||||
{
|
||||
var result = CompareNulls(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = CompareCachedNames(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return CompareGUIDs(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Compares the asset paths then GUIDs.</summary>
|
||||
private class ComparePath : IComparer<TransitionAssetBase>
|
||||
{
|
||||
public int Compare(TransitionAssetBase a, TransitionAssetBase b)
|
||||
{
|
||||
var result = CompareNulls(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = ComparePaths(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = CompareCachedNames(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return CompareGUIDs(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Compares the transition types then asset names then GUIDs.</summary>
|
||||
private class CompareTypeThenName : IComparer<TransitionAssetBase>
|
||||
{
|
||||
public int Compare(TransitionAssetBase a, TransitionAssetBase b)
|
||||
{
|
||||
var result = CompareNulls(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = CompareTypes(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = CompareCachedNames(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return CompareGUIDs(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Compares the transition types then asset paths then GUIDs.</summary>
|
||||
private class CompareTypeThenPath : IComparer<TransitionAssetBase>
|
||||
{
|
||||
public int Compare(TransitionAssetBase a, TransitionAssetBase b)
|
||||
{
|
||||
var result = CompareNulls(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = CompareTypes(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = ComparePaths(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = CompareCachedNames(a, b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return CompareGUIDs(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Compares objects to put null or destroyed ones at the end.</summary>
|
||||
private static int CompareNulls(TransitionAssetBase a, TransitionAssetBase b)
|
||||
=> (a == null).CompareTo(b == null);
|
||||
|
||||
/// <summary>Compares the asset GUIDs.</summary>
|
||||
private static int CompareGUIDs(TransitionAssetBase a, TransitionAssetBase b)
|
||||
{
|
||||
var gotA = AssetDatabase.TryGetGUIDAndLocalFileIdentifier(a, out var aGUID, out long aLocalID);
|
||||
var gotB = AssetDatabase.TryGetGUIDAndLocalFileIdentifier(b, out var bGUID, out long bLocalID);
|
||||
var result = gotA.CompareTo(gotB);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = aGUID.CompareTo(bGUID);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return aLocalID.CompareTo(bLocalID);
|
||||
}
|
||||
|
||||
/// <summary>Compares the asset names.</summary>
|
||||
private static int CompareCachedNames(TransitionAssetBase a, TransitionAssetBase b)
|
||||
=> a.GetCachedName().CompareTo(b.GetCachedName());
|
||||
|
||||
/// <summary>Compares the asset paths.</summary>
|
||||
private static int ComparePaths(TransitionAssetBase a, TransitionAssetBase b)
|
||||
=> AssetDatabase.GetAssetPath(a).CompareTo(AssetDatabase.GetAssetPath(b));
|
||||
|
||||
/// <summary>Compares the transition types.</summary>
|
||||
private static int CompareTypes(TransitionAssetBase a, TransitionAssetBase b)
|
||||
{
|
||||
if (AnimancerUtilities.TryGetWrappedObject<ITransition>(a, out var transitionA) &&
|
||||
AnimancerUtilities.TryGetWrappedObject<ITransition>(b, out var transitionB))
|
||||
{
|
||||
var result = transitionA.GetType().GetNameCS().CompareTo(transitionB.GetType().GetNameCS());
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
return a.GetType().GetNameCS().CompareTo(b.GetType().GetNameCS());
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Sorting
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static TransitionAssetBase[]
|
||||
_SortingTransitions = Array.Empty<TransitionAssetBase>();
|
||||
|
||||
private static int[] _OldIndexToNew;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sorts the <see cref="TransitionLibraryDefinition.Transitions"/>.</summary>
|
||||
public static void Sort(
|
||||
TransitionLibraryDefinition library,
|
||||
Comparison<TransitionAssetBase> comparison)
|
||||
=> Sort(library, new Comparison<TransitionAssetBase>(comparison));
|
||||
|
||||
/// <summary>Sorts the <see cref="TransitionLibraryDefinition.Transitions"/>.</summary>
|
||||
public static void Sort(
|
||||
TransitionLibraryDefinition library,
|
||||
IComparer<TransitionAssetBase> comparer)
|
||||
{
|
||||
var transitions = library.Transitions;
|
||||
var count = transitions.Length;
|
||||
|
||||
if (_SortingTransitions.Length < count)
|
||||
{
|
||||
var length = Mathf.NextPowerOfTwo(count);
|
||||
_SortingTransitions = new TransitionAssetBase[length];
|
||||
_OldIndexToNew = new int[length];
|
||||
}
|
||||
|
||||
Array.Copy(transitions, _SortingTransitions, count);
|
||||
|
||||
// Indices 0 -> Count.
|
||||
var newIndexToOld = GetTempSequentialIndices(count);
|
||||
|
||||
Array.Sort(_SortingTransitions, newIndexToOld, 0, count, comparer);
|
||||
|
||||
// Remove nulls which should have been sorted to the end.
|
||||
for (int i = count - 1; i >= 0; i--)
|
||||
if (_SortingTransitions[i] == null)
|
||||
count--;
|
||||
else
|
||||
break;
|
||||
|
||||
// _NewIndexToOld[x] is now the index that Transitions[x] was at previously.
|
||||
// We need to invert that so _OldIndexToNew[x] is the new index of whatever was previously at Transitions[x].
|
||||
// That allows the library to update any index references using a simple x = _OldIndexToNew[x];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
_OldIndexToNew[newIndexToOld[i]] = i;
|
||||
|
||||
SetTransitions(library, _SortingTransitions, _OldIndexToNew, count);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="TransitionLibraryDefinition.Transitions"/>
|
||||
/// using `oldIndexToNew` to remap any references to the old order.
|
||||
/// </summary>
|
||||
public static void SetTransitions(
|
||||
TransitionLibraryDefinition library,
|
||||
TransitionAssetBase[] transitions,
|
||||
int[] oldIndexToNew,
|
||||
int count)
|
||||
{
|
||||
var libraryTransitions = library.Transitions;
|
||||
if (libraryTransitions != transitions)
|
||||
{
|
||||
AnimancerUtilities.SetLength(ref libraryTransitions, count);
|
||||
Array.Copy(transitions, libraryTransitions, count);
|
||||
library.Transitions = libraryTransitions;
|
||||
}
|
||||
|
||||
var modifiers = library.Modifiers;
|
||||
for (int i = modifiers.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var modifier = modifiers[i];
|
||||
var isValid = true;
|
||||
var fromIndex = ConvertIndex(modifier.FromIndex, oldIndexToNew, count, ref isValid);
|
||||
var toIndex = ConvertIndex(modifier.ToIndex, oldIndexToNew, count, ref isValid);
|
||||
|
||||
if (isValid)
|
||||
modifiers[i] = modifier.WithIndices(fromIndex, toIndex);
|
||||
else
|
||||
AnimancerUtilities.RemoveAt(ref modifiers, i);
|
||||
}
|
||||
|
||||
var aliases = library.Aliases;
|
||||
for (int i = aliases.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var alias = aliases[i];
|
||||
var isValid = true;
|
||||
var index = ConvertIndex(alias.Index, oldIndexToNew, count, ref isValid);
|
||||
|
||||
if (isValid)
|
||||
aliases[i] = alias.With(index);
|
||||
else
|
||||
AnimancerUtilities.RemoveAt(ref aliases, i);
|
||||
}
|
||||
|
||||
library.SortAliases();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Converts an old index to a new one.</summary>
|
||||
private static int ConvertIndex(int index, int[] oldIndexToNew, int count, ref bool isValid)
|
||||
{
|
||||
if ((uint)index >= (uint)count)
|
||||
{
|
||||
isValid = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
index = oldIndexToNew[index];
|
||||
|
||||
if ((uint)index >= (uint)count)
|
||||
{
|
||||
isValid = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static int[] _SequentialIndices = Array.Empty<int>();
|
||||
|
||||
/// <summary>Returns a cached array containing sequential indices, i.e. <c>array[i] = i</c>.</summary>
|
||||
public static int[] GetTempSequentialIndices(int count)
|
||||
{
|
||||
if (_SequentialIndices.Length < count)
|
||||
_SequentialIndices = new int[Mathf.NextPowerOfTwo(count)];
|
||||
|
||||
for (int i = 0; i < _SequentialIndices.Length; i++)
|
||||
_SequentialIndices[i] = i;
|
||||
|
||||
return _SequentialIndices;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Changes the index of a transition.</summary>
|
||||
public static void MoveTransition(TransitionLibraryWindow window, int from, int to)
|
||||
{
|
||||
var transitions = window.Data.Transitions;
|
||||
|
||||
to = Mathf.Clamp(to, 0, transitions.Length - 1);
|
||||
if (from == to)
|
||||
return;
|
||||
|
||||
var editorData = window.SourceObject.GetOrCreateEditorData();
|
||||
|
||||
var definition = window.RecordUndo();
|
||||
|
||||
editorData.TransitionSortMode = TransitionSortMode.Custom;
|
||||
|
||||
var moving = transitions[from];
|
||||
var indices = GetTempSequentialIndices(transitions.Length);
|
||||
|
||||
if (to > from)// Moving forwards.
|
||||
{
|
||||
Array.Copy(transitions, from + 1, transitions, from, to - from);
|
||||
Array.Copy(indices, from, indices, from + 1, to - from);
|
||||
}
|
||||
else// Moving backwards.
|
||||
{
|
||||
Array.Copy(transitions, to, transitions, to + 1, from - to);
|
||||
Array.Copy(indices, to + 1, indices, to, from - to);
|
||||
}
|
||||
|
||||
transitions[to] = moving;
|
||||
indices[from] = to;
|
||||
|
||||
SetTransitions(
|
||||
definition,
|
||||
transitions,
|
||||
indices,
|
||||
transitions.Length);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1193a923ec8c6f847a7e7b6dfca91c19
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,357 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// An <see cref="EditorWindow"/> for configuring <see cref="TransitionLibraryAsset"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryWindow
|
||||
public class TransitionLibraryWindow :
|
||||
SerializedDataEditorWindow<TransitionLibraryAsset, TransitionLibraryDefinition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Opens a window for the `library`.</summary>
|
||||
public static TransitionLibraryWindow Open(TransitionLibraryAsset library)
|
||||
=> Open<TransitionLibraryWindow>(library, true, typeof(SceneView));
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Double clicking a <see cref="TransitionLibraryAsset"/>
|
||||
/// opens it in the <see cref="TransitionLibraryWindow"/>.
|
||||
/// </summary>
|
||||
[OnOpenAsset]
|
||||
private static bool OnOpenAsset(int instanceID, int line)
|
||||
{
|
||||
var library = EditorUtility.InstanceIDToObject(instanceID) as TransitionLibraryAsset;
|
||||
if (library == null)
|
||||
return false;
|
||||
|
||||
Open(library);
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The current window instance.</summary>
|
||||
public static TransitionLibraryWindow Instance { get; private set; }
|
||||
|
||||
/// <summary>Is a window currently showing the `library`.</summary>
|
||||
public static bool IsShowing(Object library)
|
||||
=> Instance != null
|
||||
&& Instance.SourceObject == library;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override TransitionLibraryDefinition SourceData
|
||||
{
|
||||
get => SourceObject.Definition;
|
||||
set => SourceObject.Definition = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TransitionLibrarySelection _Selection;
|
||||
|
||||
/// <summary>Manages the objects which can be selected within a library.</summary>
|
||||
public TransitionLibrarySelection Selection
|
||||
=> AnimancerEditorUtilities.FindOrCreate(ref _Selection);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeReference]
|
||||
private List<TransitionLibraryWindowPage> _Pages;
|
||||
|
||||
[SerializeField]
|
||||
private int _CurrentPage;
|
||||
|
||||
/// <summary>The currently selected page.</summary>
|
||||
public TransitionLibraryWindowPage CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
_CurrentPage = Mathf.Clamp(_CurrentPage, 0, _Pages.Count - 1);
|
||||
return _Pages[_CurrentPage];
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Object highlight manager.</summary>
|
||||
public readonly TransitionLibraryWindowHighlighter
|
||||
Highlighter = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called when an object is selected.</summary>
|
||||
private void OnSelectionChange()
|
||||
{
|
||||
if (_Selection != null)
|
||||
_Selection.OnSelectionChange();
|
||||
|
||||
var library = UnityEditor.Selection.activeObject as TransitionLibraryAsset;
|
||||
if (library != null && library != SourceObject)
|
||||
SetAndCaptureSource(library);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
Instance = this;
|
||||
wantsMouseMove = true;
|
||||
|
||||
// MainStageView, CanvasGroup Icon, GridLayoutGroup Icon.
|
||||
titleContent = EditorGUIUtility.IconContent("CanvasGroup Icon");
|
||||
titleContent.text = "Transition Library";
|
||||
|
||||
AnimancerEditorUtilities.InstantiateDerivedTypes(ref _Pages);
|
||||
|
||||
for (int i = 0; i < _Pages.Count; i++)
|
||||
_Pages[i].Window = this;
|
||||
|
||||
OnSelectionChange();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
if (Instance == this)
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
DestroyImmediate(_Selection);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the GUI of this window.</summary>
|
||||
protected virtual void OnGUI()
|
||||
{
|
||||
if (SourceObject == null)
|
||||
{
|
||||
GUILayout.Label("No Transition Library has been selected");
|
||||
return;
|
||||
}
|
||||
|
||||
DoHeaderGUI();
|
||||
DoBodyGUI();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void CaptureData()
|
||||
{
|
||||
base.CaptureData();
|
||||
Data.SortAliases();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply()
|
||||
{
|
||||
base.Apply();
|
||||
|
||||
for (int i = 0; i < Data.Transitions.Length; i++)
|
||||
{
|
||||
var transition = Data.Transitions[i];
|
||||
if (EditorUtility.IsPersistent(transition))
|
||||
continue;
|
||||
|
||||
AssetDatabase.AddObjectToAsset(transition, SourceObject);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static ButtonGroupStyles _ApplyRevertStyles;
|
||||
|
||||
/// <summary>Draws the header GUI.</summary>
|
||||
private void DoHeaderGUI()
|
||||
{
|
||||
if (_ApplyRevertStyles.left == null)
|
||||
_ApplyRevertStyles = new(
|
||||
EditorStyles.toolbarButton,
|
||||
EditorStyles.toolbarButton,
|
||||
EditorStyles.toolbarButton);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
var style = EditorStyles.toolbar;
|
||||
|
||||
var applyRevertWidth = CalculateApplyRevertWidth(_ApplyRevertStyles) - StandardSpacing - 1;
|
||||
|
||||
var area = GUILayoutUtility.GetRect(position.width, style.fixedHeight);
|
||||
|
||||
var currentEvent = Event.current;
|
||||
if (currentEvent.type == EventType.Repaint)
|
||||
style.Draw(area, false, false, false, false);
|
||||
|
||||
var pageArea = StealFromLeft(ref area, PageSelectionWidth);
|
||||
var applyRevertArea = StealFromRight(ref area, applyRevertWidth);
|
||||
var pathArea = area;
|
||||
|
||||
DoPageSelectionDropdown(pageArea);
|
||||
DoAssetPathButton(pathArea, currentEvent);
|
||||
|
||||
DoApplyRevertGUI(applyRevertArea, _ApplyRevertStyles);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized]
|
||||
private float _PageSelectionWidth;
|
||||
|
||||
private float PageSelectionWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_PageSelectionWidth == 0)
|
||||
{
|
||||
for (int i = 0; i < _Pages.Count; i++)
|
||||
{
|
||||
_PageSelectionWidth = Math.Max(
|
||||
_PageSelectionWidth,
|
||||
EditorStyles.toolbarDropDown.CalculateWidth(_Pages[i].DisplayName));
|
||||
}
|
||||
}
|
||||
|
||||
return _PageSelectionWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a dropdown button for selecting the <see cref="CurrentPage"/>.</summary>
|
||||
private void DoPageSelectionDropdown(Rect area)
|
||||
{
|
||||
using (var label = PooledGUIContent.Acquire(CurrentPage.DisplayName, CurrentPage.HelpTooltip))
|
||||
if (!EditorGUI.DropdownButton(area, label, FocusType.Passive, EditorStyles.toolbarDropDown))
|
||||
return;
|
||||
|
||||
var menu = new GenericMenu();
|
||||
|
||||
for (int i = 0; i < _Pages.Count; i++)
|
||||
{
|
||||
var index = i;
|
||||
var page = _Pages[index];
|
||||
menu.AddItem(
|
||||
new(page.DisplayName),
|
||||
_CurrentPage == index,
|
||||
() => _CurrentPage = index);
|
||||
}
|
||||
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(
|
||||
new("Documentation"),
|
||||
false,
|
||||
() => Application.OpenURL(Strings.DocsURLs.TransitionLibraries));
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIStyle _AssetPathStyle;
|
||||
|
||||
private readonly GUIContent AssetPath = new();
|
||||
|
||||
/// <summary>Draws the asset path of the target library and selects it if clicked.</summary>
|
||||
private void DoAssetPathButton(Rect area, Event currentEvent)
|
||||
{
|
||||
_AssetPathStyle ??= new(EditorStyles.toolbarButton)
|
||||
{
|
||||
richText = true,
|
||||
alignment = TextAnchor.MiddleRight,
|
||||
fontStyle = FontStyle.Italic,
|
||||
fontSize = (int)(EditorStyles.toolbarButton.fontSize * 0.8f),
|
||||
};
|
||||
|
||||
if (currentEvent.type == EventType.Repaint)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(SourceObject);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
AssetPath.text = "The target Transition Library isn't saved as an asset.";
|
||||
AssetPath.tooltip = null;
|
||||
}
|
||||
else if (AssetPath.tooltip != assetPath)
|
||||
{
|
||||
AssetPath.tooltip = assetPath;
|
||||
|
||||
var directory = Path.GetDirectoryName(assetPath).Replace('\\', '/');
|
||||
var file = Path.GetFileNameWithoutExtension(assetPath);
|
||||
assetPath = $"{directory}/<b>{file}</b>";
|
||||
|
||||
AssetPath.text = assetPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (GUI.Button(area, AssetPath, _AssetPathStyle))
|
||||
{
|
||||
if (Selection.Selected != (object)SourceObject)
|
||||
Selection.Select(this, SourceObject, -1, TransitionLibrarySelection.SelectionType.Library);
|
||||
else
|
||||
EditorGUIUtility.PingObject(SourceObject);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the <see cref="CurrentPage"/>.</summary>
|
||||
private void DoBodyGUI()
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
var area = GUILayoutUtility.GetLastRect();
|
||||
area.width = position.width;
|
||||
|
||||
EditorGUI.DrawRect(area, Grey(0.2f, 0.5f));
|
||||
|
||||
if (_Pages.Count > 0)
|
||||
{
|
||||
Highlighter.BeginGUI(area);
|
||||
|
||||
CurrentPage?.OnGUI(area);
|
||||
|
||||
Highlighter.EndGUI(this);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ce82e4a867719941a5e8f1fba513d28
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using Animancer.TransitionLibraries;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// An <see cref="EditorWindow"/> for configuring <see cref="TransitionLibraryAsset"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryWindowHighlighter
|
||||
public class TransitionLibraryWindowHighlighter
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Color
|
||||
SelectionHighlightColor = new(0.5f, 0.5f, 1, 0.1f),
|
||||
HoverHighlightColor = new(0.5f, 1, 0.5f, 0.1f);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The current <see cref="Event.type"/>.</summary>
|
||||
public EventType EventType { get; private set; }
|
||||
|
||||
/// <summary>The the mouse currently over the highlighter area.</summary>
|
||||
public bool IsMouseOver { get; private set; }
|
||||
|
||||
/// <summary>The the hover highlight currently visible.</summary>
|
||||
public bool DidHoverHighlight { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers the details of the <see cref="Event.current"/>.</summary>
|
||||
public void BeginGUI(Rect area)
|
||||
{
|
||||
var currentEvent = Event.current;
|
||||
EventType = currentEvent.type;
|
||||
IsMouseOver = area.Contains(currentEvent.mousePosition);
|
||||
DidHoverHighlight = false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Repaints the `window` if necessary.</summary>
|
||||
public void EndGUI(TransitionLibraryWindow window)
|
||||
{
|
||||
if (DidHoverHighlight && window != EditorWindow.mouseOverWindow)
|
||||
{
|
||||
DidHoverHighlight = false;
|
||||
window.Repaint();
|
||||
}
|
||||
else if (EventType == EventType.MouseMove)
|
||||
{
|
||||
window.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws highlights for the `area`.</summary>
|
||||
public void DrawHighlightGUI(Rect area, bool selected, bool hover)
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
EditorGUI.DrawRect(area, SelectionHighlightColor);
|
||||
}
|
||||
|
||||
if (hover)
|
||||
{
|
||||
DidHoverHighlight = true;
|
||||
EditorGUI.DrawRect(area, HoverHighlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edd6e6ad3fd0dd14cadf6ffeeefacf73
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// Sorting algorithms for <see cref="Animancer.TransitionLibraries.TransitionLibraryDefinition.Transitions"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionSortMode
|
||||
public enum TransitionSortMode
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Manual sorting.</summary>
|
||||
Custom,
|
||||
|
||||
/// <summary>Based on the transition file names.</summary>
|
||||
Name,
|
||||
|
||||
/// <summary>Based on the transition file paths.</summary>
|
||||
Path,
|
||||
|
||||
/// <summary>Based on the transition types then file names.</summary>
|
||||
TypeThenName,
|
||||
|
||||
/// <summary>Based on the transition types then file paths.</summary>
|
||||
TypeThenPath,
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryEditorData
|
||||
public partial class TransitionLibraryEditorData
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TransitionSortMode _TransitionSortMode;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The algorithm to use for sorting transitions.</summary>
|
||||
public TransitionSortMode TransitionSortMode
|
||||
{
|
||||
get => _TransitionSortMode;
|
||||
set
|
||||
{
|
||||
if (_TransitionSortMode == value)
|
||||
return;
|
||||
|
||||
_TransitionSortMode = value;
|
||||
|
||||
if (Library != null)
|
||||
TransitionLibrarySort.Sort(Library);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9387a41aa99644146aed014d11ba8bc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user