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 cachedItems = new List(); private Toggle[] categoryToggles; private readonly Dictionary fatherBaseAnchors = new Dictionary(); private readonly Dictionary toggleInvokeFrame = new Dictionary(); private readonly Dictionary fatherSpacers = new Dictionary(); private readonly Dictionary fatherAnimTokens = new Dictionary(); 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(); if (relay == null) relay = toggle.gameObject.AddComponent(); 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(); var contentGroup = svContent != null ? svContent.GetComponent() : null; if (parentGroup != null && parentGroup != contentGroup) return parentGroup; var parent = first.transform.parent; if (parent == null) return null; var created = parent.GetComponent(); if (created == null) created = parent.gameObject.AddComponent(); return created; } private void LoadAllData() { cachedItems.Clear(); var set = new HashSet(); #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(path); if (so == null) continue; if (set.Add(so)) cachedItems.Add(so); } } #endif if (!string.IsNullOrEmpty(runtime_SO_path)) { var resources = Resources.LoadAll(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(); 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(); 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(); if (toggle == null) toggle = comp.GetComponent(); if (toggle == null) toggle = comp.GetComponentInChildren(true); if (toggle == null) toggle = comp.GetComponentInParent(); 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(); if (sonGroup == null && comp.son_obj != null) sonGroup = comp.son_obj.GetComponent(); 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(); Toggle sonToggle = null; if (sonComp != null && sonComp.sonToggle != null) sonToggle = sonComp.sonToggle; if (sonToggle == null) sonToggle = obj.GetComponent(); if (sonToggle == null) sonToggle = obj.GetComponentInChildren(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(); 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(); if (cg == null) cg = rect.gameObject.AddComponent(); cg.alpha = 0f; cg.DOKill(); cg.DOFade(1f, SonFadeDuration).SetTarget(cg).SetEase(Ease.OutCubic); } else { var cg = rect.GetComponent(); 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(); if (rect == null) return; rect.DOKill(); var cg = GetComponent(); if (cg == null) cg = gameObject.AddComponent(); 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(); if (rect == null) { Destroy(gameObject); return; } rect.DOKill(); var cg = GetComponent(); if (cg == null) cg = gameObject.AddComponent(); 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(); } }