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("Optional: component implementing IHandbookGameplay. Empty uses the prototype backend.")] [SerializeField] private MonoBehaviour gameplayBackend; [Tooltip("Wartezeit nach erfolgreichem Platzieren, bevor das Buch hochfährt.")] [SerializeField, Min(0)] private float successfulPlacementDelay = 0.5f; [Header("Tab movement (Build, Statistics, Upgrades, Index)")] [Tooltip("Distance left per closed tab. -1 automatically uses its width. Rest position is the authored RectTransform position.")] [SerializeField] private float[] closedTabRevealDistances = { -1f, -1f, -1f, -1f }; [Tooltip("Distance up per open tab. -1 automatically uses its height.")] [SerializeField] private float[] openTabRevealDistances = { -1f, -1f, -1f, -1f }; [Tooltip("Overlap retained when an automatically sized tab is fully revealed.")] [SerializeField, Min(0)] private float closedTabOverlap = 10f; [SerializeField, Min(0)] private float openTabOverlap = 25f; [SerializeField] private float animationSpeed = 14f; private readonly string[] titles = { "Bauen", "Statistik", "Upgrades", "Index" }; private Vector2[] closedPositions, openPositions; private TMP_Text[] closedLabels, openLabels; [SerializeField] 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; bookButton.onClick.AddListener(() => Open(0)); closeButton.onClick.AddListener(Close); AddHover(bookButton.gameObject, () => bookHovered = true, () => bookHovered = false); buildMenu = pages[0].GetComponent(); buildMenu.Initialize(towerCatalog); placement = gameObject.GetComponent(); placement.Initialize(this, buildMenu, (RectTransform)openBook.transform, testTowerPrefab, gameplayBackend); placement.SuccessfulPlacementDelay = successfulPlacementDelay; buildMenu.Placement = placement; indexMenu = pages[3].GetComponent(); indexMenu.Initialize(towerCatalog); var upgrades = pages[2].GetComponent(); upgrades.Initialize(placement); 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; positions[i] = ((RectTransform)tabs[i].transform).anchoredPosition; labels[i] = tabs[i].GetComponentInChildren(true); if (labels[i]) labels[i].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 && index == 3 && indexMenu) indexMenu.ResetForEntry(); } 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. bool opened = tabs == openTabs; var distances = opened ? openTabRevealDistances : closedTabRevealDistances; float configured = distances != null && i < distances.Length ? distances[i] : -1f; float automatic = opened ? Mathf.Max(0f, -positions[i].y - openTabOverlap) : Mathf.Max(0f, positions[i].x - closedTabOverlap); float distance = configured >= 0f ? configured : automatic; var target = positions[i] + (show ? (opened ? Vector2.up : Vector2.left) * distance : Vector2.zero); rect.anchoredPosition = Vector2.Lerp(rect.anchoredPosition, target, blend); if (labels[i]) labels[i].gameObject.SetActive(show); } } } }