-
Tobias Schöner authored
Implemented UILine Parent facts are connected to the main fact by a direct dashed line whereas child facts are connected by a jagged continuous line.
Tobias Schöner authoredImplemented UILine Parent facts are connected to the main fact by a direct dashed line whereas child facts are connected by a jagged continuous line.
FactExplorer.cs 6.57 KiB
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using System.Linq;
using UnityEngine.UI;
public class FactExplorer : MonoBehaviour
{
#region InspectorVariables
[Header("PrefabComponents")]
[SerializeField] private Transform factParentsUI;
[SerializeField] private Transform mainFactUI;
[SerializeField] private Transform factChildrenUI;
[SerializeField] private Transform linesUI;
[Header("Prefabs")]
[SerializeField] private GameObject factSpotPrefab;
[SerializeField] private GameObject parentLine;
[SerializeField] private GameObject childLine;
#endregion InspectorVariables
#region Variables
private Fact mainFact;
private List<Fact> parentFacts;
private List<Fact> childFacts;
#endregion Variables
#region UnityMethods
private void Update()
{
DestroyIfClickedOutside();
}
public void Initialize(Fact fact)
{
mainFact = fact;
parentFacts = GetParentFacts();
childFacts = GetChildFacts();
Debug.Log($"Parents of {mainFact.Label}: {string.Join(", ", parentFacts.Select(cf => cf.Label))}");
Debug.Log($"Children of {mainFact.Label}: {string.Join(", ", childFacts.Select(cf => cf.Label))}");
UpdateFactExplorerUI();
}
#endregion UnityMethods
#region Implementation
private List<Fact> GetParentFacts()
{
//TODO: TSC how to get all current facts without this dumb workaround
var allFacts = DisplayFacts.displayedFacts.Values.Select(x => x.GetComponent<FactWrapper>().fact);
List<Fact> _parentFacts = new();
// get all facts that reference the mainFact
foreach (Fact f in allFacts)
if (GetReferencedPids(f).Contains(mainFact.Id)) // if f contains reference to mainFact
_parentFacts.Add(f);
return _parentFacts;
}
private List<Fact> GetChildFacts()
{
List<Fact> childFacts = new();
// get all properties of Type mainFactType that are strings and start with "pid"
foreach (string factId in GetReferencedPids(mainFact))
childFacts.Add(StageStatic.stage.factState[factId]);
return childFacts;
}
private void UpdateFactExplorerUI()
{
SpawnUIFacts(factParentsUI, parentFacts);
SpawnUIFacts(mainFactUI, new List<Fact>() { mainFact });
SpawnUIFacts(factChildrenUI, childFacts);
// Force rebuild of FactExplorer layout, since the positions of the factObjects will be wrong otherwise
LayoutRebuilder.ForceRebuildLayoutImmediate(transform.GetComponent<RectTransform>());
SpawnParentLines(factParentsUI.gameObject, mainFactUI);
SpawnChildLines(factChildrenUI.gameObject, mainFactUI);
}
private void DestroyIfClickedOutside()
{
// Destroy on tab press or left click outside of FactExplorer
if (Input.GetKeyDown(KeyCode.Tab)
|| (Input.GetMouseButtonDown(0) && !RectTransformUtility.RectangleContainsScreenPoint(transform.GetComponent<RectTransform>(), Input.mousePosition, null)))
{
Destroy(gameObject);
}
}
#endregion Implementation
#region Spawner
private void SpawnUIFacts(Transform uiParent, List<Fact> toSpawn)
{
// if uiParent has no children: deactivate it
if (toSpawn.Count == 0)
uiParent.gameObject.SetActive(false);
foreach (Fact f in toSpawn)
{
var spot = Instantiate(factSpotPrefab, uiParent);
// TODO: this link to DisplayFacts is not ideal: maybe refactor to SciptableObject or such
var display = f.instantiateDisplay(DisplayFacts.prefabDictionary[f.GetType()], spot.transform);
display.transform.localPosition = Vector3.zero;
}
}
private void SpawnParentLines(GameObject parent, Transform mainFactUI)
{
var mainTransform = mainFactUI.GetComponent<RectTransform>();
var factWidth = mainTransform.rect.width;
// transform.positions are weird due to LayoutGroups => manually calculate offset
float xOffset = -factParentsUI.GetComponent<RectTransform>().rect.width / 2 + factWidth / 2;
float yOffset = transform.GetComponent<VerticalLayoutGroup>().spacing;
parent.ForAllChildren(par =>
{
// position at the bottom center of par rect
var position = par.transform.TransformPoint(new Vector2(0, par.GetComponent<RectTransform>().rect.yMin));
var line = Instantiate(parentLine, position, Quaternion.identity, par.transform);
var uiLine = line.GetComponent<UILine>();
uiLine.points = new List<Vector2>() { Vector2.zero, new Vector2(-xOffset, -yOffset) };
xOffset += factWidth + factParentsUI.GetComponent<HorizontalLayoutGroup>().spacing;
});
}
private void SpawnChildLines(GameObject parent, Transform mainFactUI)
{
var mainTransform = mainFactUI.GetComponent<RectTransform>();
var factWidth = mainTransform.rect.width;
// transform.positions are weird due to LayoutGroups => manually calculate offset
float xOffset = -factChildrenUI.GetComponent<RectTransform>().rect.width / 2 + factWidth / 2;
float yOffset = -transform.GetComponent<VerticalLayoutGroup>().spacing;
parent.ForAllChildren(par =>
{
// position at the top center of par rect
var position = par.transform.TransformPoint(new Vector2(0, par.GetComponent<RectTransform>().rect.yMax));
var line = Instantiate(childLine, position, Quaternion.identity, par.transform);
var uiLine = line.GetComponent<UILine>();
uiLine.points = new List<Vector2>() {
Vector2.zero,
new Vector2(0, -yOffset/2),
new Vector2(-xOffset, -yOffset/2),
new Vector2(-xOffset, -yOffset)
};
xOffset += factWidth + factChildrenUI.GetComponent<HorizontalLayoutGroup>().spacing;
});
}
#endregion Spawner
#region Helper
/// <summary>
/// Check all fields of a Fact for references to other facts and return their ids
/// </summary>
/// <param name="f"></param>
/// <returns>An Enumerable containing the ids of facts referenced by the input fact</returns>
private static IEnumerable<string> GetReferencedPids(Fact f)
{
return (f.GetType().GetFields()
.Where(field => field.FieldType == typeof(string) && field.Name.ToLower().StartsWith("pid"))
).Select(fi => (string)fi.GetValue(f));
}
#endregion Helper
}