# FriendUI (P8) —— 好友 / 私聊名单窗(M 键)。 # # var fu := preload("res://ui/friend_ui.gd").new() # add_child(fu) # fu.setup(m2client, canvas_parent) # fu.toggle() # M 键 # # 每行 = 在线点 + 名字 + [删除]。底部输入框 + [添加好友]。 # `friends_changed` 刷新。双击某行 → emit `whisper_to(name)`(chat_ui 可接)。 extends Node signal whisper_to(name: String) var client: Node var _root: Control var _list: VBoxContainer var _name_edit: LineEdit var _invite_dialog: ConfirmationDialog var _pending_invite := "" func setup(m2client: Node, parent: Node) -> void: client = m2client _build(parent) if client.has_signal("friends_changed"): client.friends_changed.connect(refresh) if client.has_signal("friend_invite_ask"): client.friend_invite_ask.connect(_on_friend_invite) func is_open() -> bool: return _root != null and _root.visible func toggle() -> void: _root.visible = not _root.visible if _root.visible: refresh() func refresh() -> void: if not is_open() or client == null: return for c in _list.get_children(): c.queue_free() var friends: Array = client.get_friends() if friends.is_empty(): var e := Label.new() e.text = "(好友列表为空)" e.add_theme_font_size_override("font_size", 11) _list.add_child(e) return for f in friends: _list.add_child(_row(f)) func _row(f: Dictionary) -> Control: var row := HBoxContainer.new() row.custom_minimum_size = Vector2(260, 0) var dot := Label.new() dot.text = "●" dot.modulate = Color(0.4, 0.9, 0.4) if f.get("online", false) else Color(0.4, 0.4, 0.4) row.add_child(dot) var nm := Button.new() nm.text = String(f.get("name", "?")) nm.flat = true nm.custom_minimum_size = Vector2(150, 0) nm.pressed.connect(func() -> void: whisper_to.emit(String(f.get("name", "")))) row.add_child(nm) if f.get("mobile", false): var mobile := Label.new() mobile.text = "手机" mobile.modulate = Color(0.45, 0.8, 1.0) mobile.add_theme_font_size_override("font_size", 10) row.add_child(mobile) var del := Button.new() del.text = "×" del.pressed.connect(func() -> void: client.remove_friend(String(f.get("name", "")))) row.add_child(del) return row func _on_add() -> void: var nm := _name_edit.text.strip_edges() if nm != "": client.add_friend(nm) _name_edit.clear() func _on_friend_invite(name: String) -> void: if client == null or name.strip_edges() == "": return _pending_invite = name _invite_dialog.dialog_text = "接受“%s”的好友请求?" % name _invite_dialog.popup_centered() func _answer_invite(accept: bool) -> void: var name := _pending_invite _pending_invite = "" if name != "" and client != null and client.has_method("friend_answer"): client.friend_answer(name, accept) func _build(parent: Node) -> void: _root = Control.new() _root.set_anchors_preset(Control.PRESET_CENTER) _root.position = Vector2(-150, -180) _root.size = Vector2(300, 360) _root.visible = false _root.set_meta("is_titlebar", true) parent.add_child(_root) var panel := Panel.new() panel.set_anchors_preset(Control.PRESET_FULL_RECT) var sb := StyleBoxFlat.new() sb.bg_color = Color(0.07, 0.08, 0.1, 0.96) sb.set_corner_radius_all(4) panel.add_theme_stylebox_override("panel", sb) _root.add_child(panel) var title := Label.new() title.text = "好友" title.position = Vector2(12, 8) _root.add_child(title) _list = VBoxContainer.new() _list.position = Vector2(12, 34) _list.add_theme_constant_override("separation", 3) _root.add_child(_list) var bottom := HBoxContainer.new() bottom.position = Vector2(12, 320) bottom.custom_minimum_size = Vector2(276, 0) _root.add_child(bottom) _name_edit = LineEdit.new() _name_edit.placeholder_text = "角色名" _name_edit.custom_minimum_size = Vector2(190, 0) bottom.add_child(_name_edit) var add_btn := Button.new() add_btn.text = "添加" add_btn.pressed.connect(_on_add) bottom.add_child(add_btn) _invite_dialog = ConfirmationDialog.new() _invite_dialog.title = "好友请求" _invite_dialog.ok_button_text = "接受" _invite_dialog.cancel_button_text = "拒绝" _invite_dialog.confirmed.connect(func() -> void: _answer_invite(true)) _invite_dialog.canceled.connect(func() -> void: _answer_invite(false)) # Keep the dialog outside the toggleable friend panel: requests must still # be visible when the friend list window itself is closed. parent.add_child(_invite_dialog)