Files
bansonic_beta_main/Assets/artworks/notebook/notebookController.cs
T

525 lines
20 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine.EventSystems;
public class notebookController : MonoBehaviour
{
[Header("select father types")]
public Toggle all_content_beforeAnything_toggle;
public Toggle idols_toggle;
public Toggle enemies_toggle;
public Toggle organizations_toggle;
public Toggle areas_toggle;
public Toggle terminologies_toggle;
public Toggle Logs_toggle;
public ToggleGroup categoryToggleGroup;
[Header("quit")]
public Button quit_button;
[Header("prefabs")]
public GameObject fatherItem_prefab;
public GameObject svContent;
[Header("paths")]
[SerializeField] private string editor_SO_path;
[SerializeField] private string runtime_SO_path;
[Header("Details Displaying")]
public Image detailsImage;
public Text passage_title;
public Text passage_text;
[SerializeField] float panel_Enter_Time = 0.35f;
[SerializeField] float panel_Exit_Time = 0.25f;
[SerializeField] float panel_Enter_Scale_From = 0.92f;
[SerializeField] float panel_Exit_Scale_To = 0.9f;
[SerializeField] Ease panel_Enter_Ease = Ease.OutCubic;
[SerializeField] Ease panel_Exit_Ease = Ease.InCubic;
private readonly List<notebook_faterType> cachedItems = new List<notebook_faterType>();
private Toggle[] categoryToggles;
private readonly Dictionary<RectTransform, Vector2> fatherBaseAnchors = new Dictionary<RectTransform, Vector2>();
private readonly Dictionary<Toggle, int> toggleInvokeFrame = new Dictionary<Toggle, int>();
private readonly Dictionary<fatherItemPrefab, LayoutElement> fatherSpacers = new Dictionary<fatherItemPrefab, LayoutElement>();
private readonly Dictionary<fatherItemPrefab, int> fatherAnimTokens = new Dictionary<fatherItemPrefab, int>();
private bool initialized = false;
private const float SonOffsetBase = 5f;
private const float SonOffsetPerItem = 110f;
private const float SonTweenDuration = 0.5f;
private const float SonFadeDuration = 0.25f;
private void Start()
{
Initialize();
}
private void OnEnable()
{
Initialize();
PlayEnterAnim();
}
private void Initialize()
{
if (initialized) return;
initialized = true;
StoreOwnershipLedger.EnsureInstance().InitializeIfNeeded();
LoadAllData();
categoryToggles = new[]
{
all_content_beforeAnything_toggle,
idols_toggle,
enemies_toggle,
organizations_toggle,
areas_toggle,
terminologies_toggle,
Logs_toggle
};
categoryToggleGroup = ResolveCategoryToggleGroup();
if (categoryToggleGroup != null)
categoryToggleGroup.allowSwitchOff = true;
if (categoryToggles != null)
{
for (int i = 0; i < categoryToggles.Length; i++)
{
var t = categoryToggles[i];
if (t == null) continue;
t.group = categoryToggleGroup;
}
}
BindToggle(all_content_beforeAnything_toggle, () => Refresh(notebook_faterType.notebook_fatherType.content));
BindToggle(idols_toggle, () => Refresh(notebook_faterType.notebook_fatherType.idols));
BindToggle(enemies_toggle, () => Refresh(notebook_faterType.notebook_fatherType.enemies));
BindToggle(organizations_toggle, () => Refresh(notebook_faterType.notebook_fatherType.organizations));
BindToggle(areas_toggle, () => Refresh(notebook_faterType.notebook_fatherType.areas));
BindToggle(terminologies_toggle, () => Refresh(notebook_faterType.notebook_fatherType.terminologies));
BindToggle(Logs_toggle, () => Refresh(notebook_faterType.notebook_fatherType.Logs));
if (quit_button != null)
{
quit_button.onClick.RemoveAllListeners();
quit_button.onClick.AddListener(CloseSelf);
}
if (all_content_beforeAnything_toggle != null)
all_content_beforeAnything_toggle.isOn = true;
Refresh(notebook_faterType.notebook_fatherType.content);
}
private void BindToggle(Toggle toggle, System.Action onSelected)
{
if (toggle == null) return;
toggle.onValueChanged.RemoveAllListeners();
toggle.onValueChanged.AddListener(isOn =>
{
if (isOn) InvokeToggleAction(toggle, onSelected);
});
var relay = toggle.GetComponent<ToggleClickRelay>();
if (relay == null) relay = toggle.gameObject.AddComponent<ToggleClickRelay>();
relay.onClick = () =>
{
if (toggle != null && toggle.isOn) InvokeToggleAction(toggle, onSelected);
};
}
private ToggleGroup ResolveCategoryToggleGroup()
{
if (categoryToggleGroup != null) return categoryToggleGroup;
Toggle first = null;
if (categoryToggles != null)
{
for (int i = 0; i < categoryToggles.Length; i++)
{
if (categoryToggles[i] != null) { first = categoryToggles[i]; break; }
}
}
if (first == null) return null;
var parentGroup = first.GetComponentInParent<ToggleGroup>();
var contentGroup = svContent != null ? svContent.GetComponent<ToggleGroup>() : null;
if (parentGroup != null && parentGroup != contentGroup) return parentGroup;
var parent = first.transform.parent;
if (parent == null) return null;
var created = parent.GetComponent<ToggleGroup>();
if (created == null) created = parent.gameObject.AddComponent<ToggleGroup>();
return created;
}
private void LoadAllData()
{
cachedItems.Clear();
var set = new HashSet<notebook_faterType>();
#if UNITY_EDITOR
if (!string.IsNullOrEmpty(editor_SO_path))
{
var guids = UnityEditor.AssetDatabase.FindAssets("t:notebook_faterType", new[] { editor_SO_path });
for (int i = 0; i < guids.Length; i++)
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
var so = UnityEditor.AssetDatabase.LoadAssetAtPath<notebook_faterType>(path);
if (so == null) continue;
if (set.Add(so)) cachedItems.Add(so);
}
}
#endif
if (!string.IsNullOrEmpty(runtime_SO_path))
{
var resources = Resources.LoadAll<notebook_faterType>(runtime_SO_path);
for (int i = 0; i < resources.Length; i++)
{
var so = resources[i];
if (so == null) continue;
if (set.Add(so)) cachedItems.Add(so);
}
}
}
private void Refresh(notebook_faterType.notebook_fatherType? type)
{
if (fatherItem_prefab == null || svContent == null) return;
ClearContent();
var fatherGroup = svContent.GetComponent<ToggleGroup>();
if (fatherGroup == categoryToggleGroup) fatherGroup = null;
for (int i = 0; i < cachedItems.Count; i++)
{
var so = cachedItems[i];
if (so == null) continue;
if (!so.isUnlocked) continue;
if (type.HasValue && so.fatherType != type.Value) continue;
var obj = Instantiate(fatherItem_prefab, svContent.transform);
var comp = obj.GetComponent<fatherItemPrefab>();
if (comp == null) continue;
if (comp.father_name != null) comp.father_name.text = so.class_name;
if (comp.father_img != null) comp.father_img.sprite = so.class_image;
if (comp.father_description != null) comp.father_description.text = so.class_description;
SetupFatherItem(comp, so, fatherGroup);
}
CacheFatherBasePositions();
}
private void SetupFatherItem(fatherItemPrefab comp, notebook_faterType so, ToggleGroup fatherGroup)
{
if (comp == null) return;
ClearSonContainer(comp);
if (comp.son_obj != null) comp.son_obj.SetActive(false);
if (comp.son_prefab_toPut != null) comp.son_prefab_toPut.SetActive(false);
Toggle toggle = null;
if (comp.father_obj != null) toggle = comp.father_obj.GetComponent<Toggle>();
if (toggle == null) toggle = comp.GetComponent<Toggle>();
if (toggle == null) toggle = comp.GetComponentInChildren<Toggle>(true);
if (toggle == null) toggle = comp.GetComponentInParent<Toggle>();
if (toggle == null) return;
if (fatherGroup != null) toggle.group = fatherGroup;
toggle.onValueChanged.RemoveAllListeners();
toggle.onValueChanged.AddListener(isOn =>
{
ApplySonState(comp, so, isOn);
});
ApplySonState(comp, so, toggle.isOn);
}
private void ApplySonState(fatherItemPrefab comp, notebook_faterType so, bool isOn)
{
if (comp == null) return;
int token = NextAnimToken(comp);
if (isOn)
{
EnsureSonsLoaded(comp, so);
UpdateFatherSpacer(comp, so, true, token);
FadeSonContainer(comp, true, token, null);
}
else
{
FadeSonContainer(comp, false, token, () => ClearSonContainer(comp));
UpdateFatherSpacer(comp, so, false, token);
}
}
private void EnsureSonsLoaded(fatherItemPrefab comp, notebook_faterType so)
{
if (comp == null || so == null) return;
if (comp.son_prefab == null || comp.son_prefab_toPut == null) return;
if (so.sonList == null || so.sonList.Count == 0) return;
ClearSonContainer(comp);
var parent = comp.son_prefab_toPut.transform;
var prefab = comp.son_prefab;
ToggleGroup sonGroup = null;
if (comp.son_prefab_toPut != null) sonGroup = comp.son_prefab_toPut.GetComponent<ToggleGroup>();
if (sonGroup == null && comp.son_obj != null) sonGroup = comp.son_obj.GetComponent<ToggleGroup>();
for (int i = 0; i < so.sonList.Count; i++)
{
var son = so.sonList[i];
if (son == null) continue;
if (!son.son_isUnlocked) continue;
var obj = Instantiate(prefab, parent);
var sonComp = obj.GetComponent<sonItemPrefab>();
Toggle sonToggle = null;
if (sonComp != null && sonComp.sonToggle != null) sonToggle = sonComp.sonToggle;
if (sonToggle == null) sonToggle = obj.GetComponent<Toggle>();
if (sonToggle == null) sonToggle = obj.GetComponentInChildren<Toggle>(true);
if (sonGroup != null && sonToggle != null) sonToggle.group = sonGroup;
if (sonComp == null) continue;
if (sonComp.sonName != null) sonComp.sonName.text = son.son_name;
if (sonComp.sonDescription != null) sonComp.sonDescription.text = son.son_description;
if (sonComp.sonImage != null) sonComp.sonImage.sprite = son.son_image;
if (sonToggle != null)
{
sonToggle.onValueChanged.RemoveAllListeners();
sonToggle.onValueChanged.AddListener(isOn =>
{
if (isOn) ApplySonDetails(son);
});
if (sonToggle.isOn) ApplySonDetails(son);
}
}
}
private void ApplySonDetails(notebook_faterType.notebook_sonClass son)
{
if (son == null) return;
if (detailsImage != null) detailsImage.sprite = son.son_image;
if (passage_title != null) passage_title.text = son.son_title;
if (passage_text != null) passage_text.text = son.son_passage;
}
private void InvokeToggleAction(Toggle toggle, System.Action onSelected)
{
if (toggle == null || onSelected == null) return;
int frame = Time.frameCount;
if (toggleInvokeFrame.TryGetValue(toggle, out var lastFrame) && lastFrame == frame) return;
toggleInvokeFrame[toggle] = frame;
onSelected.Invoke();
}
private class ToggleClickRelay : MonoBehaviour, IPointerClickHandler
{
public System.Action onClick;
public void OnPointerClick(PointerEventData eventData)
{
onClick?.Invoke();
}
}
private void CacheFatherBasePositions()
{
if (svContent == null) return;
var parent = svContent.transform as RectTransform;
if (parent == null) return;
LayoutRebuilder.ForceRebuildLayoutImmediate(parent);
fatherBaseAnchors.Clear();
for (int i = 0; i < parent.childCount; i++)
{
var child = parent.GetChild(i) as RectTransform;
if (child == null) continue;
fatherBaseAnchors[child] = child.anchoredPosition;
}
}
private void UpdateFatherSpacer(fatherItemPrefab comp, notebook_faterType so, bool expand, int token)
{
if (svContent == null || comp == null) return;
var parent = svContent.transform as RectTransform;
if (parent == null) return;
int count = expand ? GetUnlockedSonCount(so) : 0;
float targetHeight = count > 0 ? (SonOffsetBase + (SonOffsetPerItem * count)) : 0f;
if (!fatherSpacers.TryGetValue(comp, out var spacer) || spacer == null)
{
if (!expand) return;
var spacerObj = new GameObject("NotebookSpacer", typeof(RectTransform), typeof(LayoutElement));
spacerObj.transform.SetParent(parent, false);
spacer = spacerObj.GetComponent<LayoutElement>();
spacer.minHeight = 0f;
spacer.flexibleHeight = 0f;
spacer.preferredHeight = 0f;
var spacerRectInit = spacerObj.transform as RectTransform;
if (spacerRectInit != null)
{
spacerRectInit.pivot = new Vector2(0.5f, 1f);
spacerRectInit.sizeDelta = new Vector2(spacerRectInit.sizeDelta.x, 0f);
}
fatherSpacers[comp] = spacer;
}
var spacerRect = spacer.transform as RectTransform;
int targetIndex = comp.transform.GetSiblingIndex() + 1;
if (spacerRect != null && spacerRect.GetSiblingIndex() != targetIndex)
spacerRect.SetSiblingIndex(targetIndex);
DOTween.Kill(spacer, false);
if (expand)
{
DOTween.To(() => spacer.preferredHeight, h =>
{
spacer.preferredHeight = h;
spacer.minHeight = h;
if (spacerRect != null)
spacerRect.sizeDelta = new Vector2(spacerRect.sizeDelta.x, h);
}, targetHeight, SonTweenDuration)
.SetTarget(spacer)
.SetEase(Ease.OutCubic)
.OnUpdate(() => LayoutRebuilder.ForceRebuildLayoutImmediate(parent));
}
else
{
DOTween.To(() => spacer.preferredHeight, h =>
{
spacer.preferredHeight = h;
spacer.minHeight = h;
if (spacerRect != null)
spacerRect.sizeDelta = new Vector2(spacerRect.sizeDelta.x, h);
}, 0f, SonTweenDuration)
.SetTarget(spacer)
.SetEase(Ease.OutCubic)
.OnUpdate(() => LayoutRebuilder.ForceRebuildLayoutImmediate(parent))
.OnComplete(() =>
{
if (!IsTokenCurrent(comp, token)) return;
if (spacer != null)
{
fatherSpacers.Remove(comp);
if (Application.isPlaying) Destroy(spacer.gameObject);
else DestroyImmediate(spacer.gameObject);
}
});
}
LayoutRebuilder.ForceRebuildLayoutImmediate(parent);
}
private void FadeSonContainer(fatherItemPrefab comp, bool show, int token, System.Action onComplete)
{
var rect = GetSonRect(comp);
if (rect == null)
{
onComplete?.Invoke();
return;
}
rect.DOKill();
if (show)
{
rect.gameObject.SetActive(true);
var cg = rect.GetComponent<CanvasGroup>();
if (cg == null) cg = rect.gameObject.AddComponent<CanvasGroup>();
cg.alpha = 0f;
cg.DOKill();
cg.DOFade(1f, SonFadeDuration).SetTarget(cg).SetEase(Ease.OutCubic);
}
else
{
var cg = rect.GetComponent<CanvasGroup>();
if (cg == null)
{
rect.gameObject.SetActive(false);
onComplete?.Invoke();
return;
}
cg.DOKill();
cg.DOFade(0f, SonFadeDuration).SetTarget(cg).SetEase(Ease.OutCubic).OnComplete(() =>
{
if (!IsTokenCurrent(comp, token)) return;
rect.gameObject.SetActive(false);
onComplete?.Invoke();
});
}
}
private int NextAnimToken(fatherItemPrefab comp)
{
if (comp == null) return 0;
if (!fatherAnimTokens.TryGetValue(comp, out var token)) token = 0;
token++;
fatherAnimTokens[comp] = token;
return token;
}
private bool IsTokenCurrent(fatherItemPrefab comp, int token)
{
if (comp == null) return false;
return fatherAnimTokens.TryGetValue(comp, out var current) && current == token;
}
private RectTransform GetSonRect(fatherItemPrefab comp)
{
if (comp == null) return null;
if (comp.son_prefab_toPut != null) return comp.son_prefab_toPut.transform as RectTransform;
if (comp.son_obj != null) return comp.son_obj.transform as RectTransform;
return null;
}
private int GetUnlockedSonCount(notebook_faterType so)
{
if (so == null || so.sonList == null) return 0;
int count = 0;
for (int i = 0; i < so.sonList.Count; i++)
{
var son = so.sonList[i];
if (son == null) continue;
if (!son.son_isUnlocked) continue;
count++;
}
return count;
}
private void PlayEnterAnim()
{
var rect = transform as RectTransform;
if (rect == null) rect = GetComponent<RectTransform>();
if (rect == null) return;
rect.DOKill();
var cg = GetComponent<CanvasGroup>();
if (cg == null) cg = gameObject.AddComponent<CanvasGroup>();
cg.DOKill();
rect.localScale = Vector3.one * panel_Enter_Scale_From;
cg.alpha = 0f;
rect.DOScale(Vector3.one, panel_Enter_Time).SetEase(panel_Enter_Ease);
cg.DOFade(1f, panel_Enter_Time).SetEase(panel_Enter_Ease);
}
private void CloseSelf()
{
var rect = transform as RectTransform;
if (rect == null) rect = GetComponent<RectTransform>();
if (rect == null)
{
Destroy(gameObject);
return;
}
rect.DOKill();
var cg = GetComponent<CanvasGroup>();
if (cg == null) cg = gameObject.AddComponent<CanvasGroup>();
cg.DOKill();
Sequence seq = DOTween.Sequence();
seq.Join(rect.DOScale(Vector3.one * panel_Exit_Scale_To, panel_Exit_Time).SetEase(panel_Exit_Ease));
seq.Join(cg.DOFade(0f, panel_Exit_Time).SetEase(panel_Exit_Ease));
seq.OnComplete(() =>
{
if (this != null) Destroy(gameObject);
});
}
private void ClearSonContainer(fatherItemPrefab comp)
{
if (comp == null || comp.son_prefab_toPut == null) return;
var parent = comp.son_prefab_toPut.transform;
var rect = parent as RectTransform;
if (rect != null) rect.DOKill();
for (int i = parent.childCount - 1; i >= 0; i--)
{
var child = parent.GetChild(i);
if (child == null) continue;
if (Application.isPlaying) Destroy(child.gameObject);
else DestroyImmediate(child.gameObject);
}
}
private void ClearContent()
{
var parent = svContent.transform;
for (int i = parent.childCount - 1; i >= 0; i--)
{
var child = parent.GetChild(i);
if (child == null) continue;
if (Application.isPlaying) Destroy(child.gameObject);
else DestroyImmediate(child.gameObject);
}
fatherBaseAnchors.Clear();
fatherSpacers.Clear();
}
}