// 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