using System; using System.Collections.Generic; using System.Text; using TMPro; using UnityEngine; using UnityEngine.UI; // Панель чата (слой приложения): вкладки "Общий мир" / "Круг", лента сообщений, поле ввода и кнопка отправки. // Сам чат -- модуль Чата: только через контракт IChat и событие ChatMessageReceivedEvent. Документ: Docs/Modules/Chat.md public class ChatPanel : MonoBehaviour { [Header("Вкладки")] public Button worldTabButton; public Button circleTabButton; [Header("Сообщения")] public TMP_Text messagesText; [Header("Ввод")] public TMP_InputField inputField; public Button sendButton; [Header("Разворот на весь экран")] [Tooltip("Если не задана -- создаётся сама в правом верхнем углу панели")] public Button expandButton; [Tooltip("Отступ от краёв экрана в развёрнутом виде")] public float expandedMargin = 16f; const int CompactShownMessages = 30; // старые всё равно уходят за верхний край ленты const int ExpandedShownMessages = 100; const float ExpandedFontScale = 1.25f; static readonly Color ActiveTabColor = new Color(0.16f, 0.35f, 0.7f); static readonly Color InactiveTabColor = new Color(0.12f, 0.13f, 0.17f); static readonly string[] NameColors = { "#6EC6FF", "#FF8A65", "#C792EA", "#81C784", "#FFD54F", "#4DD0E1" }; const string OwnNameColor = "#FFC857"; private ChatChannel currentChannel = ChatChannel.World; private readonly StringBuilder sb = new StringBuilder(); // Компактный вид -- запоминаем, чтобы вернуться к нему при сворачивании private RectTransform rect; private Vector2 compactAnchorMin, compactAnchorMax, compactOffsetMin, compactOffsetMax; private int compactSiblingIndex; private float compactFontSize; private TMP_Text expandLabel; public bool IsExpanded { get; private set; } void Start() { worldTabButton.onClick.AddListener(() => SwitchChannel(ChatChannel.World)); circleTabButton.onClick.AddListener(() => SwitchChannel(ChatChannel.Circle)); sendButton.onClick.AddListener(Send); inputField.onSubmit.AddListener(_ => Send()); // Enter на клавиатуре inputField.characterLimit = Chat.MaxMessageLength; EventBus.Subscribe(OnMessageReceived); EventBus.Subscribe(OnCircleJoined); EventBus.Subscribe(OnCircleLeft); rect = (RectTransform)transform; compactFontSize = messagesText.fontSize; if (expandButton == null) expandButton = CreateExpandButton(); expandLabel = expandButton.GetComponentInChildren(); expandButton.onClick.AddListener(ToggleExpanded); UpdateExpandLabel(); SwitchChannel(ChatChannel.World); } void Update() { if (IsExpanded && Input.GetKeyDown(KeyCode.Escape)) SetExpanded(false); } // ---------------- Разворот на весь экран ---------------- public void ToggleExpanded() { SetExpanded(!IsExpanded); } public void SetExpanded(bool expanded) { if (expanded == IsExpanded) return; if (expanded) { compactAnchorMin = rect.anchorMin; compactAnchorMax = rect.anchorMax; compactOffsetMin = rect.offsetMin; compactOffsetMax = rect.offsetMax; compactSiblingIndex = rect.GetSiblingIndex(); // На весь родитель: это контейнер SafeArea, поэтому окно не залезет под вырез экрана rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = new Vector2(expandedMargin, expandedMargin); rect.offsetMax = new Vector2(-expandedMargin, -expandedMargin); rect.SetAsLastSibling(); // поверх остального интерфейса messagesText.fontSize = compactFontSize * ExpandedFontScale; } else { rect.anchorMin = compactAnchorMin; rect.anchorMax = compactAnchorMax; rect.offsetMin = compactOffsetMin; rect.offsetMax = compactOffsetMax; rect.SetSiblingIndex(compactSiblingIndex); messagesText.fontSize = compactFontSize; } IsExpanded = expanded; UpdateExpandLabel(); Redraw(); } void UpdateExpandLabel() { if (expandLabel != null) expandLabel.text = IsExpanded ? "Свернуть" : "Развернуть"; } // Кнопка для сцен, собранных до появления разворота (без пересборки через Tools) Button CreateExpandButton() { var go = new GameObject("ExpandButton", typeof(RectTransform), typeof(Image), typeof(Button)); var rt = (RectTransform)go.transform; rt.SetParent(transform, false); rt.anchorMin = rt.anchorMax = rt.pivot = new Vector2(1, 1); rt.sizeDelta = new Vector2(150, 54); rt.anchoredPosition = new Vector2(-10, -10); go.GetComponent().color = InactiveTabColor; var labelGO = new GameObject("Label", typeof(RectTransform), typeof(TextMeshProUGUI)); var lrt = (RectTransform)labelGO.transform; lrt.SetParent(rt, false); lrt.anchorMin = Vector2.zero; lrt.anchorMax = Vector2.one; lrt.offsetMin = lrt.offsetMax = Vector2.zero; var tmp = labelGO.GetComponent(); tmp.fontSize = 24; tmp.alignment = TextAlignmentOptions.Center; tmp.color = Color.white; tmp.raycastTarget = false; return go.GetComponent