// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
#if UNITY_EDITOR
#if UNITY_6000_2_OR_NEWER
#pragma warning disable CS0618 // Type or member is obsolete - Tree View stuff was made generic in Unity 6.2.
#endif
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using static Animancer.Editor.AnimancerGUI;
namespace Animancer.Editor
{
/// An for editing spring definitions.
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/WeightedMaskLayersDefinitionWindow
public class WeightedMaskLayersDefinitionWindow :
TransformTreeWindow
{
/************************************************************************************************************************/
private const int
TransformColumn = 0,
IncludedColumn = 1,
FirstGroupColumn = 2,
RootMotionWeightsID = -2;
/************************************************************************************************************************/
///
public override IList Transforms
=> Data.Transforms;
///
public override WeightedMaskLayersDefinition SourceData
{
get
{
var sourceObject = SourceObject;
return sourceObject != null
? sourceObject.Definition
: null;
}
set => SourceObject.Definition = value;
}
/************************************************************************************************************************/
///
protected override MultiColumnHeaderState.Column[] CreateColumns(float width)
{
const float TransformWidth = 300;
const float GroupWidth = 100;
var groupCount = Data.GroupCount;
var oldColumns = HeaderState?.columns;
var newColumns = new MultiColumnHeaderState.Column[groupCount + 2];
int index;
if (oldColumns == null)
{
var tooltip = "Select which objects to control the weight of";
newColumns[0] = CreateColumn(
"Transform",
tooltip,
TransformWidth);
var includedColumn = newColumns[1] = CreateColumn(
"?",
tooltip,
LineHeight + StandardSpacing);
includedColumn.minWidth = includedColumn.maxWidth = includedColumn.width;
index = 2;
}
else
{
var copyCount = Math.Min(oldColumns.Length, groupCount + 2);
for (int i = 0; i < copyCount; i++)
newColumns[i] = oldColumns[i];
index = copyCount;
}
for (int i = index; i < groupCount + 2; i++)
{
var groupIndex = i - 2;
var name = "Group " + groupIndex.ToStringCached();
var tooltip = "The weights for " + name;
if (groupIndex == 0)
{
name = "Default " + name;
tooltip += " (this group will be applied on startup by default)";
}
newColumns[i] = CreateColumn(
name,
tooltip,
GroupWidth);
}
return newColumns;
}
/************************************************************************************************************************/
///
public override void AddItems(ref int id, TreeViewItem root)
{
root.AddChild(new()
{
id = RootMotionWeightsID,
depth = 0,
});
base.AddItems(ref id, root);
}
/************************************************************************************************************************/
///
protected override Color GetRowColor(TreeViewItem item)
{
if (!TreeView.Transforms.TryGetObject(item.id, out var transform))
return default;
if (Transforms.IndexOf(transform) < 0)
return default;
return GetChainColor(transform, Transforms, 0.15f);
}
/// Returns a color based on the name of the `transform`'s highest included parent.
public static Color GetChainColor(Transform transform, IList transforms, float alpha)
{
transform = GetChainRoot(transform, transforms);
return GetHashColor(transform.name.GetHashCode(), 1, 1, alpha);
}
/// Gets the highest parent of `transform` which is included in the `transforms`.
public static Transform GetChainRoot(Transform transform, IList transforms)
{
var parent = transform.parent;
while (parent != null)
{
if (transforms.IndexOf(parent) >= 0)
transform = parent;
parent = parent.parent;
}
return transform;
}
/************************************************************************************************************************/
///
public override void DrawCellGUI(Rect area, int column, int row, TreeViewItem item, ref bool isSelectionClick)
{
if (!TreeView.Transforms.TryGetObject(item.id, out var transform))
{
if (item.id == RootMotionWeightsID)
DrawRootMotionWeightsCellGUI(area, column);
return;
}
var definitionIndex = GetDefinitionIndex(item.id);
switch (column)
{
case TransformColumn:
DrawTransformCellGUI(area, transform);
break;
case IncludedColumn:
DrawIsIncludedCellGUI(area, item.id, definitionIndex, ref isSelectionClick);
break;
default:
DrawWeightGUI(area, item.id, column - 2, definitionIndex);
break;
}
}
/************************************************************************************************************************/
private static GUIStyle _RootMotionWeightsLabelStyle;
private static readonly GUIContent RootMotionWeightsLabel = new(
"Root Motion Weights",
"When a Group is applied to a Layer, this value will multiply the Root Motion output of that layer");
private void DrawRootMotionWeightsCellGUI(Rect area, int column)
{
switch (column)
{
case TransformColumn:
_RootMotionWeightsLabelStyle ??= new(GUI.skin.label)
{
alignment = TextAnchor.MiddleRight,
};
area.yMin--;
GUI.Label(area, RootMotionWeightsLabel, _RootMotionWeightsLabelStyle);
break;
case IncludedColumn:
break;
default:
column -= FirstGroupColumn;
if (!Data.RootMotionWeights.TryGet(column, out var weight))
break;
if (DoFloatFieldGUI(area, ref weight))
{
weight = Mathf.Clamp01(weight);
var data = RecordUndo();
data.Validate();
data.RootMotionWeights[column] = weight;
}
break;
}
}
/************************************************************************************************************************/
///
protected override void SetIncluded(
int treeItemID,
int definitionIndex,
bool isIncluded)
{
var data = RecordUndo();
if (isIncluded)
{
if (definitionIndex < 0 &&
TreeView.Transforms.TryGetObject(treeItemID, out var transform))
{
var groupCount = data.GroupCount;
data.AddTransform(transform);
if (groupCount != data.GroupCount)
CreateHeader();
}
}
else
{
if (definitionIndex >= 0)
{
data.RemoveTransform(definitionIndex);
if (data.GroupCount <= 0)
CreateHeader();
}
}
}
/************************************************************************************************************************/
private void DrawWeightGUI(
Rect area,
int treeItemID,
int groupIndex,
int transformIndex)
{
if (transformIndex < 0)
return;
var weight = Data.GetWeight(groupIndex, transformIndex);
if (float.IsNaN(weight))
return;
if (DoFloatFieldGUI(area, ref weight))
{
weight = Mathf.Clamp01(weight);
SetValue(treeItemID, i => RecordUndo().SetWeight(groupIndex, i, weight));
}
}
/************************************************************************************************************************/
private static bool DoFloatFieldGUI(Rect area, ref float value)
{
EditorGUI.BeginChangeCheck();
var style = EditorStyles.numberField;
var contentOffset = style.contentOffset;
style.contentOffset = new(0, -2);
value = EditorGUI.FloatField(area, value);
style.contentOffset = contentOffset;
return EditorGUI.EndChangeCheck();
}
/************************************************************************************************************************/
private static readonly GUIContent
AddGroupLabel = new(
"Add Group",
"Add another weight group"),
RemoveGroupLabel = new(
"Remove Group",
"Remove the last weight group");
///
protected override void DoFooterCenterGUI()
{
var hasTransforms = !Data.Transforms.IsNullOrEmpty();
GUI.enabled = hasTransforms;
if (GUILayout.Button(AddGroupLabel))
{
RecordUndo().GroupCount++;
CreateHeader();
}
if (hasTransforms)
GUI.enabled = Data.GroupCount > 1;
if (GUILayout.Button(RemoveGroupLabel))
{
RecordUndo().GroupCount--;
CreateHeader();
}
}
/************************************************************************************************************************/
///
public override void Apply()
{
base.Apply();
CreateHeader();
SceneView.RepaintAll();
}
///
public override void Revert()
{
base.Revert();
CreateHeader();
SceneView.RepaintAll();
}
/************************************************************************************************************************/
///
public override WeightedMaskLayersDefinition RecordUndo(string name = "Animancer")
{
SceneView.RepaintAll();
return base.RecordUndo(name);
}
/************************************************************************************************************************/
}
}
#endif