Files
agame/scripts/ui/multiplayer_chat_ui.gd
T
2026-09-12 22:18:23 +08:00

156 lines
3.6 KiB
GDScript

class_name MultiplayerChatUI
extends Control
signal message_sent(message_text: String)
const MAX_CHAT_MESSAGES: int = 100
const MAX_VISIBLE_MESSAGES: int = 8
const FEED_VISIBLE_SECONDS: float = 6.0
const FEED_FADE_SECONDS: float = 0.35
const SAFE_AREA_MARGIN := 16.0
const MIN_CHAT_WIDTH := 280.0
const MAX_CHAT_WIDTH := 480.0
const MAX_CHAT_HEIGHT := 268.0
var chat_visible: bool = false
var chat_history: Array[String] = []
var fade_tween: Tween
@onready var chat: RichTextLabel = $ChatLayout/Chat
@onready var message: LineEdit = $ChatLayout/Message
@onready var feed_timer: Timer = $FeedTimer
func _ready() -> void:
message.text_submitted.connect(_on_message_submitted)
feed_timer.timeout.connect(_on_feed_timer_timeout)
feed_timer.wait_time = FEED_VISIBLE_SECONDS
get_viewport().size_changed.connect(_update_responsive_layout)
_update_responsive_layout()
show()
clear_chat()
func _update_responsive_layout() -> void:
var viewport_size := get_viewport().get_visible_rect().size
var available_width := maxf(1.0, viewport_size.x - SAFE_AREA_MARGIN * 2.0)
var preferred_width := clampf(viewport_size.x * 0.42, MIN_CHAT_WIDTH, MAX_CHAT_WIDTH)
var target_width := minf(available_width, preferred_width)
var target_height := minf(MAX_CHAT_HEIGHT, maxf(1.0, viewport_size.y - SAFE_AREA_MARGIN * 2.0))
offset_left = SAFE_AREA_MARGIN
offset_right = SAFE_AREA_MARGIN + target_width
offset_bottom = -SAFE_AREA_MARGIN
offset_top = -SAFE_AREA_MARGIN - target_height
func toggle_chat() -> void:
if chat_visible:
close_chat()
return
chat_visible = true
_stop_feed_animation()
_show_feed()
message.show()
await get_tree().process_frame
if not chat_visible:
return
message.grab_focus()
func close_chat() -> void:
chat_visible = false
message.text = ""
message.release_focus()
message.hide()
if chat_history.is_empty():
chat.hide()
else:
_show_feed_temporarily()
func is_chat_visible() -> bool:
return chat_visible
func _on_message_submitted(_submitted_text: String) -> void:
var message_text := message.text.strip_edges()
if message_text.is_empty():
return
close_chat()
message_sent.emit(message_text)
func add_message(nick: String, msg: String) -> void:
var formatted_message := _escape_bbcode("%s: %s" % [nick, msg])
chat_history.append(formatted_message)
while chat_history.size() > MAX_CHAT_MESSAGES:
chat_history.pop_front()
_render_chat_history()
if chat_visible:
_stop_feed_animation()
_show_feed()
else:
_show_feed_temporarily()
func _render_chat_history() -> void:
chat.clear()
var first_message := maxi(0, chat_history.size() - MAX_VISIBLE_MESSAGES)
for index in range(first_message, chat_history.size()):
chat.append_text(chat_history[index])
if index < chat_history.size() - 1:
chat.append_text("\n")
func _show_feed_temporarily() -> void:
_stop_feed_animation()
_show_feed()
feed_timer.start()
func _show_feed() -> void:
chat.modulate.a = 1.0
chat.show()
func _on_feed_timer_timeout() -> void:
if chat_visible:
return
_stop_fade_tween()
fade_tween = create_tween()
fade_tween.tween_property(chat, "modulate:a", 0.0, FEED_FADE_SECONDS)
fade_tween.tween_callback(chat.hide)
func _stop_feed_animation() -> void:
feed_timer.stop()
_stop_fade_tween()
func _stop_fade_tween() -> void:
if fade_tween and fade_tween.is_valid():
fade_tween.kill()
fade_tween = null
func _escape_bbcode(text: String) -> String:
return text.replace("[", "[lb]")
func clear_chat() -> void:
_stop_feed_animation()
chat_history.clear()
chat.clear()
chat.modulate.a = 1.0
chat.hide()
message.text = ""
message.release_focus()
message.hide()
chat_visible = false