mirror of
https://github.com/originalnicodr/CinematicUnityExplorer.git
synced 2026-10-10 04:53:15 +08:00
Refactored bone panel to mimic a hierarchical-like structure, so the main bones are displayed first, and you can get more granular control by expanding their children.
This commit is contained in:
@@ -10,6 +10,7 @@ namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public Transform bone;
|
||||
public TransformControls transformControls;
|
||||
private BoneTree boneTree;
|
||||
|
||||
ComponentControl positionControl;
|
||||
ComponentControl rotationControl;
|
||||
@@ -17,6 +18,9 @@ namespace UnityExplorer.UI.Panels
|
||||
public AxisComponentControl CurrentSlidingAxisControl { get; set; }
|
||||
public BonesManager Owner;
|
||||
private ButtonRef inspectButton;
|
||||
private ButtonRef expandBones;
|
||||
private LayoutElement spaceLayout;
|
||||
static private int TREE_LEVEL_IDENTATION = 20;
|
||||
|
||||
// ICell
|
||||
public float DefaultHeight => 25f;
|
||||
@@ -27,20 +31,66 @@ namespace UnityExplorer.UI.Panels
|
||||
public void Enable() => UIRoot.SetActive(true);
|
||||
public void Disable() => UIRoot.SetActive(false);
|
||||
|
||||
public void SetBone(Transform bone, BonesManager bonesManager){
|
||||
this.bone = bone;
|
||||
inspectButton.ButtonText.text = bone.name;
|
||||
public void SetBoneTree(BoneTree boneTree, BonesManager bonesManager){
|
||||
this.boneTree = boneTree;
|
||||
this.bone = boneTree.obj.transform;
|
||||
inspectButton.ButtonText.text = boneTree.obj.name;
|
||||
Owner = bonesManager;
|
||||
|
||||
if (boneTree.childTrees.Count == 0){
|
||||
expandBones.Component.gameObject.SetActive(false);
|
||||
} else {
|
||||
expandBones.Component.gameObject.SetActive(true);
|
||||
expandBones.ButtonText.text = IsTreeExpanded() ? "▼" : "▶";
|
||||
}
|
||||
|
||||
spaceLayout.minWidth = TREE_LEVEL_IDENTATION * boneTree.level;
|
||||
}
|
||||
|
||||
private void ExpandOrCollapseBoneTree(){
|
||||
if (IsTreeExpanded()){
|
||||
// Collapse
|
||||
List<BoneTree> treesToRemove = boneTree.childTrees.Select(t => t.flatten()).SelectMany(l => l).ToList();
|
||||
Owner.boneTrees = Owner.boneTrees.Except(treesToRemove).ToList();
|
||||
expandBones.ButtonText.text = "▶";
|
||||
}
|
||||
else {
|
||||
// Expand
|
||||
int index = Owner.boneTrees.FindIndex(t => t == boneTree);
|
||||
if (index == -1) return;
|
||||
|
||||
Owner.boneTrees.InsertRange(index + 1, boneTree.childTrees);
|
||||
expandBones.ButtonText.text = "▼";
|
||||
}
|
||||
|
||||
Owner.boneScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
private bool IsTreeExpanded(){
|
||||
if (boneTree == null) return true;
|
||||
return boneTree.childTrees.Any(t1 => Owner.boneTrees.Any(t2 => t2 == t1));
|
||||
}
|
||||
|
||||
public virtual GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject("BoneCell", parent, new Vector2(25, 25));
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, false, false, true, true, 3);
|
||||
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, minWidth: 50, flexibleWidth: 9999);
|
||||
UIRoot = UIFactory.CreateUIObject("CellRoot", parent, new Vector2(25, 25));
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.MiddleRight);
|
||||
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
GameObject header = UIFactory.CreateUIObject("BoneHeader", UIRoot);
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
Rect.anchorMin = new Vector2(0, 1);
|
||||
Rect.anchorMax = new Vector2(0, 1);
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(30, 30);
|
||||
|
||||
GameObject spacer = UIFactory.CreateUIObject("Spacer", UIRoot);
|
||||
spaceLayout = UIFactory.SetLayoutElement(spacer, minWidth: 0, flexibleWidth: 0);
|
||||
|
||||
GameObject baseCell = UIFactory.CreateUIObject("BaseCell", UIRoot);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(baseCell, false, false, true, true, 3);
|
||||
UIFactory.SetLayoutElement(baseCell, minHeight: 25, minWidth: 50, flexibleWidth: 9999);
|
||||
|
||||
GameObject header = UIFactory.CreateUIObject("BoneHeader", baseCell);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(header, false, false, true, true, 4, childAlignment: TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(header, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
@@ -60,9 +110,13 @@ namespace UnityExplorer.UI.Panels
|
||||
UIFactory.SetLayoutElement(restoreBoneStateButton.GameObject, minWidth: 125, minHeight: 25);
|
||||
restoreBoneStateButton.OnClick += RestoreBoneState;
|
||||
|
||||
positionControl = ComponentControl.Create(this, UIRoot, "Local Position", TransformType.LocalPosition, 0.01f);
|
||||
rotationControl = ComponentControl.Create(this, UIRoot, "Rotation", TransformType.Rotation, 10f);
|
||||
scaleControl = ComponentControl.Create(this, UIRoot, "Scale", TransformType.Scale, 0.1f);
|
||||
positionControl = ComponentControl.Create(this, baseCell, "Local Position", TransformType.LocalPosition, 0.01f);
|
||||
rotationControl = ComponentControl.Create(this, baseCell, "Rotation", TransformType.Rotation, 10f);
|
||||
scaleControl = ComponentControl.Create(this, baseCell, "Scale", TransformType.Scale, 0.1f);
|
||||
|
||||
expandBones = UIFactory.CreateButton(baseCell, "ExpandBones", IsTreeExpanded() ? "⯆" : "▶", new Color(0.05f, 0.05f, 0.05f));
|
||||
UIFactory.SetLayoutElement(expandBones.Component.gameObject, minHeight: 25, minWidth: 25);
|
||||
expandBones.OnClick += ExpandOrCollapseBoneTree;
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
+152
-6
@@ -1,4 +1,5 @@
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Panels;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
@@ -18,8 +19,9 @@ namespace UnityExplorer.UI.Panels
|
||||
private List<Transform> bones = new List<Transform>();
|
||||
private Dictionary<string, CachedBonesTransform> bonesOriginalState = new();
|
||||
|
||||
private ScrollPool<BonesCell> boneScrollPool;
|
||||
public int ItemCount => bones.Count;
|
||||
public List<BoneTree> boneTrees = new();
|
||||
public ScrollPool<BonesCell> boneScrollPool;
|
||||
public int ItemCount => boneTrees.Count;
|
||||
private bool DoneScrollPoolInit;
|
||||
|
||||
public BonesManager(UIBase owner, List<Transform> bones, IAnimator animator) : base(owner)
|
||||
@@ -27,6 +29,40 @@ namespace UnityExplorer.UI.Panels
|
||||
this.bones = bones;
|
||||
this.animator = animator;
|
||||
skeletonName.text = $"Skeleton: {animator?.name}";
|
||||
BuildBoneTrees();
|
||||
}
|
||||
|
||||
private void BuildBoneTrees(){
|
||||
BoneTree root = new BoneTree(animator.wrappedObject.gameObject, bones);
|
||||
if (root.obj != null){
|
||||
root.AssignLevels();
|
||||
boneTrees.Add(root);
|
||||
} else {
|
||||
foreach(BoneTree childTree in root.childTrees){
|
||||
childTree.AssignLevels();
|
||||
boneTrees.Add(childTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CollapseBoneTrees(){
|
||||
boneTrees.Clear();
|
||||
BuildBoneTrees();
|
||||
boneScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
private void ExpandBoneTrees(){
|
||||
// We collapse before expanding to start from scratch and dont duplicate nodes
|
||||
CollapseBoneTrees();
|
||||
|
||||
List<BoneTree> newBoneTrees = new();
|
||||
|
||||
foreach(BoneTree childTree in boneTrees){
|
||||
newBoneTrees.AddRange(childTree.flatten());
|
||||
}
|
||||
|
||||
boneTrees = newBoneTrees;
|
||||
boneScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public override void SetActive(bool active)
|
||||
@@ -47,11 +83,23 @@ namespace UnityExplorer.UI.Panels
|
||||
UIFactory.SetLayoutElement(skeletonName.gameObject, minWidth: 100, minHeight: 25);
|
||||
skeletonName.fontSize = 16;
|
||||
|
||||
GameObject turnOffAnimatorToggleObj = UIFactory.CreateToggle(ContentRoot, "Animator toggle", out turnOffAnimatorToggle, out Text turnOffAnimatorToggleText);
|
||||
GameObject header = UIFactory.CreateUIObject("Header", ContentRoot, new Vector2(25, 25));
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(header, false, false, true, true, 4, childAlignment: TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(header, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
GameObject turnOffAnimatorToggleObj = UIFactory.CreateToggle(header, "Animator toggle", out turnOffAnimatorToggle, out Text turnOffAnimatorToggleText);
|
||||
UIFactory.SetLayoutElement(turnOffAnimatorToggleObj, minHeight: 25, flexibleWidth: 9999);
|
||||
turnOffAnimatorToggle.onValueChanged.AddListener(OnTurnOffAnimatorToggle);
|
||||
turnOffAnimatorToggleText.text = "Toggle animator (needs to be off to move bones)";
|
||||
|
||||
ButtonRef collapseAllButton = UIFactory.CreateButton(header, "CollapseAllButton", "Collapse all");
|
||||
UIFactory.SetLayoutElement(collapseAllButton.GameObject, minWidth: 150, minHeight: 25);
|
||||
collapseAllButton.OnClick += CollapseBoneTrees;
|
||||
|
||||
ButtonRef expandAllButton = UIFactory.CreateButton(header, "ExpandAllButton", "Expand all");
|
||||
UIFactory.SetLayoutElement(expandAllButton.GameObject, minWidth: 150, minHeight: 25);
|
||||
expandAllButton.OnClick += ExpandBoneTrees;
|
||||
|
||||
boneScrollPool = UIFactory.CreateScrollPool<BonesCell>(ContentRoot, "BonesList", out GameObject scrollObj,
|
||||
out GameObject scrollContent, new Color(0.06f, 0.06f, 0.06f));
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
@@ -94,14 +142,14 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
public void SetCell(BonesCell cell, int index)
|
||||
{
|
||||
if (index >= bones.Count)
|
||||
if (index >= boneTrees.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
Transform bone = bones[index];
|
||||
cell.SetBone(bone, this);
|
||||
BoneTree boneTree = boneTrees[index];
|
||||
cell.SetBoneTree(boneTree, this);
|
||||
cell.UpdateTransformControlValues(true);
|
||||
}
|
||||
|
||||
@@ -132,4 +180,102 @@ namespace UnityExplorer.UI.Panels
|
||||
public readonly Vector3 angles;
|
||||
public readonly Vector3 scale;
|
||||
}
|
||||
|
||||
public class BoneTree
|
||||
{
|
||||
public GameObject obj;
|
||||
public int level;
|
||||
public List<BoneTree> childTrees = new();
|
||||
|
||||
public BoneTree(GameObject obj, List<Transform> bones){
|
||||
// For some reason comparing GameObjects isn't working as intended in IL2CPP games, therefore we use their instance hash.
|
||||
#if CPP
|
||||
if (bones.Any(bone => bone.gameObject.GetInstanceID() == obj.GetInstanceID())) {
|
||||
this.obj = obj;
|
||||
}
|
||||
#else
|
||||
if (bones.Contains(obj.transform)) {
|
||||
this.obj = obj;
|
||||
}
|
||||
#endif
|
||||
for (int i = 0; i < obj.transform.childCount; i++)
|
||||
{
|
||||
Transform child = obj.transform.GetChild(i);
|
||||
if (child.gameObject.activeSelf){
|
||||
childTrees.Add(new BoneTree(child.gameObject, bones));
|
||||
}
|
||||
}
|
||||
|
||||
Trim();
|
||||
childTrees = childTrees.OrderBy(b => b.obj.name).ToList();
|
||||
}
|
||||
|
||||
private void Trim(){
|
||||
List<BoneTree> newList = new();
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
if (childTree.obj == null){
|
||||
newList.AddRange(childTree.childTrees);
|
||||
} else {
|
||||
newList.Add(childTree);
|
||||
}
|
||||
}
|
||||
|
||||
this.childTrees = newList;
|
||||
}
|
||||
|
||||
public void AssignLevels(){
|
||||
AssignLevel(0);
|
||||
}
|
||||
|
||||
private void AssignLevel(int distanceFromRoot){
|
||||
level = distanceFromRoot;
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
childTree.AssignLevel(distanceFromRoot + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString(){
|
||||
string return_string = "";
|
||||
if (obj != null){
|
||||
return_string = $"{obj.name} lvl: {level} - ";
|
||||
}
|
||||
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
return_string = return_string + childTree.ToString();
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
public List<GameObject> getGameObjects(){
|
||||
List<GameObject> return_list = new();
|
||||
if (obj != null){
|
||||
return_list.Add(obj);
|
||||
}
|
||||
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
return_list.AddRange(childTree.getGameObjects());
|
||||
}
|
||||
|
||||
return return_list;
|
||||
}
|
||||
|
||||
public List<BoneTree> flatten(){
|
||||
List<BoneTree> return_list = new();
|
||||
if (obj != null){
|
||||
return_list.Add(this);
|
||||
}
|
||||
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
return_list.AddRange(childTree.flatten());
|
||||
}
|
||||
|
||||
return return_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user