Files
test/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs
T
2026-04-13 11:25:55 +07:00

217 lines
7.6 KiB
C#

using System.Collections.Generic;
using CSNetwork;
using CSNetwork.GPDataType;
using UnityEngine;
using UnityEngine.UI;
namespace BrewMonster.Scripts.ChatUI
{
/// <summary>
/// Vùng xem trước chat nhỏ; đồng bộ qua EventBus với tin nhắn và bộ lọc kênh.
/// Mở panel chat đầy đủ qua <see cref="OpenChatPanelRequestedEvent"/>.
/// </summary>
public class MiniChatUI : MonoBehaviour
{
[Header("MiniChat")]
public Button onOpenChatPanelButton;
[Tooltip("Parent cho các dòng tin xem trước (nên có VerticalLayoutGroup).")]
public RectTransform miniChatContent;
[Tooltip("ScrollRect bọc mini chat (null = không cuộn). Nếu để trống, Awake sẽ thử GetComponentInParent từ miniChatContent.")]
[SerializeField] ScrollRect miniChatScrollRect;
[Tooltip("Null = dùng messagePrefab (fallback).")]
public ChatMessageView miniMessagePrefab;
[Tooltip("Prefab dòng tin khi miniMessagePrefab null (giống messagePrefab panel chính).")]
public ChatMessageView messagePrefab;
[SerializeField] int miniChatPreviewLines = 5;
[Tooltip("Tắt raycast trên dòng preview để click xuyên xuống onOpenChatPanelButton (TMP/Image mặc định chặn Button).")]
[SerializeField] bool miniChatPassThroughClicks = true;
[SerializeField] int maxStoredMessages = 2000;
[Header("Chat System Data")]
public ChatSystemSO chatSystemSO;
readonly List<ChatMessageData> _messages = new();
readonly List<ChatMessageData> _miniFilterBuffer = new();
readonly List<ChatMessageView> _miniChatViews = new();
Dictionary<byte, Sprite> _iconCache;
ChatChannel _currentFilterChannel = ChatChannel.GP_CHAT_LOCAL;
void Awake()
{
if (miniChatScrollRect == null && miniChatContent != null)
miniChatScrollRect = miniChatContent.GetComponentInParent<ScrollRect>();
_iconCache = new Dictionary<byte, Sprite>();
if (chatSystemSO != null && chatSystemSO.channelIcons != null)
{
foreach (var mapping in chatSystemSO.channelIcons)
_iconCache[(byte)mapping.channel] = mapping.icon;
}
EventBus.Subscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Subscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
}
void OnEnable()
{
RefreshMiniChat();
}
void Start()
{
if (onOpenChatPanelButton != null)
onOpenChatPanelButton.onClick.AddListener(OnOpenChatPanelButtonClicked);
}
void OnDestroy()
{
if (onOpenChatPanelButton != null)
onOpenChatPanelButton.onClick.RemoveListener(OnOpenChatPanelButtonClicked);
EventBus.Unsubscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Unsubscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
ClearMiniChatViews();
}
void OnOpenChatPanelButtonClicked()
{
EventBus.Publish(new OpenChatPanelRequestedEvent());
}
void OnChannelFilterChanged(ChatChannelFilterChangedEvent e)
{
if (this == null) return;
_currentFilterChannel = e.channel;
RefreshMiniChat();
}
bool ShouldShowMessage(ChatMessageData data)
{
if (_currentFilterChannel == ChatChannel.GP_CHAT_LOCAL) return true;
if (data.channel == (byte)ChatChannel.GP_CHAT_MISC) return true;
return data.channel == (byte)_currentFilterChannel;
}
void OnChatMessageReceived(GameSession.ChatMessageEvent x)
{
ChatThreadDispatcher.Instance.Post(() =>
{
if (this == null) return;
AddMessage(x.context, x.channel);
});
}
void OnChatMessageClear(OnEventClearChat obj)
{
ChatThreadDispatcher.Instance.Post(() =>
{
if (this == null) return;
ClearChat();
});
}
void AddMessage(string msg, byte channel)
{
if (this == null) return;
var data = new ChatMessageData { message = msg, channel = channel };
_messages.Add(data);
if (_messages.Count > maxStoredMessages)
_messages.RemoveAt(0);
RefreshMiniChat();
}
void RefreshMiniChat()
{
if (miniChatContent == null) return;
_miniFilterBuffer.Clear();
foreach (var msg in _messages)
{
if (ShouldShowMessage(msg))
_miniFilterBuffer.Add(msg);
}
int take = Mathf.Min(Mathf.Max(1, miniChatPreviewLines), _miniFilterBuffer.Count);
int startIdx = _miniFilterBuffer.Count - take;
while (_miniChatViews.Count < take)
{
var v = CreateMiniChatItem();
if (v == null) return;
_miniChatViews.Add(v);
}
while (_miniChatViews.Count > take)
{
var last = _miniChatViews[_miniChatViews.Count - 1];
_miniChatViews.RemoveAt(_miniChatViews.Count - 1);
if (last != null)
Destroy(last.gameObject);
}
for (int i = 0; i < take; i++)
{
var data = _miniFilterBuffer[startIdx + i];
var view = _miniChatViews[i];
Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null;
view.gameObject.SetActive(true);
view.transform.SetSiblingIndex(i);
if (miniChatPassThroughClicks)
DisableGraphicsRaycastUnder(view.transform);
view.Bind(icon, data.message);
}
ScrollMiniChatToBottom();
}
/// <summary>Cuộn mini log xuống dòng cuối (khi có ScrollRect và nội dung cao hơn viewport).</summary>
void ScrollMiniChatToBottom()
{
if (miniChatScrollRect == null) return;
if (miniChatContent != null)
LayoutRebuilder.ForceRebuildLayoutImmediate(miniChatContent);
Canvas.ForceUpdateCanvases();
miniChatScrollRect.verticalNormalizedPosition = 0f;
}
/// <summary>
/// Mini chat rows use TMP/Image with raycastTarget on — they sit above the open button and steal clicks.
/// </summary>
static void DisableGraphicsRaycastUnder(Transform root)
{
if (root == null) return;
foreach (var g in root.GetComponentsInChildren<Graphic>(true))
g.raycastTarget = false;
}
ChatMessageView CreateMiniChatItem()
{
var prefab = miniMessagePrefab != null ? miniMessagePrefab : messagePrefab;
if (prefab == null || miniChatContent == null) return null;
var item = Instantiate(prefab, miniChatContent, false);
return item;
}
void ClearChat()
{
_messages.Clear();
ClearMiniChatViews();
}
void ClearMiniChatViews()
{
foreach (var v in _miniChatViews)
{
if (v != null)
Destroy(v.gameObject);
}
_miniChatViews.Clear();
}
}
}