Migrate MuscleInspectorEditor to generic IMGUI TreeView API (Unity 6000) & fix RowGUIArgs usage

- Replace obsolete TreeView/TreeViewItem/TreeViewState with generic counterparts
- Correct RowGUI signature to use nested non-generic TreeView<int>.RowGUIArgs
- Implement IEnumerable<BoneNode> to avoid NotImplementedException
- Use Mathf.Approximately for stable muscle slider updates
- Initialize TreeViewState<int> on enable
This commit is contained in:
muhammadIdhamMaarif 2025-09-15 05:35:29 +07:00 committed by GitHub
parent dbef64d989
commit 1052c42a47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,20 +1,16 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace UniHumanoid
{
class BoneNode : IEnumerable<BoneNode>
{
public HumanBodyBones Bone { get; private set; }
public List<BoneNode> Children = new List<BoneNode>();
public int[] Muscles;
public BoneNode(HumanBodyBones bone, params int[] muscles)
@ -25,45 +21,33 @@ namespace UniHumanoid
public IEnumerator<BoneNode> GetEnumerator()
{
throw new NotImplementedException();
yield return this;
foreach (var c in Children)
{
foreach (var n in c) yield return n;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(BoneNode child)
{
Children.Add(child);
}
public void Add(BoneNode child) => Children.Add(child);
}
class BoneTreeViewItem : TreeViewItem
class BoneTreeViewItem : TreeViewItem<int>
{
//HumanBodyBones m_bone;
public BoneTreeViewItem(int id, int depth, HumanBodyBones bone) : base(id, depth, bone.ToString())
{
//m_bone = bone;
}
public BoneTreeViewItem(int id, int depth, HumanBodyBones bone) : base(id, depth, bone.ToString()) { }
}
class MuscleTreeViewItem : TreeViewItem
class MuscleTreeViewItem : TreeViewItem<int>
{
public int Muscle
{
get;
private set;
}
public int Muscle { get; private set; }
public MuscleTreeViewItem(int id, int depth, int muscle) : base(id, depth, HumanTrait.MuscleName[muscle])
{
Muscle = muscle;
}
}
class BoneTreeView : TreeView
class BoneTreeView : TreeView<int>
{
static BoneNode Skeleton = new BoneNode(HumanBodyBones.Hips)
{
@ -109,10 +93,8 @@ namespace UniHumanoid
}
};
//Animator m_animator;
HumanPoseHandler m_handler;
HumanPose m_pose;
bool m_updated;
public void Begin()
@ -129,24 +111,21 @@ namespace UniHumanoid
m_updated = false;
}
public BoneTreeView(TreeViewState treeViewState, MultiColumnHeader header, HumanPoseHandler handler)
public BoneTreeView(TreeViewState<int> treeViewState, MultiColumnHeader header, HumanPoseHandler handler)
: base(treeViewState, header)
{
m_handler = handler;
Reload();
}
protected override TreeViewItem BuildRoot()
protected override TreeViewItem<int> BuildRoot()
{
return new TreeViewItem { id = 0, depth = -1 };
return new TreeViewItem<int> { id = 0, depth = -1 };
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
protected override IList<TreeViewItem<int>> BuildRows(TreeViewItem<int> root)
{
var rows = GetRows() ?? new List<TreeViewItem>(200);
// We use the GameObject instanceIDs as ids for items as we want to
// select the game objects and not the transform components.
var rows = GetRows() ?? new List<TreeViewItem<int>>(200);
rows.Clear();
var item = CreateTreeViewItemForBone(HumanBodyBones.Hips);
@ -163,14 +142,14 @@ namespace UniHumanoid
}
SetupDepthsFromParentsAndChildren(root);
return rows;
}
void AddChildrenRecursive(BoneNode bone, TreeViewItem item, IList<TreeViewItem> rows)
void AddChildrenRecursive(BoneNode bone, TreeViewItem<int> item, IList<TreeViewItem<int>> rows)
{
int childCount = bone.Children.Count;
item.children = new List<TreeViewItem>(childCount);
item.children = new List<TreeViewItem<int>>(childCount);
if (bone.Muscles != null)
{
foreach (var muscle in bone.Muscles)
@ -187,26 +166,24 @@ namespace UniHumanoid
item.AddChild(childItem);
rows.Add(childItem);
//if (child.Children.Count > 0)
if (IsExpanded(childItem.id))
{
if (IsExpanded(childItem.id))
{
AddChildrenRecursive(child, childItem, rows);
}
else
{
childItem.children = CreateChildListForCollapsedParent();
}
AddChildrenRecursive(child, childItem, rows);
}
else
{
childItem.children = CreateChildListForCollapsedParent();
}
}
}
static TreeViewItem CreateTreeViewItemForBone(HumanBodyBones bone)
static TreeViewItem<int> CreateTreeViewItemForBone(HumanBodyBones bone)
{
return new TreeViewItem((int)bone, -1, Enum.GetName(typeof(HumanBodyBones), bone));
return new BoneTreeViewItem((int)bone, -1, bone);
}
protected override void RowGUI(RowGUIArgs args)
// inside class BoneTreeView : TreeView<int>
protected override void RowGUI(UnityEditor.IMGUI.Controls.TreeView<int>.RowGUIArgs args)
{
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
{
@ -214,41 +191,33 @@ namespace UniHumanoid
}
}
void CellGUI(Rect cellRect, int index, ref RowGUIArgs args)
void CellGUI(Rect cellRect, int index, ref UnityEditor.IMGUI.Controls.TreeView<int>.RowGUIArgs args)
{
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
CenterRectUsingSingleLineHeight(ref cellRect);
switch (index)
{
case 0:
{
// Default icon and label
args.rowRect = cellRect;
base.RowGUI(args);
}
{
args.rowRect = cellRect;
base.RowGUI(args);
break;
}
case 1:
{
if (args.item is MuscleTreeViewItem muscleItem)
{
var muscleItem = args.item as MuscleTreeViewItem;
if (muscleItem != null)
var muscleIndex = muscleItem.Muscle;
var muscles = m_pose.muscles;
var value = EditorGUI.Slider(cellRect, GUIContent.none, muscles[muscleIndex], -1f, 1f);
if (!Mathf.Approximately(value, muscles[muscleIndex]))
{
var muscleIndex = muscleItem.Muscle;
var muscles = m_pose.muscles;
var value = EditorGUI.Slider(cellRect, GUIContent.none, muscles[muscleIndex], -1f, 1f);
if (value != muscles[muscleIndex])
{
muscles[muscleIndex] = value;
m_updated = true;
}
}
else
{
muscles[muscleIndex] = value;
m_updated = true;
}
}
break;
}
}
}
@ -278,32 +247,24 @@ namespace UniHumanoid
}
}
[CustomEditor(typeof(MuscleInspector))]
public class MuscleInspectorEditor : Editor
{
[NonSerialized] bool m_Initialized;
[SerializeField] TreeViewState m_TreeViewState; // Serialized in the window layout file so it survives assembly reloading
//[SerializeField] MultiColumnHeaderState m_MultiColumnHeaderState;
// Note: Unity 6000 recommends generic state. It's fine if this isn't serialized by Unity's object serializer;
// the TreeView manages its own persistence in editor layouts.
[SerializeField] TreeViewState<int> m_TreeViewState;
SearchField m_SearchField;
BoneTreeView m_TreeView;
MuscleInspector m_target;
HumanPoseHandler m_handler;
MultiColumnHeader GetHeaderState()
{
//bool firstInit = m_MultiColumnHeaderState == null;
var headerState = BoneTreeView.CreateDefaultMultiColumnHeaderState();
/*
if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_MultiColumnHeaderState, headerState))
{
MultiColumnHeaderState.OverwriteSerializedFields(m_MultiColumnHeaderState, headerState);
}
m_MultiColumnHeaderState = headerState;
*/
var multiColumnHeader = new MultiColumnHeader(headerState);
multiColumnHeader.ResizeToFit();
return multiColumnHeader;
@ -311,17 +272,19 @@ namespace UniHumanoid
void OnEnable()
{
var mi = this.target as MuscleInspector;
if (mi.TryGetComponent<Animator>(out var animator)
&& animator.avatar != null
&& animator.avatar.isValid
&& animator.avatar.isHuman
)
var mi = target as MuscleInspector;
if (mi != null &&
mi.TryGetComponent<Animator>(out var animator) &&
animator.avatar != null &&
animator.avatar.isValid &&
animator.avatar.isHuman)
{
UniGLTF.UniGLTFLogger.Log("MuscleInspectorEditor.OnEnable");
m_handler = new HumanPoseHandler(animator.avatar, animator.transform);
m_TreeView = new BoneTreeView(new TreeViewState(), GetHeaderState(), m_handler);
// Use existing state if available, else create a new one
if (m_TreeViewState == null) m_TreeViewState = new TreeViewState<int>();
m_TreeView = new BoneTreeView(m_TreeViewState, GetHeaderState(), m_handler);
}
}