using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.UI; namespace DepthsOfGreed.HandbookUI { public sealed class HandbookController : MonoBehaviour { [SerializeField] private GameObject closedBook; [SerializeField] private GameObject openBook; [SerializeField] private Button bookButton; [SerializeField] private Button closeButton; [SerializeField] private RectTransform closedTabsRoot; [SerializeField] private RectTransform openTabsRoot; [SerializeField] private Button[] closedTabs; [SerializeField] private Button[] openTabs; [SerializeField] private GameObject[] pages; [SerializeField] private GameObject testTowerPrefab; [SerializeField] private HandbookTowerCatalog towerCatalog; [Tooltip("Wartezeit nach erfolgreichem Platzieren, bevor das Buch hochfährt.")] [SerializeField, Min(0)] private float successfulPlacementDelay = 0.5f; [SerializeField] private float hoverDistance = 115f; [SerializeField] private float animationSpeed = 14f; private readonly string[] titles = { "Bauen", "Statistik", "Upgrades", "Index" }; private Vector2[] closedPositions, openPositions; private TMP_Text[] closedLabels, openLabels; private RectTransform activeLayer; private int selected, hoveredClosed = -1, hoveredOpen = -1; private bool isOpen, bookHovered; private Vector3 bookScale; private BuildMenuController buildMenu; private TestPlacementController placement; private HandbookIndexController indexMenu; private void Awake() { if (!closedBook || !openBook || !bookButton || !closeButton || !openTabsRoot || !closedTabsRoot || closedTabs.Length != 4 || openTabs.Length != 4 || pages.Length != 4) { Debug.LogError("Handbuch: Inspector-Verbindungen fehlen.", this); enabled = false; return; } closedPositions = PrepareTabs(closedTabs, out closedLabels, false); openPositions = PrepareTabs(openTabs, out openLabels, true); bookScale = bookButton.transform.localScale; var layer = new GameObject("ActiveTabLayer", typeof(RectTransform)); activeLayer = layer.GetComponent(); activeLayer.SetParent(openTabsRoot.parent, false); activeLayer.anchorMin = openTabsRoot.anchorMin; activeLayer.anchorMax = openTabsRoot.anchorMax; activeLayer.pivot = openTabsRoot.pivot; activeLayer.offsetMin = openTabsRoot.offsetMin; activeLayer.offsetMax = openTabsRoot.offsetMax; var background = openBook.transform.Find("Background"); if (background) activeLayer.SetSiblingIndex(background.GetSiblingIndex() + 1); bookButton.onClick.AddListener(() => Open(0)); closeButton.onClick.AddListener(Close); AddHover(bookButton.gameObject, () => bookHovered = true, () => bookHovered = false); buildMenu = pages[0].AddComponent(); buildMenu.Initialize(towerCatalog); placement = gameObject.AddComponent(); placement.Initialize(this, buildMenu, (RectTransform)openBook.transform, testTowerPrefab); placement.SuccessfulPlacementDelay = successfulPlacementDelay; buildMenu.Placement = placement; indexMenu = pages[3].AddComponent(); indexMenu.Initialize(towerCatalog); Close(); } private Vector2[] PrepareTabs(Button[] tabs, out TMP_Text[] labels, bool opened) { var positions = new Vector2[tabs.Length]; labels = new TMP_Text[tabs.Length]; for (int i = 0; i < tabs.Length; i++) { int index = i; if (!opened) { var marker = (RectTransform)tabs[i].transform; marker.sizeDelta = new Vector2(marker.sizeDelta.x + 25f, marker.sizeDelta.y); marker.anchoredPosition += Vector2.right * 25f; } positions[i] = ((RectTransform)tabs[i].transform).anchoredPosition; labels[i] = tabs[i].GetComponentInChildren(true); if (labels[i]) { var text = labels[i]; text.text = titles[i]; text.raycastTarget = false; text.fontSize = opened ? 20 : 18; text.enableAutoSizing = true; text.fontSizeMin = 14; text.fontSizeMax = opened ? 20 : 18; text.alignment = TextAlignmentOptions.MidlineLeft; var rect = text.rectTransform; rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = new Vector2(opened ? 65 : 48, 4); rect.offsetMax = new Vector2(-6, -4); text.gameObject.SetActive(false); } tabs[i].onClick.AddListener(() => Open(index)); AddHover(tabs[i].gameObject, () => { if (opened) hoveredOpen = index; else hoveredClosed = index; }, () => { if (opened) { if (hoveredOpen == index) hoveredOpen = -1; } else if (hoveredClosed == index) hoveredClosed = -1; }); } return positions; } private static void AddHover(GameObject target, UnityEngine.Events.UnityAction enter, UnityEngine.Events.UnityAction exit) { var trigger = target.GetComponent(); if (!trigger) trigger = target.AddComponent(); var entry = new EventTrigger.Entry { eventID = EventTriggerType.PointerEnter }; entry.callback.AddListener(_ => enter()); trigger.triggers.Add(entry); var leave = new EventTrigger.Entry { eventID = EventTriggerType.PointerExit }; leave.callback.AddListener(_ => exit()); trigger.triggers.Add(leave); } public void Open(int index) { if (index < 0 || index >= pages.Length) return; bool samePage = isOpen && selected == index; selected = index; isOpen = true; hoveredClosed = -1; bookHovered = false; closedBook.SetActive(false); openBook.SetActive(true); for (int i = 0; i < pages.Length; i++) { pages[i].SetActive(i == index); var rect = (RectTransform)openTabs[i].transform; rect.SetParent(i == selected ? activeLayer : openTabsRoot, false); } if (index == 3 && !samePage && indexMenu) indexMenu.ResetForEntry(); if (samePage) foreach (var scroll in pages[index].GetComponentsInChildren(true)) scroll.verticalNormalizedPosition = 1f; } public void Close() { if (placement && placement.IsDragging) placement.Cancel(); if (placement) placement.ResetBookPresentation(); if (buildMenu) buildMenu.ResetForClose(); if (indexMenu) indexMenu.ClearSelection(); isOpen = false; hoveredOpen = hoveredClosed = -1; bookHovered = false; for (int i = 0; i < openTabs.Length; i++) { ((RectTransform)openTabs[i].transform).anchoredPosition = openPositions[i]; if (openLabels[i]) openLabels[i].gameObject.SetActive(false); } openBook.SetActive(false); closedBook.SetActive(true); } private void Update() { if (isOpen && (!placement || !placement.ConsumedCancelThisFrame) && (!placement || !placement.IsDragging) && Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame) Close(); float blend = 1f - Mathf.Exp(-animationSpeed * Time.unscaledDeltaTime); if (!isOpen) { bookButton.transform.localScale = Vector3.Lerp(bookButton.transform.localScale, bookScale * (bookHovered ? 1.04f : 1f), blend); AnimateTabs(closedTabs, closedPositions, closedLabels, hoveredClosed, -1, blend); } else AnimateTabs(openTabs, openPositions, openLabels, hoveredOpen, selected, blend); } private void AnimateTabs(Button[] tabs, Vector2[] positions, TMP_Text[] labels, int hovered, int active, float blend) { for (int i = 0; i < tabs.Length; i++) { bool show = i == hovered || i == active; var rect = (RectTransform)tabs[i].transform; // Reveal the full marker while retaining a small overlap with the page. float distance = Mathf.Clamp(hoverDistance, 0f, Mathf.Max(0f, positions[i].x - 10f)); var target = positions[i] + (show ? Vector2.left * distance : Vector2.zero); rect.anchoredPosition = Vector2.Lerp(rect.anchoredPosition, target, blend); if (labels[i]) labels[i].gameObject.SetActive(show); } } } }